Repository: phuhao00/greatestworks Branch: main Commit: 2a7ee66a8812 Files: 453 Total size: 4.3 MB Directory structure: gitextract_cpr1egp_/ ├── .dockerignore ├── .editorconfig ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── .goreleaser.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── Makefile ├── README.en.md ├── README.md ├── cmd/ │ ├── auth-service/ │ │ └── main.go │ ├── game-service/ │ │ └── main.go │ ├── gateway-service/ │ │ └── main.go │ ├── replication/ │ │ └── main.go │ └── scene/ │ └── main.go ├── configs/ │ ├── auth-service.yaml │ ├── config.dev.yaml.example │ ├── config.example.yaml │ ├── config.prod.yaml.example │ ├── data/ │ │ ├── items.json │ │ ├── maps.json │ │ ├── quests.json │ │ ├── skills.json │ │ └── units.json │ ├── docker.yaml │ ├── game-service.yaml │ ├── gateway-service.yaml │ ├── replication-service.yaml │ └── scene-service.yaml ├── docker-compose.yml ├── docs/ │ └── k8s-local.md ├── go.mod ├── go.work ├── go.work.sum ├── internal/ │ ├── application/ │ │ ├── commands/ │ │ │ ├── battle/ │ │ │ │ ├── create_battle.go │ │ │ │ └── errors.go │ │ │ └── player/ │ │ │ ├── ban_player.go │ │ │ ├── create_player.go │ │ │ ├── delete_player.go │ │ │ ├── errors.go │ │ │ ├── gm_update_player.go │ │ │ ├── level_up_player.go │ │ │ ├── move_player.go │ │ │ ├── unban_player.go │ │ │ └── update_player.go │ │ ├── handlers/ │ │ │ ├── command_bus.go │ │ │ ├── query_bus.go │ │ │ └── replication_subscribers.go │ │ ├── interfaces/ │ │ │ └── command.go │ │ ├── queries/ │ │ │ ├── battle/ │ │ │ │ ├── errors.go │ │ │ │ └── get_battle.go │ │ │ └── player/ │ │ │ ├── errors.go │ │ │ ├── get_player.go │ │ │ ├── get_player_detail.go │ │ │ ├── get_player_stats.go │ │ │ ├── list_players.go │ │ │ └── search_players.go │ │ └── services/ │ │ ├── building_service.go │ │ ├── character_service.go │ │ ├── fight_service.go │ │ ├── hangup_service.go │ │ ├── item_service.go │ │ ├── mail_service.go │ │ ├── map_service.go │ │ ├── minigame_service.go │ │ ├── npc_service.go │ │ ├── pet_service.go │ │ ├── plant_service.go │ │ ├── player_service.go │ │ ├── quest_service.go │ │ ├── ranking_service.go │ │ ├── replication_service.go │ │ ├── sacred_service.go │ │ ├── scene_service.go │ │ ├── service_registry.go │ │ ├── spawn_manager.go │ │ ├── update_manager.go │ │ ├── user_service.go │ │ └── weather_service.go │ ├── auth/ │ │ └── jwt.go │ ├── base_module.go │ ├── bootstrap/ │ │ ├── auth_bootstrap.go │ │ ├── game_bootstrap.go │ │ ├── gateway_bootstrap.go │ │ ├── replication_bootstrap.go │ │ └── scene_bootstrap.go │ ├── config/ │ │ ├── config.go │ │ ├── loader.go │ │ ├── loader_test.go │ │ ├── manager.go │ │ └── types.go │ ├── config_manager_base.go │ ├── data_base.go │ ├── database/ │ │ ├── mongodb.go │ │ └── redis.go │ ├── domain/ │ │ ├── ai/ │ │ │ └── monster_ai.go │ │ ├── battle/ │ │ │ ├── battle.go │ │ │ ├── errors.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── skill.go │ │ ├── building/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ ├── types.go │ │ │ └── value_object.go │ │ ├── character/ │ │ │ ├── actor.go │ │ │ ├── buff_attributes_test.go │ │ │ ├── buff_flags_test.go │ │ │ ├── entity.go │ │ │ ├── events.go │ │ │ ├── events_test.go │ │ │ ├── monster.go │ │ │ ├── player.go │ │ │ ├── repository.go │ │ │ ├── skill_damage_test.go │ │ │ ├── subsystems.go │ │ │ ├── subsystems_test.go │ │ │ └── value_objects.go │ │ ├── dialogue/ │ │ │ └── dialogue_manager.go │ │ ├── events/ │ │ │ └── domain_event.go │ │ ├── inventory/ │ │ │ ├── dressup/ │ │ │ │ ├── aggregate.go │ │ │ │ ├── entity.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── repository.go │ │ │ │ ├── service.go │ │ │ │ └── value_object.go │ │ │ ├── errors.go │ │ │ ├── inventory.go │ │ │ ├── repository.go │ │ │ └── synthesis/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── value_object.go │ │ ├── mapmanager/ │ │ │ └── map.go │ │ ├── minigame/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ ├── types.go │ │ │ └── value_object.go │ │ ├── npc/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── value_object.go │ │ ├── pet/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── value_object.go │ │ ├── player/ │ │ │ ├── beginner/ │ │ │ │ ├── aggregate.go │ │ │ │ ├── entity.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── repository.go │ │ │ │ ├── service.go │ │ │ │ └── value_object.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── hangup/ │ │ │ │ ├── aggregate.go │ │ │ │ ├── entity.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── repository.go │ │ │ │ ├── service.go │ │ │ │ └── value_object.go │ │ │ ├── honor/ │ │ │ │ ├── aggregate.go │ │ │ │ ├── entity.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── repository.go │ │ │ │ ├── service.go │ │ │ │ └── value_object.go │ │ │ ├── player.go │ │ │ ├── query.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── value_objects.go │ │ ├── quest/ │ │ │ ├── errors.go │ │ │ ├── quest.go │ │ │ └── repository.go │ │ ├── ranking/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── value_object.go │ │ ├── replication/ │ │ │ ├── events.go │ │ │ ├── instance.go │ │ │ ├── repository.go │ │ │ └── snapshot.go │ │ ├── scene/ │ │ │ ├── errors.go │ │ │ ├── plant/ │ │ │ │ ├── aggregate.go │ │ │ │ ├── entity.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── repository.go │ │ │ │ ├── service.go │ │ │ │ └── value_object.go │ │ │ ├── repository.go │ │ │ ├── sacred/ │ │ │ │ ├── aggregate.go │ │ │ │ ├── entity.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── repository.go │ │ │ │ ├── service.go │ │ │ │ └── value_object.go │ │ │ ├── scene.go │ │ │ └── weather/ │ │ │ ├── aggregate.go │ │ │ ├── entity.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── repository.go │ │ │ ├── service.go │ │ │ └── value_object.go │ │ ├── skill/ │ │ │ ├── errors.go │ │ │ ├── repository.go │ │ │ └── skill.go │ │ └── social/ │ │ ├── chat/ │ │ │ ├── aggregate.go │ │ │ ├── chat_service.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── member.go │ │ │ ├── message.go │ │ │ └── repository.go │ │ ├── email/ │ │ │ ├── attachment.go │ │ │ ├── email.go │ │ │ ├── email_service.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ └── repository.go │ │ ├── family/ │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── family.go │ │ │ ├── family_service.go │ │ │ ├── member.go │ │ │ └── repository.go │ │ ├── friend/ │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── friend_request.go │ │ │ ├── friend_service.go │ │ │ ├── friendship.go │ │ │ └── repository.go │ │ └── team/ │ │ ├── errors.go │ │ ├── events.go │ │ ├── repository.go │ │ ├── team.go │ │ ├── team_member.go │ │ └── team_service.go │ ├── errors/ │ │ └── domain_errors.go │ ├── events/ │ │ ├── eventbus.go │ │ ├── metrics.go │ │ ├── middleware.go │ │ └── worker.go │ ├── game/ │ │ └── player.go │ ├── icharacter.go │ ├── imodule.go │ ├── infrastructure/ │ │ ├── auth/ │ │ │ ├── jwt.go │ │ │ └── middleware.go │ │ ├── cache/ │ │ │ ├── cache_manager.go │ │ │ ├── memory_cache.go │ │ │ └── redis_cache.go │ │ ├── config/ │ │ │ ├── environments/ │ │ │ │ ├── config.dev.yaml │ │ │ │ ├── config.prod.yaml │ │ │ │ └── config.test.yaml │ │ │ ├── file_watcher.go │ │ │ ├── unified.json │ │ │ ├── unified.prod.json │ │ │ └── unified_config.go │ │ ├── container/ │ │ │ ├── container.go │ │ │ ├── providers.go │ │ │ └── simple_container.go │ │ ├── datamanager/ │ │ │ └── data_manager.go │ │ ├── errors/ │ │ │ └── errors.go │ │ ├── logging/ │ │ │ └── logger.go │ │ ├── managers/ │ │ │ ├── entity_manager.go │ │ │ └── update_manager.go │ │ ├── messaging/ │ │ │ ├── event_bus.go │ │ │ ├── event_dispatcher.go │ │ │ ├── logger_adapter.go │ │ │ ├── nats_publisher.go │ │ │ ├── nats_subscriber.go │ │ │ ├── publisher.go │ │ │ └── worker_pool.go │ │ ├── monitoring/ │ │ │ └── metrics.go │ │ ├── network/ │ │ │ ├── connection_manager.go │ │ │ ├── netcore_client.go │ │ │ └── netcore_server.go │ │ ├── persistence/ │ │ │ ├── base_repository.go │ │ │ ├── building_repository.go │ │ │ ├── db_entities.go │ │ │ ├── hangup_repository.go │ │ │ ├── minigame_repository.go │ │ │ ├── mongodb.go │ │ │ ├── npc_repository.go │ │ │ ├── plant_repository.go │ │ │ ├── player_repository.go │ │ │ ├── ranking_repository.go │ │ │ ├── replication_repository.go │ │ │ ├── repositories.go │ │ │ ├── scene_repository.go │ │ │ └── weather_repository.go │ │ ├── protocol/ │ │ │ ├── binary_protocol.go │ │ │ ├── json_protocol.go │ │ │ └── protocol.go │ │ └── weave/ │ │ └── weavelet.go │ ├── interfaces/ │ │ ├── http/ │ │ │ ├── auth/ │ │ │ │ ├── login_handler.go │ │ │ │ ├── middleware.go │ │ │ │ ├── register_handler.go │ │ │ │ └── token_handler.go │ │ │ ├── battle_handler.go │ │ │ ├── building_handler.go │ │ │ ├── gm/ │ │ │ │ ├── player_management.go │ │ │ │ └── server_monitor.go │ │ │ ├── health_handler.go │ │ │ ├── pet_handler.go │ │ │ ├── player_handler.go │ │ │ ├── replication_handlers.go │ │ │ ├── response.go │ │ │ └── server.go │ │ ├── rpc/ │ │ │ ├── battle_service.go │ │ │ ├── player_service.go │ │ │ ├── ranking_service.go │ │ │ ├── replication_service.go │ │ │ └── server.go │ │ └── tcp/ │ │ ├── connection/ │ │ │ ├── heartbeat.go │ │ │ ├── manager.go │ │ │ └── session.go │ │ ├── handlers/ │ │ │ └── game_handler.go │ │ ├── npc_handler.go │ │ ├── pet_handler.go │ │ ├── protocol/ │ │ │ ├── base_protocol.go │ │ │ ├── errors.go │ │ │ ├── game_protocol.go │ │ │ ├── message_types.go │ │ │ └── pet_protocol.go │ │ ├── router.go │ │ ├── scene_handler.go │ │ └── server.go │ ├── metrics_base.go │ ├── module_manager.go │ ├── network/ │ │ ├── codec.go │ │ ├── protocol.go │ │ └── session/ │ │ └── session.go │ ├── platform.go │ ├── proto/ │ │ ├── README.md │ │ ├── battle/ │ │ │ └── battle.pb.go │ │ ├── chat/ │ │ │ └── chat.pb.go │ │ ├── common/ │ │ │ └── common.pb.go │ │ ├── errors/ │ │ │ └── errors.pb.go │ │ ├── gateway/ │ │ │ └── gateway.pb.go │ │ ├── mail/ │ │ │ └── mail.pb.go │ │ ├── messages/ │ │ │ └── messages.pb.go │ │ ├── pet/ │ │ │ └── pet.pb.go │ │ ├── player/ │ │ │ └── player.pb.go │ │ ├── protocol/ │ │ │ └── protocol.pb.go │ │ ├── room/ │ │ │ └── room.pb.go │ │ ├── scene/ │ │ │ └── scene.pb.go │ │ └── team/ │ │ └── team.pb.go │ └── readme.md ├── k8s/ │ └── local/ │ ├── auth-service.yaml │ ├── configmap-gateway.yaml │ ├── game-service.yaml │ ├── gateway-service.yaml │ ├── mongodb.yaml │ ├── namespace.yaml │ ├── overlays/ │ │ └── registry/ │ │ └── kustomization.yaml │ └── redis.yaml ├── license ├── proto/ │ ├── auth.proto │ ├── battle.proto │ ├── character.proto │ ├── chat.proto │ ├── common.proto │ ├── entity.proto │ ├── errors.proto │ ├── fight.proto │ ├── game.proto │ ├── gateway.proto │ ├── inventory.proto │ ├── mail.proto │ ├── map.proto │ ├── messages.proto │ ├── npc.proto │ ├── pet.proto │ ├── player.proto │ ├── protocol.proto │ ├── quest.proto │ ├── room.proto │ ├── scene.proto │ └── team.proto ├── protoc/ │ ├── include/ │ │ └── google/ │ │ └── protobuf/ │ │ ├── any.proto │ │ ├── api.proto │ │ ├── compiler/ │ │ │ └── plugin.proto │ │ ├── descriptor.proto │ │ ├── duration.proto │ │ ├── empty.proto │ │ ├── field_mask.proto │ │ ├── source_context.proto │ │ ├── struct.proto │ │ ├── timestamp.proto │ │ ├── type.proto │ │ └── wrappers.proto │ └── readme.txt ├── scripts/ │ ├── build-images.ps1 │ ├── build.sh │ ├── clean.sh │ ├── db-migrate.sh │ ├── deploy.sh │ ├── docker-build.sh │ ├── generate_proto.bat │ ├── generate_proto.sh │ ├── health-check.sh │ ├── init-db.sh │ ├── k8s-deploy.ps1 │ ├── load-images-to-k8s.ps1 │ ├── mongo-init.js │ ├── publish-images.ps1 │ ├── setup-dev.sh │ ├── start-services.bat │ ├── start-services.sh │ └── stop-services.sh └── tools/ └── simclient/ ├── README_E2E.md ├── action_scenario.go ├── client.go ├── cmd/ │ └── simclient/ │ └── main.go ├── config.example.yaml ├── config.go ├── e2e.yaml ├── e2e_load.yaml ├── e2e_scenario.go ├── feature_library.go ├── main.go ├── metrics.go ├── runner.go ├── scenario.go └── simclient_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ # ============================================================================= # Docker 构建忽略文件 # ============================================================================= # 此文件定义了在 Docker 构建过程中应该忽略的文件和目录 # 减少构建上下文大小,提高构建速度 # ============================================================================= # 版本控制 # ============================================================================= .git .gitignore .gitattributes .gitmodules .github/ # ============================================================================= # 文档和说明 # ============================================================================= README.md CHANGELOG.md CONTRIBUTING.md LICENSE *.md docs/ *.txt *.rst # ============================================================================= # 开发工具配置 # ============================================================================= # IDE 配置 .vscode/ .idea/ *.swp *.swo *~ # 编辑器配置 .editorconfig # ============================================================================= # 构建产物和缓存 # ============================================================================= # Go 构建产物 *.exe *.exe~ *.dll *.so *.dylib bin/ dist/ build/ vendor/ # 测试覆盖率 *.out coverage.html coverage.xml # 性能分析文件 *.prof *.pprof # ============================================================================= # 日志和临时文件 # ============================================================================= logs/ *.log *.log.* tmp/ temp/ *.tmp *.temp *.pid *.seed *.pid.lock # ============================================================================= # 数据库和数据文件 # ============================================================================= *.db *.sqlite *.sqlite3 data/ database/ backups/ *.dump *.sql # ============================================================================= # 配置和环境文件 # ============================================================================= # 环境变量文件 .env .env.local .env.*.local .env.development .env.test .env.production # 本地配置文件 config.local.yaml config.local.yml config.local.json *.local.yaml *.local.yml *.local.json # 密钥和证书文件 *.key *.pem *.crt *.p12 *.pfx secrets/ certs/ ssl/ # ============================================================================= # Docker 相关 # ============================================================================= Dockerfile.dev Dockerfile.test docker-compose.override.yml docker-compose.local.yml .dockerignore # ============================================================================= # 测试文件 # ============================================================================= *_test.go testdata/ test/ tests/ *.test # 测试报告 test-results/ test-reports/ junit.xml # ============================================================================= # 依赖管理 # ============================================================================= # Node.js (如果有前端资源) node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* package-lock.json yarn.lock # Python (如果有 Python 脚本) __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # ============================================================================= # 操作系统文件 # ============================================================================= # macOS .DS_Store .AppleDouble .LSOverride Icon ._* .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Windows Thumbs.db ehthumbs.db Desktop.ini $RECYCLE.BIN/ *.cab *.msi *.msm *.msp *.lnk # Linux *~ .fuse_hidden* .directory .Trash-* .nfs* # ============================================================================= # 监控和分析工具 # ============================================================================= # Prometheus 数据 prometheus_data/ # Grafana 数据 grafana_data/ # 性能分析 *.pprof cpu.prof mem.prof goroutine.prof # ============================================================================= # 部署和运维 # ============================================================================= # Kubernetes 配置 k8s/ kubernetes/ *.yaml *.yml !docker-compose.yml !configs/*.yml !configs/*.yaml # Terraform *.tfstate *.tfstate.* .terraform/ # Ansible *.retry # ============================================================================= # 备份和归档 # ============================================================================= *.bak *.backup *.old *.orig *.save *.tar *.tar.gz *.tgz *.zip *.rar *.7z # ============================================================================= # 媒体文件 # ============================================================================= *.jpg *.jpeg *.png *.gif *.bmp *.tiff *.ico *.svg *.webp *.mp3 *.mp4 *.avi *.mov *.wmv *.flv *.webm # ============================================================================= # 大文件和资源 # ============================================================================= assets/ static/ public/ uploads/ downloads/ # ============================================================================= # 开发和调试工具 # ============================================================================= # Air (Go 热重载工具) .air.toml tmp/ # Delve 调试器 __debug_bin # Go 工具 *.cover *.coverprofile # ============================================================================= # CI/CD 配置 # ============================================================================= .github/ .gitlab-ci.yml .travis.yml .circleci/ Jenkinsfile .drone.yml # ============================================================================= # 脚本和工具 # ============================================================================= scripts/ tools/ utils/ *.sh *.bat *.ps1 !docker-entrypoint.sh # ============================================================================= # 许可证和法律文件 # ============================================================================= LICENSE* COPYING* NOTICE* AUTHORS* CONTRIBUTORS* # ============================================================================= # 其他 # ============================================================================= # 编译器和构建工具生成的文件 *.o *.a *.lib *.obj *.pdb *.idb *.ilk *.map *.exp # 包管理器锁文件 Gopkg.lock glide.lock # 临时和缓存目录 cache/ .cache/ .tmp/ # 本地开发配置 .local/ local/ # ============================================================================= # 注意事项 # ============================================================================= # 1. 确保不要忽略必要的配置文件 # 2. 对于生产环境,可能需要包含某些配置文件 # 3. 根据项目实际情况调整忽略规则 # 4. 定期检查和更新此文件 # ============================================================================= ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true # All files [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2 # Go files [*.go] indent_style = tab indent_size = 4 # Markdown files [*.md] trim_trailing_whitespace = false indent_size = 2 # YAML files [*.{yml,yaml}] indent_size = 2 # JSON files [*.json] indent_size = 2 # JavaScript/TypeScript files [*.{js,ts,jsx,tsx}] indent_size = 2 # HTML files [*.html] indent_size = 2 # CSS/SCSS files [*.{css,scss,sass}] indent_size = 2 # Shell scripts [*.{sh,bash}] indent_size = 2 # Docker files [{Dockerfile,Dockerfile.*}] indent_size = 2 # Makefile [{Makefile,makefile,*.mk}] indent_style = tab indent_size = 4 # Configuration files [*.{toml,ini,cfg,conf}] indent_size = 2 # SQL files [*.sql] indent_size = 2 # Protocol Buffer files [*.proto] indent_size = 2 # Git files [.gitignore] indent_size = 2 # License and README files [{LICENSE,README,CHANGELOG,CONTRIBUTING} ================================================ FILE: .gitignore ================================================ # AI工作文件 .trae/ .cursor/ # 协作说明(如需本地自定义可忽略) .github/instructions/ # 构建产物 dist/ build/ *.exe *.dll *.so *.dylib # 测试覆盖率 *.out coverage.html coverage.xml # 日志文件 log/ logs/ *.log # 临时文件 *.tmp *.temp .DS_Store Thumbs.db # IDE文件 .idea/ .vscode/ *.swp *.swo *~ # 环境配置 .env .env.local .env.*.local config/local.yaml config/dev.yaml config/prod.yaml # 依赖目录 node_modules/ vendor/ # 数据库文件 *.db *.sqlite *.sqlite3 # 证书和密钥 *.pem *.key *.crt *.p12 # 备份文件 *.bak *.backup # 操作系统生成的文件 .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # Go相关 # 如果你有vendor目录,取消下面的注释 # vendor/ # 测试二进制文件,使用go test -c构建 *.test # 输出的二进制文件(可执行文件) *.exe *.exe~ *.dll *.so *.dylib # 测试覆盖率文件 *.out # 依赖分析文件 go.sum # 本地环境文件 .env .env.local .env.development.local .env.test.local .env.production.local # 编辑器和IDE .vscode/ .idea/ *.swp *.swo *~ # 日志 *.log logs/ # 运行时数据 pids *.pid *.seed *.pid.lock # 目录用于仪器化的libs生成的覆盖率报告 lib-cov # 覆盖率目录,由诸如istanbul这样的工具使用 coverage/ # nyc测试覆盖率 .nyc_output # Grunt中间存储(https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower依赖目录(https://bower.io/) bower_components # node-waf配置 .lock-wscript # 编译的二进制插件(https://nodejs.org/api/addons.html) build/Release # 依赖目录 node_modules/ jspm_packages/ # TypeScript v1声明文件 typings/ # TypeScript缓存 *.tsbuildinfo # 可选的npm缓存目录 .npm # 可选的eslint缓存 .eslintcache # Microbundle缓存 .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # 可选的REPL历史 .node_repl_history # yarn v2的输出 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* ================================================ FILE: .gitmodules ================================================ [submodule "network/protocol"] path = network/protocol url = https://github.com/phuhao00/greatestworks-proto.git ================================================ FILE: .golangci.yml ================================================ # ============================================================================= # golangci-lint 配置文件 # 用于 Go 代码质量检查和静态分析 # ============================================================================= # 运行配置 run: # 超时时间 timeout: 5m # 要检查的 Go 版本 go: '1.21' # 并发数 concurrency: 4 # 跳过的目录 skip-dirs: - vendor - .git - bin - tmp - _output # 跳过的文件 skip-files: - ".*\\.pb\\.go$" - ".*\\.gen\\.go$" - ".*_test\\.go$" # 可选:跳过测试文件 # 构建标签 build-tags: - integration - e2e # 输出配置 output: # 输出格式: colored-line-number|line-number|json|tab|checkstyle|code-climate format: colored-line-number # 打印 linter 名称 print-issued-lines: true print-linter-name: true uniq-by-line: true sort-results: true # Linters 配置 linters: # 启用的 linters enable: # 默认启用 - errcheck # 检查未处理的错误 - gosimple # 简化代码建议 - govet # Go 官方 vet 工具 - ineffassign # 检查无效赋值 - staticcheck # 静态分析 - typecheck # 类型检查 - unused # 检查未使用的代码 # 代码风格 - gofmt # 格式化检查 - goimports # import 排序和格式化 - gocyclo # 圈复杂度检查 - goconst # 重复字符串检查 - gocritic # 代码批评 - gofumpt # 更严格的格式化 # 性能相关 - prealloc # 预分配切片 - bodyclose # HTTP body 关闭检查 # 安全相关 - gosec # 安全检查 # 错误处理 - errorlint # 错误处理最佳实践 - wrapcheck # 错误包装检查 # 命名规范 - stylecheck # 风格检查 - revive # 替代 golint # 其他有用的 - misspell # 拼写检查 - unconvert # 不必要的类型转换 - unparam # 未使用的参数 - whitespace # 空白字符检查 - exportloopref # 循环变量引用检查 - nolintlint # nolint 指令检查 # 禁用的 linters disable: - deadcode # 已废弃,使用 unused 替代 - varcheck # 已废弃,使用 unused 替代 - structcheck # 已废弃,使用 unused 替代 - maligned # 已废弃 - interfacer # 已废弃 - scopelint # 已废弃,使用 exportloopref 替代 - golint # 已废弃,使用 revive 替代 # 快速模式,只运行快速的 linters fast: false # Linters 设置 linters-settings: # errcheck 配置 errcheck: # 检查类型断言 check-type-assertions: true # 检查空白标识符 check-blank: true # 忽略的函数 ignore: fmt:.*,io/ioutil:^Read.* # govet 配置 govet: # 检查影子变量 check-shadowing: true # 启用所有检查 enable-all: true # 禁用特定检查 disable: - fieldalignment # 字段对齐检查可能过于严格 # gocyclo 配置 gocyclo: # 圈复杂度阈值 min-complexity: 15 # goconst 配置 goconst: # 最小字符串长度 min-len: 3 # 最小出现次数 min-occurrences: 3 # 忽略测试文件 ignore-tests: true # gocritic 配置 gocritic: # 启用的检查 enabled-tags: - diagnostic - style - performance - experimental # 禁用的检查 disabled-checks: - commentedOutCode - whyNoLint # gosec 配置 gosec: # 严重性级别 severity: medium # 置信度级别 confidence: medium # 排除的规则 excludes: - G104 # 审计错误未检查 # revive 配置 revive: # 最小置信度 min-confidence: 0.8 # 规则配置 rules: - name: blank-imports - name: context-as-argument - name: context-keys-type - name: dot-imports - name: error-return - name: error-strings - name: error-naming - name: exported - name: if-return - name: increment-decrement - name: var-naming - name: var-declaration - name: package-comments - name: range - name: receiver-naming - name: time-naming - name: unexported-return - name: indent-error-flow - name: errorf # stylecheck 配置 stylecheck: # 检查所有内容 checks: ["all"] # misspell 配置 misspell: # 语言 locale: US # 忽略的单词 ignore-words: - greatestworks - mmo - gameplay # whitespace 配置 whitespace: multi-if: true multi-func: true # wrapcheck 配置 wrapcheck: # 忽略的包 ignoreSigs: - .Errorf( - errors.New( - errors.Unwrap( - .Wrap( - .Wrapf( # Issues 配置 issues: # 排除默认的排除规则 exclude-use-default: false # 最大 issues 数量,0 表示无限制 max-issues-per-linter: 0 max-same-issues: 0 # 新代码检查 new: false # 排除的规则 exclude-rules: # 排除测试文件的某些检查 - path: _test\.go linters: - gocyclo - errcheck - dupl - gosec - goconst # 排除生成的文件 - path: \.pb\.go linters: - all # 排除 main 函数的某些检查 - path: cmd/ text: "main function" linters: - revive # 排除特定的错误消息 - text: "Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked" linters: - errcheck # 排除 G104: Errors unhandled - text: "G104:" linters: - gosec # 排除包注释检查(对于 main 包) - text: "package-comments" path: cmd/ linters: - revive # 包含的文件模式 include: - EXC0002 # disable excluding of issues about comments from golint # 严重性配置 severity: # 默认严重性 default-severity: error # 规则严重性 rules: - linters: - dupl severity: info - linters: - goconst severity: warning ================================================ FILE: .goreleaser.yaml ================================================ # This is an example .goreleaser.yml file with some sensible defaults. # Make sure to check the documentation at https://goreleaser.com before: hooks: # You may remove this if you don't use go modules. - go mod tidy # you may remove this if you don't need go generate - go generate ./... builds: - env: - CGO_ENABLED=0 goos: - linux - windows - darwin archives: - replacements: darwin: Darwin linux: Linux windows: Windows 386: i386 amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ incpatch .Version }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' # modelines, feel free to remove those if you don't want/use them: # yaml-language-server: $schema=https://goreleaser.com/static/schema.json # vim: set ts=2 sw=2 tw=0 fo=cnqoj ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to GreatestWorks Thanks for your interest in contributing! This project implements a distributed MMO game server in Go using DDD and a layered microservice architecture. To keep quality high and changes consistent, please follow the guidelines below. If you only read one extra document, read this one from the repo: - .github/instructions/gw.instructions.md (authoritative engineering conventions, architecture, and dos/don'ts) ## Prerequisites - Go 1.24+ - MongoDB 4.4+ (5.0+ recommended) - Redis 6.0+ (7.0+ recommended) - Optional: Docker 20.10+, Docker Compose ## Project Architecture (Quick Recap) - DDD + layered architecture (domain, application, infrastructure, interfaces) - Services: auth-service (HTTP:8080), gateway-service (TCP:9090), game-service (Go RPC:8081) - Storage: MongoDB (primary), Redis (cache) - Protocols: HTTP (auth), TCP (gateway), Go RPC (gateway↔game) - Protobuf definitions in /proto; prefer using scripts/generate_proto.(bat|sh) See more details in .github/instructions/gw.instructions.md. ## Getting Started - Fork and clone the repo - Create a branch from main: feature/ or fix/ - Run: - go fmt ./... - go mod tidy (after dependency changes) - go test ./... ## Development Standards - Follow Go naming conventions; keep package names short and lower-case. - Use internal/infrastructure/logging for all logs (no fmt.Println). - Always pass context.Context across boundaries; honor cancellation/timeouts. - Keep domain entities’ fields private; expose behavior via methods. - Application layer orchestrates use cases; do not access DB/Redis directly here. - Infrastructure layer implements persistence/cache/messaging/network. - Interface adapters (HTTP/TCP/RPC) map DTO/proto <-> domain models. ## Errors and Observability - Use internal/errors for domain errors and map appropriately in interfaces. - Wrap errors with context: fmt.Errorf("...: %w", err) - Structured logging (json) with key fields: service, module, player_id, trace_id. - Optional pprof per service via configs/*.yaml; expose only in trusted networks. ## Protocols & Compatibility - When changing .proto files, preserve backward compatibility: - Only add fields; do not change or reuse existing tags - Deletions should be reserved - Update gateway and game handling logic consistently when protocol changes - Generate code with scripts/generate_proto.bat (Windows) or .sh (Unix) ## Configuration - All services load from configs/*.yaml via internal/config; never hardcode ports/secrets/URIs. - Adding new config fields requires: - Updating the example files under configs/* - Updating loading/validation and defaults - Documenting changes in README or service docs ## Dependencies - Prefer stdlib and existing deps; avoid introducing heavy/CGO deps. - If adding a new dependency: 1) Justify the choice and alternatives; 2) Pin versions; 3) Ensure Go 1.24 compatibility. - After changes: go mod tidy ## Testing - Unit tests co-located with code (_test.go), table-driven where possible. - Cover core branches; ensure `go test ./...` passes before submitting PRs. - Integration/E2E via tools/simclient (smoke/feature/load modes). Provide minimal runnable scripts/configs for new protocols or interfaces. ## Commit Messages and PRs - Prefer Conventional Commits: - feat: new feature - fix: bug fix - refactor: refactoring without behavior change - docs: documentation updates - test: testing-related changes - chore/build/ci: build or pipeline updates - Keep changes small and focused; avoid unrelated refactors. - In PR description include: - Motivation and design summary - Impact surface (protocol/config/data/perf) - Verification steps (unit tests and/or simclient scenarios) - Any config/script/docs updates included ## Running Locally - Windows: - scripts/start-services.bat - Linux/Mac: - ./scripts/start-services.sh - Or run each service via `go run cmd//main.go` ## Generating Protobuf Code - Windows: `scripts/generate_proto.bat` - Unix: `scripts/generate_proto.sh` - Do not manually scatter generated files; use the scripts and keep backward compatibility. ## Do / Don't Quick List Do: - Keep changes minimal and reversible - Respect layering and DDD boundaries - Log via infrastructure/logging - Pass context and close resources via defer Don’t: - Break proto compatibility (tag changes or reuse) - Hardcode ports/secrets/URIs - Introduce heavy/CGO deps without strong justification - Access DB/Redis from interface or application layers ## Questions Open an issue or start a discussion if anything is unclear. Thanks for contributing! ================================================ FILE: Dockerfile ================================================ # ============================================================================= # 多阶段构建优化版 Dockerfile # ============================================================================= # 构建阶段 - 使用官方 Go 镜像 FROM golang:1.24-alpine AS builder # 设置构建参数 ARG BUILD_VERSION=dev ARG BUILD_TIME ARG GIT_COMMIT # 选择要构建的服务包,默认构建 game-service,可传入 ./cmd/auth-service 或 ./cmd/gateway-service ARG SERVICE_PACKAGE=./cmd/game-service # 安装构建依赖 RUN apk add --no-cache \ git \ ca-certificates \ tzdata \ upx # 设置工作目录 WORKDIR /build # 优化 Go 模块缓存 COPY go.mod go.sum ./ RUN go mod download && go mod verify # 复制源代码 COPY . . # 构建优化的二进制文件 RUN CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 \ go build \ -a \ -installsuffix cgo \ -ldflags="-s -w -X main.version=${BUILD_VERSION} -X main.buildTime=${BUILD_TIME} -X main.gitCommit=${GIT_COMMIT}" \ -o server \ ${SERVICE_PACKAGE} # 使用 UPX 压缩二进制文件(可选) RUN upx --best --lzma server # ============================================================================= # 运行阶段 - 使用 scratch 最小化镜像 # ============================================================================= FROM scratch AS runtime # 从构建阶段复制必要文件 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=builder /build/server /server # 复制配置文件(如果存在) COPY --from=builder /build/configs/ /configs/ # 设置环境变量 ENV TZ=Asia/Shanghai ENV GIN_MODE=release ENV LOG_LEVEL=info # 暴露端口 EXPOSE 8080 8081 9090 # 添加健康检查用户 USER 65534:65534 # 运行应用 ENTRYPOINT ["/server"] # ============================================================================= # 开发阶段 - 包含调试工具 # ============================================================================= FROM alpine:latest AS development # 安装开发工具 RUN apk add --no-cache \ ca-certificates \ tzdata \ curl \ wget \ netcat-openbsd \ htop \ strace # 创建非root用户 RUN addgroup -g 1000 appgroup && \ adduser -D -s /bin/sh -u 1000 -G appgroup appuser # 设置工作目录 WORKDIR /app # 从构建阶段复制文件 COPY --from=builder /build/server . COPY --from=builder /build/configs/ ./configs/ # 创建必要目录 RUN mkdir -p /var/log/mmo-server && \ chown -R appuser:appgroup /app /var/log/mmo-server # 切换到非root用户 USER appuser # 设置环境变量 ENV TZ=Asia/Shanghai ENV GIN_MODE=debug ENV LOG_LEVEL=debug # 暴露端口 EXPOSE 8080 8081 9090 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD wget --no-verbose --tries=1 --spider --timeout=5 http://localhost:8080/health || exit 1 # 运行应用 CMD ["./server"] # ============================================================================= # 默认目标为生产环境 # ============================================================================= FROM runtime AS final ================================================ FILE: Makefile ================================================ # GreatestWorks MMO 游戏服务器 Makefile .PHONY: help build run stop clean test lint format docker-build docker-run docker-stop health-check # 默认目标 help: @echo "GreatestWorks MMO 游戏服务器" @echo "可用命令:" @echo " build - 构建项目" @echo " run - 运行服务" @echo " stop - 停止服务" @echo " clean - 清理构建文件" @echo " test - 运行测试" @echo " lint - 代码检查" @echo " format - 格式化代码" @echo " docker-build - 构建Docker镜像" @echo " docker-run - 运行Docker服务" @echo " docker-stop - 停止Docker服务" @echo " health-check - 健康检查" # 构建项目 build: @echo "构建项目..." go build -o bin/game-service ./cmd/game-service go build -o bin/auth-service ./cmd/auth-service go build -o bin/gateway-service ./cmd/gateway-service # 运行服务 run: build @echo "启动游戏服务..." ./bin/game-service # 停止服务 stop: @echo "停止服务..." pkill -f game-service || true # 清理构建文件 clean: @echo "清理构建文件..." rm -rf bin/ rm -rf logs/ docker system prune -f # 运行测试 test: @echo "运行测试..." go test -v ./... # 代码检查 lint: @echo "代码检查..." golangci-lint run # 格式化代码 format: @echo "格式化代码..." go fmt ./... goimports -w . # 构建Docker镜像 docker-build: @echo "构建Docker镜像..." docker build -t greatestworks/mmo-server:latest . # 运行Docker服务 docker-run: @echo "启动Docker服务..." chmod +x scripts/*.sh docker-compose up -d # 停止Docker服务 docker-stop: @echo "停止Docker服务..." docker-compose down # 健康检查 health-check: @echo "健康检查..." chmod +x scripts/health-check.sh ./scripts/health-check.sh # 开发环境快速启动 dev: docker-run @echo "开发环境已启动" @echo "HTTP服务器: http://localhost:8080" @echo "健康检查: http://localhost:8080/health" @echo "指标监控: http://localhost:8080/metrics" # 生产环境部署 deploy: docker-build @echo "部署到生产环境..." docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d # 查看日志 logs: @echo "查看服务日志..." docker-compose logs -f # 进入容器 shell: @echo "进入游戏服务容器..." docker exec -it mmo-server /bin/sh # 运行模拟客户端 simclient: @echo "运行模拟客户端 (integration 模式)..." go run ./tools/simclient/cmd/simclient -mode integration ================================================ FILE: README.en.md ================================================ # Greatest Works - Distributed MMO Game Server A distributed, microservices-based MMO game server built with Go and Domain-Driven Design (DDD). It targets high throughput, horizontal scalability, and clean maintainability. ## Overview Greatest Works adopts a DDD layered architecture and a multi-node deployment model. The platform splits concerns into dedicated services and uses different transports for each link to balance performance and simplicity. ### Highlights - Production-grade build: compiles cleanly on Go 1.24+ - DDD architecture with clear boundaries and layering - Distributed by design: horizontally scalable services - Microservices: Auth, Gateway, Game, cleanly separated - Structured logging and optional profiling endpoints - Container-ready: Docker and Docker Compose ## Core Features - DDD with clear separation of concerns - Multi-protocol networking: HTTP + TCP + Go RPC - Storage strategy: MongoDB (primary) + Redis (cache) - JWT-based authentication - Realtime state sync and event broadcasting (AOI) - Fault-tolerant error handling and observability ## Distributed Architecture ### Services - Auth Service - Protocol: HTTP - Port: 8080 - Responsibilities: authentication, authorization, session - Gateway Service - Protocol: TCP - Port: 9090 - Responsibilities: client connections, protocol framing, routing - Game Service - Protocol: Go RPC - Port: 8081 - Responsibilities: core game logic, aggregates, rules ### Communication ```mermaid flowchart LR subgraph Client[Client] C1[Game Client] end subgraph Auth[Auth Service\nHTTP:8080] A1[REST API] end subgraph Gateway[Gateway Service\nTCP:9090] G1[Conn Manager] G2[Router] end subgraph Game[Game Service\nGo RPC:8081] S1[Gameplay] S2[Domain] end subgraph Data[Storage & Messaging] M[(MongoDB)] R[(Redis)] N[(NATS)] end C1 -- HTTP --> A1 C1 -- TCP --> G1 G1 -- RPC --> S1 S1 <-- RPC --> G2 S1 --> M S1 --> R S1 -.events.-> N G1 --> R ``` - Client ↔ Auth: HTTP (REST) for login/registration - Client ↔ Gateway: TCP binary protocol for game messages - Gateway ↔ Game: Go RPC for internal calls - Auth ↔ Game: Go RPC for user/session sync ## Simulator Client for E2E and Load Tests Located in `tools/simclient`. Run as a standalone CLI and drive either a single end-to-end scenario or multi-user load. ### Quickstart ```powershell go run ./tools/simclient/cmd/simclient -mode integration ``` ### E2E Scenario Built-in end-to-end flows covering login → connect → move → cast skill → logout. ```powershell # Single E2E run go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/e2e.yaml # Load (concurrent users) go run ./tools/simclient/cmd/simclient -mode load -config tools/simclient/e2e_load.yaml # Toggle auth quickly go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/e2e.yaml -auth go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/e2e.yaml -no-auth ``` For advanced details (framing, JSON payloads, timings, metrics), see `tools/simclient/README_E2E.md`. #### E2E Sequence (Overview) ```mermaid sequenceDiagram participant SC as SimClient participant AUTH as Auth Service participant GW as Gateway participant GS as Game Note over SC: Optional auth SC->>AUTH: POST /api/v1/auth/login AUTH-->>SC: 200 OK (token) SC->>GW: TCP Connect GW-->>SC: Conn Ack SC->>GW: MsgPlayerLogin(token|player) GW->>GS: RPC Login GS-->>GW: LoginOK(pos,map) GW-->>SC: LoginOK(pos,map) SC->>GW: MsgPlayerMove(x,y,z) GW->>GS: RPC Move GS-->>GW: MoveOK GW-->>SC: MoveOK GW-->>Clients: AOI broadcast SC->>GW: MsgBattleSkill(skill_id,target) GW->>GS: RPC CastSkill GS-->>GW: Result{damage,crit} GW-->>SC: SkillResult GW-->>Clients: AOI broadcast SC->>GW: MsgPlayerLogout GW->>GS: RPC Logout + persist position GS-->>GW: LogoutOK GW-->>SC: LogoutOK ``` ## Latest Updates (2025-10) - Character position persistence: restore on login; save on logout/disconnect - Combat damage and critical hits (10% chance, 1.5x multiplier); result broadcast via AOI - New E2E configs: `tools/simclient/e2e.yaml` and `e2e_load.yaml` - Gateway now requires MongoDB to persist positions (use `docker-compose up -d` locally) ## Quick Start ### Requirements - Go 1.24+ - MongoDB 4.4+ (5.0+ recommended) - Redis 6.0+ (7.0+ recommended) - Docker 20.10+ (optional) ### Install deps ```bash go mod tidy ``` ### Run services Windows (PowerShell): ```powershell scripts/start-services.bat ``` Linux/macOS: ```bash ./scripts/start-services.sh ``` Manual run: ```bash go run cmd/auth-service/main.go go run cmd/game-service/main.go go run cmd/gateway-service/main.go ``` > Note: The Gateway service requires a reachable MongoDB instance for position persistence. Consider `docker-compose up -d` to bootstrap dependencies locally. ### Service Endpoints - Auth: http://localhost:8080 - Gateway: tcp://localhost:9090 - Game: rpc://localhost:8081 (internal only) ## Architecture Diagrams ### DDD Layers ```mermaid flowchart TB subgraph Interface[Interface Layer] HTTP[HTTP\nREST] TCP[TCP\nGame Protocol] RPC[RPC\nInternal] end subgraph Application[Application Layer] CMD[Commands] QRY[Queries] APP[Services / Orchestration] end subgraph Domain[Domain Layer] AGG[Aggregates / Entities / VOs] DSVC[Domain Services] DEVT[Domain Events] end subgraph Infra[Infrastructure Layer] PERS[Persistence] MSG[Messaging] NET[Network] CFG[Config] LOG[Logging] end Interface --> Application --> Domain Domain --> Infra ``` ## Profiling (pprof) Enable via `monitoring.profiling` in config. Default ports: Game 6060, Auth 6061, Gateway 6062. Use `go tool pprof` against `http://:/debug/pprof/`. ## Deployment ### 🐳 Docker Deployment ```bash # Start complete environment (includes MongoDB, Redis) docker-compose up -d # Check service status docker-compose ps # View logs docker-compose logs -f ``` ### ☸️ Kubernetes Deployment The project provides complete local Kubernetes deployment configurations supporting both Docker Desktop and Minikube. All k8s manifests are in the `k8s/local/` directory. #### 📋 Prerequisites - **Kubernetes**: Docker Desktop built-in k8s or Minikube 1.28+ - **kubectl**: Version matching your cluster - **Docker**: 20.10+ (for building images) - **PowerShell**: 5.1+ (Windows) or Bash (Linux/macOS) #### 🚀 Quick Deploy (3 Steps) **Step 1: Build Service Images** ```powershell # Windows PowerShell ./scripts/build-images.ps1 -Tag dev # Linux / macOS ./scripts/build-images.sh -t dev ``` Build artifacts: - `greatestworks-auth:dev` (Auth service) - `greatestworks-game:dev` (Game service) - `greatestworks-gateway:dev` (Gateway service) **Step 2: Load Images into Kubernetes Nodes** > This step resolves the issue where Docker Desktop k8s cannot directly use local images. ```powershell # Windows PowerShell ./scripts/load-images-to-k8s.ps1 -Tag dev # Minikube users alternative minikube image load greatestworks-auth:dev minikube image load greatestworks-game:dev minikube image load greatestworks-gateway:dev minikube image load mongo:7 minikube image load redis:7 ``` **Step 3: Deploy to Cluster** ```powershell # Create namespace and deploy all services kubectl apply -f k8s/local/namespace.yaml kubectl apply -f k8s/local/mongodb.yaml kubectl apply -f k8s/local/redis.yaml kubectl apply -f k8s/local/configmap-gateway.yaml kubectl apply -f k8s/local/auth-service.yaml kubectl apply -f k8s/local/game-service.yaml kubectl apply -f k8s/local/gateway-service.yaml # Wait for Pods to be ready (about 1-2 minutes) kubectl -n gaming get pods -w ``` Expected output (all Pods `Running` with `READY` as `1/1`): ``` NAME READY STATUS RESTARTS AGE auth-service-xxxxxxxxx-xxxxx 1/1 Running 0 2m game-service-xxxxxxxxx-xxxxx 1/1 Running 0 2m gateway-service-xxxxxxxxx-xxxxx 1/1 Running 0 2m mongodb-xxxxxxxxx-xxxxx 1/1 Running 0 2m redis-xxxxxxxxx-xxxxx 1/1 Running 0 2m ``` #### 🌐 Accessing Services After successful deployment, services are exposed via NodePort locally: | Service | Protocol | Port | Access URL | Purpose | |---------|----------|------|------------|---------| | **Auth Service** | HTTP | 30080 | `http://localhost:30080` | User login, registration, JWT auth | | **Gateway Service** | TCP | 30909 | `localhost:30909` | Game client persistent connection | | **Game Service** | RPC | 8081 | Internal only | Game logic (not exposed) | | **MongoDB** | TCP | 27017 | Internal only | Data persistence | | **Redis** | TCP | 6379 | Internal only | Cache & sessions | **Verify Service Availability:** ```powershell # View service endpoints kubectl -n gaming get svc # View Pod logs kubectl -n gaming logs -l app=auth-service --tail=50 kubectl -n gaming logs -l app=gateway-service --tail=50 kubectl -n gaming logs -l app=game-service --tail=50 # Test auth service health (if /health endpoint is implemented) curl http://localhost:30080/health ``` #### 🔧 Common Operations **Check Cluster Status:** ```powershell # View all resources kubectl -n gaming get all # View Pod details kubectl -n gaming describe pod # Enter container for debugging kubectl -n gaming exec -it -- sh ``` **Restart Services (after config changes):** ```powershell # Restart single service kubectl -n gaming rollout restart deploy/auth-service # Restart all services kubectl -n gaming rollout restart deploy --all # Wait for rollout completion kubectl -n gaming rollout status deploy/auth-service ``` **Update Images (after code changes):** ```powershell # 1. Rebuild images ./scripts/build-images.ps1 -Tag dev # 2. Reload into k8s nodes ./scripts/load-images-to-k8s.ps1 -Tag dev # 3. Force restart Pods (trigger image reload) kubectl -n gaming rollout restart deploy --all ``` **Clean Up Environment:** ```powershell # Delete all resources (keep namespace) kubectl delete -f k8s/local/gateway-service.yaml kubectl delete -f k8s/local/game-service.yaml kubectl delete -f k8s/local/auth-service.yaml kubectl delete -f k8s/local/configmap-gateway.yaml kubectl delete -f k8s/local/redis.yaml kubectl delete -f k8s/local/mongodb.yaml # Delete namespace (cascading delete all resources) kubectl delete namespace gaming ``` #### 📦 Push Images to Remote Registry (Optional) If you need to deploy on multiple machines or in CI/CD environments, push images to Docker Hub or a private registry: **Method 1: Use Publish Script** ```powershell # Login to Docker Hub docker login # Push images to your repository ./scripts/publish-images.ps1 ` -Registry docker.io ` -Namespace YOUR_DOCKERHUB_USERNAME ` -Tag dev ` -IncludeInfra # Optional: also push mongo and redis ``` **Method 2: Use Kustomize Overlay** The project provides `k8s/local/overlays/registry/` config to automatically replace image paths during deployment: ```powershell # 1. Edit k8s/local/overlays/registry/kustomization.yaml # Replace REPLACE_ME with your registry namespace, e.g.: docker.io/phuhao00 # 2. Deploy with kustomize kubectl apply -k k8s/local/overlays/registry # 3. Verify deployment kubectl -n gaming get pods ``` #### 🐛 Troubleshooting **Issue 1: Pod Status `ImagePullBackOff` or `ErrImagePull`** **Cause**: Kubernetes cannot pull images from local Docker. **Solution**: - Ensure `./scripts/load-images-to-k8s.ps1` has been executed - Check Pod's `imagePullPolicy` is `IfNotPresent` - Verify image is loaded: `kubectl -n gaming describe pod | Select-String -Pattern "Image"` **Issue 2: Pod Status `CrashLoopBackOff`** **Cause**: Service fails to start, usually due to config errors or dependencies not ready. **Solution**: ```powershell # View crash logs kubectl -n gaming logs --previous # Common causes: # - MongoDB/Redis not ready → Wait for infrastructure Pods to start first # - Environment variable misconfiguration → Check Deployment env config # - Port conflict → Check containerPort and Service port mapping ``` **Issue 3: Cannot Access Service via NodePort** **Cause**: NodePort not properly mapped or firewall blocking. **Solution**: ```powershell # Verify Service config kubectl -n gaming get svc # Confirm NodePort range (default 30000-32767) # Check Windows Firewall or Docker Desktop network settings # Temporary workaround: use port forwarding kubectl -n gaming port-forward svc/auth-service 8080:8080 kubectl -n gaming port-forward svc/gateway-service 9090:9090 ``` **Issue 4: MongoDB/Redis Connection Failed** **Cause**: Service startup order issue or DNS resolution failure. **Solution**: ```powershell # Check if infrastructure services are running kubectl -n gaming get pods -l app=mongodb kubectl -n gaming get pods -l app=redis # Verify service DNS resolution (test in Pod) kubectl -n gaming exec -it -- nslookup mongodb kubectl -n gaming exec -it -- nslookup redis # Check service endpoints kubectl -n gaming get endpoints ``` #### 📊 Monitoring & Logging **Real-time Log Viewing:** ```powershell # Follow single service kubectl -n gaming logs -f deploy/auth-service # View all service logs (multiple windows) kubectl -n gaming logs -f -l app=auth-service kubectl -n gaming logs -f -l app=game-service kubectl -n gaming logs -f -l app=gateway-service # View Pod events kubectl -n gaming get events --sort-by='.lastTimestamp' ``` **Resource Usage:** ```powershell # View Pod resource consumption kubectl -n gaming top pods # View node resources kubectl top nodes ``` #### 🔐 Production Environment Enhancements Local deployment uses simplified configs. For production, consider: **Security:** - Use Kubernetes Secrets for sensitive data (DB passwords, JWT keys) - Enable NetworkPolicy to restrict Pod-to-Pod communication - Configure RBAC access control - Use TLS for inter-service communication **High Availability:** - Increase replicas (`replicas: 3`) - Configure PodDisruptionBudget - Use StatefulSet for stateful services (MongoDB) - Enable HorizontalPodAutoscaler for auto-scaling **Persistence:** - Configure PersistentVolumeClaim for MongoDB (avoid emptyDir) - Regular database backups - Configure data retention policies **Example: Production-grade MongoDB Deployment** ```yaml # Use StatefulSet + PVC (recommended for production) apiVersion: apps/v1 kind: StatefulSet metadata: name: mongodb namespace: gaming spec: serviceName: mongodb replicas: 3 selector: matchLabels: app: mongodb template: spec: containers: - name: mongodb image: mongo:7 volumeMounts: - name: mongo-data mountPath: /data/db volumeClaimTemplates: - metadata: name: mongo-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi ``` #### 🎯 Performance Tuning Tips **Resource Quota Adjustment:** Modify resource limits in `k8s/local/*-service.yaml` based on actual load: ```yaml resources: requests: cpu: "500m" # Guaranteed allocation memory: "512Mi" limits: cpu: "2" # Maximum usage memory: "2Gi" ``` **Concurrent Connection Optimization:** Adjust gateway config in `k8s/local/configmap-gateway.yaml`: ```yaml server: tcp: max_connections: 50000 # Adjust based on node capacity buffer_size: 8192 # Increase buffer size ``` **Database Connection Pool:** Optimize connection pool parameters in service configs: ```yaml database: mongodb: max_pool_size: 200 min_pool_size: 50 redis: pool_size: 200 min_idle_conns: 50 ``` #### 📚 Related Documentation - [Kubernetes Manifest Documentation](k8s/local/README.md) (to be created) - [Docker Image Build Script](scripts/build-images.ps1) - [Image Load Script](scripts/load-images-to-k8s.ps1) - [Kustomize Overlay](k8s/local/overlays/registry/) --- ## Docs & Contributing - Issues: https://github.com/phuhao00/greatestworks/issues - Discussions: https://github.com/phuhao00/greatestworks/discussions - See CONTRIBUTING.md for guidelines ## License MIT. See LICENSE for details. --- If this project helps you, please consider starring the repo. Thanks! ================================================ FILE: README.md ================================================ # Greatest Works - 分布式MMO游戏服务器 > English version: see [README.en.md](README.en.md) 基于Go语言和领域驱动设计(DDD)架构开发的分布式大型多人在线游戏服务器,采用现代化微服务设计,支持高并发和分布式部署。 ## 🎯 项目概述 这是一个企业级的分布式MMO游戏服务器项目,采用领域驱动设计(Domain-Driven Design)架构模式,提供高性能、可扩展、易维护的游戏服务器解决方案。项目采用分布式多节点架构,支持独立部署和扩展。 ### 🏆 项目亮点 - **✅ 编译通过**: 所有代码已修复编译错误,项目可正常构建 - **🏗️ DDD架构**: 完整的领域驱动设计实现 - **🌐 分布式**: 多服务独立部署,支持水平扩展 - **🔧 微服务**: 认证、网关、游戏服务分离 - **📊 监控**: 完整的日志和监控体系 - **🐳 容器化**: Docker和Kubernetes支持 ## ✨ 核心特性 - 🏗️ **DDD架构**: 采用领域驱动设计,清晰的架构分层和职责分离 - 🌐 **分布式设计**: 多节点独立部署,支持水平扩展 - 🚀 **高性能网络**: 基于Go原生RPC + TCP + HTTP多协议支持 - 🔧 **微服务架构**: 认证服务、网关服务、游戏服务独立部署 - 💾 **多数据库支持**: MongoDB + Redis 混合存储策略 - 🔐 **安全认证**: JWT认证系统,保障用户数据安全 - 🎮 **完整游戏功能**: 涵盖现代MMO游戏的核心系统 - 📊 **实时同步**: 高频率的游戏状态同步和事件处理 - 🛡️ **容错设计**: 完善的错误处理、监控和恢复机制 - 🐳 **容器化部署**: Docker和Kubernetes支持 - 📚 **完整文档**: 详细的API文档和架构说明 ## 🏗️ 分布式架构设计 本项目采用分布式多节点架构,将游戏服务器拆分为三个独立的服务节点: ### 服务节点 #### 🔐 认证服务 (Auth Service) - **协议**: HTTP - **端口**: 8080 - **职责**: 用户认证、授权、会话管理 - **功能**: 登录、注册、令牌管理、权限控制 #### 🌐 网关服务 (Gateway Service) - **协议**: TCP - **端口**: 9090 - **职责**: 客户端连接管理、协议转换、负载均衡 - **功能**: 连接管理、消息路由、协议转换 #### 🎮 游戏服务 (Game Service) - **协议**: Go原生RPC - **端口**: 8081 - **职责**: 核心游戏逻辑、领域模型、业务规则 - **功能**: 玩家管理、战斗系统、排行榜、社交系统 ### 通信协议 ```mermaid flowchart LR subgraph Client[客户端] C1[游戏客户端] end subgraph Auth[认证服务\nHTTP:8080] A1[REST API] end subgraph Gateway[网关服务\nTCP:9090] G1[连接管理] G2[消息路由] end subgraph Game[游戏服务\nGo RPC:8081] S1[玩法逻辑] S2[领域模型] end subgraph Data[数据与消息] M[(MongoDB)] R[(Redis)] N[(NATS)] end C1 -- HTTP --> A1 C1 -- TCP --> G1 G1 -- RPC --> S1 S1 <-- RPC --> G2 S1 --> M S1 --> R S1 -.事件发布.-> N G1 --> R ``` - **客户端 ↔ 认证服务**: HTTP (RESTful API) - 用户认证、注册、登录 - **客户端 ↔ 网关服务**: TCP (游戏协议) - 游戏数据交互 - **网关服务 ↔ 游戏服务**: Go原生RPC (内部通信) - 游戏逻辑处理 - **认证服务 ↔ 游戏服务**: Go原生RPC (服务间通信) - 用户状态同步 - **客户端不直接与游戏服务通信**: 所有游戏数据通过网关服务转发 ## 🧪 集成测试与压测模拟客户端 项目提供了位于 `tools/simclient` 的模拟客户端,可用于端到端集成测试与压测实验。 ### 快速体验 ```powershell go run ./tools/simclient/cmd/simclient -mode integration ``` 默认会跳过认证服务并连接网关发送基础心跳与移动报文。若需启用认证或自定义参数,可指定配置文件: ```powershell go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/config.example.yaml ``` ### E2E 场景(端到端流程) 已内置完整的 E2E 场景与示例配置,覆盖“认证→连接→登录→移动→施法→登出”的完整链路: ```powershell # 运行单次端到端场景(集成验证) go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/e2e.yaml # 运行端到端压测(并发多用户) go run ./tools/simclient/cmd/simclient -mode load -config tools/simclient/e2e_load.yaml # 可选:快速开关认证流程 # 强制启用认证 go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/e2e.yaml -auth # 强制跳过认证 go run ./tools/simclient/cmd/simclient -mode integration -config tools/simclient/e2e.yaml -no-auth ``` 说明与高级用法请参阅 `tools/simclient/README_E2E.md`,包含:报文头+JSON载荷封装、动作时序、错误排查、指标输出等。 #### E2E 交互时序(概览) ```mermaid sequenceDiagram participant SC as SimClient participant AUTH as 认证服务 participant GW as 网关服务 participant GS as 游戏服务 Note over SC: 可选:认证 SC->>AUTH: POST /api/v1/auth/login AUTH-->>SC: 200 OK (token) SC->>GW: TCP Connect GW-->>SC: Conn Ack SC->>GW: MsgPlayerLogin(token|player) GW->>GS: RPC Login GS-->>GW: LoginOK(pos,map) GW-->>SC: LoginOK(pos,map) SC->>GW: MsgPlayerMove(x,y,z) GW->>GS: RPC Move GS-->>GW: MoveOK GW-->>SC: MoveOK GW-->>Clients: AOI 广播 SC->>GW: MsgBattleSkill(skill_id,target) GW->>GS: RPC CastSkill GS-->>GW: Result{damage,crit} GW-->>SC: SkillResult GW-->>Clients: AOI 广播 SC->>GW: MsgPlayerLogout GW->>GS: RPC Logout + 持久化位置 GS-->>GW: LogoutOK GW-->>SC: LogoutOK ``` ### 压测模式 ```powershell go run ./tools/simclient/cmd/simclient -mode load -config tools/simclient/config.example.yaml -users 200 -concurrency 50 ``` 压测模式会按配置并发启动虚拟玩家,输出各动作的最小值、平均值、P95 与最大耗时,并记录失败样例。 ### 集成测试 在服务运行的情况下,可以开启模拟客户端的冒烟测试: ```powershell $Env:SIMCLIENT_E2E="1"; go test ./tools/simclient -run TestBasicScenarioSmoke -count=1 ``` 未设置环境变量时,测试会自动跳过,避免在未部署依赖服务时误报失败。 ### 功能级场景(独立功能验证) 模拟客户端现在支持基于功能库的可配置场景,便于对单个系统(如玩家、战斗、宠物等)进行独立验证。通过在配置文件中将 `scenario.type` 设为 `feature`,即可按功能清单驱动消息序列: ```yaml scenario: name: "pet-feature-check" type: "feature" # 可选:basic(默认)或 feature features: # 复用内置功能库(详见 tools/simclient/feature_library.go) - "player.basic" - "pet.basic" actions: # 也可追加自定义动作 - name: "quest.accept" message: "quest.accept" expect_response: true pause: "2s" ``` 常用功能标识包括 `player.login`、`battle.basic`、`pet.summon`、`building.status` 等。每个功能会自动映射到对应的网关消息并记录响应时间。若仅配置 `scenario.type` 而省略 `features`,程序会尝试匹配内置功能;也可以完全使用 `actions` 字段自定义报文序列,支持设置 `flags`、`repeat`、`pause` 等参数,实现更精细的验证脚本。 ## 📁 项目结构 ``` greatestworks/ ├── cmd/ # 应用程序入口 │ ├── auth-service/ # 认证服务 (HTTP:8080) │ │ └── main.go │ ├── gateway-service/ # 网关服务 (TCP:9090) │ │ └── main.go │ └── game-service/ # 游戏服务 (RPC:8081) │ └── main.go ├── configs/ # 配置文件 │ ├── auth-service.yaml # 认证服务配置 │ ├── gateway-service.yaml # 网关服务配置 │ ├── game-service.yaml # 游戏服务配置 │ ├── docker.yaml # Docker环境配置 │ └── config.*.yaml # 环境配置模板 ├── docs/ # 项目文档 │ └── diagrams/ # 架构图表 │ ├── module.drawio # 模块关系图 │ ├── svr.frame.drawio # 服务器架构图 │ └── uml.drawio # UML类图 ├── internal/ # 内部模块 (DDD架构) │ ├── application/ # 应用层 (CQRS + 服务编排) │ │ ├── commands/ # 命令处理器 │ │ ├── handlers/ # 命令/查询总线实现 │ │ ├── interfaces/ # 应用层接口契约 │ │ ├── queries/ # 查询处理器 │ │ └── services/ # 应用服务与 service_registry │ ├── domain/ # 领域层 │ │ ├── player/ # 玩家领域 │ │ │ ├── beginner/ # 新手引导 │ │ │ ├── hangup/ # 挂机系统 │ │ │ ├── honor/ # 荣誉系统 │ │ │ ├── player.go # 玩家聚合根 │ │ │ ├── service.go # 领域服务 │ │ │ └── repository.go # 仓储接口 │ │ ├── battle/ # 战斗领域 │ │ ├── social/ # 社交领域 (31个文件) │ │ ├── building/ # 建筑领域 │ │ ├── pet/ # 宠物领域 │ │ ├── ranking/ # 排行榜领域 │ │ ├── minigame/ # 小游戏领域 │ │ ├── npc/ # NPC领域 │ │ ├── quest/ # 任务领域 │ │ ├── scene/ # 场景领域 (24个文件) │ │ ├── skill/ # 技能领域 │ │ ├── inventory/ # 背包领域 │ │ │ ├── dressup/ # 装扮系统 │ │ │ └── synthesis/ # 合成系统 │ │ └── events/ # 领域事件 │ ├── infrastructure/ # 基础设施层 │ │ ├── persistence/ # 数据持久化 (10个文件) │ │ │ ├── base_repository.go # 基础仓储 │ │ │ ├── player_repository.go # 玩家仓储 │ │ │ ├── battle_repository.go # 战斗仓储 │ │ │ ├── hangup_repository.go # 挂机仓储 │ │ │ ├── weather_repository.go # 天气仓储 │ │ │ ├── plant_repository.go # 植物仓储 │ │ │ └── npc_repository.go # NPC仓储 │ │ ├── cache/ # 缓存服务 │ │ ├── messaging/ # 消息服务 (5个文件) │ │ │ ├── nats_publisher.go # NATS发布者 │ │ │ ├── nats_subscriber.go # NATS订阅者 │ │ │ ├── event_dispatcher.go # 事件分发器 │ │ │ └── worker_pool.go # 工作池 │ │ ├── network/ # 网络服务 │ │ ├── config/ # 配置管理 (7个文件) │ │ ├── logging/ # 日志服务 │ │ ├── auth/ # 认证服务 │ │ ├── container/ # 依赖注入容器 │ │ └── monitoring/ # 监控服务 │ ├── interfaces/ # 接口层 │ │ ├── http/ # HTTP接口 (13个文件) │ │ │ ├── auth/ # 认证接口 │ │ │ ├── gm/ # GM管理接口 │ │ │ └── server.go # HTTP服务器 │ │ ├── tcp/ # TCP接口 (14个文件) │ │ │ ├── handlers/ # TCP处理器 │ │ │ ├── connection/ # 连接管理 │ │ │ └── protocol/ # 协议定义 │ │ └── rpc/ # RPC接口 (4个文件) │ ├── events/ # 事件系统 │ │ ├── eventbus.go # 事件总线 │ │ ├── middleware.go # 事件中间件 │ │ └── worker.go # 事件工作器 │ ├── errors/ # 错误处理 │ │ └── domain_errors.go # 领域错误 │ ├── proto/ # 协议定义 │ │ ├── battle/ # 战斗协议 │ │ ├── player/ # 玩家协议 │ │ └── common/ # 通用协议 │ └── readme.md # 内部模块说明 ├── proto/ # Protocol Buffers定义 │ ├── battle.proto # 战斗协议 │ ├── player.proto # 玩家协议 │ ├── pet.proto # 宠物协议 │ └── common.proto # 通用协议 ├── scripts/ # 开发脚本 │ ├── start-services.bat # Windows启动脚本 │ ├── start-services.sh # Linux/Mac启动脚本 │ ├── build.sh # 构建脚本 │ ├── deploy.sh # 部署脚本 │ ├── generate_proto.sh # 协议生成脚本 │ └── setup-dev.sh # 开发环境设置 ├── docker-compose.yml # Docker编排 ├── Dockerfile # Docker镜像 ├── Makefile # 构建工具 ├── go.mod # Go模块定义 ├── go.work # Go工作空间 └── README.md # 项目说明 ``` ## 🛠️ 技术栈 ### 核心技术 - **语言**: Go 1.24+ - **架构模式**: 领域驱动设计 (DDD) + 分布式架构 - **网络协议**: HTTP + TCP + Go原生RPC - **数据库**: MongoDB (主数据库) + Redis (缓存) - **消息队列**: NATS (可选) - **认证**: JWT + 自定义认证 - **服务发现**: 支持Consul、Etcd等 ### 开发工具 - **构建工具**: Make + Go Modules - **容器化**: Docker + Docker Compose - **编排**: Kubernetes - **代码质量**: golangci-lint + 自定义规范 - **文档**: Markdown + 架构图 ### 监控与运维 - **日志**: 结构化日志 + 分级输出 - **监控**: 自定义指标收集(Prometheus 已移除,保留配置项仅为兼容) - **性能剖析**: 内置 `pprof` HTTP 端点,可按服务独立开启/关闭 - **健康检查**: HTTP健康检查接口 - **配置管理**: YAML配置 + 环境变量 #### 🔍 性能剖析 (pprof) - 通过 `monitoring.profiling` 配置块启用,默认 `host=0.0.0.0`,启用时若未指定端口则为 `6060`。 - 示例配置: ```yaml monitoring: profiling: enabled: true host: "0.0.0.0" port: 6061 ``` - 默认示例端口:游戏服务 `6060`、认证服务 `6061`、网关服务 `6062`(可按需调整)。 - 访问方式:`http://:/debug/pprof/`(支持 `profile`, `heap`, `goroutine` 等子路径)。 - 安全建议:仅在受信任网络内开放或通过防火墙/反向代理限制访问;生产环境建议结合 mTLS 或内网隧道。 - Go 原生工具链支持直接采样,例如: ```bash go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 ``` - 常用子路径一览: | 路径 | 数据类型 | 典型用途 | | --- | --- | --- | | `/debug/pprof/profile` | CPU 采样 (默认 30s) | 分析热点函数、CPU 使用率 | | `/debug/pprof/heap` | 堆内存快照 | 排查内存占用与泄漏 | | `/debug/pprof/goroutine` | Goroutine 堆栈 | 定位死锁、阻塞、协程泄漏 | | `/debug/pprof/block` | 阻塞 / 互斥等待 | 查看锁竞争、IO 阻塞(含网络等待) | | `/debug/pprof/mutex` | 互斥锁争用 | 识别锁热点 | | `/debug/pprof/threadcreate` | 线程创建 | 观察系统线程增量 | | `/debug/pprof/trace` | 全局执行轨迹 | 捕获调度、GC、网络事件,生成 `.trace` 文件 | - 采集跟踪 (含网络事件) 示例: ```bash go tool trace -http=:0 http://localhost:6060/debug/pprof/trace?seconds=5 ``` 打开浏览器后可查看网络/系统调用阻塞、协程调度等细节。 #### 🧪 Profiling 快速入门 1. **启用配置**:在目标服务的配置文件中确保 `monitoring.profiling.enabled: true`,并确认端口未被占用。 2. **启动服务**:通过 `make run-`、`docker-compose up` 或自定义脚本启动对应进程。 3. **采集快照**:使用以下命令保存原始数据以便离线分析: ```bash # CPU 采样 60 秒后保存为 cpu.prof curl -o cpu.prof "http://localhost:6060/debug/pprof/profile?seconds=60" # 堆内存快照 curl -o heap.prof "http://localhost:6060/debug/pprof/heap" # Goroutine 栈信息 curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=1" ``` 4. **可视化分析**: ```bash # CLI 分析热点函数 go tool pprof cpu.prof # 启动 Web UI(会自动打开浏览器) go tool pprof -http=:0 heap.prof ``` 5. **高级跟踪**:通过 `go tool trace` 加载第 3 步生成的 `.trace` 文件,可在浏览器中查看网络/系统调用、协程调度和 GC 时间线。 > 端口速查:游戏服务 `6060`、认证服务 `6061`、网关服务 `6062`,可根据部署环境在配置文件中调整。 ## 🎉 最新更新 (2025-10) ### ✅ 核心改进 - **应用层内聚**: 将原 `application/*` 目录整体迁移至 `internal/application`,统一导入路径并引入集中式 `service_registry`。 - **接口适配**: 同步更新 HTTP/TCP/RPC 适配层的依赖路径,保持命令与查询总线的运行一致性。 - **构建可靠性**: 全量执行 `go fmt ./...` 与 `go test ./...`,确保代码风格统一且测试通过。 ### ✨ 新增功能 - 角色位置持久化:登录自动恢复上次地图与坐标,登出/断线即时保存。 - 战斗伤害计算与结果广播:实现基础伤害与暴击(10% 概率,1.5x 倍率),通过 AOI 将结果广播给可见实体。 - 模拟客户端 E2E 场景:新增 `tools/simclient/e2e.yaml` 与 `tools/simclient/e2e_load.yaml`,支持端到端验证与并发压测。 - 依赖提醒:网关服务现需连接 MongoDB 才能完成位置持久化(本地可通过 `docker-compose up -d` 一键拉起依赖)。 ### 🧹 技术债务清理 - 归档旧有的应用层入口说明,将文档与现有目录结构保持一致。 - 补充最新的启动脚本说明,方便在 Windows / Linux 环境快速拉起服务。 - 梳理文档链接与示例配置,移除失效路径。 ## 🚀 快速开始 ### 📋 环境要求 - **Go**: 1.24 或更高版本 - **MongoDB**: 4.4+ (推荐 5.0+) - **Redis**: 6.0+ (推荐 7.0+) - **Docker**: 20.10+ (可选,用于容器化部署) ### 📦 安装依赖 ```bash # 克隆项目 git clone https://github.com/phuhao00/greatestworks.git cd greatestworks # 安装Go依赖 go mod tidy ``` ### ⚙️ 配置文件 项目使用独立的配置文件,每个服务都有自己的配置: **Windows (PowerShell):** ```powershell Copy-Item configs/auth-service.yaml configs/auth-service-dev.yaml Copy-Item configs/gateway-service.yaml configs/gateway-service-dev.yaml Copy-Item configs/game-service.yaml configs/game-service-dev.yaml ``` **Linux / macOS:** ```bash cp configs/auth-service.yaml configs/auth-service-dev.yaml cp configs/gateway-service.yaml configs/gateway-service-dev.yaml cp configs/game-service.yaml configs/game-service-dev.yaml ``` ### 🎮 启动服务 #### 方式一:使用启动脚本(推荐) **Windows:** ```powershell scripts/start-services.bat ``` **Linux/Mac:** ```bash ./scripts/start-services.sh ``` #### 方式二:手动启动 ```bash # 启动认证服务 go run cmd/auth-service/main.go # 启动游戏服务(新终端) go run cmd/game-service/main.go # 启动网关服务(新终端) go run cmd/gateway-service/main.go ``` > 提示:网关服务需要可用的 MongoDB 实例用于玩家位置持久化。若未手动部署数据库,建议先执行 `docker-compose up -d` 启动依赖环境。 #### 方式三:Docker启动 ```bash # 启动完整环境 docker-compose up -d # 查看服务状态 docker-compose ps ``` ### 🔧 服务地址 启动后,各服务将在以下地址运行: - **认证服务**: http://localhost:8080 (客户端可访问) - **网关服务**: tcp://localhost:9090 (客户端可访问) - **游戏服务**: rpc://localhost:8081 (仅内部服务访问,客户端不可直接访问) ### 📡 客户端访问说明 - **客户端 → 认证服务**: 直接HTTP访问,用于用户认证 - **客户端 → 网关服务**: 直接TCP连接,用于游戏数据交互 - **客户端 → 游戏服务**: ❌ 不直接访问,所有游戏逻辑通过网关服务转发 ## 🏛️ 技术架构图 ### 🎯 整体架构 ```mermaid flowchart TB C[客户端] -->|HTTP| AUTH[认证服务\nHTTP:8080] C -->|TCP| GW[网关服务\nTCP:9090] GW -->|Go RPC| GAME[游戏服务\nRPC:8081] GAME --> M[(MongoDB)] GAME --> R[(Redis)] GAME -.事件.-> N[(NATS)] ``` ### 🏗️ DDD分层架构 ```mermaid flowchart TB subgraph Interface[接口层] HTTP[HTTP\nREST] TCP[TCP\nGame Protocol] RPC[RPC\nInternal] end subgraph Application[应用层] CMD[命令处理器] QRY[查询处理器] APP[应用服务/编排] end subgraph Domain[领域层] AGG[聚合根/实体/值对象] DSVC[领域服务] DEVT[领域事件] end subgraph Infra[基础设施层] PERS[持久化] MSG[消息] NET[网络] CFG[配置] LOG[日志] end Interface --> Application --> Domain Domain --> Infra ``` ## 🏛️ DDD领域架构 ### 核心领域 (Core Domains) #### 🎮 玩家领域 (Player Domain) - **职责**: 玩家基础信息、等级经验、属性管理 - **核心实体**: Player, PlayerStats, PlayerProfile - **主要功能**: 玩家创建、升级、属性计算、状态管理 #### ⚔️ 战斗领域 (Battle Domain) - **职责**: 战斗逻辑、技能系统、伤害计算 - **核心实体**: Battle, Skill, Damage, BattleResult - **主要功能**: PvP/PvE战斗、技能释放、战斗结算 #### 🏠 社交领域 (Social Domain) - **职责**: 聊天、好友、家族、队伍系统 - **核心实体**: Chat, Friend, Guild, Team, Mail - **主要功能**: 社交互动、组队协作、消息通信 #### 🏗️ 建筑领域 (Building Domain) - **职责**: 建筑系统、家园管理、建筑升级 - **核心实体**: Building, BuildingTemplate, BuildingUpgrade - **主要功能**: 建筑建造、升级、功能管理 #### 🐾 宠物领域 (Pet Domain) - **职责**: 宠物系统、宠物培养、宠物战斗 - **核心实体**: Pet, PetTemplate, PetSkill - **主要功能**: 宠物获取、培养、进化、战斗辅助 #### 🏆 排行榜领域 (Ranking Domain) - **职责**: 各类排行榜、积分统计、奖励发放 - **核心实体**: Ranking, RankingEntry, RankingReward - **主要功能**: 排名计算、榜单更新、奖励分发 #### 🎯 小游戏领域 (Minigame Domain) - **职责**: 各种小游戏、活动玩法、特殊奖励 - **核心实体**: Minigame, MinigameSession, MinigameReward - **主要功能**: 小游戏逻辑、积分计算、奖励发放 ## 🌐 网络协议设计 ### 多协议支持 - **HTTP**: 认证服务,RESTful API - **TCP**: 网关服务,游戏客户端连接 - **RPC**: 服务间通信,Go原生RPC ### TCP协议格式 ``` +--------+--------+--------+----------+ | Magic | Length | Type | Data | | 2bytes | 4bytes | 2bytes | Variable | +--------+--------+--------+----------+ ``` ### 消息分类 - **0x1xxx**: 系统消息 (登录、心跳、错误) - **0x2xxx**: 玩家消息 (属性、状态、升级) - **0x3xxx**: 社交消息 (聊天、好友、邮件) - **0x4xxx**: 战斗消息 (技能、伤害、结果) - **0x5xxx**: 建筑消息 (建造、升级、管理) - **0x6xxx**: 宠物消息 (获取、培养、战斗) - **0x7xxx**: 排行榜消息 (查询、更新、奖励) - **0x8xxx**: 小游戏消息 (开始、操作、结算) - **0x9xxx**: 管理消息 (GM命令、系统公告) ## 🗄️ 数据存储设计 ### MongoDB 集合设计 #### 核心业务集合 - **players**: 玩家基础信息和状态 - **player_stats**: 玩家统计数据和属性 - **battles**: 战斗记录和结果 - **guilds**: 公会信息和成员关系 - **buildings**: 建筑数据和状态 - **pets**: 宠物信息和属性 - **rankings**: 排行榜数据和历史 - **minigames**: 小游戏记录和积分 ### Redis 缓存策略 #### 热点数据缓存 - **在线玩家**: `online:players:{server_id}` - **玩家会话**: `session:{player_id}` - **排行榜**: `ranking:{type}:{period}` - **公会信息**: `guild:{guild_id}` #### 临时数据缓存 - **战斗状态**: `battle:{battle_id}` - **队伍信息**: `team:{team_id}` - **聊天频道**: `chat:{channel_id}` - **活动状态**: `event:{event_id}` ## 👨‍💻 开发指南 ### 🏗️ DDD开发模式 #### 添加新领域 1. 在 `internal/domain/` 下创建领域目录 2. 定义领域实体、值对象和聚合根 3. 实现领域服务和仓储接口 4. 创建对应的应用服务 5. 实现基础设施层的具体实现 6. 添加接口层的处理器 #### 领域开发规范 ```go // 领域实体示例 type Player struct { id PlayerID name string level int exp int64 stats PlayerStats // 领域行为 } func (p *Player) LevelUp() error { // 领域逻辑实现 } ``` ### 🔧 开发工具使用 #### Make命令 ```bash make setup # 初始化开发环境 make dev # 启动开发服务器 make build # 构建生产版本 make test # 运行测试 make lint # 代码质量检查 make clean # 清理构建产物 make docs # 生成文档 ``` ### 📊 性能优化策略 #### 数据库优化 - **连接池管理**: 合理配置MongoDB和Redis连接池 - **索引优化**: 为查询频繁的字段创建合适索引 - **分片策略**: 大数据量集合采用分片存储 - **读写分离**: 读操作使用从库,写操作使用主库 #### 缓存策略 - **多级缓存**: 内存缓存 + Redis缓存 + 数据库 - **缓存预热**: 服务启动时预加载热点数据 - **缓存更新**: 采用Cache-Aside模式更新缓存 - **缓存穿透**: 使用布隆过滤器防止缓存穿透 #### 网络优化 - **连接复用**: TCP连接池和HTTP Keep-Alive - **消息批处理**: 批量处理非实时消息 - **压缩传输**: 大数据包启用压缩 - **协议优化**: 使用二进制协议减少传输开销 ## 🚀 部署指南 ### 🐳 Docker部署 #### Docker Compose部署 ```bash # 启动完整环境(包含MongoDB、Redis) docker-compose up -d # 查看服务状态 docker-compose ps # 查看日志 docker-compose logs -f ``` #### 单容器部署 ```bash # 构建镜像 docker build -t greatestworks . # 运行认证服务 docker run -d --name auth-service -p 8080:8080 greatestworks auth-service # 运行游戏服务 docker run -d --name game-service -p 8081:8081 greatestworks game-service # 运行网关服务 docker run -d --name gateway-service -p 9090:9090 greatestworks gateway-service ``` ### ☸️ Kubernetes部署 项目提供了完整的 Kubernetes 本地部署方案,支持 Docker Desktop 和 Minikube。所有 k8s 配置文件位于 `k8s/local/` 目录。 #### 📋 前置要求 - **Kubernetes**: Docker Desktop 内置 k8s 或 Minikube 1.28+ - **kubectl**: 与集群版本匹配 - **Docker**: 20.10+ (用于构建镜像) - **PowerShell**: 5.1+ (Windows) 或 Bash (Linux/macOS) #### 🚀 快速部署(三步启动) **步骤 1: 构建服务镜像** ```powershell # Windows PowerShell ./scripts/build-images.ps1 -Tag dev # Linux / macOS ./scripts/build-images.sh -t dev ``` 构建产物: - `greatestworks-auth:dev` (认证服务) - `greatestworks-game:dev` (游戏服务) - `greatestworks-gateway:dev` (网关服务) **步骤 2: 加载镜像到 Kubernetes 节点** > 此步骤解决 Docker Desktop k8s 无法直接使用本地镜像的问题。 ```powershell # Windows PowerShell ./scripts/load-images-to-k8s.ps1 -Tag dev # Minikube 用户替代方案 minikube image load greatestworks-auth:dev minikube image load greatestworks-game:dev minikube image load greatestworks-gateway:dev minikube image load mongo:7 minikube image load redis:7 ``` **步骤 3: 部署到集群** ```powershell # 创建命名空间和部署所有服务 kubectl apply -f k8s/local/namespace.yaml kubectl apply -f k8s/local/mongodb.yaml kubectl apply -f k8s/local/redis.yaml kubectl apply -f k8s/local/configmap-gateway.yaml kubectl apply -f k8s/local/auth-service.yaml kubectl apply -f k8s/local/game-service.yaml kubectl apply -f k8s/local/gateway-service.yaml # 等待 Pod 就绪(约 1-2 分钟) kubectl -n gaming get pods -w ``` 预期输出(所有 Pod 状态为 `Running` 且 `READY` 为 `1/1`): ``` NAME READY STATUS RESTARTS AGE auth-service-xxxxxxxxx-xxxxx 1/1 Running 0 2m game-service-xxxxxxxxx-xxxxx 1/1 Running 0 2m gateway-service-xxxxxxxxx-xxxxx 1/1 Running 0 2m mongodb-xxxxxxxxx-xxxxx 1/1 Running 0 2m redis-xxxxxxxxx-xxxxx 1/1 Running 0 2m ``` #### 🌐 访问服务 部署成功后,服务通过 NodePort 暴露在本地: | 服务 | 协议 | 端口 | 访问地址 | 用途 | |-----|------|------|---------|------| | **认证服务** | HTTP | 30080 | `http://localhost:30080` | 用户登录、注册、JWT 验证 | | **网关服务** | TCP | 30909 | `localhost:30909` | 游戏客户端长连接入口 | | **游戏服务** | RPC | 8081 | 仅集群内部 | 游戏逻辑处理(不对外暴露) | | **MongoDB** | TCP | 27017 | 仅集群内部 | 数据持久化 | | **Redis** | TCP | 6379 | 仅集群内部 | 缓存与会话 | **验证服务可用性:** ```powershell # 查看服务端点 kubectl -n gaming get svc # 查看 Pod 日志 kubectl -n gaming logs -l app=auth-service --tail=50 kubectl -n gaming logs -l app=gateway-service --tail=50 kubectl -n gaming logs -l app=game-service --tail=50 # 测试认证服务健康检查(如果实现了 /health 端点) curl http://localhost:30080/health ``` #### 🔧 常见操作 **查看集群状态:** ```powershell # 查看所有资源 kubectl -n gaming get all # 查看 Pod 详情 kubectl -n gaming describe pod # 进入容器调试 kubectl -n gaming exec -it -- sh ``` **重启服务(应用配置更改后):** ```powershell # 重启单个服务 kubectl -n gaming rollout restart deploy/auth-service # 重启所有服务 kubectl -n gaming rollout restart deploy --all # 等待滚动更新完成 kubectl -n gaming rollout status deploy/auth-service ``` **更新镜像(代码变更后):** ```powershell # 1. 重新构建镜像 ./scripts/build-images.ps1 -Tag dev # 2. 重新加载到 k8s 节点 ./scripts/load-images-to-k8s.ps1 -Tag dev # 3. 强制重启 Pod(触发镜像重新拉取) kubectl -n gaming rollout restart deploy --all ``` **清理环境:** ```powershell # 删除所有资源(保留命名空间) kubectl delete -f k8s/local/gateway-service.yaml kubectl delete -f k8s/local/game-service.yaml kubectl delete -f k8s/local/auth-service.yaml kubectl delete -f k8s/local/configmap-gateway.yaml kubectl delete -f k8s/local/redis.yaml kubectl delete -f k8s/local/mongodb.yaml # 删除命名空间(会级联删除所有资源) kubectl delete namespace gaming ``` #### 📦 推送镜像到远程仓库(可选) 如果需要在多台机器或 CI/CD 环境中部署,可以将镜像推送到 Docker Hub 或私有仓库: **方式 1: 使用发布脚本** ```powershell # 登录 Docker Hub docker login # 推送镜像到你的仓库 ./scripts/publish-images.ps1 ` -Registry docker.io ` -Namespace YOUR_DOCKERHUB_USERNAME ` -Tag dev ` -IncludeInfra # 可选:同时推送 mongo 和 redis ``` **方式 2: 使用 Kustomize 覆盖层** 项目提供了 `k8s/local/overlays/registry/` 配置,可以在部署时自动替换镜像路径: ```powershell # 1. 编辑 k8s/local/overlays/registry/kustomization.yaml # 将 REPLACE_ME 替换为你的仓库命名空间,例如:docker.io/phuhao00 # 2. 使用 kustomize 部署 kubectl apply -k k8s/local/overlays/registry # 3. 验证部署 kubectl -n gaming get pods ``` #### 🐛 故障排查 **问题 1: Pod 状态为 `ImagePullBackOff` 或 `ErrImagePull`** **原因**: Kubernetes 无法从本地 Docker 拉取镜像。 **解决方案**: - 确保已执行 `./scripts/load-images-to-k8s.ps1` - 检查 Pod 的 `imagePullPolicy` 是否为 `IfNotPresent` - 验证镜像已加载: `kubectl -n gaming describe pod | Select-String -Pattern "Image"` **问题 2: Pod 状态为 `CrashLoopBackOff`** **原因**: 服务启动失败,通常是配置错误或依赖未就绪。 **解决方案**: ```powershell # 查看崩溃日志 kubectl -n gaming logs --previous # 常见原因: # - MongoDB/Redis 未就绪 → 等待基础设施 Pod 先启动 # - 环境变量配置错误 → 检查 Deployment 的 env 配置 # - 端口冲突 → 检查 containerPort 和 Service port 映射 ``` **问题 3: 无法通过 NodePort 访问服务** **原因**: NodePort 未正确映射或防火墙阻止。 **解决方案**: ```powershell # 验证 Service 配置 kubectl -n gaming get svc # 确认 NodePort 范围(默认 30000-32767) # 检查 Windows 防火墙或 Docker Desktop 网络设置 # 临时替代方案:使用端口转发 kubectl -n gaming port-forward svc/auth-service 8080:8080 kubectl -n gaming port-forward svc/gateway-service 9090:9090 ``` **问题 4: MongoDB/Redis 连接失败** **原因**: 服务启动顺序问题或 DNS 解析失败。 **解决方案**: ```powershell # 检查基础设施服务是否运行 kubectl -n gaming get pods -l app=mongodb kubectl -n gaming get pods -l app=redis # 验证服务 DNS 解析(在 Pod 内测试) kubectl -n gaming exec -it -- nslookup mongodb kubectl -n gaming exec -it -- nslookup redis # 检查服务端点 kubectl -n gaming get endpoints ``` #### 📊 监控与日志 **实时查看日志:** ```powershell # 跟踪单个服务 kubectl -n gaming logs -f deploy/auth-service # 查看所有服务日志(多窗口) kubectl -n gaming logs -f -l app=auth-service kubectl -n gaming logs -f -l app=game-service kubectl -n gaming logs -f -l app=gateway-service # 查看 Pod 事件 kubectl -n gaming get events --sort-by='.lastTimestamp' ``` **资源使用情况:** ```powershell # 查看 Pod 资源占用 kubectl -n gaming top pods # 查看节点资源 kubectl top nodes ``` #### 🔐 生产环境增强配置 本地部署使用简化配置,生产环境建议增强: **安全性:** - 使用 Kubernetes Secrets 管理敏感信息(数据库密码、JWT密钥) - 启用 NetworkPolicy 限制 Pod 间通信 - 配置 RBAC 权限控制 - 使用 TLS 加密服务间通信 **高可用:** - 增加副本数 (`replicas: 3`) - 配置 PodDisruptionBudget - 使用 StatefulSet 部署有状态服务(MongoDB) - 启用 HorizontalPodAutoscaler 自动扩缩容 **持久化:** - 为 MongoDB 配置 PersistentVolumeClaim(避免使用 emptyDir) - 定期备份数据库 - 配置数据保留策略 **示例:生产级 MongoDB 部署** ```yaml # 使用 StatefulSet + PVC(生产环境推荐) apiVersion: apps/v1 kind: StatefulSet metadata: name: mongodb namespace: gaming spec: serviceName: mongodb replicas: 3 selector: matchLabels: app: mongodb template: spec: containers: - name: mongodb image: mongo:7 volumeMounts: - name: mongo-data mountPath: /data/db volumeClaimTemplates: - metadata: name: mongo-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi ``` #### 🎯 性能调优建议 **资源配额调整:** 根据实际负载修改 `k8s/local/*-service.yaml` 中的资源限制: ```yaml resources: requests: cpu: "500m" # 保证分配 memory: "512Mi" limits: cpu: "2" # 最大使用 memory: "2Gi" ``` **并发连接优化:** 在 `k8s/local/configmap-gateway.yaml` 中调整网关配置: ```yaml server: tcp: max_connections: 50000 # 根据节点能力调整 buffer_size: 8192 # 增大缓冲区 ``` **数据库连接池:** 在各服务配置中优化连接池参数: ```yaml database: mongodb: max_pool_size: 200 min_pool_size: 50 redis: pool_size: 200 min_idle_conns: 50 ``` #### 📚 相关文档 - [Kubernetes 配置清单说明](k8s/local/README.md)(待创建) - [Docker 镜像构建脚本](scripts/build-images.ps1) - [镜像加载脚本](scripts/load-images-to-k8s.ps1) - [Kustomize 覆盖层](k8s/local/overlays/registry/) --- ### 🔧 生产环境配置 #### 环境变量 ```bash # 服务配置 export SERVICE_TYPE="auth-service" # auth-service, game-service, gateway-service export SERVER_PORT=8080 export SERVER_HOST=0.0.0.0 # 数据库配置 export MONGODB_URI="mongodb://mongo-cluster:27017/gamedb" export REDIS_ADDR="redis-cluster:6379" # 认证配置 export JWT_SECRET="your-production-secret-key" # 日志配置 export LOG_LEVEL=info export LOG_FORMAT=json ``` ## 📚 文档与图示 - `docs/diagrams/README.md`:架构示意与上下游关系说明,附带 module / svr.frame / uml 等 Draw.io 源文件。 - `internal/readme.md`:内部模块快速索引,包括领域层、基础设施层与接口层的职责梳理。 - `module.drawio.png`、`svr.drawio.png`:面向汇报的静态架构图,可直接嵌入文档或 PPT。 - 若需要生成协议文档,可运行 `scripts/generate_proto.sh` / `.bat` 后在 `proto/` 目录查阅最新的 `.proto` 定义。 ## 🤝 贡献指南 我们欢迎所有形式的贡献!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详细信息。 ### 贡献流程 1. **Fork** 项目到你的GitHub账户 2. **创建** 功能分支 (`git checkout -b feature/amazing-feature`) 3. **提交** 你的更改 (`git commit -m 'Add some amazing feature'`) 4. **推送** 到分支 (`git push origin feature/amazing-feature`) 5. **创建** Pull Request ### 开发规范 - 遵循 [Go代码规范](https://golang.org/doc/effective_go.html) - 编写单元测试,保持测试覆盖率 > 80% - 更新相关文档 - 通过所有CI检查 ## 📄 许可证 本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。 ## 📞 联系我们 - **QQ群**: 366905799 - **项目主页**: [https://github.com/phuhao00/greatestworks](https://github.com/phuhao00/greatestworks) - **问题反馈**: [GitHub Issues](https://github.com/phuhao00/greatestworks/issues) - **讨论交流**: [GitHub Discussions](https://github.com/phuhao00/greatestworks/discussions) ## 📈 项目状态 ![Build Status](https://github.com/phuhao00/greatestworks/workflows/CI/badge.svg) ![Go Version](https://img.shields.io/badge/Go-1.24+-blue.svg) ![License](https://img.shields.io/badge/License-MIT-green.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/greatestworks/server.svg) ## 🎯 路线图 ### v2.0.0 (计划中) - [ ] 服务网格集成 - [ ] GraphQL API支持 - [ ] 实时数据分析和BI - [ ] 多语言客户端SDK - [ ] 云原生部署优化 ### v1.5.0 (开发中) - [ ] 管理后台界面 - [ ] 性能监控面板 - [ ] 自动化测试覆盖 - [ ] 服务发现集成 ### v1.0.0 ✅ (已发布) - [x] 分布式架构重构完成 - [x] 多节点服务分离 - [x] Go原生RPC通信 - [x] Docker容器化支持 - [x] 基础监控和日志 - [x] 完整文档体系 ---
**⭐ 如果这个项目对你有帮助,请给我们一个Star!⭐** *Built with ❤️ by the Greatest Works Team*
================================================ FILE: cmd/auth-service/main.go ================================================ // Package main 认证服务主程序 // 基于DDD架构的分布式认证服务 package main import ( "context" "greatestworks/internal/bootstrap" "log" "os" "os/signal" "syscall" "greatestworks/internal/config" "greatestworks/internal/infrastructure/logging" ) // AuthServiceConfig aliases the shared configuration schema for readability. type AuthServiceConfig = config.Config // loadInitialConfig 加载配置并返回配置与文件来源 func loadInitialConfig() (*AuthServiceConfig, []string, *config.Loader, error) { loader := config.NewLoader( config.WithService("auth-service"), ) cfg, sources, err := loader.Load() if err != nil { return nil, nil, nil, err } return cfg, sources, loader, nil } // main 主函数 func main() { logger := logging.NewBaseLogger(logging.InfoLevel) logger.Info("启动认证服务") cfg, sources, loader, err := loadInitialConfig() if err != nil { log.Fatalf("加载配置失败: %v", err) } logger.Info("配置加载成功", logging.Fields{ "environment": cfg.App.Environment, "sources": sources, }) manager, err := config.NewManager(loader) if err != nil { log.Fatalf("创建配置管理器失败: %v", err) } defer func() { _ = manager.Close() }() runtimeCfg := manager.Config() service := bootstrap.NewAuthBootstrap(runtimeCfg, logger) manager.OnChange(func(next *config.Config) { if next == nil { return } service.UpdateConfig(next) logger.Info("认证服务配置已刷新", logging.Fields{ "service_version": next.Service.Version, }) }) watchCtx, watchCancel := context.WithCancel(context.Background()) defer watchCancel() if runtimeCfg != nil && runtimeCfg.Environment.HotReload { if err := manager.StartWatching(watchCtx); err != nil { logger.Error("启动配置热更新监听失败", err, logging.Fields{}) } else { logger.Info("已启用配置热更新", logging.Fields{}) } } if err := service.Start(); err != nil { log.Fatalf("启动认证服务失败: %v", err) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) select { case sig := <-sigChan: logger.Info("收到关闭信号", logging.Fields{"signal": sig.String()}) case <-service.Done(): logger.Info("上下文已取消") } logger.Info("正在关闭认证服务...") watchCancel() if err := service.Stop(); err != nil { logger.Error("关闭认证服务失败", err, logging.Fields{}) os.Exit(1) } logger.Info("认证服务已关闭") } ================================================ FILE: cmd/game-service/main.go ================================================ // Package main 游戏服务主程序 // 基于DDD架构的分布式游戏服务 package main import ( "context" "greatestworks/internal/bootstrap" "log" "os" "os/signal" "syscall" "greatestworks/internal/config" "greatestworks/internal/infrastructure/logging" ) // GameServiceConfig 游戏服务配置 type GameServiceConfig = config.Config // loadConfig 加载配置 func loadInitialConfig() (*GameServiceConfig, []string, *config.Loader, error) { loader := config.NewLoader( config.WithService("game-service"), ) cfg, files, err := loader.Load() if err != nil { return nil, nil, nil, err } return cfg, files, loader, nil } // main 主函数 func main() { logger := logging.NewBaseLogger(logging.InfoLevel) logger.Info("启动游戏服务", logging.Fields{}) cfg, sources, loader, err := loadInitialConfig() if err != nil { log.Fatalf("加载配置失败: %v", err) } logger.Info("配置加载成功", logging.Fields{ "environment": cfg.App.Environment, "sources": sources, }) manager, err := config.NewManager(loader) if err != nil { log.Fatalf("创建配置管理器失败: %v", err) } defer func() { _ = manager.Close() }() runtimeCfg := manager.Config() service := bootstrap.NewGameBootstrap(runtimeCfg, logger) manager.OnChange(func(next *config.Config) { if next == nil { return } service.UpdateConfig(next) logger.Info("游戏服务配置已刷新", logging.Fields{ "service_version": next.Service.Version, }) }) watchCtx, watchCancel := context.WithCancel(context.Background()) defer watchCancel() if runtimeCfg != nil && runtimeCfg.Environment.HotReload { if err := manager.StartWatching(watchCtx); err != nil { logger.Error("启动配置热更新监听失败", err, logging.Fields{}) } else { logger.Info("已启用配置热更新", logging.Fields{}) } } if err := service.Start(); err != nil { log.Fatalf("启动游戏服务失败: %v", err) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) select { case sig := <-sigChan: logger.Info("收到关闭信号", logging.Fields{"signal": sig.String()}) case <-service.Done(): logger.Info("上下文已取消", logging.Fields{}) } logger.Info("正在关闭游戏服务...", logging.Fields{}) watchCancel() if err := service.Stop(); err != nil { logger.Error("关闭游戏服务失败", err, logging.Fields{}) os.Exit(1) } logger.Info("游戏服务已关闭", logging.Fields{}) } ================================================ FILE: cmd/gateway-service/main.go ================================================ // Package main 网关服务主程序 // 基于DDD架构的分布式网关服务 package main import ( "context" "greatestworks/internal/bootstrap" "log" "os" "os/signal" "syscall" "greatestworks/internal/config" "greatestworks/internal/infrastructure/logging" ) // GatewayServiceConfig aliases the shared configuration schema for readability. type GatewayServiceConfig = config.Config // loadInitialConfig 加载配置并返回配置与文件来源 func loadInitialConfig() (*GatewayServiceConfig, []string, *config.Loader, error) { loader := config.NewLoader( config.WithService("gateway-service"), ) cfg, sources, err := loader.Load() if err != nil { return nil, nil, nil, err } return cfg, sources, loader, nil } // main 主函数 func main() { logger := logging.NewBaseLogger(logging.InfoLevel) logger.Info("启动网关服务") cfg, sources, loader, err := loadInitialConfig() if err != nil { log.Fatalf("加载配置失败: %v", err) } logger.Info("配置加载成功", logging.Fields{ "environment": cfg.App.Environment, "sources": sources, }) manager, err := config.NewManager(loader) if err != nil { log.Fatalf("创建配置管理器失败: %v", err) } defer func() { _ = manager.Close() }() runtimeCfg := manager.Config() service := bootstrap.NewGatewayBootstrap(runtimeCfg, logger) manager.OnChange(func(next *config.Config) { if next == nil { return } service.UpdateConfig(next) logger.Info("网关服务配置已刷新", logging.Fields{ "service_version": next.Service.Version, }) }) watchCtx, watchCancel := context.WithCancel(context.Background()) defer watchCancel() if runtimeCfg != nil && runtimeCfg.Environment.HotReload { if err := manager.StartWatching(watchCtx); err != nil { logger.Error("启动配置热更新监听失败", err, logging.Fields{}) } else { logger.Info("已启用配置热更新", logging.Fields{}) } } if err := service.Start(); err != nil { log.Fatalf("启动网关服务失败: %v", err) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) select { case sig := <-sigChan: logger.Info("收到关闭信号", logging.Fields{"signal": sig.String()}) case <-service.Done(): logger.Info("上下文已取消") } logger.Info("正在关闭网关服务...") watchCancel() if err := service.Stop(); err != nil { logger.Error("关闭网关服务失败", err, logging.Fields{}) os.Exit(1) } logger.Info("网关服务已关闭") } ================================================ FILE: cmd/replication/main.go ================================================ // Package main 副本服务主程序 // 负责副本/实例的创建、匹配与回收 package main import ( "context" "greatestworks/internal/bootstrap" "log" "os" "os/signal" "syscall" "greatestworks/internal/config" "greatestworks/internal/infrastructure/logging" ) // ReplicationServiceConfig 配置别名 type ReplicationServiceConfig = config.Config // loadInitialConfig 加载配置 func loadInitialConfig() (*ReplicationServiceConfig, []string, *config.Loader, error) { loader := config.NewLoader( config.WithService("replication-service"), ) cfg, files, err := loader.Load() if err != nil { return nil, nil, nil, err } return cfg, files, loader, nil } // main 入口 func main() { logger := logging.NewBaseLogger(logging.InfoLevel) logger.Info("启动副本服务", logging.Fields{}) cfg, sources, loader, err := loadInitialConfig() if err != nil { log.Fatalf("加载配置失败: %v", err) } logger.Info("配置加载成功", logging.Fields{"environment": cfg.App.Environment, "sources": sources}) manager, err := config.NewManager(loader) if err != nil { log.Fatalf("创建配置管理器失败: %v", err) } defer func() { _ = manager.Close() }() runtimeCfg := manager.Config() service := bootstrap.NewReplicationBootstrap(runtimeCfg, logger) manager.OnChange(func(next *config.Config) { if next == nil { return } service.UpdateConfig(next) logger.Info("副本服务配置已刷新", logging.Fields{"service_version": next.Service.Version}) }) watchCtx, watchCancel := context.WithCancel(context.Background()) defer watchCancel() if runtimeCfg != nil && runtimeCfg.Environment.HotReload { if err := manager.StartWatching(watchCtx); err != nil { logger.Error("启动配置热更新监听失败", err, logging.Fields{}) } else { logger.Info("已启用配置热更新", logging.Fields{}) } } if err := service.Start(); err != nil { log.Fatalf("启动副本服务失败: %v", err) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) select { case sig := <-sigChan: logger.Info("收到关闭信号", logging.Fields{"signal": sig.String()}) case <-service.Done(): logger.Info("上下文已取消", logging.Fields{}) } logger.Info("正在关闭副本服务...", logging.Fields{}) watchCancel() if err := service.Stop(); err != nil { logger.Error("关闭副本服务失败", err, logging.Fields{}) os.Exit(1) } logger.Info("副本服务已关闭", logging.Fields{}) } ================================================ FILE: cmd/scene/main.go ================================================ // Package main 场景服务主程序 // 负责地图/副本/区域等场景的生命周期与调度 package main import ( "context" "greatestworks/internal/bootstrap" "log" "os" "os/signal" "syscall" "greatestworks/internal/config" "greatestworks/internal/infrastructure/logging" ) // SceneServiceConfig 配置别名 type SceneServiceConfig = config.Config // loadInitialConfig 加载配置 func loadInitialConfig() (*SceneServiceConfig, []string, *config.Loader, error) { loader := config.NewLoader( config.WithService("scene-service"), ) cfg, files, err := loader.Load() if err != nil { return nil, nil, nil, err } return cfg, files, loader, nil } // main 主函数 func main() { logger := logging.NewBaseLogger(logging.InfoLevel) logger.Info("启动场景服务", logging.Fields{}) cfg, sources, loader, err := loadInitialConfig() if err != nil { log.Fatalf("加载配置失败: %v", err) } logger.Info("配置加载成功", logging.Fields{ "environment": cfg.App.Environment, "sources": sources, }) manager, err := config.NewManager(loader) if err != nil { log.Fatalf("创建配置管理器失败: %v", err) } defer func() { _ = manager.Close() }() runtimeCfg := manager.Config() service := bootstrap.NewSceneBootstrap(runtimeCfg, logger) manager.OnChange(func(next *config.Config) { if next == nil { return } service.UpdateConfig(next) logger.Info("场景服务配置已刷新", logging.Fields{ "service_version": next.Service.Version, }) }) watchCtx, watchCancel := context.WithCancel(context.Background()) defer watchCancel() if runtimeCfg != nil && runtimeCfg.Environment.HotReload { if err := manager.StartWatching(watchCtx); err != nil { logger.Error("启动配置热更新监听失败", err, logging.Fields{}) } else { logger.Info("已启用配置热更新", logging.Fields{}) } } if err := service.Start(); err != nil { log.Fatalf("启动场景服务失败: %v", err) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) select { case sig := <-sigChan: logger.Info("收到关闭信号", logging.Fields{"signal": sig.String()}) case <-service.Done(): logger.Info("上下文已取消", logging.Fields{}) } logger.Info("正在关闭场景服务...", logging.Fields{}) watchCancel() if err := service.Stop(); err != nil { logger.Error("关闭场景服务失败", err, logging.Fields{}) os.Exit(1) } logger.Info("场景服务已关闭", logging.Fields{}) } ================================================ FILE: configs/auth-service.yaml ================================================ app: name: "GreatestWorks Auth" version: "1.0.0" environment: "development" debug: false service: name: "auth-service" version: "1.0.0" environment: "development" node_id: "auth-node-1" server: http: host: "0.0.0.0" port: 8080 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" max_header_bytes: 1048576 enable_cors: true enable_metrics: true enable_swagger: true rate_limit_enabled: true rate_limit_requests: 100 rate_limit_window: "1s" database: mongodb: uri: "mongodb://localhost:27017" database: "auth_service" username: "admin" password: "admin123" auth_source: "admin" max_pool_size: 50 min_pool_size: 5 max_idle_time: "10m" connect_timeout: "10s" socket_timeout: "30s" redis: addr: "localhost:6379" password: "redis123" db: 0 pool_size: 50 min_idle_conns: 5 max_retries: 3 dial_timeout: "5s" read_timeout: "3s" write_timeout: "3s" pool_timeout: "4s" idle_timeout: "5m" security: jwt: secret: "your-super-secret-jwt-key-change-in-production" issuer: "greatestworks-auth" audience: "mmo-players" access_token_ttl: "1h" refresh_token_ttl: "24h" encryption: enabled: true key: "32-character-encryption-key-here" algorithm: "AES-256-GCM" password_policy: min_length: 8 require_uppercase: true require_lowercase: true require_numbers: true require_symbols: false max_attempts: 5 lockout_duration: "15m" rate_limit: enabled: true requests_per_minute: 1000 burst: 200 interval: "1m" global_limit: 1000 per_ip_limit: 100 ddos_protection: enabled: true threshold: 1000 ban_duration: "1h" cors: allowed_origins: - "http://localhost:3000" - "https://greatestworks.com" allowed_methods: - "GET" - "POST" - "PUT" - "DELETE" - "OPTIONS" allowed_headers: - "Content-Type" - "Authorization" - "X-Requested-With" allow_credentials: true session: max_sessions_per_user: 3 session_timeout: "24h" cleanup_interval: "1h" store_type: "redis" logging: level: "info" format: "json" output: "stdout" fields: service: "auth-service" version: "1.0.0" monitoring: health: enabled: true path: "/health" metrics: # deprecated legacy settings enabled: false namespace: "auth_service" profiling: enabled: true host: "0.0.0.0" port: 6061 # 访问 http://:6061/debug/pprof/{profile,heap,goroutine,block,trace,...} third_party: email: smtp: host: "smtp.gmail.com" port: 587 username: "your-email@gmail.com" password: "your-app-password" oauth: providers: google: client_id: "" client_secret: "" github: client_id: "" client_secret: "" environment: hot_reload: false mock_data: false test_mode: false ================================================ FILE: configs/config.dev.yaml.example ================================================ # ============================================================================= # 开发环境配置模板 # 用于本地开发和测试 # ============================================================================= # 应用基础配置 app: name: "GreatestWorks MMO Server (Dev)" version: "1.0.0-dev" environment: "development" debug: true # 服务器配置 server: http: host: "localhost" port: 8080 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" websocket: host: "localhost" port: 8081 check_origin: false # 开发环境允许跨域 tcp: host: "localhost" port: 8082 max_connections: 1000 # 开发环境限制连接数 metrics: host: "localhost" port: 9090 # 数据库配置 database: mongodb: uri: "mongodb://localhost:27017" database: "mmo_game_dev" username: "admin" password: "admin123" auth_source: "admin" max_pool_size: 10 # 开发环境较小的连接池 min_pool_size: 2 redis: addr: "localhost:6379" password: "redis123" db: 1 # 使用不同的数据库 pool_size: 10 min_idle_conns: 2 # 消息队列配置 messaging: nats: url: "nats://localhost:4222" cluster_id: "mmo-cluster-dev" client_id: "mmo-server-dev-1" # 安全配置(开发环境使用简单配置) security: jwt: secret: "dev-jwt-secret-not-for-production" access_token_ttl: "24h" # 开发环境更长的过期时间 refresh_token_ttl: "7d" cors: allowed_origins: - "http://localhost:3000" - "http://localhost:3001" - "http://127.0.0.1:3000" allow_credentials: true # 日志配置 logging: level: "debug" # 开发环境使用 debug 级别 format: "text" # 开发环境使用易读的文本格式 output: "stdout" fields: service: "mmo-server-dev" version: "1.0.0-dev" # 游戏配置(开发环境调整) game: player: max_level: 100 initial_gold: 10000 # 开发环境给更多初始金币 initial_experience: 1000 battle: max_battle_time: "1m" # 开发环境缩短战斗时间 chat: rate_limit: 100 # 开发环境放宽限制 # 性能配置(开发环境优化) performance: worker_pool: size: 10 # 较小的工作池 queue_size: 100 cache: default_ttl: "10m" # 较短的缓存时间 max_entries: 1000 rate_limit: enabled: false # 开发环境禁用限流 # 监控配置 monitoring: health: enabled: true metrics: enabled: true tracing: enabled: false # 开发环境可选启用 profiling: enabled: true # 开发环境启用性能分析 host: "localhost" port: 6060 # 开发工具配置 development: hot_reload: enabled: true watch_dirs: - "./internal" - "./cmd" - "./configs" debug: pprof_enabled: true race_detector: true test_data: enabled: true auto_create_users: 5 auto_create_items: 50 # 第三方服务(开发环境使用测试配置) third_party: email: provider: "mock" # 使用模拟邮件服务 push: provider: "mock" # 使用模拟推送服务 payment: provider: "mock" ================================================ FILE: configs/config.example.yaml ================================================ # ============================================================================= # GreatestWorks MMO 游戏服务器配置模板 # 复制此文件为 config.yaml 并根据环境修改相应配置 # ============================================================================= # 应用基础配置 app: name: "GreatestWorks MMO Server" version: "1.0.0" environment: "development" # development, staging, production debug: true # 服务器配置 server: # HTTP 服务器 http: host: "0.0.0.0" port: 8080 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" max_header_bytes: 1048576 # 1MB # WebSocket 服务器 websocket: host: "0.0.0.0" port: 8081 read_buffer_size: 4096 write_buffer_size: 4096 check_origin: false # TCP 游戏服务器 tcp: host: "0.0.0.0" port: 8082 max_connections: 10000 read_timeout: "30s" write_timeout: "30s" # 指标服务器 metrics: host: "0.0.0.0" port: 9090 path: "/metrics" # 数据库配置 database: # MongoDB 主数据库 mongodb: uri: "mongodb://localhost:27017" database: "mmo_game" username: "admin" password: "admin123" auth_source: "admin" max_pool_size: 100 min_pool_size: 10 max_idle_time: "10m" connect_timeout: "10s" socket_timeout: "30s" # Redis 缓存 redis: addr: "localhost:6379" password: "redis123" db: 0 pool_size: 100 min_idle_conns: 10 max_retries: 3 dial_timeout: "5s" read_timeout: "3s" write_timeout: "3s" pool_timeout: "4s" idle_timeout: "5m" # 消息队列配置 messaging: # NATS 配置 nats: url: "nats://localhost:4222" cluster_id: "mmo-cluster" client_id: "mmo-server-1" max_reconnect: 10 reconnect_wait: "2s" timeout: "5s" # JetStream 配置 jetstream: enabled: true domain: "mmo" # 主题配置 subjects: player_events: "player.events.>" game_events: "game.events.>" system_events: "system.events.>" # 认证和安全配置 security: # JWT 配置 jwt: secret: "your-super-secret-jwt-key-change-in-production" issuer: "greatestworks-mmo" audience: "mmo-players" access_token_ttl: "1h" refresh_token_ttl: "24h" # 加密配置 encryption: key: "32-character-encryption-key-here" algorithm: "AES-256-GCM" # CORS 配置 cors: allowed_origins: - "http://localhost:3000" - "https://yourdomain.com" allowed_methods: - "GET" - "POST" - "PUT" - "DELETE" - "OPTIONS" allowed_headers: - "Content-Type" - "Authorization" - "X-Requested-With" expose_headers: - "X-Total-Count" allow_credentials: true max_age: 86400 # 日志配置 logging: level: "info" # debug, info, warn, error format: "json" # json, text output: "stdout" # stdout, stderr, file # 文件日志配置(当 output 为 file 时) file: path: "/var/log/mmo-server/app.log" max_size: 100 # MB max_backups: 10 max_age: 30 # days compress: true # 日志字段 fields: service: "mmo-server" version: "1.0.0" # 游戏配置 game: # 玩家配置 player: max_level: 100 initial_gold: 1000 initial_experience: 0 max_inventory_slots: 100 # 战斗配置 battle: max_battle_time: "10m" damage_variance: 0.1 # 10% 伤害浮动 critical_rate_base: 0.05 # 5% 基础暴击率 critical_damage_base: 1.5 # 150% 暴击伤害 # 经验配置 experience: base_exp_per_level: 100 exp_multiplier: 1.2 max_exp_bonus: 2.0 # 聊天配置 chat: max_message_length: 500 rate_limit: 10 # 每分钟最大消息数 banned_words: - "spam" - "cheat" # 性能配置 performance: # 工作池配置 worker_pool: size: 100 queue_size: 1000 # 缓存配置 cache: default_ttl: "1h" max_entries: 10000 cleanup_interval: "10m" # 限流配置 rate_limit: enabled: true requests_per_minute: 1000 burst: 100 # 连接池配置 connection_pool: max_idle: 100 max_open: 200 max_lifetime: "1h" # 监控配置 monitoring: # 健康检查 health: enabled: true path: "/health" # 指标收集 metrics: # deprecated, reserved for legacy Prometheus setups enabled: false namespace: "mmo_server" # 链路追踪 tracing: enabled: false jaeger_endpoint: "http://localhost:14268/api/traces" sample_rate: 0.1 # 性能分析 profiling: enabled: true host: "0.0.0.0" port: 6060 # 第三方服务配置 third_party: # 邮件服务 email: provider: "smtp" # smtp, sendgrid, ses smtp: host: "smtp.gmail.com" port: 587 username: "your-email@gmail.com" password: "your-app-password" # 推送通知 push: provider: "fcm" # fcm, apns fcm: server_key: "your-fcm-server-key" # 支付服务 payment: provider: "stripe" # stripe, paypal stripe: public_key: "pk_test_..." secret_key: "sk_test_..." webhook_secret: "whsec_..." # 开发工具配置 development: # 热重载 hot_reload: enabled: true watch_dirs: - "./internal" - "./cmd" - "./configs" # 调试工具 debug: pprof_enabled: true race_detector: true # 测试数据 test_data: enabled: true auto_create_users: 10 auto_create_items: 100 ================================================ FILE: configs/config.prod.yaml.example ================================================ # ============================================================================= # 生产环境配置模板 # 用于生产环境部署 # 注意:所有敏感信息应通过环境变量或密钥管理系统提供 # ============================================================================= # 应用基础配置 app: name: "GreatestWorks MMO Server" version: "1.0.0" environment: "production" debug: false # 服务器配置 server: http: host: "0.0.0.0" port: 8080 read_timeout: "30s" write_timeout: "30s" idle_timeout: "120s" max_header_bytes: 1048576 websocket: host: "0.0.0.0" port: 8081 read_buffer_size: 8192 write_buffer_size: 8192 check_origin: true # 生产环境严格检查来源 tcp: host: "0.0.0.0" port: 8082 max_connections: 50000 # 生产环境支持更多连接 read_timeout: "60s" write_timeout: "60s" metrics: host: "0.0.0.0" port: 9090 # 数据库配置 database: mongodb: # 生产环境使用环境变量 uri: "${MONGODB_URI}" database: "${MONGODB_DATABASE}" username: "${MONGODB_USERNAME}" password: "${MONGODB_PASSWORD}" auth_source: "admin" max_pool_size: 200 min_pool_size: 20 max_idle_time: "30m" connect_timeout: "10s" socket_timeout: "60s" # 生产环境额外配置 replica_set: "${MONGODB_REPLICA_SET}" read_preference: "secondaryPreferred" write_concern: w: "majority" j: true wtimeout: "5s" redis: addr: "${REDIS_ADDR}" password: "${REDIS_PASSWORD}" db: 0 pool_size: 200 min_idle_conns: 20 max_retries: 5 dial_timeout: "10s" read_timeout: "5s" write_timeout: "5s" pool_timeout: "10s" idle_timeout: "10m" # Redis 集群配置(如果使用) cluster: enabled: false addrs: - "${REDIS_CLUSTER_ADDR_1}" - "${REDIS_CLUSTER_ADDR_2}" - "${REDIS_CLUSTER_ADDR_3}" # 消息队列配置 messaging: nats: url: "${NATS_URL}" cluster_id: "${NATS_CLUSTER_ID}" client_id: "${HOSTNAME}-${POD_NAME}" max_reconnect: 50 reconnect_wait: "5s" timeout: "10s" # 生产环境 TLS 配置 tls: enabled: true cert_file: "/etc/ssl/certs/nats-client.crt" key_file: "/etc/ssl/private/nats-client.key" ca_file: "/etc/ssl/certs/ca.crt" jetstream: enabled: true domain: "mmo-prod" subjects: player_events: "prod.player.events.>" game_events: "prod.game.events.>" system_events: "prod.system.events.>" # 安全配置 security: jwt: secret: "${JWT_SECRET}" # 必须通过环境变量提供 issuer: "greatestworks-mmo-prod" audience: "mmo-players" access_token_ttl: "1h" refresh_token_ttl: "24h" encryption: key: "${ENCRYPTION_KEY}" # 必须通过环境变量提供 algorithm: "AES-256-GCM" cors: allowed_origins: - "https://yourdomain.com" - "https://www.yourdomain.com" - "https://game.yourdomain.com" allowed_methods: - "GET" - "POST" - "PUT" - "DELETE" - "OPTIONS" allowed_headers: - "Content-Type" - "Authorization" - "X-Requested-With" expose_headers: - "X-Total-Count" - "X-Rate-Limit-Remaining" allow_credentials: true max_age: 86400 # TLS 配置 tls: enabled: true cert_file: "/etc/ssl/certs/server.crt" key_file: "/etc/ssl/private/server.key" min_version: "1.2" # 安全头配置 security_headers: enabled: true hsts_max_age: 31536000 content_type_nosniff: true frame_deny: true xss_protection: true # 日志配置 logging: level: "info" # 生产环境使用 info 级别 format: "json" # 生产环境使用 JSON 格式便于解析 output: "stdout" # 容器环境输出到标准输出 # 结构化日志字段 fields: service: "mmo-server" version: "1.0.0" environment: "production" datacenter: "${DATACENTER}" region: "${REGION}" # 敏感信息过滤 sensitive_fields: - "password" - "token" - "secret" - "key" # 游戏配置 game: player: max_level: 100 initial_gold: 1000 initial_experience: 0 max_inventory_slots: 100 battle: max_battle_time: "10m" damage_variance: 0.1 critical_rate_base: 0.05 critical_damage_base: 1.5 experience: base_exp_per_level: 100 exp_multiplier: 1.2 max_exp_bonus: 2.0 chat: max_message_length: 500 rate_limit: 10 profanity_filter: true banned_words: - "spam" - "cheat" - "hack" # 性能配置 performance: worker_pool: size: 500 # 生产环境更大的工作池 queue_size: 10000 cache: default_ttl: "1h" max_entries: 100000 cleanup_interval: "5m" rate_limit: enabled: true requests_per_minute: 1000 burst: 200 connection_pool: max_idle: 200 max_open: 500 max_lifetime: "1h" # 内存管理 memory: gc_percent: 100 max_heap_size: "2GB" # CPU 配置 cpu: max_procs: 0 # 使用所有可用 CPU # 监控配置 monitoring: health: enabled: true path: "/health" metrics: enabled: true namespace: "mmo_server" tracing: enabled: true jaeger_endpoint: "${JAEGER_ENDPOINT}" sample_rate: 0.01 # 生产环境低采样率 profiling: enabled: false # 生产环境默认禁用 # 告警配置 alerting: enabled: true webhook_url: "${ALERT_WEBHOOK_URL}" # 审计日志 audit: enabled: true log_file: "/var/log/audit/mmo-server.log" # 第三方服务配置 third_party: email: provider: "${EMAIL_PROVIDER}" smtp: host: "${SMTP_HOST}" port: 587 username: "${SMTP_USERNAME}" password: "${SMTP_PASSWORD}" push: provider: "${PUSH_PROVIDER}" fcm: server_key: "${FCM_SERVER_KEY}" payment: provider: "${PAYMENT_PROVIDER}" stripe: public_key: "${STRIPE_PUBLIC_KEY}" secret_key: "${STRIPE_SECRET_KEY}" webhook_secret: "${STRIPE_WEBHOOK_SECRET}" # 备份配置 backup: enabled: true schedule: "0 2 * * *" # 每天凌晨 2 点 retention_days: 30 storage: type: "s3" bucket: "${BACKUP_BUCKET}" region: "${BACKUP_REGION}" access_key: "${BACKUP_ACCESS_KEY}" secret_key: "${BACKUP_SECRET_KEY}" # 灾难恢复配置 disaster_recovery: enabled: true backup_region: "${DR_REGION}" rto: "1h" # 恢复时间目标 rpo: "15m" # 恢复点目标 # 合规配置 compliance: gdpr: enabled: true data_retention_days: 365 audit_log: enabled: true retention_days: 2555 # 7 年 encryption_at_rest: enabled: true encryption_in_transit: enabled: true ================================================ FILE: configs/data/items.json ================================================ [ { "id": 10001, "name": "Health Potion", "type": 1, "quality": 1, "max_stack": 99, "price": 50, "sell_price": 10, "effect_type": 1, "effect_value": 300, "description": "Restores 300 HP" }, { "id": 10002, "name": "Mana Potion", "type": 1, "quality": 1, "max_stack": 99, "price": 50, "sell_price": 10, "effect_type": 2, "effect_value": 200, "description": "Restores 200 MP" }, { "id": 20001, "name": "Iron Sword", "type": 2, "quality": 2, "max_stack": 1, "price": 500, "sell_price": 100, "equip_slot": 1, "required_level": 5, "str_bonus": 10, "ad_bonus": 50, "description": "A sturdy iron sword" }, { "id": 20002, "name": "Magic Staff", "type": 2, "quality": 2, "max_stack": 1, "price": 600, "sell_price": 120, "equip_slot": 1, "required_level": 5, "int_bonus": 15, "ap_bonus": 80, "description": "A mystical staff imbued with magic" }, { "id": 20003, "name": "Leather Armor", "type": 2, "quality": 2, "max_stack": 1, "price": 400, "sell_price": 80, "equip_slot": 2, "required_level": 3, "vit_bonus": 8, "def_bonus": 30, "description": "Light leather armor" }, { "id": 20004, "name": "Steel Helmet", "type": 2, "quality": 2, "max_stack": 1, "price": 300, "sell_price": 60, "equip_slot": 3, "required_level": 5, "vit_bonus": 5, "def_bonus": 20, "description": "A protective steel helmet" }, { "id": 30001, "name": "Gold Coin", "type": 3, "quality": 1, "max_stack": 9999, "price": 1, "sell_price": 1, "description": "Currency used for trading" }, { "id": 30002, "name": "Goblin Ear", "type": 3, "quality": 1, "max_stack": 99, "price": 5, "sell_price": 1, "description": "Proof of slaying a goblin" }, { "id": 30003, "name": "Magic Crystal", "type": 3, "quality": 3, "max_stack": 99, "price": 100, "sell_price": 20, "description": "A crystal containing magical energy" } ] ================================================ FILE: configs/data/maps.json ================================================ [ { "id": 1, "name": "Newbie Village", "width": 1000, "height": 1000, "spawn_points": [ { "id": 1, "x": 100.0, "y": 0.0, "z": 100.0, "type": 1 }, { "id": 2, "x": 500.0, "y": 0.0, "z": 500.0, "type": 2 } ], "npcs": [ { "id": 3001, "x": 200.0, "y": 0.0, "z": 200.0 }, { "id": 3002, "x": 250.0, "y": 0.0, "z": 200.0 } ], "monsters": [ { "id": 2001, "spawn_count": 10, "respawn_time": 30 } ] }, { "id": 2, "name": "Dark Forest", "width": 2000, "height": 2000, "spawn_points": [ { "id": 1, "x": 100.0, "y": 0.0, "z": 100.0, "type": 1 } ], "monsters": [ { "id": 2002, "spawn_count": 20, "respawn_time": 60 }, { "id": 2003, "spawn_count": 5, "respawn_time": 120 } ] }, { "id": 3, "name": "Boss Arena", "width": 500, "height": 500, "spawn_points": [ { "id": 1, "x": 250.0, "y": 0.0, "z": 50.0, "type": 1 } ], "monsters": [ { "id": 2003, "spawn_count": 1, "respawn_time": 300 } ] } ] ================================================ FILE: configs/data/quests.json ================================================ [ { "id": 1001, "name": "Goblin Hunt", "description": "Help the village by eliminating goblins", "level": 1, "objectives": [ { "type": 1, "target_id": 2001, "required": 10, "description": "Kill 10 Goblins" } ], "rewards": { "exp": 500, "gold": 100, "items": [ { "item_id": 10001, "count": 5 } ] }, "prerequisite_quests": [] }, { "id": 1002, "name": "Collect Goblin Ears", "description": "Collect goblin ears as proof of your hunt", "level": 1, "objectives": [ { "type": 2, "target_id": 30002, "required": 5, "description": "Collect 5 Goblin Ears" } ], "rewards": { "exp": 300, "gold": 50, "items": [] }, "prerequisite_quests": [1001] }, { "id": 1003, "name": "Talk to the Elder", "description": "Report your success to the village elder", "level": 1, "objectives": [ { "type": 4, "target_id": 3002, "required": 1, "description": "Talk to Quest Giver" } ], "rewards": { "exp": 200, "gold": 50, "items": [ { "item_id": 20001, "count": 1 } ] }, "prerequisite_quests": [1002] }, { "id": 2001, "name": "Venture into the Dark Forest", "description": "Explore the dangerous dark forest", "level": 5, "objectives": [ { "type": 3, "target_id": 100, "required": 1, "description": "Reach Dark Forest Entrance" } ], "rewards": { "exp": 800, "gold": 200, "items": [] }, "prerequisite_quests": [1003] }, { "id": 2002, "name": "Defeat the Orc Warriors", "description": "Clear the orc warriors blocking the path", "level": 8, "objectives": [ { "type": 1, "target_id": 2002, "required": 15, "description": "Kill 15 Orc Warriors" } ], "rewards": { "exp": 1500, "gold": 300, "items": [ { "item_id": 20003, "count": 1 } ] }, "prerequisite_quests": [2001] }, { "id": 3001, "name": "Boss Challenge: Dark Mage", "description": "Defeat the powerful dark mage", "level": 15, "objectives": [ { "type": 1, "target_id": 2003, "required": 1, "description": "Defeat the Dark Mage" } ], "rewards": { "exp": 5000, "gold": 1000, "items": [ { "item_id": 20002, "count": 1 }, { "item_id": 30003, "count": 3 } ] }, "prerequisite_quests": [2002] } ] ================================================ FILE: configs/data/skills.json ================================================ [ { "id": 1, "name": "Basic Attack", "type": 1, "base_damage": 100, "scale_ad": 1.0, "scale_ap": 0.0, "damage_type": 1, "cooldown": 1.0, "cast_time": 0.3, "range": 2.0, "mp_cost": 0, "target_type": 1 }, { "id": 2, "name": "Power Strike", "type": 1, "base_damage": 200, "scale_ad": 1.5, "scale_ap": 0.0, "damage_type": 1, "cooldown": 5.0, "cast_time": 0.5, "range": 2.0, "mp_cost": 30, "target_type": 1 }, { "id": 3, "name": "Whirlwind", "type": 2, "base_damage": 150, "scale_ad": 1.2, "scale_ap": 0.0, "damage_type": 1, "cooldown": 8.0, "cast_time": 1.0, "range": 5.0, "mp_cost": 50, "target_type": 2 }, { "id": 11, "name": "Fire Bolt", "type": 1, "base_damage": 150, "scale_ad": 0.0, "scale_ap": 1.5, "damage_type": 2, "cooldown": 3.0, "cast_time": 0.8, "range": 15.0, "mp_cost": 40, "target_type": 1 }, { "id": 12, "name": "Ice Storm", "type": 2, "base_damage": 200, "scale_ad": 0.0, "scale_ap": 2.0, "damage_type": 2, "cooldown": 10.0, "cast_time": 1.5, "range": 12.0, "mp_cost": 80, "target_type": 2, "buff_id": 101 }, { "id": 13, "name": "Lightning Chain", "type": 1, "base_damage": 180, "scale_ad": 0.0, "scale_ap": 1.8, "damage_type": 2, "cooldown": 6.0, "cast_time": 1.0, "range": 10.0, "mp_cost": 60, "target_type": 1 }, { "id": 21, "name": "Heal", "type": 3, "base_damage": -200, "scale_ad": 0.0, "scale_ap": 1.5, "damage_type": 0, "cooldown": 8.0, "cast_time": 1.0, "range": 10.0, "mp_cost": 100, "target_type": 3 }, { "id": 22, "name": "Buff Strength", "type": 4, "base_damage": 0, "scale_ad": 0.0, "scale_ap": 0.0, "damage_type": 0, "cooldown": 60.0, "cast_time": 0.5, "range": 0.0, "mp_cost": 50, "target_type": 4, "buff_id": 201 } ] ================================================ FILE: configs/data/units.json ================================================ [ { "id": 1001, "name": "Warrior", "type": 1, "level": 1, "max_hp": 1000, "max_mp": 200, "str": 50, "int": 20, "agi": 30, "vit": 40, "spr": 25, "ad": 100, "ap": 20, "def": 50, "res": 30, "spd": 100, "move_speed": 5.0, "skills": [1, 2, 3] }, { "id": 1002, "name": "Mage", "type": 1, "level": 1, "max_hp": 600, "max_mp": 800, "str": 20, "int": 60, "agi": 30, "vit": 25, "spr": 50, "ad": 30, "ap": 150, "def": 20, "res": 80, "spd": 90, "move_speed": 4.5, "skills": [11, 12, 13] }, { "id": 2001, "name": "Goblin", "type": 2, "level": 5, "max_hp": 500, "max_mp": 100, "str": 30, "int": 10, "agi": 40, "vit": 25, "spr": 15, "ad": 60, "ap": 10, "def": 30, "res": 20, "spd": 120, "move_speed": 6.0, "skills": [1], "ai_type": 1, "exp_reward": 50, "gold_reward": 10 }, { "id": 2002, "name": "Orc Warrior", "type": 2, "level": 10, "max_hp": 1500, "max_mp": 100, "str": 60, "int": 10, "agi": 25, "vit": 50, "spr": 20, "ad": 120, "ap": 10, "def": 70, "res": 30, "spd": 80, "move_speed": 4.0, "skills": [1, 2], "ai_type": 1, "exp_reward": 150, "gold_reward": 30 }, { "id": 2003, "name": "Dark Mage", "type": 2, "level": 15, "max_hp": 800, "max_mp": 1200, "str": 20, "int": 80, "agi": 30, "vit": 30, "spr": 60, "ad": 30, "ap": 200, "def": 25, "res": 100, "spd": 85, "move_speed": 4.0, "skills": [11, 12, 13], "ai_type": 2, "exp_reward": 300, "gold_reward": 50 }, { "id": 3001, "name": "Shop Keeper", "type": 3, "level": 1, "max_hp": 100, "max_mp": 100, "str": 10, "int": 10, "agi": 10, "vit": 10, "spr": 10, "ad": 10, "ap": 10, "def": 10, "res": 10, "spd": 50, "move_speed": 0.0, "npc_type": 1 }, { "id": 3002, "name": "Quest Giver", "type": 3, "level": 1, "max_hp": 100, "max_mp": 100, "str": 10, "int": 10, "agi": 10, "vit": 10, "spr": 10, "ad": 10, "ap": 10, "def": 10, "res": 10, "spd": 50, "move_speed": 0.0, "npc_type": 2 } ] ================================================ FILE: configs/docker.yaml ================================================ # ============================================================================= # Docker 环境配置文件 # 用于 Docker Compose 部署 # ============================================================================= # 应用基础配置 app: name: "GreatestWorks MMO Server (Docker)" version: "1.0.0" environment: "docker" debug: false # 服务器配置 server: http: host: "0.0.0.0" port: 8080 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" websocket: host: "0.0.0.0" port: 8081 check_origin: false tcp: host: "0.0.0.0" port: 8082 max_connections: 5000 metrics: host: "0.0.0.0" port: 9090 # 数据库配置(使用 Docker 服务名) database: mongodb: uri: "mongodb://mongodb:27017" database: "mmo_game" username: "admin" password: "admin123" auth_source: "admin" max_pool_size: 50 min_pool_size: 5 redis: addr: "redis:6379" password: "redis123" db: 0 pool_size: 50 min_idle_conns: 5 # 消息队列配置 messaging: nats: url: "nats://nats:4222" cluster_id: "mmo-cluster" client_id: "mmo-server-docker-1" # 安全配置 security: jwt: secret: "docker-jwt-secret-change-in-production" access_token_ttl: "1h" refresh_token_ttl: "24h" cors: allowed_origins: - "http://localhost:3000" - "http://localhost:8080" allow_credentials: true # 日志配置 logging: level: "info" format: "json" output: "stdout" fields: service: "mmo-server-docker" version: "1.0.0" container: "true" # 游戏配置 game: player: max_level: 100 initial_gold: 1000 initial_experience: 0 battle: max_battle_time: "5m" chat: rate_limit: 20 # 性能配置 performance: worker_pool: size: 50 queue_size: 500 cache: default_ttl: "30m" max_entries: 5000 rate_limit: enabled: true requests_per_minute: 500 burst: 50 # 监控配置 monitoring: health: enabled: true metrics: # deprecated legacy settings enabled: false tracing: enabled: false profiling: enabled: true host: "0.0.0.0" port: 6060 # 开发工具配置(Docker 环境禁用) development: hot_reload: enabled: false debug: pprof_enabled: false race_detector: false test_data: enabled: false ================================================ FILE: configs/game-service.yaml ================================================ # 游戏服务配置文件 # 应用基础配置 app: name: "GreatestWorks MMO Server" version: "1.0.0" environment: "development" debug: true # 服务器配置 server: # HTTP 服务器 http: host: "0.0.0.0" port: 8080 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" # RPC 服务器 rpc: host: "0.0.0.0" port: 8081 max_connections: 1000 timeout: "30s" keep_alive: true keep_alive_period: "30s" read_timeout: "30s" write_timeout: "30s" # 数据库配置 database: # MongoDB 主数据库 mongodb: uri: "mongodb://admin:admin123@mongodb:27017" database: "mmo_game" username: "admin" password: "admin123" auth_source: "admin" max_pool_size: 100 min_pool_size: 10 max_idle_time: "10m" connect_timeout: "10s" socket_timeout: "30s" # Redis 缓存 redis: addr: "redis:6379" password: "redis123" db: 0 pool_size: 100 min_idle_conns: 10 max_retries: 3 dial_timeout: "5s" read_timeout: "3s" write_timeout: "3s" pool_timeout: "4s" idle_timeout: "5m" # 消息队列配置 messaging: # NATS 配置 nats: url: "nats://nats:4222" cluster_id: "mmo-cluster" client_id: "mmo-server-1" max_reconnect: 10 reconnect_wait: "2s" timeout: "5s" # JetStream 配置 jetstream: enabled: true domain: "mmo" # 主题配置 subjects: player_events: "player.events.>" game_events: "game.events.>" system_events: "system.events.>" # 日志配置 logging: level: "info" format: "json" output: "stdout" fields: service: "mmo-server" version: "1.0.0" security: jwt: secret: "dev-secret-change-me" issuer: "greatestworks" audience: "players" access_token_ttl: "15m" refresh_token_ttl: "168h" rate_limit: enabled: true requests_per_minute: 1000 burst: 100 # 游戏配置 game: # 玩家配置 player: max_level: 100 initial_gold: 1000 initial_experience: 0 max_inventory_slots: 100 # 战斗配置 battle: max_battle_time: "10m" damage_variance: 0.1 critical_rate_base: 0.05 critical_damage_base: 1.5 # 经验配置 experience: base_exp_per_level: 100 exp_multiplier: 1.2 max_exp_bonus: 2.0 # 聊天配置 chat: max_message_length: 500 rate_limit: 10 banned_words: - "spam" - "cheat" # 性能配置 performance: # 工作池配置 worker_pool: size: 100 queue_size: 1000 # 缓存配置 cache: default_ttl: "1h" max_entries: 10000 cleanup_interval: "10m" # 限流配置 rate_limit: enabled: true requests_per_minute: 1000 burst: 100 # 监控配置 monitoring: health: enabled: true path: "/health" metrics: # deprecated legacy settings enabled: false namespace: "game_service" profiling: enabled: true host: "0.0.0.0" port: 6060 # 访问 http://:6060/debug/pprof/{profile,heap,goroutine,block,trace,...} ================================================ FILE: configs/gateway-service.yaml ================================================ app: name: "GreatestWorks Gateway" version: "1.0.0" environment: "development" debug: false service: name: "gateway-service" version: "1.0.0" environment: "development" node_id: "gateway-node-1" server: tcp: host: "0.0.0.0" port: 9090 max_connections: 10000 read_timeout: "30s" write_timeout: "30s" buffer_size: 4096 compression_enabled: false encryption_enabled: false heartbeat_enabled: true heartbeat_interval: "30s" heartbeat_timeout: "10s" heartbeat_max_missed: 3 keep_alive: true keep_alive_interval: "30s" no_delay: true database: redis: addr: "localhost:6379" password: "redis123" db: 1 pool_size: 100 min_idle_conns: 10 max_retries: 3 dial_timeout: "5s" read_timeout: "3s" write_timeout: "3s" pool_timeout: "4s" idle_timeout: "5m" security: rate_limit: enabled: true requests_per_minute: 10000 burst: 200 interval: "1m" global_limit: 10000 per_ip_limit: 100 ddos_protection: enabled: true threshold: 1000 ban_duration: "1h" encryption: enabled: true algorithm: "AES-256-GCM" key: "" gateway: game_services: discovery: type: "consul" consul: address: "localhost:8500" datacenter: "dc1" service_name: "game-service" etcd: endpoints: - "localhost:2379" static: endpoints: - "localhost:8081" - "localhost:8082" - "localhost:8083" rpc: protocol: "grpc" timeout: "30s" retry_attempts: 3 retry_delay: "1s" circuit_breaker: enabled: true failure_threshold: 5 timeout: "30s" max_requests: 100 load_balancer: strategy: "round_robin" health_check: enabled: true interval: "10s" timeout: "5s" path: "/health" auth_service: base_url: "http://localhost:8080" timeout: "10s" retry_attempts: 3 retry_delay: "1s" circuit_breaker: enabled: true failure_threshold: 5 timeout: "30s" max_requests: 100 connection: max_connections: 10000 connection_timeout: "30s" idle_timeout: "5m" cleanup_interval: "1m" session: timeout: "24h" cleanup_interval: "1h" store_type: "redis" message_queue: enabled: true provider: "redis" topics: player_events: "gateway.player.events" game_events: "gateway.game.events" system_events: "gateway.system.events" protocol: client: type: "tcp" codec: "binary" compression: false encryption: false game: type: "grpc" codec: "protobuf" compression: true encryption: true routing: rules: - pattern: "player.*" target: "game-service" method: "rpc" - pattern: "battle.*" target: "game-service" method: "rpc" - pattern: "auth.*" target: "auth-service" method: "http" load_balancer: strategy: "round_robin" health_check: true failover: true logging: level: "info" format: "json" output: "stdout" fields: service: "gateway-service" version: "1.0.0" monitoring: health: enabled: true path: "/health" metrics: # deprecated legacy settings enabled: false namespace: "gateway_service" profiling: enabled: true host: "0.0.0.0" port: 6062 # 访问 http://:6062/debug/pprof/{profile,heap,goroutine,block,trace,...} performance: worker_pool: size: 100 queue_size: 1000 cache: default_ttl: "1h" max_entries: 10000 cleanup_interval: "10m" eviction_policy: "lfu" connection_pool: max_idle: 100 max_open: 200 max_lifetime: "1h" environment: hot_reload: false mock_data: false test_mode: false ================================================ FILE: configs/replication-service.yaml ================================================ app: name: "GreatestWorks Replication" version: "1.0.0" environment: "development" debug: false service: name: "replication-service" version: "1.0.0" environment: "development" node_id: "replication-node-1" server: http: host: "0.0.0.0" port: 8085 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" logging: level: "info" format: "json" output: "stdout" fields: service: "replication-service" version: "1.0.0" monitoring: health: enabled: true path: "/health" metrics: # deprecated legacy settings enabled: false namespace: "replication_service" profiling: enabled: true host: "0.0.0.0" port: 6064 ================================================ FILE: configs/scene-service.yaml ================================================ app: name: "GreatestWorks Scene" version: "1.0.0" environment: "development" debug: false service: name: "scene-service" version: "1.0.0" environment: "development" node_id: "scene-node-1" server: http: host: "0.0.0.0" port: 8083 read_timeout: "30s" write_timeout: "30s" idle_timeout: "60s" rpc: host: "0.0.0.0" port: 8084 max_connections: 1000 timeout: "30s" keep_alive: true keep_alive_period: "30s" read_timeout: "30s" write_timeout: "30s" logging: level: "info" format: "json" output: "stdout" fields: service: "scene-service" version: "1.0.0" monitoring: health: enabled: true path: "/health" metrics: # deprecated legacy settings enabled: false namespace: "scene_service" profiling: enabled: true host: "0.0.0.0" port: 6063 ================================================ FILE: docker-compose.yml ================================================ # ============================================================================= # GreatestWorks MMO 游戏服务器 - 优化版 Docker Compose # ============================================================================= version: '3.8' # 全局配置 x-logging: &default-logging driver: "json-file" options: max-size: "10m" max-file: "3" x-restart-policy: &restart-policy restart: unless-stopped services: # ============================================================================= # MMO 游戏服务器 - 主应用 # ============================================================================= mmo-server: build: context: . dockerfile: Dockerfile target: ${BUILD_TARGET:-final} args: BUILD_VERSION: ${BUILD_VERSION:-dev} BUILD_TIME: ${BUILD_TIME} GIT_COMMIT: ${GIT_COMMIT} image: greatestworks/mmo-server:${IMAGE_TAG:-latest} container_name: mmo-server ports: - "${SERVER_HTTP_PORT:-8080}:8080" - "${SERVER_WS_PORT:-8081}:8081" - "${SERVER_METRICS_PORT:-9090}:9090" environment: # 应用配置 - APP_ENV=${APP_ENV:-production} - GIN_MODE=${GIN_MODE:-release} - LOG_LEVEL=${LOG_LEVEL:-info} - LOG_FORMAT=${LOG_FORMAT:-json} # 数据库配置 - MONGODB_URI=mongodb://${MONGODB_USER:-admin}:${MONGODB_PASSWORD:-admin123}@mongodb:27017/${MONGODB_DATABASE:-mmo_game}?authSource=admin - REDIS_URL=redis://:${REDIS_PASSWORD:-redis123}@redis:6379/0 # 消息队列配置 - NATS_URL=nats://nats:4222 - NATS_CLUSTER_ID=${NATS_CLUSTER_ID:-mmo-cluster} # 安全配置 - JWT_SECRET=${JWT_SECRET:-change-me-in-production} - ENCRYPTION_KEY=${ENCRYPTION_KEY:-32-char-encryption-key-change-me} # 性能配置 - MAX_CONNECTIONS=${MAX_CONNECTIONS:-10000} - WORKER_POOL_SIZE=${WORKER_POOL_SIZE:-100} - CACHE_TTL=${CACHE_TTL:-3600} env_file: - .env depends_on: mongodb: condition: service_healthy redis: condition: service_healthy nats: condition: service_healthy volumes: - ./logs:/var/log/mmo-server:rw - ./configs:/configs:ro - /etc/localtime:/etc/localtime:ro <<: *restart-policy logging: *default-logging networks: - mmo-network deploy: resources: limits: cpus: '${SERVER_CPU_LIMIT:-2.0}' memory: ${SERVER_MEMORY_LIMIT:-2G} reservations: cpus: '${SERVER_CPU_RESERVATION:-0.5}' memory: ${SERVER_MEMORY_RESERVATION:-512M} healthcheck: test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider --timeout=5 http://localhost:8080/health || exit 1" ] interval: 30s timeout: 10s retries: 5 start_period: 60s security_opt: - no-new-privileges:true read_only: false tmpfs: - /tmp:noexec,nosuid,size=100m # MongoDB数据库 mongodb: image: mongo:7.0 ports: - "27017:27017" environment: - MONGO_INITDB_ROOT_USERNAME=admin - MONGO_INITDB_ROOT_PASSWORD=admin123 - MONGO_INITDB_DATABASE=mmo_game volumes: - mongodb_data:/data/db - ./scripts/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro restart: unless-stopped networks: - mmo-network healthcheck: test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/mmo_game --quiet interval: 30s timeout: 10s retries: 3 start_period: 40s # NATS消息系统 nats: image: nats:2.10-alpine ports: - "4222:4222" - "8222:8222" # HTTP监控端口 - "6222:6222" # 集群端口 command: [ "--http_port", "8222", "--port", "4222", "--cluster_name", "mmo-cluster", "--cluster", "nats://0.0.0.0:6222", "--routes", "nats-route://nats:6222", "--jetstream", "--store_dir", "/data" ] volumes: - nats_data:/data restart: unless-stopped networks: - mmo-network healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8222/healthz"] interval: 30s timeout: 10s retries: 3 start_period: 10s # Redis缓存 redis: image: redis:7.2-alpine ports: - "6379:6379" command: redis-server --appendonly yes --requirepass redis123 volumes: - redis_data:/data restart: unless-stopped networks: - mmo-network healthcheck: test: ["CMD", "redis-cli", "-a", "redis123", "ping"] interval: 30s timeout: 10s retries: 3 start_period: 10s # Nginx反向代理(可选) nginx: image: nginx:1.25-alpine ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro - ./logs/nginx:/var/log/nginx depends_on: - mmo-server restart: unless-stopped networks: - mmo-network profiles: - production # MongoDB管理界面(开发环境) mongo-express: image: mongo-express:1.0.0 ports: - "8081:8081" environment: - ME_CONFIG_MONGODB_ADMINUSERNAME=admin - ME_CONFIG_MONGODB_ADMINPASSWORD=admin123 - ME_CONFIG_MONGODB_URL=mongodb://admin:admin123@mongodb:27017/ - ME_CONFIG_BASICAUTH_USERNAME=admin - ME_CONFIG_BASICAUTH_PASSWORD=admin123 depends_on: - mongodb restart: unless-stopped networks: - mmo-network profiles: - development # Redis管理界面(开发环境) redis-commander: image: rediscommander/redis-commander:latest ports: - "8082:8081" environment: - REDIS_HOSTS=local:redis:6379:0:redis123 - HTTP_USER=admin - HTTP_PASSWORD=admin123 depends_on: - redis restart: unless-stopped networks: - mmo-network profiles: - development # Prometheus监控(生产环境) prometheus: image: prom/prometheus:v2.47.0 ports: - "9090:9090" volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' - '--storage.tsdb.retention.time=200h' - '--web.enable-lifecycle' restart: unless-stopped networks: - mmo-network profiles: - monitoring # Grafana仪表板(生产环境) grafana: image: grafana/grafana:10.1.0 ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=admin123 - GF_USERS_ALLOW_SIGN_UP=false volumes: - grafana_data:/var/lib/grafana - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro depends_on: - prometheus restart: unless-stopped networks: - mmo-network profiles: - monitoring volumes: mongodb_data: driver: local nats_data: driver: local redis_data: driver: local prometheus_data: driver: local grafana_data: driver: local networks: mmo-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16 ================================================ FILE: docs/k8s-local.md ================================================ # Run GreatestWorks locally on Kubernetes (Windows) This guide helps you run the Go services locally on a Kubernetes cluster using Docker Desktop or minikube on Windows PowerShell. ## Prerequisites - Windows with PowerShell 5.1+ - Docker Desktop (recommended) or minikube - kubectl in PATH - (If using minikube) minikube in PATH ## Build images Build three service images (auth, game, gateway) using the unified Dockerfile: ``` # From repo root ./scripts/build-images.ps1 -Tag dev ``` This produces: - greatestworks-auth:dev - greatestworks-game:dev - greatestworks-gateway:dev ## Deploy to Kubernetes Docker Desktop (Kubernetes enabled): ``` ./scripts/k8s-deploy.ps1 -Namespace gaming -Tag dev ``` Minikube: ``` # Ensure 'minikube start' is already running ./scripts/k8s-deploy.ps1 -UseMinikube -Namespace gaming -Tag dev ``` The script applies: - Namespace: `gaming` - Infra: MongoDB (user: admin / pass: admin123), Redis (password: redis123) - Services: auth-service (HTTP 8080), game-service (RPC 8081), gateway-service (TCP 9090) - NodePorts: auth-service 30080, gateway-service 30909 ## Verify ``` kubectl -n gaming get pods kubectl -n gaming get svc ``` - Auth HTTP: http://localhost:30080/health - Gateway TCP: connect to port 30909 from your client ## Notes - Config files are bundled in the image under `/configs`. - Auth-service DB URIs and Redis address are overridden via env vars in the Deployment. - Gateway-service config is provided via a ConfigMap mounted to `/configs/gateway-service.yaml` to point at in-cluster services. - For non-NodePort clusters, use `kubectl port-forward` as needed. ## Cleanup ``` kubectl delete ns gaming ``` ================================================ FILE: go.mod ================================================ module greatestworks go 1.24.0 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/gin-gonic/gin v1.10.0 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/uuid v1.6.0 github.com/nats-io/nats.go v1.31.0 github.com/redis/go-redis/v9 v9.6.1 go.mongodb.org/mongo-driver v1.13.4 go.opentelemetry.io/otel/trace v1.38.0 golang.org/x/crypto v0.37.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 ) replace github.com/phuhao00/greatestworks-proto v1.4.8 => ../greatestworks-proto require ( github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/nats-io/nkeys v0.4.5 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect go.opentelemetry.io/otel v1.38.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) ================================================ FILE: go.work ================================================ go 1.24.0 use . ================================================ FILE: go.work.sum ================================================ cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go/auth v0.9.1 h1:+pMtLEV2k0AXKvs/tGZojuj6QaioxfUjOpMsG5Gtx+w= cloud.google.com/go/auth v0.9.1/go.mod h1:Sw8ocT5mhhXxFklyhT12Eiy0ed6tTrPMCJjSI8KhYLk= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.0 h1:3I9AAI63HfcLtphd9g39ruUwRI+Ca+z/f36KHPFRUss= github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= github.com/Microsoft/hcsshim v0.9.6/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.11.0 h1:7EFNIY4igHEXUdj1zXgAyU3fLc7QfOKHbkldRVTBdiM= github.com/Microsoft/hcsshim v0.11.0/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= github.com/NYTimes/gziphandler v1.0.1 h1:iLrQrdwjDd52kHDA5op2UBJFjmOb9g+7scBan4RN8F0= github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6 h1:5kUcJJAKWWI82Xnp/CaU0eu5hLlHkmm9acjowSkwCd0= github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6/go.mod h1:JwrycNnC8+sZPDyzM3MQ86LvaGzSpfxg885KOOwFRW4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/kingpin/v2 v2.3.1 h1:ANLJcKmQm4nIaog7xdr/id6FM6zm5hHnfZrvtKPxqGg= github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/aliyun/alibaba-cloud-sdk-go v1.62.156 h1:K4N91T1+RlSlx+t2dujeDviy4ehSGVjEltluDgmeHS4= github.com/aliyun/alibaba-cloud-sdk-go v1.62.156/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs= github.com/aws/aws-sdk-go-v2 v1.33.0/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/config v1.29.1 h1:JZhGawAyZ/EuJeBtbQYnaoftczcb2drR2Iq36Wgz4sQ= github.com/aws/aws-sdk-go-v2/config v1.29.1/go.mod h1:7bR2YD5euaxBhzt2y/oDkt3uNRb6tjFp98GlTFueRwk= github.com/aws/aws-sdk-go-v2/credentials v1.17.54 h1:4UmqeOqJPvdvASZWrKlhzpRahAulBfyTJQUaYy4+hEI= github.com/aws/aws-sdk-go-v2/credentials v1.17.54/go.mod h1:RTdfo0P0hbbTxIhmQrOsC/PquBZGabEPnCaxxKRPSnI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 h1:igORFSiH3bfq4lxKFkTSYDhJEUCYo6C8VKiWJjYwQuQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28/go.mod h1:3So8EA/aAYm36L7XIvCVwLa0s5N0P7o2b1oqnx/2R4g= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 h1:1mOW9zAUMhTSrMDssEHS/ajx8JcAj/IcftzcmNlmVLI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28/go.mod h1:kGlXVIWDfvt2Ox5zEaNglmq0hXPHgQFNMix33Tw22jA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ec2 v1.200.0 h1:3hH6o7Z2WeE1twvz44Aitn6Qz8DZN3Dh5IB4Eh2xq7s= github.com/aws/aws-sdk-go-v2/service/ec2 v1.200.0/go.mod h1:I76S7jN0nfsYTBtuTgTsJtK2Q8yJVDgrLr5eLN64wMA= github.com/aws/aws-sdk-go-v2/service/ecs v1.53.8 h1:v1OectQdV/L+KSFSiqK00fXGN8FbaljRfNFysmWB8D0= github.com/aws/aws-sdk-go-v2/service/ecs v1.53.8/go.mod h1:F0DbgxpvuSvtYun5poG67EHLvci4SgzsMVO6SsPUqKk= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 h1:TQmKDyETFGiXVhZfQ/I0cCFziqqX58pi4tKJGYGFSz0= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9/go.mod h1:HVLPK2iHQBUx7HfZeOQSEu3v2ubZaAY2YPbAm5/WUyY= github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 h1:kuIyu4fTT38Kj7YCC7ouNbVZSSpqkZ+LzIfhCr6Dg+I= github.com/aws/aws-sdk-go-v2/service/sso v1.24.11/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 h1:l+dgv/64iVlQ3WsBbnn+JSbkj01jIi+SM0wYsj3y/hY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8= github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 h1:BRVDbewN6VZcwr+FBOszDKvYeXY1kJ+GGMCcpghlw0U= github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC8+vGZA= github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= github.com/containerd/containerd v1.7.3 h1:cKwYKkP1eTj54bP3wCdXXBymmKRQMrWjkLSWZZJDa8o= github.com/containerd/containerd v1.7.3/go.mod h1:32FOM4/O0RkNg7AjQj3hDzN9cUGtu+HMvaKUNiqCZB8= github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/etcd v3.3.27+incompatible h1:QIudLb9KeBsE5zyYxd1mjzRSkzLg9Wf9QlRwFgd6oTA= github.com/coreos/etcd v3.3.27+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf h1:GOPo6vn/vTN+3IwZBvXX0y5doJfSC7My0cdzelyOCsQ= github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0vYvBG9EY0X0Yku5WYIPoFWt8f6o= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 h1:lrWnAyy/F72MbxIxFUzKmcMCdt9Oi8RzpAxzTNQHD7o= github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dmarkham/enumer v1.5.8 h1:fIF11F9l5jyD++YYvxcSH5WgHfeaSGPaN/T4kOQ4qEM= github.com/dmarkham/enumer v1.5.8/go.mod h1:d10o8R3t/gROm2p3BXqTkMt2+HMuxEmWCXzorAruYak= github.com/dmarkham/enumer v1.5.9 h1:NM/1ma/AUNieHZg74w67GkHFBNB15muOt3sj486QVZk= github.com/dmarkham/enumer v1.5.9/go.mod h1:e4VILe2b1nYK3JKJpRmNdl5xbDQvELc6tQ8b+GsGk6E= github.com/dmarkham/enumer v1.6.1 h1:aSc9awYtZL07TUueWs40QcHtxTvHTAwG0EqrNsK45w4= github.com/dmarkham/enumer v1.6.1/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.22+incompatible h1:6jX4yB+NtcbldT90k7vBSaWJDB3i+zkVJT9BEK8kQkk= github.com/docker/docker v20.10.22+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane/contrib v1.32.4 h1:/udV6s9xkDGe13WfrT2MHAxXTNDMBYBPxI1GkleCrmM= github.com/envoyproxy/go-control-plane/contrib v1.32.4/go.mod h1:gkGYoY7plfQg7FPBDhyKtP1cDA9frFR/3YsCx8taRvI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/go-control-plane/xdsmatcher v0.13.4 h1:fjfDu4egQNml0gl63Jd9gRFruSW+sDgJPIt4lcVvNCs= github.com/envoyproxy/go-control-plane/xdsmatcher v0.13.4/go.mod h1:Yi7sK6Hs9iURfbHRKDKWvCJ1KpVMzWM2rktIIosIxKU= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= github.com/gin-contrib/requestid v0.0.6/go.mod h1:9i4vKATX/CdggbkY252dPVasgVucy/ggBeELXuQztm4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/timeout v1.1.0/go.mod h1:NpRo4gd1Ad8ZQ4T6bQLVFDqiplCmPRs2nvfckxS2Fw4= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.21.5 h1:3tHfEBh6Ia8eKc4M7khOGjPOAlWKJ10d877Cr9teujI= github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.20.1 h1:MkK4VEIEZMj4wT9PmjaUmGflVBr9nvud4Q4UVFbDoBE= github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ= github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM= github.com/go-openapi/loads v0.21.3 h1:8sSH2FIm/SnbDUGv572md4YqVMFne/a9Eubvcd3anew= github.com/go-openapi/loads v0.21.3/go.mod h1:Y3aMR24iHbKHppOj91nQ/SHc0cuPbAr4ndY4a02xydc= github.com/go-openapi/runtime v0.26.2 h1:elWyB9MacRzvIVgAZCBJmqTi7hBzU0hlKD4IvfX0Zl0= github.com/go-openapi/runtime v0.26.2/go.mod h1:O034jyRZ557uJKzngbMDJXkcKJVzXJiymdSfgejrcRw= github.com/go-openapi/spec v0.20.12 h1:cgSLbrsmziAP2iais+Vz7kSazwZ8rsUZd6TUzdDgkVI= github.com/go-openapi/spec v0.20.12/go.mod h1:iSCgnBcwbMW9SfzJb8iYynXvcY6C/QFrI7otzF7xGM4= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugys= github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= github.com/go-openapi/validate v0.22.4 h1:5v3jmMyIPKTR8Lv9syBAIRxG6lY0RqeBPB1LKEijzk8= github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gophercloud/gophercloud v0.3.0 h1:6sjpKIpVwRIIwmcEGp+WwNovNsem+c+2vm6oxshRpL8= github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashi-derek/grpc-proxy v0.0.0-20231207191910-191266484d75 h1:V5Uqf7VoWMd6UhNf/5EMA8LMPUm95GYvk2YF5SzT24o= github.com/hashi-derek/grpc-proxy v0.0.0-20231207191910-191266484d75/go.mod h1:5eEnHfK72jOkp4gC1dI/Q/E9MFNOM/ewE/vql5ijV3g= github.com/hashicorp/consul-awsauth v0.0.0-20250825122907-9e35fe9ded3a h1:Qd0N8lIr1QP/d7FYxseYjRLUtJp2+2R8k+mjiC2rmiY= github.com/hashicorp/consul-awsauth v0.0.0-20250825122907-9e35fe9ded3a/go.mod h1:++exZ1sI8JLIv4QvzGvTjZdf1eZARoZcaNEjNT9SZYA= github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE= github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4= github.com/hashicorp/consul/troubleshoot v0.7.6 h1:Ts+cbAn0aM9sWXY+clpfJx0mFl8IKB/rPqh172cOr0I= github.com/hashicorp/consul/troubleshoot v0.7.6/go.mod h1:Wjh5zhikXsr7fhvvTf0qXc1FmfwCOrf50d92vmY9nfA= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-connlimit v0.3.0 h1:oAojHGjFxUTTTA8c5XXnDqWJ2HLuWbDiBPTpWvNzvqM= github.com/hashicorp/go-connlimit v0.3.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= github.com/hashicorp/go-discover v1.1.0 h1:FN5AXXBCXbEMVq/BYk+qkYRhr+lwYgvBro2hMBUtnlA= github.com/hashicorp/go-discover v1.1.0/go.mod h1:jqvs0vDZPpnKlN21oG80bwkiIKPGCrmKChV6qItAjI0= github.com/hashicorp/go-discover/provider/gce v0.0.0-20241120163552-5eb1507d16b4 h1:ywaDsVo7n5ko12YD8uXjuQ8G2mQhC2mxAc4Kj3WW3GE= github.com/hashicorp/go-discover/provider/gce v0.0.0-20241120163552-5eb1507d16b4/go.mod h1:yxikfLXA8Y5JA3FcFTR720PfqVEFd0dZY9FBpmcsO54= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-raftchunking v0.7.0 h1:APNMnCXmTOhumkFv/GpJIbq7HteWF7EnGZ3875lRN0Y= github.com/hashicorp/go-raftchunking v0.7.0/go.mod h1:Dg/eBOaJzE0jYKNwNLs5IA5j0OSmL5HoCUiMy3mDmrI= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA= github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.0 h1:Lf+9eD8m5pncvHAOCQj49GSN6aQI8XGfI5OpXNkoWaA= github.com/hashicorp/golang-lru/v2 v2.0.0/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcdiag v0.5.1 h1:KZcx9xzRfEOQ2OMbwPxVvHyXwLLRqYpSHxCEOtHfQ6w= github.com/hashicorp/hcdiag v0.5.1/go.mod h1:RMC2KkffN9uJ+5mFSaL67ZFVj4CDeetPF2d/53XpwXo= github.com/hashicorp/hcl/v2 v2.14.1 h1:x0BpjfZ+CYdbiz+8yZTQ+gdLO7IXvOut7Da+XJayx34= github.com/hashicorp/hcl/v2 v2.14.1/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= github.com/hashicorp/hcp-scada-provider v0.2.6 h1:nGBrrPpL9B+6jOrvYkOUULuEB+HpAjySYa5rrL4eaaE= github.com/hashicorp/hcp-scada-provider v0.2.6/go.mod h1:ZFTgGwkzNv99PLQjTsulzaCplCzOTBh0IUQsPKzrQFo= github.com/hashicorp/hcp-sdk-go v0.80.0 h1:oKGx7+X0llBN5NEpkWg0Qe3x9DIAH6cc3MrxZptDB7Y= github.com/hashicorp/hcp-sdk-go v0.80.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk= github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 h1:n9J0rwVWXDpNd5iZnwY7w4WZyq53/rROeI7OVvLW8Ok= github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0 h1:kBpVVl1sl3MaSrs97e0+pDQhSrqJv9gVbSUrPpVfl1w= github.com/hashicorp/net-rpc-msgpackrpc/v2 v2.0.0/go.mod h1:6pdNz0vo0mF0GvhwDG56O3N18qBrAz/XRIcfINfTbwo= github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea h1:xykPFhrBAS2J0VBzVa5e80b5ZtYuNQtgXjN40qBZlD4= github.com/hashicorp/raft-boltdb/v2 v2.2.2 h1:rlkPtOllgIcKLxVT4nutqlTH2NRFn+tO1wwZk/4Dxqw= github.com/hashicorp/raft-boltdb/v2 v2.2.2/go.mod h1:N8YgaZgNJLpZC+h+by7vDu5rzsRgONThTEeUS3zWbfY= github.com/hashicorp/raft-wal v0.4.1 h1:aU8XZ6x8R9BAIB/83Z1dTDtXvDVmv9YVYeXxd/1QBSA= github.com/hashicorp/raft-wal v0.4.1/go.mod h1:A6vP5o8hGOs1LHfC1Okh9xPwWDcmb6Vvuz/QyqUXlOE= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault-plugin-auth-alicloud v0.14.0 h1:O6tNk0s/arubLUbLeCyaRs5xGo9VwmbQazISY/BfPK4= github.com/hashicorp/vault-plugin-auth-alicloud v0.14.0/go.mod h1:We3fJplmALwK1VpjwrLuXr/4QCQHYMdnXLHmLUU6Ntg= github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= github.com/hashicorp/vault/api/auth/gcp v0.3.0 h1:taum+3pCmOXnNgEKHlQbmgXmKw5daWHk7YJrLPP/w8g= github.com/hashicorp/vault/api/auth/gcp v0.3.0/go.mod h1:gnNBFOASYUaFunedTHOzdir7vKcHL3skWBUzEn263bo= github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jhump/protoreflect v1.11.0 h1:bvACHUD1Ua/3VxY4aAMpItKMhhwbimlKFJKsLsVgDjU= github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno= github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y= github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jvM= github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mitchellh/cli v1.1.4 h1:qj8czE26AU4PbiaPXK5uVmMSM+V5BYsFBiM9HhGRLUA= github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615 h1:/mD+ABZyXD39BzJI2XyRJlqdZG11gXFo0SSynL+OFeU= github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce h1:TqjP/BTDrwN7zP9xyXVuLsMBXYMt6LLYi55PlrIcq8U= github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:ifHPsLndGGzvgzcaXUvzmt6LxKT4pJ+uzEhtnMt+f7A= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcMb0= github.com/pascaldekloe/name v1.0.1/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/paulmach/protoscan v0.2.1 h1:rM0FpcTjUMvPUNk2BhPJrreDKetq43ChnL+x1sRg8O8= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/phuhao00/netcore-go v1.0.1/go.mod h1:kW1PqBl/F24G/XolcotkSyVYrpsAl3yY7JuL+GXTaU4= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rboyer/safeio v0.2.3 h1:gUybicx1kp8nuM4vO0GA5xTBX58/OBd8MQuErBfDxP8= github.com/rboyer/safeio v0.2.3/go.mod h1:d7RMmt7utQBJZ4B7f0H/cU/EdZibQAU1Y8NWepK2dS8= github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 h1:8fDzz4GuVg4skjY2B0nMN7h6uN61EDVkuLyI2+qGHhI= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8= github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ= github.com/testcontainers/testcontainers-go v0.23.0 h1:ERYTSikX01QczBLPZpqsETTBO7lInqEP349phDOVJVs= github.com/testcontainers/testcontainers-go v0.23.0/go.mod h1:3gzuZfb7T9qfcH2pHpV4RLlWrPjeWNQah6XlYQ32c4I= github.com/testcontainers/testcontainers-go v0.25.0 h1:erH6cQjsaJrH+rJDU9qIf89KFdhK0Bft0aEZHlYC3Vs= github.com/testcontainers/testcontainers-go v0.25.0/go.mod h1:4sC9SiJyzD1XFi59q8umTQYWxnkweEc5OjVtTUlJzqQ= github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw= github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xtaci/kcp-go/v5 v5.6.24 h1:0tZL4NfpoESDrhaScrZfVDnYZ/3LhyVAbN/dQ2b4hbI= github.com/xtaci/kcp-go/v5 v5.6.24/go.mod h1:7cAxNX/qFGeRUmUSnnDMoOg53FbXDK9IWBXAUfh+aBA= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/zclconf/go-cty v1.11.1 h1:UMMYDL4riBFaPdzjEWcDdWG7x/Adz8E8f9OX/MGR7V4= github.com/zclconf/go-cty v1.11.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488 h1:3doPGa+Gg4snce233aCWnbZVFsyFMo/dR40KK/6skyE= golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.195.0 h1:Ude4N8FvTKnnQJHU48RFI40jOBgIrL8Zqr3/QeST6yU= google.golang.org/api v0.195.0/go.mod h1:DOGRWuv3P8TU8Lnz7uQc4hyNqrBpMtD9ppW3wBJurgc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20240823204242-4ba0660f739c h1:TYOEhrQMrNDTAd2rX9m+WgGr8Ku6YNuj1D7OX6rWSok= google.golang.org/genproto v0.0.0-20240823204242-4ba0660f739c/go.mod h1:2rC5OendXvZ8wGEo/cSLheztrZDZaSoHanUcd1xtZnw= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/vmihailenco/msgpack.v2 v2.9.2 h1:gjPqo9orRVlSAH/065qw3MsFCDpH7fa1KpiizXyllY4= gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nullprogram.com/x/optparse v1.0.0 h1:xGFgVi5ZaWOnYdac2foDT3vg0ZZC9ErXFV57mr4OHrI= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= ================================================ FILE: internal/application/commands/battle/create_battle.go ================================================ package battle import ( "context" "time" ) // BattleType 战斗类型 type BattleType int const ( BattleTypePvP BattleType = iota BattleTypePvE BattleTypeTeamPvP BattleTypeRaid ) // CreateBattleCommand 创建战斗命令 type CreateBattleCommand struct { BattleType BattleType `json:"battle_type" validate:"required"` CreatorID string `json:"creator_id" validate:"required"` } // CreateBattleResult 创建战斗结果 type CreateBattleResult struct { BattleID string `json:"battle_id"` BattleType BattleType `json:"battle_type"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` } // CreateBattleHandler 创建战斗命令处理器 type CreateBattleHandler struct { battleService BattleService } // BattleService 战斗服务接口 type BattleService interface { CreateBattle(ctx context.Context, battleType BattleType, creatorID string) (*CreateBattleResult, error) } // NewCreateBattleHandler 创建命令处理器 func NewCreateBattleHandler(battleService BattleService) *CreateBattleHandler { return &CreateBattleHandler{ battleService: battleService, } } // Handle 处理创建战斗命令 func (h *CreateBattleHandler) Handle(ctx context.Context, cmd *CreateBattleCommand) (*CreateBattleResult, error) { return h.battleService.CreateBattle(ctx, cmd.BattleType, cmd.CreatorID) } // CommandType 返回命令类型 func (cmd *CreateBattleCommand) CommandType() string { return "CreateBattle" } // Validate 验证命令 func (cmd *CreateBattleCommand) Validate() error { if cmd.CreatorID == "" { return ErrInvalidCreatorID } if cmd.BattleType < BattleTypePvP || cmd.BattleType > BattleTypeRaid { return ErrInvalidBattleType } return nil } ================================================ FILE: internal/application/commands/battle/errors.go ================================================ package battle import ( "errors" protoerrors "greatestworks/internal/proto/errors" ) // Battle命令相关错误码 - 使用proto生成的常量 const ( ErrCodeInvalidCreatorID = int32(protoerrors.BattleErrorCode_ERR_INVALID_CREATOR_ID) ErrCodeInvalidBattleType = int32(protoerrors.BattleErrorCode_ERR_INVALID_BATTLE_TYPE) ErrCodeInvalidBattleID = int32(protoerrors.BattleErrorCode_ERR_INVALID_BATTLE_ID) ErrCodeInvalidPlayerID = int32(protoerrors.BattleErrorCode_ERR_INVALID_PLAYER_ID) ErrCodeInvalidTargetID = int32(protoerrors.BattleErrorCode_ERR_INVALID_TARGET_ID) ErrCodeInvalidSkillID = int32(protoerrors.BattleErrorCode_ERR_INVALID_SKILL_ID) ErrCodeInvalidTeam = int32(protoerrors.BattleErrorCode_ERR_INVALID_TEAM) ErrCodeBattleNotFound = int32(protoerrors.CommonErrorCode_ERR_BATTLE_NOT_FOUND) ErrCodeBattleAlreadyStarted = int32(protoerrors.BattleErrorCode_ERR_BATTLE_ALREADY_STARTED) ErrCodeBattleNotStarted = int32(protoerrors.BattleErrorCode_ERR_BATTLE_NOT_STARTED) ErrCodePlayerNotInBattle = int32(protoerrors.BattleErrorCode_ERR_PLAYER_NOT_IN_BATTLE) ErrCodeInsufficientMana = int32(protoerrors.BattleErrorCode_ERR_INSUFFICIENT_MANA) ErrCodeSkillOnCooldown = int32(protoerrors.BattleErrorCode_ERR_SKILL_ON_COOLDOWN) ErrCodeInvalidAction = int32(protoerrors.BattleErrorCode_ERR_INVALID_ACTION) ) // Battle命令相关错误 var ( ErrInvalidCreatorID = errors.New("invalid creator id") ErrInvalidBattleType = errors.New("invalid battle type") ErrInvalidBattleID = errors.New("invalid battle id") ErrInvalidPlayerID = errors.New("invalid player id") ErrInvalidTargetID = errors.New("invalid target id") ErrInvalidSkillID = errors.New("invalid skill id") ErrInvalidTeam = errors.New("invalid team") ErrBattleNotFound = errors.New("battle not found") ErrBattleAlreadyStarted = errors.New("battle already started") ErrBattleNotStarted = errors.New("battle not started") ErrPlayerNotInBattle = errors.New("player not in battle") ErrInsufficientMana = errors.New("insufficient mana") ErrSkillOnCooldown = errors.New("skill on cooldown") ErrInvalidAction = errors.New("invalid action") ) ================================================ FILE: internal/application/commands/player/ban_player.go ================================================ package player import ( "context" "time" ) // BanPlayerCommand GM封禁玩家命令 type BanPlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` BannedBy string `json:"banned_by" validate:"required"` BannedByName string `json:"banned_by_name" validate:"required"` Reason string `json:"reason" validate:"required"` BanType string `json:"ban_type" validate:"required"` // permanent, temporary BanUntil time.Time `json:"ban_until,omitempty"` } // BanPlayerResult GM封禁玩家结果 type BanPlayerResult struct { PlayerID string `json:"player_id"` Success bool `json:"success"` BannedAt time.Time `json:"banned_at"` BanUntil time.Time `json:"ban_until,omitempty"` } // BanPlayerHandler GM封禁玩家命令处理器 type BanPlayerHandler struct { playerService PlayerService } // NewBanPlayerHandler 创建命令处理器 func NewBanPlayerHandler(playerService PlayerService) *BanPlayerHandler { return &BanPlayerHandler{ playerService: playerService, } } // Handle 处理GM封禁玩家命令 func (h *BanPlayerHandler) Handle(ctx context.Context, cmd *BanPlayerCommand) (*BanPlayerResult, error) { // 验证命令 if err := cmd.Validate(); err != nil { return nil, err } // TODO: 实现GM封禁玩家逻辑 // err := h.playerService.BanPlayer(ctx, cmd) // if err != nil { // return nil, err // } return &BanPlayerResult{ PlayerID: cmd.PlayerID, Success: true, BannedAt: time.Now(), BanUntil: cmd.BanUntil, }, nil } // CommandType 返回命令类型 func (cmd *BanPlayerCommand) CommandType() string { return "BanPlayer" } // Validate 验证命令 func (cmd *BanPlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } if cmd.BannedBy == "" || cmd.BannedByName == "" { return ErrInvalidRequest } if cmd.Reason == "" { return ErrInvalidRequest } if cmd.BanType != "permanent" && cmd.BanType != "temporary" { return ErrInvalidRequest } if cmd.BanType == "temporary" && cmd.BanUntil.IsZero() { return ErrInvalidRequest } return nil } ================================================ FILE: internal/application/commands/player/create_player.go ================================================ package player import ( "context" "greatestworks/internal/application/interfaces" "time" ) // CreatePlayerCommand 创建玩家命令 type CreatePlayerCommand struct { Name string `json:"name" validate:"required,min=2,max=20"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty" validate:"min=0,max=2"` } // CreatePlayerResult 创建玩家结果 type CreatePlayerResult struct { PlayerID string `json:"player_id"` Name string `json:"name"` Level int `json:"level"` CreatedAt time.Time `json:"created_at"` } // CreatePlayerWithAccountCommand 创建带账户的玩家命令 type CreatePlayerWithAccountCommand struct { Username string `json:"username" validate:"required,min=3,max=50"` PasswordHash string `json:"password_hash" validate:"required"` Email string `json:"email" validate:"required,email"` PlayerName string `json:"player_name" validate:"required,min=2,max=50"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty" validate:"min=0,max=2"` } // CommandType 返回命令类型 func (cmd *CreatePlayerWithAccountCommand) CommandType() string { return "CreatePlayerWithAccount" } // Validate 验证命令 func (cmd *CreatePlayerWithAccountCommand) Validate() error { if cmd.Username == "" { return ErrInvalidUsername } if cmd.PasswordHash == "" { return ErrInvalidPassword } if cmd.Email == "" { return ErrInvalidEmail } if cmd.PlayerName == "" { return ErrInvalidPlayerName } return nil } // CreatePlayerWithAccountResult 创建带账户的玩家结果 type CreatePlayerWithAccountResult struct { PlayerID string `json:"player_id"` Username string `json:"username"` Email string `json:"email"` CreatedAt time.Time `json:"created_at"` } // CreatePlayerHandler 创建玩家命令处理器 type CreatePlayerHandler struct { playerService PlayerService } // 确保实现了接口 var _ interfaces.CommandHandler[*CreatePlayerCommand, *CreatePlayerResult] = (*CreatePlayerHandler)(nil) // PlayerService 玩家服务接口 type PlayerService interface { CreatePlayer(ctx context.Context, name string) (*CreatePlayerResult, error) MovePlayer(ctx context.Context, playerID string, position Position) error LevelUpPlayer(ctx context.Context, cmd *LevelUpPlayerCommand) (*LevelUpPlayerResult, error) DeletePlayer(ctx context.Context, cmd *DeletePlayerCommand) (*DeletePlayerResult, error) } // NewCreatePlayerHandler 创建命令处理器 func NewCreatePlayerHandler(playerService PlayerService) *CreatePlayerHandler { return &CreatePlayerHandler{ playerService: playerService, } } // Handle 处理创建玩家命令 func (h *CreatePlayerHandler) Handle(ctx context.Context, cmd *CreatePlayerCommand) (*CreatePlayerResult, error) { return h.playerService.CreatePlayer(ctx, cmd.Name) } // CommandType 返回命令类型 func (cmd *CreatePlayerCommand) CommandType() string { return "CreatePlayer" } // Validate 验证命令 func (cmd *CreatePlayerCommand) Validate() error { if cmd.Name == "" { return ErrInvalidPlayerName } if len(cmd.Name) < 2 || len(cmd.Name) > 20 { return ErrInvalidPlayerNameLength } return nil } ================================================ FILE: internal/application/commands/player/delete_player.go ================================================ package player import ( "context" "time" // TODO: Add necessary imports when implementing Delete functionality // "greatestworks/internal/domain/player" ) // DeletePlayerCommand 删除玩家命令 type DeletePlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` Reason string `json:"reason,omitempty"` } // DeletePlayerResult 删除玩家结果 type DeletePlayerResult struct { PlayerID string `json:"player_id"` Deleted bool `json:"deleted"` DeletedAt time.Time `json:"deleted_at"` } // DeletePlayerHandler 删除玩家命令处理器 type DeletePlayerHandler struct { playerService PlayerService } // NewDeletePlayerHandler 创建删除玩家命令处理器 func NewDeletePlayerHandler(playerService PlayerService) *DeletePlayerHandler { return &DeletePlayerHandler{ playerService: playerService, } } // Handle 处理删除玩家命令 func (h *DeletePlayerHandler) Handle(ctx context.Context, cmd *DeletePlayerCommand) (*DeletePlayerResult, error) { // 验证命令 if err := cmd.Validate(); err != nil { return nil, err } // 调用服务层执行删除 result, err := h.playerService.DeletePlayer(ctx, cmd) if err != nil { return nil, err } return result, nil } // CommandType 返回命令类型 func (cmd *DeletePlayerCommand) CommandType() string { return "DeletePlayer" } // Validate 验证命令 func (cmd *DeletePlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } return nil } ================================================ FILE: internal/application/commands/player/errors.go ================================================ package player import "errors" // Player命令相关错误 var ( ErrInvalidPlayerName = errors.New("invalid player name") ErrInvalidPlayerNameLength = errors.New("player name must be between 2 and 20 characters") ErrPlayerNotFound = errors.New("player not found") ErrPlayerAlreadyExists = errors.New("player already exists") ErrInvalidPlayerID = errors.New("invalid player id") ErrInvalidPosition = errors.New("invalid position") ErrInvalidExperience = errors.New("invalid experience amount") ErrInvalidHealAmount = errors.New("invalid heal amount") ErrPlayerOffline = errors.New("player is offline") ErrInsufficientPermission = errors.New("insufficient permission") ErrInvalidUsername = errors.New("invalid username") ErrInvalidPassword = errors.New("invalid password") ErrInvalidEmail = errors.New("invalid email") ErrInvalidRequest = errors.New("invalid request") ) ================================================ FILE: internal/application/commands/player/gm_update_player.go ================================================ package player import ( "context" "time" ) // GMUpdatePlayerCommand GM更新玩家命令 type GMUpdatePlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` GMUserID string `json:"gm_user_id" validate:"required"` GMUser string `json:"gm_user" validate:"required"` Reason string `json:"reason" validate:"required"` Updates map[string]interface{} `json:"updates"` } // GMUpdatePlayerResult GM更新玩家结果 type GMUpdatePlayerResult struct { PlayerID string `json:"player_id"` Success bool `json:"success"` Updates map[string]interface{} `json:"updates"` UpdatedAt time.Time `json:"updated_at"` } // GMUpdatePlayerHandler GM更新玩家命令处理器 type GMUpdatePlayerHandler struct { playerService PlayerService } // NewGMUpdatePlayerHandler 创建命令处理器 func NewGMUpdatePlayerHandler(playerService PlayerService) *GMUpdatePlayerHandler { return &GMUpdatePlayerHandler{ playerService: playerService, } } // Handle 处理GM更新玩家命令 func (h *GMUpdatePlayerHandler) Handle(ctx context.Context, cmd *GMUpdatePlayerCommand) (*GMUpdatePlayerResult, error) { // 验证命令 if err := cmd.Validate(); err != nil { return nil, err } // TODO: 实现GM更新玩家逻辑 // err := h.playerService.GMUpdatePlayer(ctx, cmd) // if err != nil { // return nil, err // } return &GMUpdatePlayerResult{ PlayerID: cmd.PlayerID, Success: true, Updates: cmd.Updates, UpdatedAt: time.Now(), }, nil } // CommandType 返回命令类型 func (cmd *GMUpdatePlayerCommand) CommandType() string { return "GMUpdatePlayer" } // Validate 验证命令 func (cmd *GMUpdatePlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } if cmd.GMUserID == "" || cmd.GMUser == "" { return ErrInvalidRequest } if cmd.Reason == "" { return ErrInvalidRequest } return nil } ================================================ FILE: internal/application/commands/player/level_up_player.go ================================================ package player import ( "context" "time" "greatestworks/internal/domain/player" ) // LevelUpPlayerCommand 玩家升级命令 type LevelUpPlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` ExpGain int64 `json:"exp_gain,omitempty"` } // LevelUpPlayerResult 玩家升级结果 type LevelUpPlayerResult struct { PlayerID string `json:"player_id"` OldLevel int `json:"old_level"` NewLevel int `json:"new_level"` OldExp int64 `json:"old_exp"` NewExp int64 `json:"new_exp"` LeveledUp bool `json:"leveled_up"` Status player.PlayerStatus `json:"status"` UpdatedAt time.Time `json:"updated_at"` } // LevelUpPlayerHandler 玩家升级命令处理器 type LevelUpPlayerHandler struct { playerService PlayerService } // NewLevelUpPlayerHandler 创建玩家升级命令处理器 func NewLevelUpPlayerHandler(playerService PlayerService) *LevelUpPlayerHandler { return &LevelUpPlayerHandler{ playerService: playerService, } } // Handle 处理玩家升级命令 func (h *LevelUpPlayerHandler) Handle(ctx context.Context, cmd *LevelUpPlayerCommand) (*LevelUpPlayerResult, error) { // 验证命令 if err := cmd.Validate(); err != nil { return nil, err } // 调用服务层执行升级 result, err := h.playerService.LevelUpPlayer(ctx, cmd) if err != nil { return nil, err } return result, nil } // CommandType 返回命令类型 func (cmd *LevelUpPlayerCommand) CommandType() string { return "LevelUpPlayer" } // Validate 验证命令 func (cmd *LevelUpPlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } return nil } ================================================ FILE: internal/application/commands/player/move_player.go ================================================ package player import ( "context" ) // Position 位置信息 type Position struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // MovePlayerCommand 移动玩家命令 type MovePlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` Position Position `json:"position" validate:"required"` } // MovePlayerResult 移动玩家结果 type MovePlayerResult struct { PlayerID string `json:"player_id"` OldPosition Position `json:"old_position"` NewPosition Position `json:"new_position"` Success bool `json:"success"` } // MovePlayerHandler 移动玩家命令处理器 type MovePlayerHandler struct { playerService PlayerService } // NewMovePlayerHandler 创建移动玩家命令处理器 func NewMovePlayerHandler(playerService PlayerService) *MovePlayerHandler { return &MovePlayerHandler{ playerService: playerService, } } // Handle 处理移动玩家命令 func (h *MovePlayerHandler) Handle(ctx context.Context, cmd *MovePlayerCommand) (*MovePlayerResult, error) { err := h.playerService.MovePlayer(ctx, cmd.PlayerID, cmd.Position) if err != nil { return nil, err } return &MovePlayerResult{ PlayerID: cmd.PlayerID, Success: true, }, nil } // CommandType 返回命令类型 func (cmd *MovePlayerCommand) CommandType() string { return "MovePlayer" } // Validate 验证命令 func (cmd *MovePlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } // 验证位置范围 if cmd.Position.X < -1000 || cmd.Position.X > 1000 { return ErrInvalidPosition } if cmd.Position.Y < -1000 || cmd.Position.Y > 1000 { return ErrInvalidPosition } if cmd.Position.Z < -100 || cmd.Position.Z > 100 { return ErrInvalidPosition } return nil } ================================================ FILE: internal/application/commands/player/unban_player.go ================================================ package player import ( "context" "time" ) // UnbanPlayerCommand GM解封玩家命令 type UnbanPlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` UnbannedBy string `json:"unbanned_by" validate:"required"` UnbannedByName string `json:"unbanned_by_name" validate:"required"` Reason string `json:"reason" validate:"required"` } // UnbanPlayerResult GM解封玩家结果 type UnbanPlayerResult struct { PlayerID string `json:"player_id"` Success bool `json:"success"` UnbannedAt time.Time `json:"unbanned_at"` } // UnbanPlayerHandler GM解封玩家命令处理器 type UnbanPlayerHandler struct { playerService PlayerService } // NewUnbanPlayerHandler 创建命令处理器 func NewUnbanPlayerHandler(playerService PlayerService) *UnbanPlayerHandler { return &UnbanPlayerHandler{ playerService: playerService, } } // Handle 处理GM解封玩家命令 func (h *UnbanPlayerHandler) Handle(ctx context.Context, cmd *UnbanPlayerCommand) (*UnbanPlayerResult, error) { // 验证命令 if err := cmd.Validate(); err != nil { return nil, err } // TODO: 实现GM解封玩家逻辑 // err := h.playerService.UnbanPlayer(ctx, cmd) // if err != nil { // return nil, err // } return &UnbanPlayerResult{ PlayerID: cmd.PlayerID, Success: true, UnbannedAt: time.Now(), }, nil } // CommandType 返回命令类型 func (cmd *UnbanPlayerCommand) CommandType() string { return "UnbanPlayer" } // Validate 验证命令 func (cmd *UnbanPlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } if cmd.UnbannedBy == "" || cmd.UnbannedByName == "" { return ErrInvalidRequest } if cmd.Reason == "" { return ErrInvalidRequest } return nil } ================================================ FILE: internal/application/commands/player/update_player.go ================================================ package player import ( "context" "time" ) // UpdatePlayerCommand 更新玩家命令 type UpdatePlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` Name string `json:"name,omitempty" validate:"omitempty,min=2,max=20"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty" validate:"min=0,max=2"` } // UpdatePlayerResult 更新玩家结果 type UpdatePlayerResult struct { PlayerID string `json:"player_id"` Name string `json:"name"` Level int `json:"level"` Exp int64 `json:"exp"` Status string `json:"status"` UpdatedAt time.Time `json:"updated_at"` } // UpdatePlayerHandler 更新玩家命令处理器 type UpdatePlayerHandler struct { playerService PlayerService } // NewUpdatePlayerHandler 创建命令处理器 func NewUpdatePlayerHandler(playerService PlayerService) *UpdatePlayerHandler { return &UpdatePlayerHandler{ playerService: playerService, } } // Handle 处理更新玩家命令 func (h *UpdatePlayerHandler) Handle(ctx context.Context, cmd *UpdatePlayerCommand) (*UpdatePlayerResult, error) { // TODO: 实现更新玩家逻辑 return nil, nil } // CommandType 返回命令类型 func (cmd *UpdatePlayerCommand) CommandType() string { return "UpdatePlayer" } // Validate 验证命令 func (cmd *UpdatePlayerCommand) Validate() error { if cmd.PlayerID == "" { return ErrInvalidPlayerID } if cmd.Name != "" && (len(cmd.Name) < 2 || len(cmd.Name) > 20) { return ErrInvalidPlayerNameLength } return nil } ================================================ FILE: internal/application/handlers/command_bus.go ================================================ package handlers import ( "context" "fmt" "reflect" ) // Command 命令接口 type Command interface { CommandType() string Validate() error } // CommandHandler 命令处理器接口 type CommandHandler[T Command, R any] interface { Handle(ctx context.Context, cmd T) (R, error) } // CommandBus 命令总线 type CommandBus struct { handlers map[string]interface{} } // NewCommandBus 创建命令总线 func NewCommandBus() *CommandBus { return &CommandBus{ handlers: make(map[string]interface{}), } } // RegisterHandler 注册命令处理器 func (bus *CommandBus) RegisterHandler(commandType string, handler interface{}) { bus.handlers[commandType] = handler } // Execute 执行命令 func (bus *CommandBus) Execute(ctx context.Context, cmd Command) (interface{}, error) { // 验证命令 if err := cmd.Validate(); err != nil { return nil, fmt.Errorf("command validation failed: %w", err) } // 获取处理器 handler, exists := bus.handlers[cmd.CommandType()] if !exists { return nil, fmt.Errorf("no handler registered for command type: %s", cmd.CommandType()) } // 使用反射调用处理器 handlerValue := reflect.ValueOf(handler) handlerType := reflect.TypeOf(handler) // 查找Handle方法 handleMethod, exists := handlerType.MethodByName("Handle") if !exists { return nil, fmt.Errorf("handler does not have Handle method") } // 调用Handle方法 args := []reflect.Value{ handlerValue, reflect.ValueOf(ctx), reflect.ValueOf(cmd), } results := handleMethod.Func.Call(args) if len(results) != 2 { return nil, fmt.Errorf("handler Handle method should return (result, error)") } // 检查错误 if !results[1].IsNil() { return nil, results[1].Interface().(error) } return results[0].Interface(), nil } // ExecuteTyped 执行类型化命令 func ExecuteTyped[T Command, R any](ctx context.Context, bus *CommandBus, cmd T) (R, error) { result, err := bus.Execute(ctx, cmd) if err != nil { var zero R return zero, err } if typedResult, ok := result.(R); ok { return typedResult, nil } var zero R return zero, fmt.Errorf("unexpected result type") } // Middleware 中间件接口 type Middleware interface { Execute(ctx context.Context, cmd Command, next func(context.Context, Command) (interface{}, error)) (interface{}, error) } // ValidationMiddleware 验证中间件 type ValidationMiddleware struct{} // Execute 执行验证中间件 func (m *ValidationMiddleware) Execute(ctx context.Context, cmd Command, next func(context.Context, Command) (interface{}, error)) (interface{}, error) { if err := cmd.Validate(); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } return next(ctx, cmd) } // LoggingMiddleware 日志中间件 type LoggingMiddleware struct { logger Logger } // Logger 日志接口 type Logger interface { Info(msg string, fields ...interface{}) Error(msg string, err error, fields ...interface{}) } // NewLoggingMiddleware 创建日志中间件 func NewLoggingMiddleware(logger Logger) *LoggingMiddleware { return &LoggingMiddleware{logger: logger} } // Execute 执行日志中间件 func (m *LoggingMiddleware) Execute(ctx context.Context, cmd Command, next func(context.Context, Command) (interface{}, error)) (interface{}, error) { m.logger.Info("executing command", "type", cmd.CommandType()) result, err := next(ctx, cmd) if err != nil { m.logger.Error("command execution failed", err, "type", cmd.CommandType()) return nil, err } m.logger.Info("command executed successfully", "type", cmd.CommandType()) return result, nil } ================================================ FILE: internal/application/handlers/query_bus.go ================================================ package handlers import ( "context" "fmt" "reflect" ) // Query 查询接口 type Query interface { QueryType() string Validate() error } // QueryHandler 查询处理器接口 type QueryHandler[T Query, R any] interface { Handle(ctx context.Context, query T) (R, error) } // QueryBus 查询总线 type QueryBus struct { handlers map[string]interface{} } // NewQueryBus 创建查询总线 func NewQueryBus() *QueryBus { return &QueryBus{ handlers: make(map[string]interface{}), } } // RegisterHandler 注册查询处理器 func (bus *QueryBus) RegisterHandler(queryType string, handler interface{}) { bus.handlers[queryType] = handler } // Execute 执行查询 func (bus *QueryBus) Execute(ctx context.Context, query Query) (interface{}, error) { // 验证查询 if err := query.Validate(); err != nil { return nil, fmt.Errorf("query validation failed: %w", err) } // 获取处理器 handler, exists := bus.handlers[query.QueryType()] if !exists { return nil, fmt.Errorf("no handler registered for query type: %s", query.QueryType()) } // 使用反射调用处理器 handlerValue := reflect.ValueOf(handler) handlerType := reflect.TypeOf(handler) // 查找Handle方法 handleMethod, exists := handlerType.MethodByName("Handle") if !exists { return nil, fmt.Errorf("handler does not have Handle method") } // 调用Handle方法 args := []reflect.Value{ handlerValue, reflect.ValueOf(ctx), reflect.ValueOf(query), } results := handleMethod.Func.Call(args) if len(results) != 2 { return nil, fmt.Errorf("handler Handle method should return (result, error)") } // 检查错误 if !results[1].IsNil() { return nil, results[1].Interface().(error) } return results[0].Interface(), nil } // ExecuteTyped 执行类型化查询 func ExecuteQueryTyped[T Query, R any](ctx context.Context, bus *QueryBus, query T) (R, error) { result, err := bus.Execute(ctx, query) if err != nil { var zero R return zero, err } if typedResult, ok := result.(R); ok { return typedResult, nil } var zero R return zero, fmt.Errorf("unexpected result type") } // QueryMiddleware 查询中间件接口 type QueryMiddleware interface { Execute(ctx context.Context, query Query, next func(context.Context, Query) (interface{}, error)) (interface{}, error) } // CachingMiddleware 缓存中间件 type CachingMiddleware struct { cache Cache } // Cache 缓存接口 type Cache interface { Get(key string) (interface{}, bool) Set(key string, value interface{}, ttl int) error Delete(key string) error } // NewCachingMiddleware 创建缓存中间件 func NewCachingMiddleware(cache Cache) *CachingMiddleware { return &CachingMiddleware{cache: cache} } // Execute 执行缓存中间件 func (m *CachingMiddleware) Execute(ctx context.Context, query Query, next func(context.Context, Query) (interface{}, error)) (interface{}, error) { // 生成缓存键 cacheKey := fmt.Sprintf("%s:%v", query.QueryType(), query) // 尝试从缓存获取 if cached, found := m.cache.Get(cacheKey); found { return cached, nil } // 执行查询 result, err := next(ctx, query) if err != nil { return nil, err } // 缓存结果(TTL 5分钟) m.cache.Set(cacheKey, result, 300) return result, nil } // QueryLoggingMiddleware 查询日志中间件 type QueryLoggingMiddleware struct { logger Logger } // NewQueryLoggingMiddleware 创建查询日志中间件 func NewQueryLoggingMiddleware(logger Logger) *QueryLoggingMiddleware { return &QueryLoggingMiddleware{logger: logger} } // Execute 执行查询日志中间件 func (m *QueryLoggingMiddleware) Execute(ctx context.Context, query Query, next func(context.Context, Query) (interface{}, error)) (interface{}, error) { m.logger.Info("executing query", "type", query.QueryType()) result, err := next(ctx, query) if err != nil { m.logger.Error("query execution failed", err, "type", query.QueryType()) return nil, err } m.logger.Info("query executed successfully", "type", query.QueryType()) return result, nil } ================================================ FILE: internal/application/handlers/replication_subscribers.go ================================================ package handlers import ( "context" "reflect" "greatestworks/internal/domain/replication" "greatestworks/internal/events" "greatestworks/internal/infrastructure/logging" ) // RegisterReplicationSubscribers subscribes to replication-related events and logs them. func RegisterReplicationSubscribers(bus *events.EventBus, logger logging.Logger) { types := []string{ reflect.TypeOf(&replication.PlayerJoinedEvent{}).String(), reflect.TypeOf(&replication.PlayerLeftEvent{}).String(), reflect.TypeOf(&replication.InstanceStartedEvent{}).String(), reflect.TypeOf(&replication.InstanceFullEvent{}).String(), reflect.TypeOf(&replication.InstanceProgressUpdatedEvent{}).String(), reflect.TypeOf(&replication.InstanceCompletedEvent{}).String(), reflect.TypeOf(&replication.InstanceClosingEvent{}).String(), reflect.TypeOf(&replication.InstanceClosedEvent{}).String(), } for _, t := range types { bus.Subscribe(t, func(ctx context.Context, e events.Event) error { logger.Info("replication event", logging.Fields{"type": t, "data": e.GetData()}) return nil }) } } ================================================ FILE: internal/application/interfaces/command.go ================================================ // Package interfaces 定义应用层接口 package interfaces import "context" // Command 命令接口 type Command interface { CommandType() string Validate() error } // CommandHandler 命令处理器接口 type CommandHandler[T Command, R any] interface { Handle(ctx context.Context, cmd T) (R, error) } // CommandBus 命令总线接口 type CommandBus interface { RegisterHandler(commandType string, handler interface{}) Execute(ctx context.Context, cmd Command) (interface{}, error) } // Query 查询接口 type Query interface { QueryType() string Validate() error } // QueryHandler 查询处理器接口 type QueryHandler[T Query, R any] interface { Handle(ctx context.Context, query T) (R, error) } // QueryBus 查询总线接口 type QueryBus interface { RegisterHandler(queryType string, handler interface{}) Execute(ctx context.Context, query Query) (interface{}, error) } // Event 事件接口 type Event interface { EventType() string AggregateID() string Version() int64 OccurredAt() int64 } // EventHandler 事件处理器接口 type EventHandler[T Event] interface { Handle(ctx context.Context, event T) error } // EventBus 事件总线接口 type EventBus interface { Publish(ctx context.Context, event Event) error Subscribe(eventType string, handler EventHandler[Event]) error Unsubscribe(eventType string, handler EventHandler[Event]) error } // Middleware 中间件接口 type Middleware interface { Execute(ctx context.Context, cmd Command, next func(context.Context, Command) (interface{}, error)) (interface{}, error) } // QueryMiddleware 查询中间件接口 type QueryMiddleware interface { Execute(ctx context.Context, query Query, next func(context.Context, Query) (interface{}, error)) (interface{}, error) } ================================================ FILE: internal/application/queries/battle/errors.go ================================================ package battle // import "errors" // English comment ================================================ FILE: internal/application/queries/battle/get_battle.go ================================================ package battle // import "time" // 未使用 // English comment// English comment// English comment// English comment// English comment ================================================ FILE: internal/application/queries/player/errors.go ================================================ package player import "errors" // Player查询相关错误 var ( ErrPlayerNotFound = errors.New("player not found") ErrInvalidPlayerID = errors.New("invalid player id") ErrInvalidPlayerName = errors.New("invalid player name") ErrInvalidLevel = errors.New("invalid level") ErrInvalidLimit = errors.New("invalid limit") ErrQueryFailed = errors.New("query failed") ErrInvalidParameters = errors.New("invalid parameters") ErrInvalidUsername = errors.New("invalid username") ) ================================================ FILE: internal/application/queries/player/get_player.go ================================================ package player import ( "context" "time" ) // GetPlayerQuery 获取玩家查询 type GetPlayerQuery struct { PlayerID string `json:"player_id" validate:"required"` } // PlayerDTO 玩家数据传输对象 type PlayerDTO struct { ID string `json:"id"` Name string `json:"name"` Level int `json:"level"` Exp int64 `json:"exp"` Status string `json:"status"` Position PositionDTO `json:"position"` Stats StatsDTO `json:"stats"` Username string `json:"username,omitempty"` Email string `json:"email,omitempty"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty"` BanInfo *BanInfoDTO `json:"ban_info,omitempty"` LastLoginAt *time.Time `json:"last_login_at,omitempty"` LastLogoutAt *time.Time `json:"last_logout_at,omitempty"` OnlineTime int64 `json:"online_time,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PositionDTO 位置数据传输对象 type PositionDTO struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // StatsDTO 属性数据传输对象 type StatsDTO struct { HP int `json:"hp"` MaxHP int `json:"max_hp"` MP int `json:"mp"` MaxMP int `json:"max_mp"` Attack int `json:"attack"` Defense int `json:"defense"` Speed int `json:"speed"` } // BanInfoDTO 封禁信息数据传输对象 type BanInfoDTO struct { IsBanned bool `json:"is_banned"` BanType string `json:"ban_type,omitempty"` Reason string `json:"reason,omitempty"` BannedBy string `json:"banned_by,omitempty"` BannedAt *time.Time `json:"banned_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` } // GetPlayerResult 获取玩家结果 type GetPlayerResult struct { Player *PlayerDTO `json:"player"` Found bool `json:"found"` } // GetPlayerByUsernameQuery 根据用户名获取玩家查询 type GetPlayerByUsernameQuery struct { Username string `json:"username" validate:"required"` } // QueryType 返回查询类型 func (query *GetPlayerByUsernameQuery) QueryType() string { return "GetPlayerByUsername" } // Validate 验证查询 func (query *GetPlayerByUsernameQuery) Validate() error { if query.Username == "" { return ErrInvalidUsername } return nil } // GetPlayerByUsernameResult 根据用户名获取玩家结果 type GetPlayerByUsernameResult struct { Player *PlayerWithAccountDTO `json:"player"` Found bool `json:"found"` } // PlayerWithAccountDTO 带账户信息的玩家数据传输对象 type PlayerWithAccountDTO struct { ID string `json:"id"` Name string `json:"name"` Level int `json:"level"` Exp int64 `json:"exp"` Status string `json:"status"` Position PositionDTO `json:"position"` Stats StatsDTO `json:"stats"` Username string `json:"username"` Email string `json:"email"` PasswordHash string `json:"password_hash"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // GetPlayerHandler 获取玩家查询处理器 type GetPlayerHandler struct { playerQueryService PlayerQueryService } // PlayerQueryService 玩家查询服务接口 type PlayerQueryService interface { GetPlayer(ctx context.Context, playerID string) (*PlayerDTO, error) GetPlayerByName(ctx context.Context, name string) (*PlayerDTO, error) GetOnlinePlayers(ctx context.Context, limit int) ([]*PlayerDTO, error) GetPlayersByLevel(ctx context.Context, minLevel, maxLevel int) ([]*PlayerDTO, error) ListPlayers(ctx context.Context, query *ListPlayersQuery) ([]*PlayerDTO, int64, error) } // NewGetPlayerHandler 创建查询处理器 func NewGetPlayerHandler(playerQueryService PlayerQueryService) *GetPlayerHandler { return &GetPlayerHandler{ playerQueryService: playerQueryService, } } // Handle 处理获取玩家查询 func (h *GetPlayerHandler) Handle(ctx context.Context, query *GetPlayerQuery) (*GetPlayerResult, error) { player, err := h.playerQueryService.GetPlayer(ctx, query.PlayerID) if err != nil { if err == ErrPlayerNotFound { return &GetPlayerResult{Player: nil, Found: false}, nil } return nil, err } return &GetPlayerResult{Player: player, Found: true}, nil } // QueryType 返回查询类型 func (query *GetPlayerQuery) QueryType() string { return "GetPlayer" } // Validate 验证查询 func (query *GetPlayerQuery) Validate() error { if query.PlayerID == "" { return ErrInvalidPlayerID } return nil } ================================================ FILE: internal/application/queries/player/get_player_detail.go ================================================ package player import ( "context" ) // GetPlayerDetailQuery GM获取玩家详情查询 type GetPlayerDetailQuery struct { PlayerID string `json:"player_id" validate:"required"` } // GetPlayerDetailResult GM获取玩家详情结果 type GetPlayerDetailResult struct { Player *PlayerDTO `json:"player"` Found bool `json:"found"` } // GetPlayerDetailHandler GM获取玩家详情处理器 type GetPlayerDetailHandler struct { playerQueryService PlayerQueryService } // NewGetPlayerDetailHandler 创建GM获取玩家详情处理器 func NewGetPlayerDetailHandler(playerQueryService PlayerQueryService) *GetPlayerDetailHandler { return &GetPlayerDetailHandler{ playerQueryService: playerQueryService, } } // Handle 处理GM获取玩家详情请求 func (h *GetPlayerDetailHandler) Handle(ctx context.Context, query *GetPlayerDetailQuery) (*GetPlayerDetailResult, error) { // 验证查询参数 if err := query.Validate(); err != nil { return nil, err } // TODO: 调用服务层获取玩家详情 // player, err := h.playerQueryService.GetPlayerDetail(ctx, query.PlayerID) // if err != nil { // return nil, err // } return &GetPlayerDetailResult{ Player: nil, Found: false, }, nil } // Validate 验证查询 func (q *GetPlayerDetailQuery) Validate() error { if q.PlayerID == "" { return ErrInvalidPlayerID } return nil } ================================================ FILE: internal/application/queries/player/get_player_stats.go ================================================ package player import ( "context" "time" "greatestworks/internal/domain/player" ) // GetPlayerStatsQuery 获取玩家统计信息查询 type GetPlayerStatsQuery struct { PlayerID player.PlayerID `json:"player_id" validate:"required"` } // GetPlayerStatsResult 获取玩家统计信息结果 type GetPlayerStatsResult struct { Found bool `json:"found"` PlayerID player.PlayerID `json:"player_id"` TotalBattles int `json:"total_battles"` Wins int `json:"wins"` Losses int `json:"losses"` WinRate float64 `json:"win_rate"` TotalExp int64 `json:"total_exp"` PlayTime time.Duration `json:"play_time"` LastLogin *time.Time `json:"last_login"` Achievements []string `json:"achievements"` } // GetPlayerStatsHandler 获取玩家统计信息处理器 type GetPlayerStatsHandler struct { playerStatsService PlayerStatsService } // PlayerStatsService 玩家统计服务接口 type PlayerStatsService interface { GetPlayerStats(ctx context.Context, playerID player.PlayerID) (*GetPlayerStatsResult, error) } // NewGetPlayerStatsHandler 创建获取玩家统计信息处理器 func NewGetPlayerStatsHandler(playerStatsService PlayerStatsService) *GetPlayerStatsHandler { return &GetPlayerStatsHandler{ playerStatsService: playerStatsService, } } // Handle 处理获取玩家统计信息查询 func (h *GetPlayerStatsHandler) Handle(ctx context.Context, query *GetPlayerStatsQuery) (*GetPlayerStatsResult, error) { return h.playerStatsService.GetPlayerStats(ctx, query.PlayerID) } // QueryType 返回查询类型 func (query *GetPlayerStatsQuery) QueryType() string { return "GetPlayerStatsQuery" } // Validate 验证查询参数 func (query *GetPlayerStatsQuery) Validate() error { if query.PlayerID.String() == "" { return ErrInvalidPlayerID } return nil } ================================================ FILE: internal/application/queries/player/list_players.go ================================================ package player import ( "context" ) // ListPlayersQuery 列表查询玩家请求 type ListPlayersQuery struct { Page int `json:"page" validate:"min=1"` PageSize int `json:"page_size" validate:"min=1,max=100"` Name string `json:"name,omitempty"` Status string `json:"status,omitempty"` Level int `json:"level,omitempty"` } // ListPlayersResult 列表查询玩家结果 type ListPlayersResult struct { Players []*PlayerDTO `json:"players"` Total int64 `json:"total"` Page int `json:"page"` Size int `json:"size"` } // ListPlayersHandler 列表查询玩家处理器 type ListPlayersHandler struct { playerQueryService PlayerQueryService } // NewListPlayersHandler 创建列表查询玩家处理器 func NewListPlayersHandler(playerQueryService PlayerQueryService) *ListPlayersHandler { return &ListPlayersHandler{ playerQueryService: playerQueryService, } } // Handle 处理列表查询玩家请求 func (h *ListPlayersHandler) Handle(ctx context.Context, query *ListPlayersQuery) (*ListPlayersResult, error) { // 验证查询参数 if err := query.Validate(); err != nil { return nil, err } // 调用服务层获取玩家列表 players, total, err := h.playerQueryService.ListPlayers(ctx, query) if err != nil { return nil, err } return &ListPlayersResult{ Players: players, Total: total, Page: query.Page, Size: len(players), }, nil } // QueryType 返回查询类型 func (q *ListPlayersQuery) QueryType() string { return "list_players" } // Validate 验证查询参数 func (q *ListPlayersQuery) Validate() error { if q.Page <= 0 { q.Page = 1 } if q.PageSize <= 0 { q.PageSize = 20 } if q.PageSize > 100 { q.PageSize = 100 } return nil } ================================================ FILE: internal/application/queries/player/search_players.go ================================================ package player import ( "context" ) // SearchPlayersQuery GM搜索玩家查询 type SearchPlayersQuery struct { Keyword string `json:"keyword,omitempty"` PlayerID string `json:"player_id,omitempty"` Username string `json:"username,omitempty"` Email string `json:"email,omitempty"` Status string `json:"status,omitempty"` MinLevel int `json:"min_level,omitempty"` MaxLevel int `json:"max_level,omitempty"` Page int `json:"page" validate:"min=1"` PageSize int `json:"page_size" validate:"min=1,max=100"` SortBy string `json:"sort_by,omitempty"` SortOrder string `json:"sort_order,omitempty"` } // SearchPlayersResult GM搜索玩家结果 type SearchPlayersResult struct { Players []*PlayerDTO `json:"players"` Total int64 `json:"total"` Page int `json:"page"` Size int `json:"size"` } // SearchPlayersHandler GM搜索玩家处理器 type SearchPlayersHandler struct { playerQueryService PlayerQueryService } // NewSearchPlayersHandler 创建GM搜索玩家处理器 func NewSearchPlayersHandler(playerQueryService PlayerQueryService) *SearchPlayersHandler { return &SearchPlayersHandler{ playerQueryService: playerQueryService, } } // Handle 处理GM搜索玩家请求 func (h *SearchPlayersHandler) Handle(ctx context.Context, query *SearchPlayersQuery) (*SearchPlayersResult, error) { // 验证查询参数 if err := query.Validate(); err != nil { return nil, err } // TODO: 调用服务层搜索玩家 // players, total, err := h.playerQueryService.SearchPlayers(ctx, query) // if err != nil { // return nil, err // } return &SearchPlayersResult{ Players: make([]*PlayerDTO, 0), Total: 0, Page: query.Page, Size: query.PageSize, }, nil } // Validate 验证查询 func (q *SearchPlayersQuery) Validate() error { if q.Page < 1 { q.Page = 1 } if q.PageSize < 1 || q.PageSize > 100 { q.PageSize = 20 } return nil } ================================================ FILE: internal/application/services/building_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/domain/building" ) // BuildingApplicationService 建筑应用服务 type BuildingApplicationService struct { buildingRepo building.BuildingRepository constructionRepo building.ConstructionRepository buildingService *building.BuildingService eventBus building.BuildingEventBus } // NewBuildingApplicationService 创建建筑应用服务 func NewBuildingApplicationService( buildingRepo building.BuildingRepository, constructionRepo building.ConstructionRepository, buildingService *building.BuildingService, eventBus building.BuildingEventBus, ) *BuildingApplicationService { return &BuildingApplicationService{ buildingRepo: buildingRepo, constructionRepo: constructionRepo, buildingService: buildingService, eventBus: eventBus, } } // CreateBuildingTypeRequest 创建建筑类型请求 type CreateBuildingTypeRequest struct { Name string `json:"name"` Description string `json:"description"` Category string `json:"category"` MaxLevel int32 `json:"max_level"` BaseCost int64 `json:"base_cost"` BuildTime int32 `json:"build_time"` Requirements map[string]interface{} `json:"requirements,omitempty"` IsActive bool `json:"is_active"` } // CreateBuildingTypeResponse 创建建筑类型响应 type CreateBuildingTypeResponse struct { BuildingTypeID string `json:"building_type_id"` Name string `json:"name"` Description string `json:"description"` Category string `json:"category"` MaxLevel int32 `json:"max_level"` BaseCost int64 `json:"base_cost"` BuildTime int32 `json:"build_time"` IsActive bool `json:"is_active"` CreatedAt time.Time `json:"created_at"` } // CreateBuildingType 创建建筑类型 func (s *BuildingApplicationService) CreateBuildingType(ctx context.Context, req *CreateBuildingTypeRequest) (*CreateBuildingTypeResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateCreateBuildingTypeRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 转换建筑类别 _, err := s.parseBuildingCategory(req.Category) if err != nil { return nil, fmt.Errorf("invalid category: %w", err) } // 创建建筑类型 // TODO: 实现NewBuildingType方法 buildingType := &building.BuildingAggregate{} // TODO: 实现BuildingAggregate的方法 // buildingType.SetDescription(req.Description) // buildingType.SetBaseCost(req.BaseCost) // buildingType.SetBuildTime(req.BuildTime) // 设置需求条件 // if req.Requirements != nil { // for key, value := range req.Requirements { // buildingType.AddRequirement(key, value) // } // } // if req.IsActive { // buildingType.Activate() // } // 保存建筑类型 // TODO: 实现SaveBuildingType方法 if err := s.buildingRepo.Save(ctx, buildingType); err != nil { return nil, fmt.Errorf("failed to save building type: %w", err) } // 发布事件 // TODO: 实现NewBuildingTypeCreatedEvent方法 event := &building.BuildingCreatedEvent{} if err := s.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish building type created event: %v\n", err) } return &CreateBuildingTypeResponse{ BuildingTypeID: "", // TODO: buildingType.GetID(), Name: req.Name, Description: req.Description, Category: req.Category, MaxLevel: req.MaxLevel, BaseCost: req.BaseCost, BuildTime: req.BuildTime, IsActive: req.IsActive, CreatedAt: time.Now(), }, nil } // StartConstructionRequest 开始建造请求 type StartConstructionRequest struct { PlayerID uint64 `json:"player_id"` BuildingTypeID string `json:"building_type_id"` Position string `json:"position"` SlotID string `json:"slot_id"` } // StartConstructionResponse 开始建造响应 type StartConstructionResponse struct { ConstructionID string `json:"construction_id"` BuildingID string `json:"building_id"` PlayerID uint64 `json:"player_id"` BuildingTypeID string `json:"building_type_id"` Position string `json:"position"` SlotID string `json:"slot_id"` Status string `json:"status"` StartedAt time.Time `json:"started_at"` CompletesAt time.Time `json:"completes_at"` Cost int64 `json:"cost"` } // StartConstruction 开始建造 func (s *BuildingApplicationService) StartConstruction(ctx context.Context, req *StartConstructionRequest) (*StartConstructionResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateStartConstructionRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 获取建筑类型 // TODO: 实现FindBuildingTypeByID方法 buildingType, err := s.buildingRepo.FindByID(ctx, req.BuildingTypeID) if err != nil { return nil, fmt.Errorf("failed to find building type: %w", err) } if buildingType == nil { return nil, fmt.Errorf("building type not found") } // 检查建筑类型是否激活 if !buildingType.IsActive() { return nil, fmt.Errorf("building type is not active") } // 检查位置是否可用 // TODO: 修复Position类型转换 position := &building.Position{} // 临时解决方案 existingBuilding, err := s.buildingRepo.FindByPlayerAndPosition(ctx, req.PlayerID, position) if err != nil { return nil, fmt.Errorf("failed to check position: %w", err) } if existingBuilding != nil { return nil, fmt.Errorf("position already occupied") } // 开始建造 // TODO: 修复StartConstruction方法调用 // startReq := &building.StartConstructionRequest{ // PlayerID: req.PlayerID, // BuildingTypeID: req.BuildingTypeID, // Position: req.Position, // SlotID: req.SlotID, // } _, err = s.buildingService.StartConstruction(ctx, nil) if err != nil { return nil, fmt.Errorf("failed to start construction: %w", err) } // 发布事件 // TODO: 修复NewConstructionStartedEvent方法调用 event := &building.ConstructionStartedEvent{} if err := s.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish construction started event: %v\n", err) } return &StartConstructionResponse{ ConstructionID: "", // TODO: construction.GetID(), BuildingID: "", // TODO: buildingAggregate.GetID(), PlayerID: req.PlayerID, BuildingTypeID: req.BuildingTypeID, Position: req.Position, SlotID: req.SlotID, Status: "started", // TODO: construction.GetStatus().String(), StartedAt: time.Now(), CompletesAt: time.Now().Add(24 * time.Hour), // TODO: construction.GetCompletesAt(), Cost: 0, // TODO: construction.GetCost(), }, nil } // CompleteConstructionRequest 完成建造请求 type CompleteConstructionRequest struct { ConstructionID string `json:"construction_id"` InstantComplete bool `json:"instant_complete"` } // CompleteConstructionResponse 完成建造响应 type CompleteConstructionResponse struct { ConstructionID string `json:"construction_id"` BuildingID string `json:"building_id"` Status string `json:"status"` CompletedAt time.Time `json:"completed_at"` Rewards []*BuildingRewardResponse `json:"rewards,omitempty"` } // BuildingRewardResponse 建筑奖励响应 type BuildingRewardResponse struct { Type string `json:"type"` ItemID string `json:"item_id,omitempty"` Quantity int32 `json:"quantity"` Reason string `json:"reason"` } // CompleteConstruction 完成建造 func (s *BuildingApplicationService) CompleteConstruction(ctx context.Context, req *CompleteConstructionRequest) (*CompleteConstructionResponse, error) { if req == nil || req.ConstructionID == "" { return nil, fmt.Errorf("construction ID is required") } // 获取建造进程 construction, err := s.constructionRepo.FindByID(ctx, req.ConstructionID) if err != nil { return nil, fmt.Errorf("failed to find construction: %w", err) } if construction == nil { return nil, fmt.Errorf("construction not found") } // 检查建造状态 // TODO: 实现IsInProgress方法 // if !construction.IsInProgress() { // return nil, fmt.Errorf("construction is not in progress") // } // 完成建造 // TODO: 实现CompleteConstruction方法 // _, err = s.buildingService.CompleteConstruction(ctx, req.ConstructionID) // if err != nil { // return nil, fmt.Errorf("failed to complete construction: %w", err) // } // 转换奖励响应 // TODO: 修复result变量 // rewardResponses := make([]*BuildingRewardResponse, len(result.Rewards)) // for i, reward := range result.Rewards { // rewardResponses[i] = &BuildingRewardResponse{ // Type: reward.GetType().String(), // ItemID: reward.GetItemID(), // Quantity: reward.GetQuantity(), // Reason: reward.GetReason(), // } // } rewardResponses := []*BuildingRewardResponse{} // 发布事件 // TODO: 修复NewConstructionCompletedEvent方法调用 event := &building.ConstructionCompletedEvent{} if err := s.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish construction completed event: %v\n", err) } return &CompleteConstructionResponse{ ConstructionID: req.ConstructionID, BuildingID: "", // TODO: result.BuildingID, Status: "completed", // TODO: result.Status.String(), CompletedAt: time.Now(), // TODO: result.CompletedAt, Rewards: rewardResponses, }, nil } // UpgradeBuildingRequest 升级建筑请求 type UpgradeBuildingRequest struct { BuildingID string `json:"building_id"` TargetLevel int32 `json:"target_level"` } // UpgradeBuildingResponse 升级建筑响应 type UpgradeBuildingResponse struct { BuildingID string `json:"building_id"` OldLevel int32 `json:"old_level"` NewLevel int32 `json:"new_level"` UpgradeCost int64 `json:"upgrade_cost"` UpgradeTime int32 `json:"upgrade_time"` StartedAt time.Time `json:"started_at"` CompletesAt time.Time `json:"completes_at"` } // UpgradeBuilding 升级建筑 func (s *BuildingApplicationService) UpgradeBuilding(ctx context.Context, req *UpgradeBuildingRequest) (*UpgradeBuildingResponse, error) { if req == nil || req.BuildingID == "" { return nil, fmt.Errorf("building ID is required") } if req.TargetLevel <= 0 { return nil, fmt.Errorf("target level must be positive") } // 获取建筑 buildingAggregate, err := s.buildingRepo.FindByID(ctx, req.BuildingID) if err != nil { return nil, fmt.Errorf("failed to find building: %w", err) } if buildingAggregate == nil { return nil, fmt.Errorf("building not found") } // 检查建筑状态 // TODO: 实现IsCompleted方法 // if !buildingAggregate.IsCompleted() { // return nil, fmt.Errorf("building is not completed") // } // TODO: 实现GetLevel方法 // oldLevel := buildingAggregate.GetLevel().GetCurrentLevel() // 升级建筑 // TODO: 实现UpgradeBuilding方法 // _, err = s.buildingService.UpgradeBuilding(ctx, req.BuildingID) // if err != nil { // return nil, fmt.Errorf("failed to upgrade building: %w", err) // } // 发布事件 // TODO: 修复NewBuildingUpgradeStartedEvent方法调用 event := &building.BuildingCreatedEvent{} if err := s.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish building upgrade started event: %v\n", err) } return &UpgradeBuildingResponse{ BuildingID: req.BuildingID, OldLevel: 1, // TODO: oldLevel, NewLevel: req.TargetLevel, UpgradeCost: 0, // TODO: result.Cost, UpgradeTime: 0, // TODO: result.Time, StartedAt: time.Now(), CompletesAt: time.Now().Add(24 * time.Hour), // TODO: result.CompletesAt, }, nil } // GetPlayerBuildingsRequest 获取玩家建筑请求 type GetPlayerBuildingsRequest struct { PlayerID uint64 `json:"player_id"` Category string `json:"category,omitempty"` Status string `json:"status,omitempty"` } // BuildingResponse 建筑响应 type BuildingResponse struct { BuildingID string `json:"building_id"` BuildingTypeID string `json:"building_type_id"` Name string `json:"name"` Category string `json:"category"` Level int32 `json:"level"` MaxLevel int32 `json:"max_level"` Position string `json:"position"` SlotID string `json:"slot_id"` Status string `json:"status"` Effects []*BuildingEffectResponse `json:"effects,omitempty"` Production map[string]interface{} `json:"production,omitempty"` CreatedAt time.Time `json:"created_at"` CompletedAt *time.Time `json:"completed_at,omitempty"` LastUpdated time.Time `json:"last_updated"` } // BuildingEffectResponse 建筑效果响应 type BuildingEffectResponse struct { Type string `json:"type"` Target string `json:"target"` Value float64 `json:"value"` Description string `json:"description"` } // GetPlayerBuildingsResponse 获取玩家建筑响应 type GetPlayerBuildingsResponse struct { PlayerID uint64 `json:"player_id"` Buildings []*BuildingResponse `json:"buildings"` Total int64 `json:"total"` } // GetPlayerBuildings 获取玩家建筑 func (s *BuildingApplicationService) GetPlayerBuildings(ctx context.Context, req *GetPlayerBuildingsRequest) (*GetPlayerBuildingsResponse, error) { if req == nil || req.PlayerID == 0 { return nil, fmt.Errorf("player ID is required") } // 构建查询 // TODO: 修复NewBuildingQuery方法调用 query := &building.BuildingQuery{} if req.Category != "" { _, err := s.parseBuildingCategory(req.Category) if err != nil { return nil, fmt.Errorf("invalid category: %w", err) } // TODO: 修复WithCategory方法调用 // query = query.WithCategory(category) } if req.Status != "" { _, err := s.parseBuildingStatus(req.Status) if err != nil { return nil, fmt.Errorf("invalid status: %w", err) } // TODO: 修复WithStatus方法调用 // query = query.WithStatus(status) } // 查询建筑 buildings, total, err := s.buildingRepo.FindByQuery(ctx, query) if err != nil { return nil, fmt.Errorf("failed to find buildings: %w", err) } // 转换响应 buildingResponses := make([]*BuildingResponse, len(buildings)) for i, _ := range buildings { // 获取建筑类型信息 // TODO: 修复FindBuildingTypeByID方法调用 // buildingType, _ := s.buildingRepo.FindBuildingTypeByID(ctx, buildingAggregate.GetBuildingTypeID()) // 转换效果 // TODO: 实现GetEffects方法 effectResponses := make([]*BuildingEffectResponse, 0) // for _, effect := range buildingAggregate.GetEffects() { // effectResponses = append(effectResponses, &BuildingEffectResponse{ // Type: effect.GetType().String(), // Target: effect.GetTarget(), // Value: effect.GetValue(), // Description: effect.GetDescription(), // }) // } buildingResponse := &BuildingResponse{ BuildingID: "", // TODO: buildingAggregate.GetID(), BuildingTypeID: "", // TODO: buildingAggregate.GetBuildingTypeID(), Level: 1, // TODO: buildingAggregate.GetLevel().GetCurrentLevel(), Position: "", // TODO: buildingAggregate.GetPosition(), SlotID: "", // TODO: buildingAggregate.GetSlotID(), Status: "active", // TODO: buildingAggregate.GetStatus().String(), Effects: effectResponses, Production: nil, // TODO: buildingAggregate.GetProduction(), CreatedAt: time.Now(), // TODO: buildingAggregate.GetCreatedAt(), LastUpdated: time.Now(), // TODO: buildingAggregate.GetUpdatedAt(), } // TODO: 修复buildingType变量 // if buildingType != nil { // buildingResponse.Name = buildingType.GetName() // buildingResponse.Category = buildingType.GetCategory().String() // buildingResponse.MaxLevel = buildingType.GetMaxLevel() // } // TODO: 实现IsCompleted方法 // if buildingAggregate.IsCompleted() { // completedAt := buildingAggregate.GetCompletedAt() // buildingResponse.CompletedAt = &completedAt // } buildingResponses[i] = buildingResponse } return &GetPlayerBuildingsResponse{ PlayerID: req.PlayerID, Buildings: buildingResponses, Total: total, }, nil } // GetBuildingDetailsRequest 获取建筑详情请求 type GetBuildingDetailsRequest struct { BuildingID string `json:"building_id"` } // GetBuildingDetailsResponse 获取建筑详情响应 type GetBuildingDetailsResponse struct { Building *BuildingResponse `json:"building"` Construction *ConstructionResponse `json:"construction,omitempty"` } // ConstructionResponse 建造响应 type ConstructionResponse struct { ConstructionID string `json:"construction_id"` Status string `json:"status"` Progress float64 `json:"progress"` StartedAt time.Time `json:"started_at"` CompletesAt time.Time `json:"completes_at"` RemainingTime int32 `json:"remaining_time"` Cost int64 `json:"cost"` } // GetBuildingDetails 获取建筑详情 func (s *BuildingApplicationService) GetBuildingDetails(ctx context.Context, req *GetBuildingDetailsRequest) (*GetBuildingDetailsResponse, error) { if req == nil || req.BuildingID == "" { return nil, fmt.Errorf("building ID is required") } // 获取建筑 buildingAggregate, err := s.buildingRepo.FindByID(ctx, req.BuildingID) if err != nil { return nil, fmt.Errorf("failed to find building: %w", err) } if buildingAggregate == nil { return nil, fmt.Errorf("building not found") } // 获取建筑类型信息 // TODO: 修复FindBuildingTypeByID方法调用 // buildingType, err := s.buildingRepo.FindBuildingTypeByID(ctx, buildingAggregate.GetBuildingTypeID()) // if err != nil { // return nil, fmt.Errorf("failed to find building type: %w", err) // } // 转换建筑响应 // TODO: 实现GetEffects方法 effectResponses := make([]*BuildingEffectResponse, 0) // for _, effect := range buildingAggregate.GetEffects() { // effectResponses = append(effectResponses, &BuildingEffectResponse{ // Type: effect.GetType().String(), // Target: effect.GetTarget(), // Value: effect.GetValue(), // Description: effect.GetDescription(), // }) // } buildingResponse := &BuildingResponse{ BuildingID: "", // TODO: buildingAggregate.GetID(), BuildingTypeID: "", // TODO: buildingAggregate.GetBuildingTypeID(), Level: 1, // TODO: buildingAggregate.GetLevel().GetCurrentLevel(), Position: "", // TODO: buildingAggregate.GetPosition(), SlotID: "", // TODO: buildingAggregate.GetSlotID(), Status: "active", // TODO: buildingAggregate.GetStatus().String(), Effects: effectResponses, Production: nil, // TODO: buildingAggregate.GetProduction(), CreatedAt: time.Now(), // TODO: buildingAggregate.GetCreatedAt(), LastUpdated: time.Now(), // TODO: buildingAggregate.GetUpdatedAt(), } // TODO: 修复buildingType变量 // if buildingType != nil { // buildingResponse.Name = buildingType.GetName() // buildingResponse.Category = buildingType.GetCategory().String() // buildingResponse.MaxLevel = buildingType.GetMaxLevel() // } // TODO: 实现IsCompleted方法 // if buildingAggregate.IsCompleted() { // completedAt := buildingAggregate.GetCompletedAt() // buildingResponse.CompletedAt = &completedAt // } response := &GetBuildingDetailsResponse{ Building: buildingResponse, } // 获取建造信息(如果存在) // TODO: 修复FindByBuildingID方法调用 // construction, err := s.constructionRepo.FindByBuildingID(ctx, req.BuildingID) // if err == nil && construction != nil && construction.IsInProgress() { // // 计算进度和剩余时间 // progress := construction.GetProgress() // remainingTime := int32(0) // if !construction.IsCompleted() { // remainingTime = int32(construction.GetCompletesAt().Sub(time.Now()).Seconds()) // if remainingTime < 0 { // remainingTime = 0 // } // } // response.Construction = &ConstructionResponse{ // ConstructionID: construction.GetID(), // Status: construction.GetStatus().String(), // Progress: progress, // StartedAt: construction.GetStartedAt(), // CompletesAt: construction.GetCompletesAt(), // RemainingTime: remainingTime, // Cost: construction.GetCost(), // } // } return response, nil } // 私有方法 // validateCreateBuildingTypeRequest 验证创建建筑类型请求 func (s *BuildingApplicationService) validateCreateBuildingTypeRequest(req *CreateBuildingTypeRequest) error { if req.Name == "" { return fmt.Errorf("name is required") } if len(req.Name) > 100 { return fmt.Errorf("name too long (max 100 characters)") } if req.Category == "" { return fmt.Errorf("category is required") } if req.MaxLevel <= 0 { return fmt.Errorf("max level must be positive") } if req.BaseCost < 0 { return fmt.Errorf("base cost cannot be negative") } if req.BuildTime <= 0 { return fmt.Errorf("build time must be positive") } return nil } // validateStartConstructionRequest 验证开始建造请求 func (s *BuildingApplicationService) validateStartConstructionRequest(req *StartConstructionRequest) error { if req.PlayerID == 0 { return fmt.Errorf("player ID is required") } if req.BuildingTypeID == "" { return fmt.Errorf("building type ID is required") } if req.Position == "" { return fmt.Errorf("position is required") } return nil } // parseBuildingCategory 解析建筑类别 // TODO: 实现BuildingCategory类型 func (s *BuildingApplicationService) parseBuildingCategory(categoryStr string) (string, error) { switch categoryStr { case "residential": return "residential", nil case "commercial": return "commercial", nil case "industrial": return "industrial", nil case "military": return "military", nil case "decoration": return "decoration", nil case "special": return "special", nil default: return "residential", fmt.Errorf("unknown category: %s", categoryStr) } } // parseBuildingStatus 解析建筑状态 // TODO: 实现BuildingStatus类型 func (s *BuildingApplicationService) parseBuildingStatus(statusStr string) (string, error) { switch statusStr { case "planning": return "planning", nil case "constructing": return "constructing", nil case "completed": return "completed", nil case "upgrading": return "upgrading", nil case "demolished": return "demolished", nil default: return "planning", fmt.Errorf("unknown status: %s", statusStr) } } ================================================ FILE: internal/application/services/character_service.go ================================================ package services import ( "context" "errors" "fmt" "time" "greatestworks/internal/domain/character" "greatestworks/internal/infrastructure/datamanager" "greatestworks/internal/infrastructure/persistence" ) // CharacterService 角色服务 type CharacterService struct { characterRepo *persistence.CharacterRepository itemRepo *persistence.ItemRepository questRepo *persistence.QuestRepository } // NewCharacterService 创建角色服务 func NewCharacterService( characterRepo *persistence.CharacterRepository, itemRepo *persistence.ItemRepository, questRepo *persistence.QuestRepository, ) *CharacterService { return &CharacterService{ characterRepo: characterRepo, itemRepo: itemRepo, questRepo: questRepo, } } // CreateCharacter 创建角色 func (s *CharacterService) CreateCharacter(ctx context.Context, userID int64, name string, race, class int32) (int64, error) { // 生成角色ID characterID := time.Now().UnixNano() // 获取角色初始配置 unitDefine := datamanager.GetInstance().GetUnit(class) if unitDefine == nil { return 0, errors.New("invalid character class") } // 创建角色 dbChar := &persistence.DbCharacter{ CharacterID: characterID, UserID: userID, Name: name, Race: race, Class: class, Level: 1, Exp: 0, Gold: 1000, // 初始金币 MapID: 1, // 默认地图 PositionX: 0.0, PositionY: 0.0, PositionZ: 0.0, Direction: 0.0, HP: unitDefine.MaxHP, MP: unitDefine.MaxMP, MaxHP: unitDefine.MaxHP, MaxMP: unitDefine.MaxMP, STR: unitDefine.STR, INT: unitDefine.INT, AGI: unitDefine.AGI, VIT: unitDefine.VIT, SPR: unitDefine.SPR, AD: unitDefine.AD, AP: unitDefine.AP, DEF: unitDefine.DEF, RES: unitDefine.RES, SPD: unitDefine.SPD, CRI: 50, // 默认暴击率 5% CRID: 150, // 默认暴击伤害 150% HitRate: 950, // 默认命中率 95% Dodge: 50, // 默认闪避率 5% } if err := s.characterRepo.Create(ctx, dbChar); err != nil { return 0, fmt.Errorf("failed to create character: %w", err) } // TODO: 创建初始物品 return characterID, nil } // GetCharacter 获取角色信息 func (s *CharacterService) GetCharacter(ctx context.Context, characterID int64) (*persistence.DbCharacter, error) { return s.characterRepo.FindByID(ctx, characterID) } // GetCharactersByUser 获取用户的所有角色 func (s *CharacterService) GetCharactersByUser(ctx context.Context, userID int64) ([]*persistence.DbCharacter, error) { return s.characterRepo.FindByUserID(ctx, userID) } // DeleteCharacter 删除角色 func (s *CharacterService) DeleteCharacter(ctx context.Context, characterID int64) error { return s.characterRepo.Delete(ctx, characterID) } // LoadCharacter 加载角色到内存(转换为领域对象) func (s *CharacterService) LoadCharacter(ctx context.Context, characterID int64) (*character.Player, error) { dbChar, err := s.characterRepo.FindByID(ctx, characterID) if err != nil { return nil, fmt.Errorf("failed to load character: %w", err) } // 创建领域对象 // 构造实体所需的位置信息与朝向(方向先用默认前向) pos := character.NewVector3(dbChar.PositionX, dbChar.PositionY, dbChar.PositionZ) dir := character.NewVector3(0, 0, 1) player := character.NewPlayer( character.EntityID(int32(dbChar.CharacterID)), // 实体ID(简化转换) dbChar.CharacterID, // 角色ID dbChar.UserID, // 用户ID dbChar.Class, // 使用职业作为unitID pos, dir, dbChar.Name, dbChar.Level, ) // 设置属性 player.SetLevel(dbChar.Level) // 设置基础属性到属性管理器 am := player.GetAttributeManager() base := character.Attributes{ MaxHP: float32(dbChar.MaxHP), MaxMP: float32(dbChar.MaxMP), HPRegen: 0, MPRegen: 0, AD: float32(dbChar.AD), AP: float32(dbChar.AP), Def: float32(dbChar.DEF), MDef: float32(dbChar.RES), Cri: float32(dbChar.CRI) / 1000.0, // 依据存储比例进行简单换算(占位) Crd: float32(dbChar.CRID) / 100.0, // 依据存储比例进行简单换算(占位) HitRate: float32(dbChar.HitRate) / 1000.0, // 占位 DodgeRate: float32(dbChar.Dodge) / 1000.0, // 占位 Speed: float32(dbChar.SPD), AttackSpeed: 1, } am.SetBase(base) // 设置当前HP/MP(以增量方式设置到目标数值) if dbChar.HP > 0 { player.ChangeHP(float32(dbChar.HP)) } if dbChar.MP > 0 { player.ChangeMP(float32(dbChar.MP)) } // 设置基础属性 // 由于当前领域模型未包含STR/INT/AGI/VIT/SPR等细分属性,暂不映射这些字段 // 加载物品 items, err := s.itemRepo.FindByCharacterID(ctx, characterID) if err == nil { // TODO: 加载物品到背包 _ = items } // 加载任务 quests, err := s.questRepo.FindByCharacterID(ctx, characterID) if err == nil { // TODO: 加载任务到任务管理器 _ = quests } return player, nil } // SaveCharacter 保存角色到数据库 func (s *CharacterService) SaveCharacter(ctx context.Context, player *character.Player) error { // 读取最终属性用于持久化 attrs := player.GetAttributeManager().Final() dbChar := &persistence.DbCharacter{ CharacterID: player.CharacterID(), Name: player.GetName(), Race: player.GetRace(), Class: player.GetClass(), Level: player.Level(), Exp: player.GetExp(), Gold: player.GetGold(), HP: int32(player.HP()), MP: int32(player.MP()), MaxHP: int32(attrs.MaxHP), MaxMP: int32(attrs.MaxMP), // 领域模型暂不包含以下基础属性的独立管理,使用0占位 STR: 0, INT: 0, AGI: 0, VIT: 0, SPR: 0, AD: int32(attrs.AD), AP: int32(attrs.AP), DEF: int32(attrs.Def), RES: int32(attrs.MDef), SPD: int32(attrs.Speed), CRI: int32(attrs.Cri * 1000), // 与Load时的占位换算保持一致 CRID: int32(attrs.Crd * 100), // 占位 HitRate: int32(attrs.HitRate * 1000), // 占位 Dodge: int32(attrs.DodgeRate * 1000), } return s.characterRepo.Update(ctx, dbChar) } // UpdatePosition 更新角色位置 func (s *CharacterService) UpdatePosition(ctx context.Context, characterID int64, mapID int32, x, y, z, dir float32) error { return s.characterRepo.UpdatePosition(ctx, characterID, mapID, x, y, z, dir) } // UpdateLastLocation 更新角色上次位置(用于登出保存) func (s *CharacterService) UpdateLastLocation(ctx context.Context, characterID int64, mapID int32, x, y, z float32) error { // 直接调用底层仓储更新位置,direction 传0即可 return s.characterRepo.UpdatePosition(ctx, characterID, mapID, x, y, z, 0) } // AddExp 添加经验 func (s *CharacterService) AddExp(ctx context.Context, player *character.Player, exp int64) error { player.AddExp(exp) // 检查升级 for player.CanLevelUp() { player.LevelUp() } return s.SaveCharacter(ctx, player) } // AddGold 添加金币 func (s *CharacterService) AddGold(ctx context.Context, player *character.Player, gold int64) error { player.AddGold(gold) return s.SaveCharacter(ctx, player) } ================================================ FILE: internal/application/services/fight_service.go ================================================ package services import ( "context" "errors" "math/rand/v2" "greatestworks/internal/domain/character" "greatestworks/internal/infrastructure/datamanager" ) // SkillCastResult 技能释放结果 type SkillCastResult struct { CasterID int32 TargetID int32 SkillID int32 Damage int32 IsCritical bool Success bool Message string } // FightService 战斗服务 type FightService struct { characterService *CharacterService mapService *MapService } // NewFightService 创建战斗服务 func NewFightService(characterService *CharacterService) *FightService { return &FightService{ characterService: characterService, } } // SetMapService 注入地图服务用于查找实体 func (s *FightService) SetMapService(ms *MapService) { s.mapService = ms } // CastSkill 释放技能 func (s *FightService) CastSkill(ctx context.Context, caster *character.Actor, targetID int64, skillID int32) error { // 获取技能定义 skillDefine := datamanager.GetInstance().GetSkill(skillID) if skillDefine == nil { return errors.New("skill not found") } // 检查技能是否学会 skill := caster.GetSkillManager().GetSkill(skillID) if skill == nil { return errors.New("skill not learned") } // 检查技能状态 if skill.State() != character.SkillStateReady { return errors.New("skill is not ready") } // 使用施法器释放技能(此处未解析targetID,默认无目标) if ok := caster.GetSpell().Cast(skillID, nil); !ok { return errors.New("cast failed") } return nil } // CastSkillByID 基于ID的施法接口:计算伤害并返回结果(不直接应用) // handler 层负责广播结果;实际伤害应用可选地通过后续流程完成 func (s *FightService) CastSkillByID(ctx context.Context, casterEntityID int32, targetEntityID int32, skillID int32) (*SkillCastResult, error) { result := &SkillCastResult{ CasterID: casterEntityID, TargetID: targetEntityID, SkillID: skillID, Success: false, } // 获取技能定义 skillDefine := datamanager.GetInstance().GetSkill(skillID) if skillDefine == nil { result.Message = "skill not found" return result, errors.New("skill not found") } // 简化计算:基于技能基础伤害与固定暴击率 baseDamage := skillDefine.BaseDamage if baseDamage == 0 { baseDamage = 100 // 默认技能伤害 } // 暴击判定(固定10%暴击率,伤害1.5倍) isCrit := (rand.Int32N(100) < 10) totalDamage := baseDamage if isCrit { totalDamage = int32(float32(totalDamage) * 1.5) } result.Damage = totalDamage result.IsCritical = isCrit result.Success = true result.Message = "skill cast calculated" return result, nil } // ApplyDamage 应用伤害 func (s *FightService) ApplyDamage(ctx context.Context, attacker, target *character.Actor, damage int32, dmgType int32) error { if target == nil { return errors.New("target is nil") } // 应用伤害 info := &character.DamageInfo{ TargetID: target.ID(), AttackerInfo: character.AttackerInfo{ AttackerID: attacker.ID(), AttackerType: character.AttackerTypeNormal, }, Amount: damage, DamageType: character.DamageType(dmgType), } _ = target.OnHurt(ctx, info) // 检查死亡 if target.IsDeath() { s.onActorDeath(ctx, target, attacker) } return nil } // ApplyHeal 应用治疗 func (s *FightService) ApplyHeal(ctx context.Context, caster, target *character.Actor, heal int32) error { if target == nil { return errors.New("target is nil") } currentHP := target.HP() maxHP := target.GetAttributeManager().Final().MaxHP newHP := currentHP + float32(heal) if newHP > maxHP { newHP = maxHP } target.ChangeHP(newHP - currentHP) return nil } // ApplyBuff 应用Buff func (s *FightService) ApplyBuff(ctx context.Context, caster, target *character.Actor, buffID int32, duration float32) error { if target == nil { return errors.New("target is nil") } // 创建并添加Buff buff := character.NewBuff(buffID, target, caster, duration) target.GetBuffManager().AddBuff(buff) return nil } // RemoveBuff 移除Buff func (s *FightService) RemoveBuff(ctx context.Context, target *character.Actor, buffID int32) error { if target == nil { return errors.New("target is nil") } target.GetBuffManager().RemoveBuffByID(buffID) return nil } // UpdateFight 更新战斗状态 func (s *FightService) UpdateFight(ctx context.Context, actor *character.Actor, deltaTime float32) { // 更新技能与Buff _ = actor.GetSkillManager().Update(ctx, deltaTime) _ = actor.GetBuffManager().Update(ctx, deltaTime) } // onActorDeath 角色死亡处理 func (s *FightService) onActorDeath(ctx context.Context, deadActor, killer *character.Actor) { // TODO: 掉落物品、经验奖励、复活处理等 } // Resurrect 复活 func (s *FightService) Resurrect(ctx context.Context, actor *character.Actor) error { if !actor.IsDeath() { return errors.New("actor is not dead") } return actor.Revive(ctx) } ================================================ FILE: internal/application/services/hangup_service.go ================================================ package services import ( "context" "time" "greatestworks/internal/domain/player/hangup" ) // HangupService 挂机应用服务 type HangupService struct { hangupRepo hangup.HangupRepository // TODO: 实现这些仓储接口 // locationRepo hangup.LocationRepository // rewardRepo hangup.RewardRepository // statisticsRepo hangup.StatisticsRepository // cacheRepo hangup.CacheRepository hangupService *hangup.HangupService } // NewHangupService 创建挂机应用服务 func NewHangupService( hangupRepo hangup.HangupRepository, // TODO: 实现这些仓储接口 // locationRepo hangup.LocationRepository, // rewardRepo hangup.RewardRepository, // statisticsRepo hangup.StatisticsRepository, // cacheRepo hangup.CacheRepository, hangupService *hangup.HangupService, ) *HangupService { return &HangupService{ hangupRepo: hangupRepo, // TODO: 实现这些仓储接口 // locationRepo: locationRepo, // rewardRepo: rewardRepo, // statisticsRepo: statisticsRepo, // cacheRepo: cacheRepo, hangupService: hangupService, } } // StartHangup 开始挂机 func (s *HangupService) StartHangup(ctx context.Context, playerID string, locationID string) error { // 检查玩家是否已在挂机 // TODO: 修复FindActiveByPlayer方法调用 // existingHangup, err := s.hangupRepo.FindActiveByPlayer(playerID) // if err != nil && !hangup.IsNotFoundError(err) { // return fmt.Errorf("failed to check existing hangup: %w", err) // } // if existingHangup != nil { // return hangup.ErrAlreadyHanging // } // 获取挂机地点信息 // TODO: 修复locationRepo字段 // location, err := s.locationRepo.FindByID(locationID) // if err != nil { // return fmt.Errorf("failed to get hangup location: %w", err) // } // 创建挂机记录 // TODO: 修复StartHangup方法调用 // hangupRecord, err := s.hangupService.StartHangup(playerID, location) // if err != nil { // return fmt.Errorf("failed to start hangup: %w", err) // } // 保存挂机记录 // TODO: 修复Save方法调用 // if err := s.hangupRepo.Save(hangupRecord); err != nil { // return fmt.Errorf("failed to save hangup record: %w", err) // } // 更新缓存 // TODO: 修复SetActiveHangup方法调用 // if err := s.cacheRepo.SetActiveHangup(playerID, hangupRecord, time.Hour); err != nil { // // 缓存失败不影响主流程,只记录日志 // // TODO: 添加日志记录 // } return nil } // StopHangup 停止挂机 // TODO: 修复OfflineReward类型 func (s *HangupService) StopHangup(ctx context.Context, playerID string) (interface{}, error) { // 获取当前挂机记录 // TODO: 修复FindActiveByPlayer方法调用 // hangupRecord, err := s.hangupRepo.FindActiveByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get hangup record: %w", err) // } // if hangupRecord == nil { // return nil, hangup.ErrNotHanging // } // 计算离线奖励 // TODO: 修复CalculateOfflineReward方法调用 // reward, err := s.hangupService.CalculateOfflineReward(hangupRecord) // if err != nil { // return nil, fmt.Errorf("failed to calculate offline reward: %w", err) // } // 停止挂机 // TODO: 修复Stop方法调用 // if err := hangupRecord.Stop(); err != nil { // return nil, fmt.Errorf("failed to stop hangup: %w", err) // } // 更新挂机记录 // TODO: 修复Update方法调用 // if err := s.hangupRepo.Update(hangupRecord); err != nil { // return nil, fmt.Errorf("failed to update hangup record: %w", err) // } // 保存奖励记录 // TODO: 修复Save方法调用 // if err := s.rewardRepo.Save(reward); err != nil { // return nil, fmt.Errorf("failed to save reward record: %w", err) // } // 更新统计数据 // TODO: 修复updateStatistics方法调用 // if err := s.updateStatistics(ctx, playerID, hangupRecord, reward); err != nil { // // 统计更新失败不影响主流程 // // TODO: 添加日志记录 // } // 清除缓存 // TODO: 修复DeleteActiveHangup方法调用 // if err := s.cacheRepo.DeleteActiveHangup(playerID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } // TODO: 修复reward变量 return nil, nil } // GetHangupStatus 获取挂机状态 func (s *HangupService) GetHangupStatus(ctx context.Context, playerID string) (*HangupStatusDTO, error) { // 先从缓存获取 // TODO: 修复cacheRepo字段 // cachedHangup, err := s.cacheRepo.GetActiveHangup(playerID) // if err == nil && cachedHangup != nil { // return s.buildHangupStatusDTO(cachedHangup), nil // } // 从数据库获取 // TODO: 修复FindActiveByPlayer方法调用 // hangupRecord, err := s.hangupRepo.FindActiveByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get hangup record: %w", err) // } // if hangupRecord == nil { // return &HangupStatusDTO{ // PlayerID: playerID, // IsHanging: false, // }, nil // } // 更新缓存 // TODO: 修复SetActiveHangup方法调用 // if err := s.cacheRepo.SetActiveHangup(playerID, hangupRecord, time.Hour); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } // TODO: 修复buildHangupStatusDTO方法调用 // return s.buildHangupStatusDTO(hangupRecord), nil return &HangupStatusDTO{ PlayerID: playerID, IsHanging: false, }, nil } // GetAvailableLocations 获取可用挂机地点 func (s *HangupService) GetAvailableLocations(ctx context.Context, playerID string) ([]*HangupLocationDTO, error) { // 先从缓存获取 // TODO: 修复cacheRepo字段 // cachedLocations, err := s.cacheRepo.GetAvailableLocations(playerID) // if err == nil && len(cachedLocations) > 0 { // return s.buildLocationDTOs(cachedLocations), nil // } // 从数据库获取 // TODO: 修复locationRepo字段 // locations, err := s.locationRepo.FindAvailableForPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get available locations: %w", err) // } // 更新缓存 // TODO: 修复SetAvailableLocations方法调用 // if err := s.cacheRepo.SetAvailableLocations(playerID, locations, time.Hour*2); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } // TODO: 修复buildLocationDTOs方法调用 // return s.buildLocationDTOs(locations), nil return []*HangupLocationDTO{}, nil } // GetHangupHistory 获取挂机历史 func (s *HangupService) GetHangupHistory(ctx context.Context, playerID string, limit int) ([]*HangupHistoryDTO, error) { // TODO: 修复FindHistoryByPlayer方法调用 // history, err := s.hangupRepo.FindHistoryByPlayer(playerID, limit) // if err != nil { // return nil, fmt.Errorf("failed to get hangup history: %w", err) // } // TODO: 修复buildHistoryDTOs方法调用 // return s.buildHistoryDTOs(history), nil return []*HangupHistoryDTO{}, nil } // GetHangupStatistics 获取挂机统计 func (s *HangupService) GetHangupStatistics(ctx context.Context, playerID string) (*HangupStatisticsDTO, error) { // TODO: 修复statisticsRepo字段 // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get hangup statistics: %w", err) // } // TODO: 修复buildStatisticsDTO方法调用 // return s.buildStatisticsDTO(stats), nil return &HangupStatisticsDTO{}, nil } // ClaimOfflineReward 领取离线奖励 func (s *HangupService) ClaimOfflineReward(ctx context.Context, playerID string, rewardID string) error { // 获取奖励记录 // TODO: 修复rewardRepo字段 // reward, err := s.rewardRepo.FindByID(rewardID) // if err != nil { // return fmt.Errorf("failed to get reward record: %w", err) // } // if reward.PlayerID != playerID { // return hangup.ErrUnauthorized // } // if reward.IsClaimed() { // return hangup.ErrRewardAlreadyClaimed // } // 领取奖励 // TODO: 修复Claim方法调用 // if err := reward.Claim(); err != nil { // return fmt.Errorf("failed to claim reward: %w", err) // } // 更新奖励记录 // TODO: 修复Update方法调用 // if err := s.rewardRepo.Update(reward); err != nil { // return fmt.Errorf("failed to update reward record: %w", err) // } return nil } // 私有方法 // updateStatistics 更新统计数据 func (s *HangupService) updateStatistics(ctx context.Context, playerID string, hangupRecord *hangup.HangupAggregate, reward *hangup.OfflineReward) error { // TODO: 修复statisticsRepo字段 // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil && !hangup.IsNotFoundError(err) { // return err // } // if stats == nil { // stats = hangup.NewHangupStatistics(playerID) // } // 更新统计数据 // TODO: 修复AddHangupSession方法调用 // stats.AddHangupSession(hangupRecord.GetDuration(), reward.GetTotalValue()) // stats.AddLocationTime(hangupRecord.GetLocationID(), hangupRecord.GetDuration()) // 保存统计数据 // TODO: 修复Save方法调用 // return s.statisticsRepo.Save(stats) return nil } // buildHangupStatusDTO 构建挂机状态DTO func (s *HangupService) buildHangupStatusDTO(hangupRecord *hangup.HangupAggregate) *HangupStatusDTO { // TODO: 修复HangupAggregate方法调用 return &HangupStatusDTO{ PlayerID: "", // TODO: hangupRecord.GetPlayerID(), IsHanging: false, // TODO: hangupRecord.IsActive(), LocationID: "", // TODO: hangupRecord.GetLocationID(), LocationName: "", // TODO: hangupRecord.GetLocationName(), StartTime: time.Now(), // TODO: hangupRecord.GetStartTime(), Duration: 0, // TODO: hangupRecord.GetDuration(), Efficiency: 0, // TODO: hangupRecord.GetEfficiency(), EstimatedReward: 0, // TODO: s.calculateEstimatedReward(hangupRecord), } } // buildLocationDTOs 构建地点DTO列表 func (s *HangupService) buildLocationDTOs(locations []*hangup.HangupLocation) []*HangupLocationDTO { dtos := make([]*HangupLocationDTO, len(locations)) for i, _ := range locations { dtos[i] = &HangupLocationDTO{ ID: "", // TODO: location.GetID(), Name: "", // TODO: location.GetName(), Description: "", // TODO: location.GetDescription(), BaseRate: 0, // TODO: location.GetBaseRate(), RequiredLevel: 0, // TODO: location.GetRequiredLevel(), IsUnlocked: false, // TODO: location.IsUnlocked(), RewardTypes: []string{}, // TODO: location.GetRewardTypes(), } } return dtos } // buildHistoryDTOs 构建历史DTO列表 func (s *HangupService) buildHistoryDTOs(history []*hangup.HangupAggregate) []*HangupHistoryDTO { dtos := make([]*HangupHistoryDTO, len(history)) for i, _ := range history { dtos[i] = &HangupHistoryDTO{ ID: "", // TODO: record.GetID(), LocationID: "", // TODO: record.GetLocationID(), LocationName: "", // TODO: record.GetLocationName(), StartTime: time.Now(), // TODO: record.GetStartTime(), EndTime: time.Now(), // TODO: record.GetEndTime(), Duration: 0, // TODO: record.GetDuration(), Efficiency: 0, // TODO: record.GetEfficiency(), TotalReward: 0, // TODO: record.GetTotalReward(), } } return dtos } // buildStatisticsDTO 构建统计DTO func (s *HangupService) buildStatisticsDTO(stats *hangup.HangupStatistics) *HangupStatisticsDTO { // TODO: 修复HangupStatistics方法调用 return &HangupStatisticsDTO{ PlayerID: "", // TODO: stats.GetPlayerID(), TotalSessions: 0, // TODO: stats.GetTotalSessions(), TotalDuration: 0, // TODO: stats.GetTotalDuration(), TotalReward: 0, // TODO: stats.GetTotalReward(), AverageDuration: 0, // TODO: stats.GetAverageDuration(), AverageReward: 0, // TODO: stats.GetAverageReward(), FavoriteLocation: "", // TODO: stats.GetFavoriteLocation(), LocationStats: nil, // TODO: stats.GetLocationStats(), LastHangupTime: time.Now(), // TODO: stats.GetLastHangupTime(), } } // calculateEstimatedReward 计算预估奖励 func (s *HangupService) calculateEstimatedReward(hangupRecord *hangup.HangupAggregate) int64 { // 基于当前挂机时长和效率计算预估奖励 duration := hangupRecord.GetDuration() efficiency := hangupRecord.GetEfficiency() baseRate := hangupRecord.GetBaseRate() return int64(duration.Hours() * efficiency * baseRate) } // DTO 定义 // HangupStatusDTO 挂机状态DTO type HangupStatusDTO struct { PlayerID string `json:"player_id"` IsHanging bool `json:"is_hanging"` LocationID string `json:"location_id,omitempty"` LocationName string `json:"location_name,omitempty"` StartTime time.Time `json:"start_time,omitempty"` Duration time.Duration `json:"duration,omitempty"` Efficiency float64 `json:"efficiency,omitempty"` EstimatedReward int64 `json:"estimated_reward,omitempty"` } // HangupLocationDTO 挂机地点DTO type HangupLocationDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` BaseRate float64 `json:"base_rate"` RequiredLevel int `json:"required_level"` IsUnlocked bool `json:"is_unlocked"` RewardTypes []string `json:"reward_types"` } // HangupHistoryDTO 挂机历史DTO type HangupHistoryDTO struct { ID string `json:"id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` Efficiency float64 `json:"efficiency"` TotalReward int64 `json:"total_reward"` } // HangupStatisticsDTO 挂机统计DTO type HangupStatisticsDTO struct { PlayerID string `json:"player_id"` TotalSessions int64 `json:"total_sessions"` TotalDuration time.Duration `json:"total_duration"` TotalReward int64 `json:"total_reward"` AverageDuration time.Duration `json:"average_duration"` AverageReward float64 `json:"average_reward"` FavoriteLocation string `json:"favorite_location"` LocationStats map[string]*LocationStats `json:"location_stats"` LastHangupTime time.Time `json:"last_hangup_time"` } // LocationStats 地点统计 type LocationStats struct { LocationID string `json:"location_id"` LocationName string `json:"location_name"` TotalTime time.Duration `json:"total_time"` TotalSessions int64 `json:"total_sessions"` TotalReward int64 `json:"total_reward"` AverageReward float64 `json:"average_reward"` } ================================================ FILE: internal/application/services/item_service.go ================================================ package services import ( "context" "errors" "fmt" "time" "greatestworks/internal/infrastructure/datamanager" "greatestworks/internal/infrastructure/persistence" ) // ItemService 物品服务 type ItemService struct { itemRepo *persistence.ItemRepository } // NewItemService 创建物品服务 func NewItemService(itemRepo *persistence.ItemRepository) *ItemService { return &ItemService{ itemRepo: itemRepo, } } // CreateItem 创建物品 func (s *ItemService) CreateItem(ctx context.Context, characterID int64, itemID, count, slot, location int32) (int64, error) { // 获取物品配置 itemDefine := datamanager.GetInstance().GetItem(itemID) if itemDefine == nil { return 0, errors.New("item not found") } // 生成物品唯一ID itemUID := time.Now().UnixNano() // 创建物品 item := &persistence.DbItem{ ItemUID: itemUID, CharacterID: characterID, ItemID: itemID, Count: count, Slot: slot, Location: location, Bound: false, Expire: 0, } if err := s.itemRepo.Create(ctx, item); err != nil { return 0, fmt.Errorf("failed to create item: %w", err) } return itemUID, nil } // GetItem 获取物品 func (s *ItemService) GetItem(ctx context.Context, itemUID int64) (*persistence.DbItem, error) { return s.itemRepo.FindByUID(ctx, itemUID) } // GetCharacterItems 获取角色的所有物品 func (s *ItemService) GetCharacterItems(ctx context.Context, characterID int64) ([]*persistence.DbItem, error) { return s.itemRepo.FindByCharacterID(ctx, characterID) } // UseItem 使用物品 func (s *ItemService) UseItem(ctx context.Context, itemUID int64) error { item, err := s.itemRepo.FindByUID(ctx, itemUID) if err != nil { return err } // 获取物品配置 itemDefine := datamanager.GetInstance().GetItem(item.ItemID) if itemDefine == nil { return errors.New("item not found") } // TODO: 根据物品类型执行不同的使用逻辑 // 减少数量 item.Count-- if item.Count <= 0 { // 删除物品 return s.itemRepo.Delete(ctx, itemUID) } // 更新物品 return s.itemRepo.Update(ctx, item) } // MoveItem 移动物品 func (s *ItemService) MoveItem(ctx context.Context, itemUID int64, newSlot, newLocation int32) error { item, err := s.itemRepo.FindByUID(ctx, itemUID) if err != nil { return err } item.Slot = newSlot item.Location = newLocation return s.itemRepo.Update(ctx, item) } // DeleteItem 删除物品 func (s *ItemService) DeleteItem(ctx context.Context, itemUID int64) error { return s.itemRepo.Delete(ctx, itemUID) } // SplitItem 拆分物品 func (s *ItemService) SplitItem(ctx context.Context, itemUID int64, splitCount int32) (int64, error) { item, err := s.itemRepo.FindByUID(ctx, itemUID) if err != nil { return 0, err } if item.Count <= splitCount { return 0, errors.New("invalid split count") } // 减少原物品数量 item.Count -= splitCount if err := s.itemRepo.Update(ctx, item); err != nil { return 0, err } // 创建新物品 newItemUID := time.Now().UnixNano() newItem := &persistence.DbItem{ ItemUID: newItemUID, CharacterID: item.CharacterID, ItemID: item.ItemID, Count: splitCount, Slot: -1, // 未放置槽位 Location: item.Location, Bound: item.Bound, Expire: item.Expire, } if err := s.itemRepo.Create(ctx, newItem); err != nil { return 0, err } return newItemUID, nil } // MergeItem 合并物品 func (s *ItemService) MergeItem(ctx context.Context, fromUID, toUID int64) error { fromItem, err := s.itemRepo.FindByUID(ctx, fromUID) if err != nil { return err } toItem, err := s.itemRepo.FindByUID(ctx, toUID) if err != nil { return err } // 检查是否可以合并 if fromItem.ItemID != toItem.ItemID { return errors.New("cannot merge different items") } // 获取物品配置 itemDefine := datamanager.GetInstance().GetItem(fromItem.ItemID) if itemDefine == nil { return errors.New("item not found") } // 合并数量 totalCount := fromItem.Count + toItem.Count if totalCount > itemDefine.MaxStack { // 超过堆叠上限 toItem.Count = itemDefine.MaxStack fromItem.Count = totalCount - itemDefine.MaxStack if err := s.itemRepo.Update(ctx, fromItem); err != nil { return err } } else { // 全部合并 toItem.Count = totalCount if err := s.itemRepo.Delete(ctx, fromUID); err != nil { return err } } return s.itemRepo.Update(ctx, toItem) } ================================================ FILE: internal/application/services/mail_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/infrastructure/persistence" ) // MailService 邮件服务 type MailService struct { mailRepo *persistence.MailRepository } // NewMailService 创建邮件服务 func NewMailService(mailRepo *persistence.MailRepository) *MailService { return &MailService{ mailRepo: mailRepo, } } // SendMail 发送邮件 func (s *MailService) SendMail(ctx context.Context, receiverID int64, senderName, title, content string, attachments []persistence.DbAttachment, expireDays int) (int64, error) { // 生成邮件ID mailID := time.Now().UnixNano() hasItems := len(attachments) > 0 expireAt := time.Now().AddDate(0, 0, expireDays) mail := &persistence.DbMail{ MailID: mailID, ReceiverID: receiverID, SenderName: senderName, Title: title, Content: content, IsRead: false, HasItems: hasItems, Attachments: attachments, ExpireAt: expireAt, } if err := s.mailRepo.Create(ctx, mail); err != nil { return 0, fmt.Errorf("failed to send mail: %w", err) } return mailID, nil } // GetMails 获取邮件列表 func (s *MailService) GetMails(ctx context.Context, receiverID int64, limit int) ([]*persistence.DbMail, error) { if limit <= 0 || limit > 100 { limit = 50 // 默认50封 } return s.mailRepo.FindByReceiverID(ctx, receiverID, limit) } // ReadMail 读取邮件 func (s *MailService) ReadMail(ctx context.Context, mailID int64) error { return s.mailRepo.MarkAsRead(ctx, mailID) } // DeleteMail 删除邮件 func (s *MailService) DeleteMail(ctx context.Context, mailID int64) error { return s.mailRepo.Delete(ctx, mailID) } // ClaimAttachments 领取附件 func (s *MailService) ClaimAttachments(ctx context.Context, mailID int64) ([]persistence.DbAttachment, error) { // TODO: 实现附件领取逻辑 // 1. 检查背包空间 // 2. 添加物品到背包 // 3. 清空邮件附件或删除邮件 return nil, nil } // SendSystemMail 发送系统邮件 func (s *MailService) SendSystemMail(ctx context.Context, receiverID int64, title, content string, attachments []persistence.DbAttachment) (int64, error) { return s.SendMail(ctx, receiverID, "System", title, content, attachments, 7) } // SendRewardMail 发送奖励邮件 func (s *MailService) SendRewardMail(ctx context.Context, receiverID int64, title string, itemRewards map[int32]int32) (int64, error) { // 构建附件 attachments := make([]persistence.DbAttachment, 0, len(itemRewards)) for itemID, count := range itemRewards { attachments = append(attachments, persistence.DbAttachment{ ItemID: itemID, Count: count, }) } content := "Congratulations! You have received rewards." return s.SendMail(ctx, receiverID, "System", title, content, attachments, 7) } // CleanupExpiredMails 清理过期邮件(定时任务) func (s *MailService) CleanupExpiredMails(ctx context.Context) error { return s.mailRepo.DeleteExpired(ctx) } ================================================ FILE: internal/application/services/map_service.go ================================================ package services import ( "context" "errors" "sync" "time" "greatestworks/internal/domain/character" "greatestworks/internal/domain/mapmanager" "greatestworks/internal/infrastructure/datamanager" ) // MapService 地图服务 type MapService struct { mu sync.RWMutex maps map[int32]*mapmanager.Map // 应用层广播适配器 broadcaster mapmanager.BroadcastFn // 异步刷怪/掉落等任务 spawnMgr *SpawnManager } // NewMapService 创建地图服务 func NewMapService() *MapService { return &MapService{ maps: make(map[int32]*mapmanager.Map), } } // SetBroadcaster 设置地图广播函数(通常由接口层注入,用于向会话发送消息) func (s *MapService) SetBroadcaster(fn mapmanager.BroadcastFn) { s.mu.Lock() s.broadcaster = fn // 将已加载地图也设置广播器 for _, m := range s.maps { m.SetBroadcaster(fn) } s.mu.Unlock() } // SetSpawnManager 注入SpawnManager以在地图生命周期中投递异步任务 func (s *MapService) SetSpawnManager(sm *SpawnManager) { s.mu.Lock() s.spawnMgr = sm s.mu.Unlock() } // LoadMap 加载地图 func (s *MapService) LoadMap(ctx context.Context, mapID int32) error { s.mu.Lock() defer s.mu.Unlock() // 检查是否已加载 if _, exists := s.maps[mapID]; exists { return nil } // 获取地图配置 mapDefine := datamanager.GetInstance().GetMap(mapID) if mapDefine == nil { return errors.New("map not found") } // 创建地图实例 gameMap := mapmanager.NewMap(mapID, mapDefine.Name, mapDefine.Width, mapDefine.Height) if s.broadcaster != nil { gameMap.SetBroadcaster(s.broadcaster) } s.maps[mapID] = gameMap return nil } // GetMap 获取地图 func (s *MapService) GetMap(mapID int32) (*mapmanager.Map, error) { s.mu.RLock() defer s.mu.RUnlock() gameMap, exists := s.maps[mapID] if !exists { return nil, errors.New("map not loaded") } return gameMap, nil } // EnterMap 进入地图 func (s *MapService) EnterMap(ctx context.Context, entity *character.Entity, mapID int32, x, y, z float32) error { gameMap, err := s.GetMap(mapID) if err != nil { // 尝试加载地图 if err := s.LoadMap(ctx, mapID); err != nil { return err } gameMap, err = s.GetMap(mapID) if err != nil { return err } } // 进入地图 // 设置初始位置 entity.SetPosition(character.NewVector3(x, y, z)) _ = gameMap.Enter(ctx, entity) // 可选:在进入地图时投递一次异步任务(示例) if s.spawnMgr != nil { s.spawnMgr.Enqueue(func(ctx context.Context) { // 占位:后续可在此触发进入地图后的刷怪或欢迎事件 }) } return nil } // LeaveMap 离开地图 func (s *MapService) LeaveMap(ctx context.Context, entity *character.Entity, mapID int32) error { gameMap, err := s.GetMap(mapID) if err != nil { return err } _ = gameMap.Leave(ctx, entity.ID()) return nil } // LeaveMapByID 使用地图ID和实体ID离开地图(便于接口/清理逻辑调用) func (s *MapService) LeaveMapByID(ctx context.Context, mapID int32, entityID int32) error { gameMap, err := s.GetMap(mapID) if err != nil { return err } return gameMap.Leave(ctx, character.EntityID(entityID)) } // UpdatePosition 更新位置 func (s *MapService) UpdatePosition(ctx context.Context, entity *character.Entity, mapID int32, x, y, z float32) error { gameMap, err := s.GetMap(mapID) if err != nil { return err } return gameMap.UpdatePosition(entity.ID(), character.NewVector3(x, y, z)) } // UpdatePositionByID 使用地图ID和实体ID更新位置(便于接口层调用) func (s *MapService) UpdatePositionByID(ctx context.Context, mapID int32, entityID int32, x, y, z float32) error { gameMap, err := s.GetMap(mapID) if err != nil { return err } return gameMap.UpdatePosition(character.EntityID(entityID), character.NewVector3(x, y, z)) } // GetEntitiesInRange 获取范围内的实体 func (s *MapService) GetEntitiesInRange(ctx context.Context, mapID int32, x, y, z, range_ float32) ([]*character.Entity, error) { gameMap, err := s.GetMap(mapID) if err != nil { return nil, err } return gameMap.GetEntitiesInRange(x, z, range_), nil } // TransferMap 传送到其他地图 func (s *MapService) TransferMap(ctx context.Context, entity *character.Entity, fromMapID, toMapID int32, x, y, z float32) error { // 离开当前地图 if err := s.LeaveMap(ctx, entity, fromMapID); err != nil { return err } // 进入目标地图 if err := s.EnterMap(ctx, entity, toMapID, x, y, z); err != nil { // 回滚:重新进入原地图 _ = s.EnterMap(ctx, entity, fromMapID, 0, 0, 0) return err } return nil } // BroadcastToMap 向地图广播消息 func (s *MapService) BroadcastToMap(ctx context.Context, mapID int32, message interface{}) error { gameMap, err := s.GetMap(mapID) if err != nil { return err } // 获取地图内所有实体并广播 entities := gameMap.GetAllEntities() recipients := make([]character.EntityID, 0, len(entities)) for _, e := range entities { recipients = append(recipients, e.ID()) } gameMap.BroadcastTo(recipients, "map_broadcast", message) return nil } // BroadcastToRange 向范围内广播消息 func (s *MapService) BroadcastToRange(ctx context.Context, mapID int32, x, y, z, range_ float32, message interface{}) error { gameMap, err := s.GetMap(mapID) if err != nil { return err } // 使用AOI广播到范围内 gameMap.BroadcastInRange(x, z, range_, "range_broadcast", message) return nil } // Tick 地图更新(供 UpdateManager 调用) func (s *MapService) Tick(ctx context.Context, delta time.Duration) { // 目前无需要逐帧更新的逻辑;保留入口以便未来接入地图定时器/效果 // 示例:遍历地图执行周期性任务 s.mu.RLock() defer s.mu.RUnlock() _ = delta _ = ctx // for _, m := range s.maps { /* m.DoPeriodicStuff() */ } } ================================================ FILE: internal/application/services/minigame_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/domain/minigame" ) // MinigameApplicationService 小游戏应用服务 type MinigameApplicationService struct { minigameRepo minigame.MinigameRepository sessionRepo minigame.GameSessionRepository minigameService *minigame.MinigameService eventBus minigame.MinigameEventBus } // NewMinigameApplicationService 创建小游戏应用服务 func NewMinigameApplicationService( minigameRepo minigame.MinigameRepository, sessionRepo minigame.GameSessionRepository, minigameService *minigame.MinigameService, eventBus minigame.MinigameEventBus, ) *MinigameApplicationService { return &MinigameApplicationService{ minigameRepo: minigameRepo, sessionRepo: sessionRepo, minigameService: minigameService, eventBus: eventBus, } } // CreateMinigameRequest 创建小游戏请求 type CreateMinigameRequest struct { Name string `json:"name"` Description string `json:"description"` GameType string `json:"game_type"` Difficulty string `json:"difficulty"` MaxPlayers int32 `json:"max_players"` TimeLimit int32 `json:"time_limit"` IsActive bool `json:"is_active"` } // CreateMinigameResponse 创建小游戏响应 type CreateMinigameResponse struct { MinigameID string `json:"minigame_id"` Name string `json:"name"` Description string `json:"description"` GameType string `json:"game_type"` Difficulty string `json:"difficulty"` MaxPlayers int32 `json:"max_players"` TimeLimit int32 `json:"time_limit"` IsActive bool `json:"is_active"` CreatedAt time.Time `json:"created_at"` } // CreateMinigame 创建小游戏 func (s *MinigameApplicationService) CreateMinigame(ctx context.Context, req *CreateMinigameRequest) (*CreateMinigameResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateCreateMinigameRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 转换游戏类型 _, err := s.parseGameType(req.GameType) if err != nil { return nil, fmt.Errorf("invalid game type: %w", err) } // 转换难度 _, err = s.parseDifficulty(req.Difficulty) if err != nil { return nil, fmt.Errorf("invalid difficulty: %w", err) } // 创建小游戏聚合根 // TODO: 修复NewMinigameAggregate方法调用 // minigameAggregate := minigame.NewMinigameAggregate(req.Name, gameType, difficulty) minigameAggregate := &minigame.MinigameAggregate{} // TODO: 修复SetDescription方法调用 // minigameAggregate.SetDescription(req.Description) // minigameAggregate.SetMaxPlayers(req.MaxPlayers) // minigameAggregate.SetTimeLimit(req.TimeLimit) // if req.IsActive { // minigameAggregate.Activate() // } // 保存小游戏 if err := s.minigameRepo.Save(ctx, minigameAggregate); err != nil { return nil, fmt.Errorf("failed to save minigame: %w", err) } // 发布事件 // TODO: 修复NewMinigameCreatedEvent方法调用 // event := minigame.NewMinigameCreatedEvent(minigameAggregate.GetID(), req.Name, gameType, difficulty) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish minigame created event: %v\n", err) // } return &CreateMinigameResponse{ MinigameID: "", // TODO: minigameAggregate.GetID(), Name: req.Name, Description: req.Description, GameType: req.GameType, Difficulty: req.Difficulty, MaxPlayers: req.MaxPlayers, TimeLimit: req.TimeLimit, IsActive: req.IsActive, CreatedAt: time.Now(), // TODO: minigameAggregate.GetCreatedAt(), }, nil } // StartGameSessionRequest 开始游戏会话请求 type StartGameSessionRequest struct { MinigameID string `json:"minigame_id"` PlayerID uint64 `json:"player_id"` Settings map[string]interface{} `json:"settings,omitempty"` } // StartGameSessionResponse 开始游戏会话响应 type StartGameSessionResponse struct { SessionID string `json:"session_id"` MinigameID string `json:"minigame_id"` PlayerID uint64 `json:"player_id"` Status string `json:"status"` TimeLimit int32 `json:"time_limit"` StartedAt time.Time `json:"started_at"` ExpiresAt time.Time `json:"expires_at"` } // StartGameSession 开始游戏会话 func (s *MinigameApplicationService) StartGameSession(ctx context.Context, req *StartGameSessionRequest) (*StartGameSessionResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateStartGameSessionRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 获取小游戏 minigameAggregate, err := s.minigameRepo.FindByID(ctx, req.MinigameID) if err != nil { return nil, fmt.Errorf("failed to find minigame: %w", err) } if minigameAggregate == nil { return nil, fmt.Errorf("minigame not found") } // 检查游戏是否激活 // TODO: 修复IsActive方法调用 // if !minigameAggregate.IsActive() { // return nil, fmt.Errorf("minigame is not active") // } // 检查玩家是否有进行中的会话 // TODO: 修复FindActiveByPlayer方法调用 // activeSession, err := s.sessionRepo.FindActiveByPlayer(ctx, req.PlayerID) // if err != nil { // return nil, fmt.Errorf("failed to check active session: %w", err) // } // if activeSession != nil { // return nil, fmt.Errorf("player already has an active game session") // } // 创建游戏会话 // TODO: 修复StartGameSession方法调用 // session, err := s.minigameService.StartGameSession(ctx, req.MinigameID, req.PlayerID) // if err != nil { // return nil, fmt.Errorf("failed to start game session: %w", err) // } // 设置游戏设置 // TODO: 修复session变量 // if req.Settings != nil { // for key, value := range req.Settings { // session.SetSetting(key, value) // } // if err := s.sessionRepo.Save(ctx, session); err != nil { // return nil, fmt.Errorf("failed to save session settings: %w", err) // } // } // 发布事件 // TODO: 修复NewGameSessionStartedEvent方法调用 // event := minigame.NewGameSessionStartedEvent(session.GetID(), req.MinigameID, req.PlayerID) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish game session started event: %v\n", err) // } return &StartGameSessionResponse{ SessionID: "", // TODO: session.GetID(), MinigameID: req.MinigameID, PlayerID: req.PlayerID, Status: "active", // TODO: session.GetStatus().String(), TimeLimit: 0, // TODO: req.TimeLimit, StartedAt: time.Now(), // TODO: session.GetStartedAt(), ExpiresAt: time.Now().Add(30 * time.Minute), // TODO: session.GetExpiresAt(), }, nil } // SubmitGameScoreRequest 提交游戏分数请求 type SubmitGameScoreRequest struct { SessionID string `json:"session_id"` Score int64 `json:"score"` GameData map[string]interface{} `json:"game_data,omitempty"` } // SubmitGameScoreResponse 提交游戏分数响应 type SubmitGameScoreResponse struct { SessionID string `json:"session_id"` Score int64 `json:"score"` BestScore int64 `json:"best_score"` IsNewRecord bool `json:"is_new_record"` Rewards []*GameRewardResponse `json:"rewards,omitempty"` Achievements []string `json:"achievements,omitempty"` CompletedAt time.Time `json:"completed_at"` } // GameRewardResponse 游戏奖励响应 type GameRewardResponse struct { Type string `json:"type"` ItemID string `json:"item_id,omitempty"` Quantity int32 `json:"quantity"` Reason string `json:"reason"` } // SubmitGameScore 提交游戏分数 func (s *MinigameApplicationService) SubmitGameScore(ctx context.Context, req *SubmitGameScoreRequest) (*SubmitGameScoreResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateSubmitGameScoreRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 获取游戏会话 session, err := s.sessionRepo.FindByID(ctx, req.SessionID) if err != nil { return nil, fmt.Errorf("failed to find session: %w", err) } if session == nil { return nil, fmt.Errorf("session not found") } // 检查会话状态 if !session.IsActive() { return nil, fmt.Errorf("session is not active") } // 检查会话是否过期 // TODO: 修复IsExpired方法调用 // if session.IsExpired() { // return nil, fmt.Errorf("session has expired") // } // 提交分数并完成会话 // TODO: 修复SubmitGameScore方法调用 // result, err := s.minigameService.SubmitGameScore(ctx, req.SessionID, req.Score) // if err != nil { // return nil, fmt.Errorf("failed to submit game score: %w", err) // } // 设置游戏数据 // TODO: 修复session变量 // if req.GameData != nil { // for key, value := range req.GameData { // session.SetGameData(key, value) // } // if err := s.sessionRepo.Save(ctx, session); err != nil { // return nil, fmt.Errorf("failed to save session game data: %w", err) // } // } // 转换奖励响应 // TODO: 修复result变量 // rewardResponses := make([]*GameRewardResponse, len(result.Rewards)) // for i, reward := range result.Rewards { // rewardResponses[i] = &GameRewardResponse{ // Type: reward.GetType().String(), // ItemID: reward.GetItemID(), // Quantity: reward.GetQuantity(), // Reason: reward.GetReason(), // } // } // 发布事件 // TODO: 修复NewGameScoreSubmittedEvent方法调用 // event := minigame.NewGameScoreSubmittedEvent( // req.SessionID, // session.GetMinigameID(), // session.GetPlayerID(), // req.Score, // result.IsNewRecord, // ) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish game score submitted event: %v\n", err) // } return &SubmitGameScoreResponse{ SessionID: req.SessionID, Score: req.Score, BestScore: 0, // TODO: result.BestScore, IsNewRecord: false, // TODO: result.IsNewRecord, Rewards: nil, // TODO: rewardResponses, Achievements: nil, // TODO: result.Achievements, CompletedAt: time.Now(), // TODO: session.GetCompletedAt(), }, nil } // GetGameSessionRequest 获取游戏会话请求 type GetGameSessionRequest struct { SessionID string `json:"session_id"` } // GetGameSessionResponse 获取游戏会话响应 type GetGameSessionResponse struct { SessionID string `json:"session_id"` MinigameID string `json:"minigame_id"` PlayerID uint64 `json:"player_id"` Status string `json:"status"` Score int64 `json:"score"` TimeLimit int32 `json:"time_limit"` TimeElapsed int32 `json:"time_elapsed"` Settings map[string]interface{} `json:"settings,omitempty"` GameData map[string]interface{} `json:"game_data,omitempty"` StartedAt time.Time `json:"started_at"` ExpiresAt time.Time `json:"expires_at"` CompletedAt *time.Time `json:"completed_at,omitempty"` } // GetGameSession 获取游戏会话 func (s *MinigameApplicationService) GetGameSession(ctx context.Context, req *GetGameSessionRequest) (*GetGameSessionResponse, error) { if req == nil || req.SessionID == "" { return nil, fmt.Errorf("session ID is required") } // 获取游戏会话 session, err := s.sessionRepo.FindByID(ctx, req.SessionID) if err != nil { return nil, fmt.Errorf("failed to find session: %w", err) } if session == nil { return nil, fmt.Errorf("session not found") } // 计算已用时间 timeElapsed := int32(0) if session.IsActive() { timeElapsed = int32(time.Since(session.GetStartedAt()).Seconds()) } else if !session.GetCompletedAt().IsZero() { timeElapsed = int32(session.GetCompletedAt().Sub(session.GetStartedAt()).Seconds()) } response := &GetGameSessionResponse{ SessionID: session.GetID(), MinigameID: session.GetMinigameID(), PlayerID: session.GetPlayerID(), Status: session.GetStatus().String(), Score: session.GetScore(), TimeLimit: int32(session.GetTimeLimit().Seconds()), // TODO: 修复类型转换 TimeElapsed: timeElapsed, Settings: session.GetSettings(), GameData: nil, // TODO: session.GetGameData(), StartedAt: session.GetStartedAt(), ExpiresAt: session.GetExpiresAt(), } if !session.GetCompletedAt().IsZero() { completedAt := session.GetCompletedAt() response.CompletedAt = &completedAt } return response, nil } // GetPlayerScoresRequest 获取玩家分数请求 type GetPlayerScoresRequest struct { PlayerID uint64 `json:"player_id"` MinigameID string `json:"minigame_id,omitempty"` Limit int `json:"limit"` } // PlayerScoreResponse 玩家分数响应 type PlayerScoreResponse struct { SessionID string `json:"session_id"` MinigameID string `json:"minigame_id"` Score int64 `json:"score"` Rank int32 `json:"rank"` CompletedAt time.Time `json:"completed_at"` } // GetPlayerScoresResponse 获取玩家分数响应 type GetPlayerScoresResponse struct { PlayerID uint64 `json:"player_id"` Scores []*PlayerScoreResponse `json:"scores"` Total int64 `json:"total"` } // GetPlayerScores 获取玩家分数 func (s *MinigameApplicationService) GetPlayerScores(ctx context.Context, req *GetPlayerScoresRequest) (*GetPlayerScoresResponse, error) { if req == nil || req.PlayerID == 0 { return nil, fmt.Errorf("player ID is required") } // 设置默认值 if req.Limit <= 0 { req.Limit = 20 } if req.Limit > 100 { req.Limit = 100 } // 构建查询 // TODO: 修复NewGameSessionQuery方法调用 // query := minigame.NewGameSessionQuery(). // WithPlayer(req.PlayerID). // WithStatus(minigame.SessionStatusCompleted). // WithSort("completed_at", "desc"). // WithLimit(req.Limit) // TODO: 修复query变量 // if req.MinigameID != "" { // query = query.WithMinigame(req.MinigameID) // } // 查询会话 // TODO: 修复FindByQuery方法调用 // sessions, total, err := s.sessionRepo.FindByQuery(ctx, query) sessions, total := []*minigame.GameSession{}, 0 // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to find sessions: %w", err) // } // 转换响应 scoreResponses := make([]*PlayerScoreResponse, len(sessions)) for i, session := range sessions { // 获取排名(这里简化处理,实际可能需要更复杂的排名计算) // TODO: 修复GetPlayerRank方法调用 // rank, _ := s.minigameService.GetPlayerRank(ctx, session.GetMinigameID(), req.PlayerID) rank := 0 scoreResponses[i] = &PlayerScoreResponse{ SessionID: session.GetID(), MinigameID: session.GetMinigameID(), Score: session.GetScore(), Rank: int32(rank), // TODO: 修复类型转换 CompletedAt: session.GetCompletedAt(), } } return &GetPlayerScoresResponse{ PlayerID: req.PlayerID, Scores: scoreResponses, Total: int64(total), // TODO: 修复类型转换 }, nil } // GetMinigameLeaderboardRequest 获取小游戏排行榜请求 type GetMinigameLeaderboardRequest struct { MinigameID string `json:"minigame_id"` Period string `json:"period"` Limit int `json:"limit"` } // LeaderboardEntryResponse 排行榜条目响应 type LeaderboardEntryResponse struct { PlayerID uint64 `json:"player_id"` Rank int32 `json:"rank"` Score int64 `json:"score"` SessionID string `json:"session_id"` CompletedAt time.Time `json:"completed_at"` } // GetMinigameLeaderboardResponse 获取小游戏排行榜响应 type GetMinigameLeaderboardResponse struct { MinigameID string `json:"minigame_id"` Period string `json:"period"` Entries []*LeaderboardEntryResponse `json:"entries"` UpdatedAt time.Time `json:"updated_at"` } // GetMinigameLeaderboard 获取小游戏排行榜 func (s *MinigameApplicationService) GetMinigameLeaderboard(ctx context.Context, req *GetMinigameLeaderboardRequest) (*GetMinigameLeaderboardResponse, error) { if req == nil || req.MinigameID == "" { return nil, fmt.Errorf("minigame ID is required") } // 设置默认值 if req.Limit <= 0 { req.Limit = 10 } if req.Limit > 100 { req.Limit = 100 } if req.Period == "" { req.Period = "all_time" } // 获取排行榜 // TODO: 修复GetLeaderboard方法调用 // leaderboard, err := s.minigameService.GetLeaderboard(ctx, req.MinigameID, req.Period, req.Limit) // TODO: 修复LeaderboardEntry类型 // leaderboard := []*minigame.LeaderboardEntry{} // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to get leaderboard: %w", err) // } // 转换响应 // TODO: 修复leaderboard.Entries // entryResponses := make([]*LeaderboardEntryResponse, len(leaderboard.Entries)) // for i, entry := range leaderboard.Entries { // entryResponses[i] = &LeaderboardEntryResponse{ // PlayerID: entry.PlayerID, // Rank: entry.Rank, // Score: entry.Score, // SessionID: entry.SessionID, // CompletedAt: entry.CompletedAt, // } // } return &GetMinigameLeaderboardResponse{ MinigameID: req.MinigameID, Period: req.Period, Entries: nil, // TODO: entryResponses, UpdatedAt: time.Now(), // TODO: leaderboard.UpdatedAt, }, nil } // 私有方法 // validateCreateMinigameRequest 验证创建小游戏请求 func (s *MinigameApplicationService) validateCreateMinigameRequest(req *CreateMinigameRequest) error { if req.Name == "" { return fmt.Errorf("name is required") } if len(req.Name) > 100 { return fmt.Errorf("name too long (max 100 characters)") } if req.GameType == "" { return fmt.Errorf("game type is required") } if req.Difficulty == "" { return fmt.Errorf("difficulty is required") } if req.MaxPlayers <= 0 { return fmt.Errorf("max players must be positive") } if req.TimeLimit <= 0 { return fmt.Errorf("time limit must be positive") } return nil } // validateStartGameSessionRequest 验证开始游戏会话请求 func (s *MinigameApplicationService) validateStartGameSessionRequest(req *StartGameSessionRequest) error { if req.MinigameID == "" { return fmt.Errorf("minigame ID is required") } if req.PlayerID == 0 { return fmt.Errorf("player ID is required") } return nil } // validateSubmitGameScoreRequest 验证提交游戏分数请求 func (s *MinigameApplicationService) validateSubmitGameScoreRequest(req *SubmitGameScoreRequest) error { if req.SessionID == "" { return fmt.Errorf("session ID is required") } if req.Score < 0 { return fmt.Errorf("score cannot be negative") } return nil } // parseGameType 解析游戏类型 func (s *MinigameApplicationService) parseGameType(gameTypeStr string) (minigame.GameType, error) { switch gameTypeStr { case "puzzle": return minigame.GameTypePuzzle, nil case "action": return minigame.GameTypePuzzle, nil // TODO: minigame.GameTypeAction case "strategy": return minigame.GameTypePuzzle, nil // TODO: minigame.GameTypeStrategy case "arcade": return minigame.GameTypePuzzle, nil // TODO: minigame.GameTypeArcade case "card": return minigame.GameTypePuzzle, nil // TODO: minigame.GameTypeCard case "quiz": return minigame.GameTypePuzzle, nil // TODO: minigame.GameTypeQuiz default: return minigame.GameTypePuzzle, fmt.Errorf("unknown game type: %s", gameTypeStr) } } // parseDifficulty 解析难度 func (s *MinigameApplicationService) parseDifficulty(difficultyStr string) (minigame.GameDifficulty, error) { switch difficultyStr { case "easy": return minigame.GameDifficultyEasy, nil case "normal": return minigame.GameDifficultyNormal, nil case "hard": return minigame.GameDifficultyHard, nil case "expert": return minigame.GameDifficultyExpert, nil default: return minigame.GameDifficultyNormal, fmt.Errorf("unknown difficulty: %s", difficultyStr) } } ================================================ FILE: internal/application/services/npc_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/domain/npc" ) // NPCService NPC应用服务 type NPCService struct { npcRepo npc.NPCRepository dialogueRepo npc.DialogueRepository questRepo npc.QuestRepository shopRepo npc.ShopRepository relationshipRepo npc.RelationshipRepository statisticsRepo npc.NPCStatisticsRepository cacheRepo npc.NPCCacheRepository npcService *npc.NPCService } // NewNPCService 创建NPC应用服务 func NewNPCService( npcRepo npc.NPCRepository, dialogueRepo npc.DialogueRepository, questRepo npc.QuestRepository, shopRepo npc.ShopRepository, relationshipRepo npc.RelationshipRepository, statisticsRepo npc.NPCStatisticsRepository, cacheRepo npc.NPCCacheRepository, npcService *npc.NPCService, ) *NPCService { return &NPCService{ npcRepo: npcRepo, dialogueRepo: dialogueRepo, questRepo: questRepo, shopRepo: shopRepo, relationshipRepo: relationshipRepo, statisticsRepo: statisticsRepo, cacheRepo: cacheRepo, npcService: npcService, } } // GetNPCInfo 获取NPC信息 func (s *NPCService) GetNPCInfo(ctx context.Context, npcID string) (*NPCDTO, error) { // 先从缓存获取 cachedNPC, err := s.cacheRepo.GetNPC(npcID) if err == nil && cachedNPC != nil { return s.buildNPCDTO(cachedNPC), nil } // 从数据库获取 npcAggregate, err := s.npcRepo.FindByID(npcID) if err != nil { return nil, fmt.Errorf("failed to get NPC info: %w", err) } // 更新缓存 if err := s.cacheRepo.SetNPC(npcID, npcAggregate, time.Hour); err != nil { // 缓存更新失败不影响主流程 // TODO: 添加日志记录 } return s.buildNPCDTO(npcAggregate), nil } // GetNearbyNPCs 获取附近的NPC func (s *NPCService) GetNearbyNPCs(ctx context.Context, playerID string, location *npc.Location, radius float64) ([]*NPCDTO, error) { // 先从缓存获取 cachedNPCs, err := s.cacheRepo.GetLocationIndex(location.GetRegion()) if err == nil && len(cachedNPCs) > 0 { // 过滤距离 nearbyNPCs := s.filterNPCsByDistance(cachedNPCs, location, radius) return s.buildNPCDTOs(nearbyNPCs), nil } // 从数据库获取 nearbyNPCs, err := s.npcRepo.FindByLocation(location, radius) if err != nil { return nil, fmt.Errorf("failed to get nearby NPCs: %w", err) } // 更新缓存 if err := s.cacheRepo.SetLocationIndex(location.GetRegion(), nearbyNPCs, time.Minute*30); err != nil { // 缓存更新失败不影响主流程 // TODO: 添加日志记录 } return s.buildNPCDTOs(nearbyNPCs), nil } // StartDialogue 开始对话 func (s *NPCService) StartDialogue(ctx context.Context, playerID string, npcID string) (*DialogueSessionDTO, error) { // 获取NPC信息 npcAggregate, err := s.npcRepo.FindByID(npcID) if err != nil { return nil, fmt.Errorf("failed to get NPC info: %w", err) } // 检查是否已有对话会话 existingSession, err := s.cacheRepo.GetSession(npcID, playerID) if err == nil && existingSession != nil { return s.buildDialogueSessionDTO(existingSession), nil } // 开始新对话 session, err := s.npcService.StartDialogue(playerID, npcAggregate) if err != nil { return nil, fmt.Errorf("failed to start dialogue: %w", err) } // 缓存对话会话 if err := s.cacheRepo.SetSession(npcID, playerID, session, time.Hour); err != nil { // 缓存失败不影响主流程 // TODO: 添加日志记录 } return s.buildDialogueSessionDTO(session), nil } // ContinueDialogue 继续对话 func (s *NPCService) ContinueDialogue(ctx context.Context, playerID string, npcID string, choiceID string) (*DialogueResponseDTO, error) { // 获取对话会话 session, err := s.cacheRepo.GetSession(npcID, playerID) if err != nil { return nil, fmt.Errorf("failed to get dialogue session: %w", err) } if session == nil { return nil, npc.ErrDialogueSessionNotFound } // 处理对话选择 // TODO: 修复choiceID类型转换 // response, err := s.npcService.ProcessDialogueChoice(session, choiceID) response := &npc.DialogueResponse{} // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to process dialogue choice: %w", err) // } // 更新会话缓存 if err := s.cacheRepo.SetSession(npcID, playerID, session, time.Hour); err != nil { // 缓存更新失败不影响主流程 // TODO: 添加日志记录 } return s.buildDialogueResponseDTO(response), nil } // EndDialogue 结束对话 func (s *NPCService) EndDialogue(ctx context.Context, playerID string, npcID string) error { // 获取对话会话 session, err := s.cacheRepo.GetSession(npcID, playerID) if err != nil { return fmt.Errorf("failed to get dialogue session: %w", err) } if session != nil { // 结束对话 if err := s.npcService.EndDialogue(session); err != nil { return fmt.Errorf("failed to end dialogue: %w", err) } // 更新统计数据 if err := s.updateDialogueStatistics(ctx, playerID, npcID, session); err != nil { // 统计更新失败不影响主流程 // TODO: 添加日志记录 } } // 清除会话缓存 if err := s.cacheRepo.DeleteSession(npcID, playerID); err != nil { // 缓存清除失败不影响主流程 // TODO: 添加日志记录 } return nil } // GetAvailableQuests 获取可用任务 func (s *NPCService) GetAvailableQuests(ctx context.Context, playerID string, npcID string) ([]*QuestDTO, error) { // 获取NPC的任务 quests, err := s.questRepo.FindByNPC(npcID) if err != nil { return nil, fmt.Errorf("failed to get NPC quests: %w", err) } // 过滤可用任务 availableQuests := make([]*npc.Quest, 0) for _, quest := range quests { if s.npcService.IsQuestAvailable(playerID, quest) { availableQuests = append(availableQuests, quest) } } return s.buildQuestDTOs(availableQuests), nil } // AcceptQuest 接受任务 func (s *NPCService) AcceptQuest(ctx context.Context, playerID string, questID string) (*QuestInstanceDTO, error) { // 获取任务信息 quest, err := s.questRepo.FindByID(questID) if err != nil { return nil, fmt.Errorf("failed to get quest info: %w", err) } // 接受任务 questInstance, err := s.npcService.AcceptQuest(playerID, quest) if err != nil { return nil, fmt.Errorf("failed to accept quest: %w", err) } // 保存任务实例 if err := s.questRepo.SaveInstance(questInstance); err != nil { return nil, fmt.Errorf("failed to save quest instance: %w", err) } return s.buildQuestInstanceDTO(questInstance), nil } // CompleteQuest 完成任务 func (s *NPCService) CompleteQuest(ctx context.Context, playerID string, questID string) (*QuestRewardDTO, error) { // 获取任务实例 questInstance, err := s.questRepo.FindInstance(questID, playerID) if err != nil { return nil, fmt.Errorf("failed to get quest instance: %w", err) } // 完成任务 reward, err := s.npcService.CompleteQuest(questInstance) if err != nil { return nil, fmt.Errorf("failed to complete quest: %w", err) } // 更新任务实例 if err := s.questRepo.UpdateInstance(questInstance); err != nil { return nil, fmt.Errorf("failed to update quest instance: %w", err) } // 更新统计数据 if err := s.updateQuestStatistics(ctx, playerID, questInstance, reward); err != nil { // 统计更新失败不影响主流程 // TODO: 添加日志记录 } return s.buildQuestRewardDTO(reward), nil } // GetShopInfo 获取商店信息 func (s *NPCService) GetShopInfo(ctx context.Context, npcID string) (*ShopDTO, error) { // 获取商店信息 shop, err := s.shopRepo.FindByNPC(npcID) if err != nil { return nil, fmt.Errorf("failed to get shop info: %w", err) } return s.buildShopDTO(shop), nil } // BuyItem 购买物品 func (s *NPCService) BuyItem(ctx context.Context, playerID string, shopID string, itemID string, quantity int) (*TradeResultDTO, error) { // 获取商店信息 shop, err := s.shopRepo.FindByID(shopID) if err != nil { return nil, fmt.Errorf("failed to get shop info: %w", err) } // 执行购买 _, err = s.npcService.BuyItem(playerID, shop, itemID, quantity) if err != nil { return nil, fmt.Errorf("failed to buy item: %w", err) } // 保存交易记录 // TODO: 修复tradeResult.Price // tradeRecord := npc.NewTradeRecord(shopID, playerID, itemID, quantity, tradeResult.Price) tradeRecord := npc.NewTradeRecord(shopID, playerID, itemID, quantity, 0) // TODO: tradeResult.Price if err := s.shopRepo.SaveTradeRecord(tradeRecord); err != nil { // 交易记录保存失败不影响主流程 // TODO: 添加日志记录 } // 更新商店 if err := s.shopRepo.Update(shop); err != nil { return nil, fmt.Errorf("failed to update shop: %w", err) } // TODO: 修复buildTradeResultDTO方法调用 // return s.buildTradeResultDTO(tradeResult), nil return &TradeResultDTO{}, nil } // GetRelationship 获取关系信息 func (s *NPCService) GetRelationship(ctx context.Context, playerID string, npcID string) (*RelationshipDTO, error) { // 先从缓存获取 cachedRelationship, err := s.cacheRepo.GetRelationship(playerID, npcID) if err == nil && cachedRelationship != nil { return s.buildRelationshipDTO(cachedRelationship), nil } // 从数据库获取 relationship, err := s.relationshipRepo.FindByID(playerID, npcID) if err != nil { return nil, fmt.Errorf("failed to get relationship: %w", err) } // 更新缓存 if err := s.cacheRepo.SetRelationship(playerID, npcID, relationship, time.Hour*2); err != nil { // 缓存更新失败不影响主流程 // TODO: 添加日志记录 } return s.buildRelationshipDTO(relationship), nil } // UpdateRelationship 更新关系 func (s *NPCService) UpdateRelationship(ctx context.Context, playerID string, npcID string, changeType npc.RelationshipChangeType, value int, reason string) error { // 获取关系信息 relationship, err := s.relationshipRepo.FindByID(playerID, npcID) if err != nil && !npc.IsNotFoundError(err) { return fmt.Errorf("failed to get relationship: %w", err) } if relationship == nil { // 创建新关系 relationship = npc.NewRelationship(playerID, npcID) } // 更新关系值 oldValue := relationship.GetValue() oldLevel := relationship.GetLevel() if err := relationship.ChangeValue(value, reason); err != nil { return fmt.Errorf("failed to change relationship value: %w", err) } // 保存关系 if err := s.relationshipRepo.Save(relationship); err != nil { return fmt.Errorf("failed to save relationship: %w", err) } // 记录关系变化事件 if relationship.GetValue() != oldValue { event := npc.NewRelationshipChangedEvent( npcID, playerID, oldValue, relationship.GetValue(), oldLevel, relationship.GetLevel(), changeType, reason, ) // TODO: 发布事件 _ = event } // 清除缓存 if err := s.cacheRepo.DeleteRelationship(playerID, npcID); err != nil { // 缓存清除失败不影响主流程 // TODO: 添加日志记录 } return nil } // GetNPCStatistics 获取NPC统计 func (s *NPCService) GetNPCStatistics(ctx context.Context, npcID string) (*NPCStatisticsDTO, error) { stats, err := s.statisticsRepo.FindStatistics(npcID) if err != nil { return nil, fmt.Errorf("failed to get NPC statistics: %w", err) } return s.buildNPCStatisticsDTO(stats), nil } // 私有方法 // filterNPCsByDistance 按距离过滤NPC func (s *NPCService) filterNPCsByDistance(npcs []*npc.NPCAggregate, location *npc.Location, radius float64) []*npc.NPCAggregate { filtered := make([]*npc.NPCAggregate, 0) for _, npcAgg := range npcs { if npcAgg.GetLocation().DistanceTo(location) <= radius { filtered = append(filtered, npcAgg) } } return filtered } // updateDialogueStatistics 更新对话统计 func (s *NPCService) updateDialogueStatistics(ctx context.Context, playerID string, npcID string, session *npc.DialogueSession) error { stats, err := s.statisticsRepo.FindStatistics(npcID) if err != nil && !npc.IsNotFoundError(err) { return err } if stats == nil { stats = npc.NewNPCStatistics(npcID) } // 更新统计数据 stats.AddDialogueSession(playerID, session.GetDuration()) stats.UpdateLastInteractionTime(session.GetEndTime()) // 保存统计数据 return s.statisticsRepo.SaveStatistics(stats) } // updateQuestStatistics 更新任务统计 func (s *NPCService) updateQuestStatistics(ctx context.Context, playerID string, questInstance *npc.QuestInstance, reward *npc.QuestReward) error { stats, err := s.statisticsRepo.FindStatistics(questInstance.GetNPCID()) if err != nil && !npc.IsNotFoundError(err) { return err } if stats == nil { stats = npc.NewNPCStatistics(questInstance.GetNPCID()) } // 更新统计数据 stats.AddQuestCompletion(playerID, questInstance.GetQuestID(), reward.GetTotalValue()) stats.UpdateLastInteractionTime(questInstance.GetCompletedAt()) // 保存统计数据 return s.statisticsRepo.SaveStatistics(stats) } // 构建DTO方法 // buildNPCDTO 构建NPC DTO func (s *NPCService) buildNPCDTO(npcAggregate *npc.NPCAggregate) *NPCDTO { return &NPCDTO{ ID: npcAggregate.GetID(), Name: npcAggregate.GetName(), Description: npcAggregate.GetDescription(), Type: npcAggregate.GetType().String(), Status: npcAggregate.GetStatus().String(), Location: s.buildLocationDTO(npcAggregate.GetLocation()), Attributes: s.buildAttributesDTO(npcAggregate.GetAttributes()), Behavior: s.buildBehaviorDTO(npcAggregate.GetBehavior()), HasDialogue: npcAggregate.HasDialogue(), HasQuests: npcAggregate.HasQuests(), HasShop: npcAggregate.HasShop(), CreatedAt: npcAggregate.GetCreatedAt(), UpdatedAt: npcAggregate.GetUpdatedAt(), } } // buildNPCDTOs 构建NPC DTO列表 func (s *NPCService) buildNPCDTOs(npcs []*npc.NPCAggregate) []*NPCDTO { dtos := make([]*NPCDTO, len(npcs)) for i, npcAgg := range npcs { dtos[i] = s.buildNPCDTO(npcAgg) } return dtos } // buildLocationDTO 构建位置DTO func (s *NPCService) buildLocationDTO(location *npc.Location) *LocationDTO { return &LocationDTO{ X: location.GetX(), Y: location.GetY(), Z: location.GetZ(), Region: location.GetRegion(), Zone: location.GetZone(), } } // buildAttributesDTO 构建属性DTO func (s *NPCService) buildAttributesDTO(attributes *npc.NPCAttributes) *NPCAttributesDTO { return &NPCAttributesDTO{ Level: attributes.GetLevel(), Health: attributes.GetHealth(), MaxHealth: attributes.GetMaxHealth(), Attack: attributes.GetAttack(), Defense: attributes.GetDefense(), Speed: attributes.GetSpeed(), Intelligence: attributes.GetIntelligence(), } } // buildBehaviorDTO 构建行为DTO func (s *NPCService) buildBehaviorDTO(behavior *npc.NPCBehavior) *NPCBehaviorDTO { return &NPCBehaviorDTO{ CurrentAction: behavior.GetCurrentAction().String(), NextAction: behavior.GetNextAction().String(), Cooldown: behavior.GetCooldown(), IsActive: behavior.IsActive(), } } // buildDialogueSessionDTO 构建对话会话DTO func (s *NPCService) buildDialogueSessionDTO(session *npc.DialogueSession) *DialogueSessionDTO { return &DialogueSessionDTO{ SessionID: session.GetID(), NPCID: session.GetNPCID(), PlayerID: session.GetPlayerID(), CurrentNodeID: session.GetCurrentNodeID(), StartTime: session.GetStartTime(), IsActive: session.IsActive(), Context: session.GetContext(), } } // buildDialogueResponseDTO 构建对话响应DTO func (s *NPCService) buildDialogueResponseDTO(response *npc.DialogueResponse) *DialogueResponseDTO { return &DialogueResponseDTO{ NodeID: response.GetNodeID(), Text: response.GetText(), Choices: s.buildDialogueChoiceDTOs(response.GetChoices()), Actions: nil, // TODO: response.GetActions(), IsEnd: false, // TODO: response.IsEnd(), NextNodeID: "", // TODO: response.GetNextNodeID(), } } // buildDialogueChoiceDTOs 构建对话选择DTO列表 func (s *NPCService) buildDialogueChoiceDTOs(choices []*npc.DialogueOption) []*DialogueChoiceDTO { dtos := make([]*DialogueChoiceDTO, len(choices)) for i, choice := range choices { dtos[i] = &DialogueChoiceDTO{ ID: choice.GetID(), Text: choice.GetText(), Condition: "", // TODO: choice.GetCondition(), NextNodeID: "", // TODO: choice.GetNextNodeID(), IsAvailable: true, // TODO: choice.IsAvailable(), } } return dtos } // buildQuestDTOs 构建任务DTO列表 func (s *NPCService) buildQuestDTOs(quests []*npc.Quest) []*QuestDTO { dtos := make([]*QuestDTO, len(quests)) for i, quest := range quests { dtos[i] = &QuestDTO{ ID: quest.GetID(), Name: quest.GetName(), Description: quest.GetDescription(), Type: quest.GetType().String(), RequiredLevel: 0, // TODO: quest.GetRequiredLevel(), Rewards: nil, // TODO: quest.GetRewards(), Objectives: []string{}, // TODO: quest.GetObjectives(), IsRepeatable: false, // TODO: quest.IsRepeatable(), Cooldown: 0, // TODO: quest.GetCooldown(), } } return dtos } // buildQuestInstanceDTO 构建任务实例DTO func (s *NPCService) buildQuestInstanceDTO(instance *npc.QuestInstance) *QuestInstanceDTO { return &QuestInstanceDTO{ ID: "", // TODO: instance.GetID(), QuestID: instance.GetQuestID(), PlayerID: instance.GetPlayerID(), Status: instance.GetStatus().String(), Progress: map[string]int{}, // TODO: instance.GetProgress(), StartTime: time.Now(), // TODO: instance.GetStartTime(), EndTime: time.Now(), // TODO: instance.GetEndTime(), IsCompleted: false, // TODO: instance.IsCompleted(), } } // buildQuestRewardDTO 构建任务奖励DTO func (s *NPCService) buildQuestRewardDTO(reward *npc.QuestReward) *QuestRewardDTO { return &QuestRewardDTO{ Experience: 0, // TODO: reward.GetExperience(), Items: nil, // TODO: reward.GetItems(), Gold: 0, // TODO: reward.GetGold(), TotalValue: int64(0), // TODO: reward.GetTotalValue(), } } // buildShopDTO 构建商店DTO func (s *NPCService) buildShopDTO(shop *npc.Shop) *ShopDTO { return &ShopDTO{ ID: shop.GetID(), NPCID: "", // TODO: shop.GetNPCID(), Name: shop.GetName(), Description: "", // TODO: shop.GetDescription(), Items: s.buildShopItemDTOs([]*npc.ShopItem{}), // TODO: shop.GetItems(), IsOpen: shop.IsOpen(), Schedule: s.buildShopScheduleDTO(nil), // TODO: shop.GetSchedule(), } } // buildShopItemDTOs 构建商店物品DTO列表 func (s *NPCService) buildShopItemDTOs(items []*npc.ShopItem) []*ShopItemDTO { dtos := make([]*ShopItemDTO, len(items)) for i, item := range items { dtos[i] = &ShopItemDTO{ ID: item.GetID(), Name: item.GetName(), Description: "", // TODO: item.GetDescription(), Price: int64(item.GetPrice()), // TODO: 修复类型转换 Stock: item.GetStock(), MaxStock: 0, // TODO: item.GetMaxStock(), IsAvailable: item.IsAvailable(), } } return dtos } // buildShopScheduleDTO 构建商店日程DTO func (s *NPCService) buildShopScheduleDTO(schedule *npc.ShopSchedule) *ShopScheduleDTO { return &ShopScheduleDTO{ OpenTime: time.Now(), // TODO: schedule.GetOpenTime(), CloseTime: time.Now(), // TODO: schedule.GetCloseTime(), IsOpen24H: false, // TODO: schedule.IsOpen24H(), Weekdays: []int{}, // TODO: schedule.GetWeekdays(), } } // buildTradeResultDTO 构建交易结果DTO func (s *NPCService) buildTradeResultDTO(result *npc.TradeResult) *TradeResultDTO { return &TradeResultDTO{ ItemID: "", // TODO: result.GetItemID(), Quantity: 0, // TODO: result.GetQuantity(), Price: 0, // TODO: result.GetPrice(), TotalPrice: 0, // TODO: result.GetTotalPrice(), Success: false, // TODO: result.IsSuccess(), Message: "", // TODO: result.GetMessage(), } } // buildRelationshipDTO 构建关系DTO func (s *NPCService) buildRelationshipDTO(relationship *npc.Relationship) *RelationshipDTO { return &RelationshipDTO{ PlayerID: "", // TODO: relationship.GetPlayerID(), NPCID: "", // TODO: relationship.GetNPCID(), Value: relationship.GetValue(), Level: relationship.GetLevel().String(), LastChanged: time.Now(), // TODO: relationship.GetLastChanged(), IsLocked: false, // TODO: relationship.IsLocked(), } } // buildNPCStatisticsDTO 构建NPC统计DTO func (s *NPCService) buildNPCStatisticsDTO(stats *npc.NPCStatistics) *NPCStatisticsDTO { return &NPCStatisticsDTO{ NPCID: "", // TODO: stats.GetNPCID(), TotalInteractions: 0, // TODO: stats.GetTotalInteractions(), DialogueCount: 0, // TODO: stats.GetDialogueCount(), QuestCount: 0, // TODO: stats.GetQuestCount(), TradeCount: 0, // TODO: stats.GetTradeCount(), UniqueVisitors: 0, // TODO: stats.GetUniqueVisitors(), AverageInteractionTime: 0, // TODO: stats.GetAverageInteractionTime(), LastInteractionTime: time.Now(), // TODO: stats.GetLastInteractionTime(), PopularityScore: 0, // TODO: stats.GetPopularityScore(), } } // DTO 定义 // NPCDTO NPC DTO type NPCDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` Status string `json:"status"` Location *LocationDTO `json:"location"` Attributes *NPCAttributesDTO `json:"attributes"` Behavior *NPCBehaviorDTO `json:"behavior"` HasDialogue bool `json:"has_dialogue"` HasQuests bool `json:"has_quests"` HasShop bool `json:"has_shop"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // LocationDTO 位置DTO type LocationDTO struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` Region string `json:"region"` Zone string `json:"zone"` } // NPCAttributesDTO NPC属性DTO type NPCAttributesDTO struct { Level int `json:"level"` Health int `json:"health"` MaxHealth int `json:"max_health"` Attack int `json:"attack"` Defense int `json:"defense"` Speed float64 `json:"speed"` Intelligence int `json:"intelligence"` } // NPCBehaviorDTO NPC行为DTO type NPCBehaviorDTO struct { CurrentAction string `json:"current_action"` NextAction string `json:"next_action"` Cooldown time.Duration `json:"cooldown"` IsActive bool `json:"is_active"` } // DialogueSessionDTO 对话会话DTO type DialogueSessionDTO struct { SessionID string `json:"session_id"` NPCID string `json:"npc_id"` PlayerID string `json:"player_id"` CurrentNodeID string `json:"current_node_id"` StartTime time.Time `json:"start_time"` IsActive bool `json:"is_active"` Context map[string]interface{} `json:"context"` } // DialogueResponseDTO 对话响应DTO type DialogueResponseDTO struct { NodeID string `json:"node_id"` Text string `json:"text"` Choices []*DialogueChoiceDTO `json:"choices"` Actions []string `json:"actions"` IsEnd bool `json:"is_end"` NextNodeID string `json:"next_node_id,omitempty"` } // DialogueChoiceDTO 对话选择DTO type DialogueChoiceDTO struct { ID string `json:"id"` Text string `json:"text"` Condition string `json:"condition,omitempty"` NextNodeID string `json:"next_node_id"` IsAvailable bool `json:"is_available"` } // QuestDTO 任务DTO type QuestDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` RequiredLevel int `json:"required_level"` Rewards map[string]interface{} `json:"rewards"` Objectives []string `json:"objectives"` IsRepeatable bool `json:"is_repeatable"` Cooldown time.Duration `json:"cooldown"` } // QuestInstanceDTO 任务实例DTO type QuestInstanceDTO struct { ID string `json:"id"` QuestID string `json:"quest_id"` PlayerID string `json:"player_id"` Status string `json:"status"` Progress map[string]int `json:"progress"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` IsCompleted bool `json:"is_completed"` } // QuestRewardDTO 任务奖励DTO type QuestRewardDTO struct { Experience int64 `json:"experience"` Items map[string]int `json:"items"` Gold int64 `json:"gold"` TotalValue int64 `json:"total_value"` } // ShopDTO 商店DTO type ShopDTO struct { ID string `json:"id"` NPCID string `json:"npc_id"` Name string `json:"name"` Description string `json:"description"` Items []*ShopItemDTO `json:"items"` IsOpen bool `json:"is_open"` Schedule *ShopScheduleDTO `json:"schedule"` } // ShopItemDTO 商店物品DTO type ShopItemDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Price int64 `json:"price"` Stock int `json:"stock"` MaxStock int `json:"max_stock"` IsAvailable bool `json:"is_available"` } // ShopScheduleDTO 商店日程DTO type ShopScheduleDTO struct { OpenTime time.Time `json:"open_time"` CloseTime time.Time `json:"close_time"` IsOpen24H bool `json:"is_open_24h"` Weekdays []int `json:"weekdays"` } // TradeResultDTO 交易结果DTO type TradeResultDTO struct { ItemID string `json:"item_id"` Quantity int `json:"quantity"` Price int64 `json:"price"` TotalPrice int64 `json:"total_price"` Success bool `json:"success"` Message string `json:"message"` } // RelationshipDTO 关系DTO type RelationshipDTO struct { PlayerID string `json:"player_id"` NPCID string `json:"npc_id"` Value int `json:"value"` Level string `json:"level"` LastChanged time.Time `json:"last_changed"` IsLocked bool `json:"is_locked"` } // NPCStatisticsDTO NPC统计DTO type NPCStatisticsDTO struct { NPCID string `json:"npc_id"` TotalInteractions int64 `json:"total_interactions"` DialogueCount int64 `json:"dialogue_count"` QuestCount int64 `json:"quest_count"` TradeCount int64 `json:"trade_count"` UniqueVisitors int64 `json:"unique_visitors"` AverageInteractionTime time.Duration `json:"average_interaction_time"` LastInteractionTime time.Time `json:"last_interaction_time"` PopularityScore float64 `json:"popularity_score"` } ================================================ FILE: internal/application/services/pet_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/domain/pet" ) // PetApplicationService 宠物应用服务 type PetApplicationService struct { petRepo pet.PetRepository fragmentRepo pet.PetFragmentRepository skinRepo pet.PetSkinRepository // TODO: 实现这些仓储接口 // bondRepo pet.PetBondRepository pictorialRepo pet.PetPictorialRepository petService *pet.PetService eventBus pet.PetEventBus } // NewPetApplicationService 创建宠物应用服务 func NewPetApplicationService( petRepo pet.PetRepository, fragmentRepo pet.PetFragmentRepository, skinRepo pet.PetSkinRepository, // TODO: 实现这些仓储接口 // bondRepo pet.PetBondRepository, pictorialRepo pet.PetPictorialRepository, petService *pet.PetService, eventBus pet.PetEventBus, ) *PetApplicationService { return &PetApplicationService{ petRepo: petRepo, fragmentRepo: fragmentRepo, skinRepo: skinRepo, // TODO: 实现这些仓储接口 // bondRepo: bondRepo, pictorialRepo: pictorialRepo, petService: petService, eventBus: eventBus, } } // CreatePetRequest 创建宠物请求 type CreatePetRequest struct { OwnerID uint64 `json:"owner_id"` PetType string `json:"pet_type"` Name string `json:"name"` Rarity string `json:"rarity"` Source string `json:"source"` } // CreatePetResponse 创建宠物响应 type CreatePetResponse struct { PetID string `json:"pet_id"` Name string `json:"name"` PetType string `json:"pet_type"` Rarity string `json:"rarity"` Level int32 `json:"level"` Exp int64 `json:"exp"` CreatedAt time.Time `json:"created_at"` } // CreatePet 创建宠物 func (s *PetApplicationService) CreatePet(ctx context.Context, req *CreatePetRequest) (*CreatePetResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateCreatePetRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 转换稀有度 // TODO: 修复parseRarity方法调用 // _, err = s.parseRarity(req.Rarity) // if err != nil { // return nil, fmt.Errorf("invalid rarity: %w", err) // } // 转换来源 // TODO: 修复parseSource方法调用 // _, err = s.parseSource(req.Source) // if err != nil { // return nil, fmt.Errorf("invalid source: %w", err) // } // 创建宠物聚合根 // TODO: 修复NewPetAggregate方法调用 // petAggregate := pet.NewPetAggregate(req.OwnerID, req.PetType, req.Name) // petAggregate := &pet.PetAggregate{} // petAggregate.SetRarity(rarity) // petAggregate.SetSource(source) // 保存宠物 // TODO: 修复Save方法调用 // if err := s.petRepo.Save(ctx, petAggregate); err != nil { // return nil, fmt.Errorf("failed to save pet: %w", err) // } // 发布事件 // TODO: 修复NewPetCreatedEvent方法调用 // event := pet.NewPetCreatedEvent(petAggregate.GetID(), req.OwnerID, req.PetType, req.Name) // if err := s.eventBus.Publish(ctx, event); err != nil { // // 记录错误但不影响主流程 // fmt.Printf("failed to publish pet created event: %v\n", err) // } return &CreatePetResponse{ PetID: "", // TODO: petAggregate.GetID(), Name: req.Name, PetType: req.PetType, Rarity: "", // TODO: petAggregate.GetRarity().String(), Level: int32(1), // TODO: petAggregate.GetLevel(), Exp: 0, // TODO: petAggregate.GetExp(), CreatedAt: time.Now(), // TODO: petAggregate.GetCreatedAt(), }, nil } // FeedPetRequest 喂养宠物请求 type FeedPetRequest struct { PetID string `json:"pet_id"` FoodType string `json:"food_type"` Amount int32 `json:"amount"` } // FeedPetResponse 喂养宠物响应 type FeedPetResponse struct { PetID string `json:"pet_id"` ExpGained int64 `json:"exp_gained"` LeveledUp bool `json:"leveled_up"` NewLevel int32 `json:"new_level"` NewExp int64 `json:"new_exp"` Happiness int32 `json:"happiness"` } // FeedPet 喂养宠物 func (s *PetApplicationService) FeedPet(ctx context.Context, req *FeedPetRequest) (*FeedPetResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateFeedPetRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 获取宠物 // TODO: 修复FindByID方法调用 // petAggregate, err := s.petRepo.FindByID(ctx, req.PetID) // if err != nil { // return nil, fmt.Errorf("failed to find pet: %w", err) // } // if petAggregate == nil { // return nil, fmt.Errorf("pet not found") // } // petAggregate := &pet.PetAggregate{} // 计算经验值 expGained := s.calculateFoodExp(req.FoodType, req.Amount) // 喂养宠物 // TODO: 修复AddExp方法调用 // leveledUp := petAggregate.AddExp(expGained) leveledUp := false // TODO: 修复Feed方法调用 // petAggregate.Feed(req.FoodType, req.Amount) // 保存宠物 // TODO: 修复Save方法调用 // if err := s.petRepo.Save(ctx, petAggregate); err != nil { // return nil, fmt.Errorf("failed to save pet: %w", err) // } // 发布事件 // TODO: 修复NewPetFedEvent方法调用 // event := pet.NewPetFedEvent(petAggregate.GetID(), req.FoodType, req.Amount, expGained) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish pet fed event: %v\n", err) // } // TODO: 修复leveledUp检查 // if leveledUp { // levelUpEvent := pet.NewPetLevelUpEvent(petAggregate.GetID(), petAggregate.GetLevel()-1, petAggregate.GetLevel()) // if err := s.eventBus.Publish(ctx, levelUpEvent); err != nil { // fmt.Printf("failed to publish pet level up event: %v\n", err) // } // } return &FeedPetResponse{ PetID: "", // TODO: petAggregate.GetID(), ExpGained: expGained, LeveledUp: leveledUp, NewLevel: int32(1), // TODO: petAggregate.GetLevel(), NewExp: 0, // TODO: petAggregate.GetExp(), Happiness: 0, // TODO: petAggregate.GetHappiness(), }, nil } // TrainPetRequest 训练宠物请求 type TrainPetRequest struct { PetID string `json:"pet_id"` TrainingType string `json:"training_type"` Duration int32 `json:"duration"` // 训练时长(分钟) } // TrainPetResponse 训练宠物响应 type TrainPetResponse struct { PetID string `json:"pet_id"` TrainingType string `json:"training_type"` AttributeGains map[string]int32 `json:"attribute_gains"` ExpGained int64 `json:"exp_gained"` LeveledUp bool `json:"leveled_up"` NewLevel int32 `json:"new_level"` SkillsLearned []string `json:"skills_learned"` } // TrainPet 训练宠物 func (s *PetApplicationService) TrainPet(ctx context.Context, req *TrainPetRequest) (*TrainPetResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateTrainPetRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 获取宠物 // TODO: 修复FindByID方法调用 // petAggregate, err := s.petRepo.FindByID(ctx, req.PetID) // if err != nil { // return nil, fmt.Errorf("failed to find pet: %w", err) // } // if petAggregate == nil { // return nil, fmt.Errorf("pet not found") // } // petAggregate := &pet.PetAggregate{} // 计算训练收益 attributeGains := s.calculateTrainingGains(req.TrainingType, req.Duration, int32(1)) // TODO: petAggregate.GetLevel() expGained := s.calculateTrainingExp(req.TrainingType, req.Duration) // 训练宠物 // TODO: 修复AddExp方法调用 // leveledUp := petAggregate.AddExp(expGained) leveledUp := false // for attr, gain := range attributeGains { // petAggregate.AddAttribute(attr, gain) // } // 检查是否学会新技能 // TODO: 修复checkSkillLearning方法调用 // skillsLearned := s.checkSkillLearning(petAggregate, req.TrainingType) // for _, skill := range skillsLearned { // petAggregate.LearnSkill(skill) // } // 保存宠物 // TODO: 修复Save方法调用 // if err := s.petRepo.Save(ctx, petAggregate); err != nil { // return nil, fmt.Errorf("failed to save pet: %w", err) // } // 发布事件 // TODO: 修复NewPetTrainedEvent方法调用 // event := pet.NewPetTrainedEvent(petAggregate.GetID(), req.TrainingType, attributeGains, expGained) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish pet trained event: %v\n", err) // } return &TrainPetResponse{ PetID: "", // TODO: petAggregate.GetID(), TrainingType: req.TrainingType, AttributeGains: attributeGains, ExpGained: expGained, LeveledUp: leveledUp, NewLevel: int32(1), // TODO: petAggregate.GetLevel(), SkillsLearned: []string{}, // TODO: skillsLearned, }, nil } // GetPetRequest 获取宠物请求 type GetPetRequest struct { PetID string `json:"pet_id"` } // GetPetResponse 获取宠物响应 type GetPetResponse struct { PetID string `json:"pet_id"` OwnerID uint64 `json:"owner_id"` Name string `json:"name"` PetType string `json:"pet_type"` Rarity string `json:"rarity"` Level int32 `json:"level"` Exp int64 `json:"exp"` MaxExp int64 `json:"max_exp"` Happiness int32 `json:"happiness"` Health int32 `json:"health"` Attributes map[string]int32 `json:"attributes"` Skills []string `json:"skills"` Skins []string `json:"skins"` CurrentSkin string `json:"current_skin"` Bonds []string `json:"bonds"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // GetPet 获取宠物信息 func (s *PetApplicationService) GetPet(ctx context.Context, req *GetPetRequest) (*GetPetResponse, error) { if req == nil || req.PetID == "" { return nil, fmt.Errorf("pet ID is required") } // 获取宠物 // TODO: 修复FindByID方法调用 // petAggregate, err := s.petRepo.FindByID(ctx, req.PetID) // if err != nil { // return nil, fmt.Errorf("failed to find pet: %w", err) // } // if petAggregate == nil { // return nil, fmt.Errorf("pet not found") // } // petAggregate := &pet.PetAggregate{} return &GetPetResponse{ PetID: "", // TODO: petAggregate.GetID(), OwnerID: uint64(0), // TODO: petAggregate.GetOwnerID(), Name: "", // TODO: petAggregate.GetName(), PetType: "", // TODO: petAggregate.GetPetType(), Rarity: "", // TODO: petAggregate.GetRarity().String(), Level: int32(1), // TODO: petAggregate.GetLevel(), Exp: 0, // TODO: petAggregate.GetExp(), MaxExp: 0, // TODO: petAggregate.GetMaxExp(), Happiness: 0, // TODO: petAggregate.GetHappiness(), Health: 0, // TODO: petAggregate.GetHealth(), Attributes: map[string]int32{}, // TODO: petAggregate.GetAttributes(), Skills: []string{}, // TODO: petAggregate.GetSkills(), Skins: []string{}, // TODO: petAggregate.GetUnlockedSkins(), CurrentSkin: "", // TODO: petAggregate.GetCurrentSkin(), Bonds: []string{}, // TODO: petAggregate.GetBonds(), Status: "active", // TODO: petAggregate.GetStatus().String(), CreatedAt: time.Now(), // TODO: petAggregate.GetCreatedAt(), UpdatedAt: time.Now(), // TODO: petAggregate.GetUpdatedAt(), }, nil } // GetPlayerPetsRequest 获取玩家宠物列表请求 type GetPlayerPetsRequest struct { OwnerID uint64 `json:"owner_id"` Page int `json:"page"` PageSize int `json:"page_size"` SortBy string `json:"sort_by"` // level, exp, happiness, created_at SortOrder string `json:"sort_order"` // asc, desc } // GetPlayerPetsResponse 获取玩家宠物列表响应 type GetPlayerPetsResponse struct { Pets []*GetPetResponse `json:"pets"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int64 `json:"total_pages"` } // GetPlayerPets 获取玩家宠物列表 func (s *PetApplicationService) GetPlayerPets(ctx context.Context, req *GetPlayerPetsRequest) (*GetPlayerPetsResponse, error) { if req == nil || req.OwnerID == 0 { return nil, fmt.Errorf("owner ID is required") } // 设置默认值 if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } if req.SortBy == "" { req.SortBy = "created_at" } if req.SortOrder == "" { req.SortOrder = "desc" } // 构建查询 // TODO: 修复NewPetQuery方法调用 // query := pet.NewPetQuery(). // WithOwner(req.OwnerID). // WithSort(req.SortBy, req.SortOrder). // WithPagination(req.Page, req.PageSize) // 查询宠物 // TODO: 修复FindByQuery方法调用 // pets, total, err := s.petRepo.FindByQuery(ctx, query) pets, total := []*pet.PetAggregate{}, 0 // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to find pets: %w", err) // } // 转换响应 petResponses := make([]*GetPetResponse, len(pets)) for i, _ := range pets { petResponses[i] = &GetPetResponse{ PetID: "", // TODO: petAggregate.GetID(), OwnerID: uint64(0), // TODO: petAggregate.GetOwnerID(), Name: "", // TODO: petAggregate.GetName(), PetType: "", // TODO: petAggregate.GetPetType(), Rarity: "", // TODO: petAggregate.GetRarity().String(), Level: int32(1), // TODO: petAggregate.GetLevel(), Exp: 0, // TODO: petAggregate.GetExp(), MaxExp: 0, // TODO: petAggregate.GetMaxExp(), Happiness: 0, // TODO: petAggregate.GetHappiness(), Health: 0, // TODO: petAggregate.GetHealth(), Attributes: map[string]int32{}, // TODO: petAggregate.GetAttributes(), Skills: []string{}, // TODO: petAggregate.GetSkills(), Skins: []string{}, // TODO: petAggregate.GetUnlockedSkins(), CurrentSkin: "", // TODO: petAggregate.GetCurrentSkin(), Bonds: []string{}, // TODO: petAggregate.GetBonds(), Status: "active", // TODO: petAggregate.GetStatus().String(), CreatedAt: time.Now(), // TODO: petAggregate.GetCreatedAt(), UpdatedAt: time.Now(), // TODO: petAggregate.GetUpdatedAt(), } } totalPages := (int64(total) + int64(req.PageSize) - 1) / int64(req.PageSize) return &GetPlayerPetsResponse{ Pets: petResponses, Total: int64(total), Page: req.Page, PageSize: req.PageSize, TotalPages: totalPages, }, nil } // 私有方法 // validateCreatePetRequest 验证创建宠物请求 func (s *PetApplicationService) validateCreatePetRequest(req *CreatePetRequest) error { if req.OwnerID == 0 { return fmt.Errorf("owner ID is required") } if req.PetType == "" { return fmt.Errorf("pet type is required") } if req.Name == "" { return fmt.Errorf("name is required") } if len(req.Name) > 50 { return fmt.Errorf("name too long (max 50 characters)") } return nil } // validateFeedPetRequest 验证喂养宠物请求 func (s *PetApplicationService) validateFeedPetRequest(req *FeedPetRequest) error { if req.PetID == "" { return fmt.Errorf("pet ID is required") } if req.FoodType == "" { return fmt.Errorf("food type is required") } if req.Amount <= 0 { return fmt.Errorf("amount must be positive") } return nil } // validateTrainPetRequest 验证训练宠物请求 func (s *PetApplicationService) validateTrainPetRequest(req *TrainPetRequest) error { if req.PetID == "" { return fmt.Errorf("pet ID is required") } if req.TrainingType == "" { return fmt.Errorf("training type is required") } if req.Duration <= 0 { return fmt.Errorf("duration must be positive") } return nil } // parseRarity 解析稀有度 func (s *PetApplicationService) parseRarity(rarityStr string) (pet.PetRarity, error) { // TODO: 修复PetRarity常量 switch rarityStr { case "common": return pet.PetRarity(0), nil // TODO: pet.RarityCommon case "uncommon": return pet.PetRarity(1), nil // TODO: pet.RarityUncommon case "rare": return pet.PetRarity(2), nil // TODO: pet.RarityRare case "epic": return pet.PetRarity(3), nil // TODO: pet.RarityEpic case "legendary": return pet.PetRarity(4), nil // TODO: pet.RarityLegendary default: return pet.PetRarity(0), fmt.Errorf("unknown rarity: %s", rarityStr) // TODO: pet.RarityCommon } } // parseSource 解析来源 func (s *PetApplicationService) parseSource(sourceStr string) (string, error) { switch sourceStr { case "shop": return "shop", nil case "wild": return "wild", nil case "breed": return "breed", nil case "event": return "event", nil case "gift": return "gift", nil default: return "shop", fmt.Errorf("unknown source: %s", sourceStr) } } // calculateFoodExp 计算食物经验值 func (s *PetApplicationService) calculateFoodExp(foodType string, amount int32) int64 { baseExp := map[string]int64{ "basic_food": 10, "premium_food": 25, "luxury_food": 50, "special_food": 100, } exp, exists := baseExp[foodType] if !exists { exp = 10 // 默认经验值 } return exp * int64(amount) } // calculateTrainingGains 计算训练收益 func (s *PetApplicationService) calculateTrainingGains(trainingType string, duration int32, level int32) map[string]int32 { gains := make(map[string]int32) baseGains := map[string]map[string]int32{ "strength": { "attack": 2, "defense": 1, }, "agility": { "speed": 2, "accuracy": 1, }, "intelligence": { "magic_attack": 2, "mana": 1, }, } if baseGain, exists := baseGains[trainingType]; exists { for attr, gain := range baseGain { // 基础收益 * 时长倍数 * 等级倍数 multiplier := float64(duration) / 60.0 * (1.0 + float64(level)*0.1) gains[attr] = int32(float64(gain) * multiplier) } } return gains } // calculateTrainingExp 计算训练经验值 func (s *PetApplicationService) calculateTrainingExp(trainingType string, duration int32) int64 { baseExp := int64(5) // 每分钟5经验 return baseExp * int64(duration) } // checkSkillLearning 检查技能学习 func (s *PetApplicationService) checkSkillLearning(petAggregate *pet.PetAggregate, trainingType string) []string { skills := make([]string, 0) // 简单的技能学习逻辑 level := petAggregate.GetLevel() learnedSkills := petAggregate.GetSkills() // 根据等级和训练类型判断可学习的技能 potentialSkills := map[string][]string{ "strength": {"power_strike", "berserker_rage", "iron_defense"}, "agility": {"quick_attack", "dodge", "critical_strike"}, "intelligence": {"magic_missile", "heal", "mana_shield"}, } if skillList, exists := potentialSkills[trainingType]; exists { for i, skill := range skillList { requiredLevel := int32((i + 1) * 10) // 10, 20, 30级学习 if int32(level) >= requiredLevel { // 检查是否已学会 alreadyLearned := false for _, learned := range learnedSkills { if learned.GetName() == skill { alreadyLearned = true break } } if !alreadyLearned { skills = append(skills, skill) } } } } return skills } ================================================ FILE: internal/application/services/plant_service.go ================================================ package services import ( "context" "time" "greatestworks/internal/domain/scene/plant" ) // PlantService 种植应用服务 type PlantService struct { farmRepo plant.FarmRepository cropRepo plant.CropRepository // seedRepo plant.SeedRepository // TODO: Define SeedRepository harvestRepo plant.HarvestRepository // statisticsRepo plant.StatisticsRepository // TODO: Define StatisticsRepository cacheRepo plant.PlantCacheRepository plantService *plant.PlantService } // NewPlantService 创建种植应用服务 func NewPlantService( farmRepo plant.FarmRepository, cropRepo plant.CropRepository, // seedRepo plant.SeedRepository, harvestRepo plant.HarvestRepository, // statisticsRepo plant.StatisticsRepository, cacheRepo plant.PlantCacheRepository, plantService *plant.PlantService, ) *PlantService { return &PlantService{ farmRepo: farmRepo, cropRepo: cropRepo, // seedRepo: seedRepo, harvestRepo: harvestRepo, // statisticsRepo: statisticsRepo, cacheRepo: cacheRepo, plantService: plantService, } } // GetFarmInfo 获取农场信息 func (s *PlantService) GetFarmInfo(ctx context.Context, playerID string) (*FarmDTO, error) { // 先从缓存获取 // TODO: 修复GetFarm方法调用 // cachedFarm, err := s.cacheRepo.GetFarm(playerID) // if err == nil && cachedFarm != nil { // return s.buildFarmDTO(cachedFarm), nil // } // 从数据库获取 // TODO: 修复FindByPlayer方法调用 // farm, err := s.farmRepo.FindByPlayer(playerID) farm := &plant.FarmAggregate{} // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to get farm info: %w", err) // } // 更新缓存 // TODO: 修复SetFarm方法调用 // if err := s.cacheRepo.SetFarm(playerID, farm, time.Hour); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildFarmDTO(farm), nil } // PlantSeed 种植种子 func (s *PlantService) PlantSeed(ctx context.Context, playerID string, plotID string, seedID string) error { // 获取农场信息 // TODO: 修复FindByPlayer方法调用 // farm, err := s.farmRepo.FindByPlayer(playerID) // if err != nil { // return fmt.Errorf("failed to get farm info: %w", err) // } // farm := &plant.FarmAggregate{} // 获取种子信息 // TODO: 修复seedRepo字段 // seed, err := s.seedRepo.FindByID(seedID) // if err != nil { // return fmt.Errorf("failed to get seed info: %w", err) // } // seed := &plant.Seed{} // 种植种子 // TODO: 修复PlantSeed方法调用 // crop, err := s.plantService.PlantSeed(farm, plotID, seed) // crop := &plant.Crop{} // TODO: 修复err变量 // if err != nil { // return fmt.Errorf("failed to plant seed: %w", err) // } // 保存作物 // TODO: 修复Save方法调用 // if err := s.cropRepo.Save(crop); err != nil { // return fmt.Errorf("failed to save crop: %w", err) // } // 更新农场 // TODO: 修复Update方法调用 // if err := s.farmRepo.Update(farm); err != nil { // return fmt.Errorf("failed to update farm: %w", err) // } // 更新统计数据 // TODO: 修复updatePlantingStatistics方法调用 // if err := s.updatePlantingStatistics(ctx, playerID, seedID); err != nil { // // 统计更新失败不影响主流程 // // TODO: 添加日志记录 // } // 清除缓存 // if err := s.cacheRepo.DeleteFarm(playerID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // WaterCrop 浇水 func (s *PlantService) WaterCrop(ctx context.Context, playerID string, cropID string) error { // 获取作物信息 // TODO: 修复FindByID方法调用 // crop, err := s.cropRepo.FindByID(cropID) // if err != nil { // return fmt.Errorf("failed to get crop info: %w", err) // } // crop := &plant.Crop{} // TODO: 修复GetPlayerID方法调用 // if crop.GetPlayerID() != playerID { // return plant.ErrUnauthorized // } // 浇水 // TODO: 修复Water方法调用 // if err := crop.Water(); err != nil { // return fmt.Errorf("failed to water crop: %w", err) // } // 更新作物 // TODO: 修复Update方法调用 // if err := s.cropRepo.Update(crop); err != nil { // return fmt.Errorf("failed to update crop: %w", err) // } // 清除相关缓存 // TODO: 修复DeleteFarm方法调用 // if err := s.cacheRepo.DeleteFarm(playerID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // FertilizeCrop 施肥 func (s *PlantService) FertilizeCrop(ctx context.Context, playerID string, cropID string, fertilizerType plant.FertilizerType) error { // 获取作物信息 // TODO: 修复FindByID方法调用 // crop, err := s.cropRepo.FindByID(cropID) // crop := &plant.Crop{} // TODO: 修复err变量 // if err != nil { // return fmt.Errorf("failed to get crop info: %w", err) // } // TODO: 修复GetPlayerID方法调用 // if crop.GetPlayerID() != playerID { // return plant.ErrUnauthorized // } // 施肥 // TODO: 修复Fertilize方法调用 // if err := crop.Fertilize(fertilizerType); err != nil { // return fmt.Errorf("failed to fertilize crop: %w", err) // } // 更新作物 // TODO: 修复Update方法调用 // if err := s.cropRepo.Update(crop); err != nil { // return fmt.Errorf("failed to update crop: %w", err) // } // 清除相关缓存 // TODO: 修复DeleteFarm方法调用 // if err := s.cacheRepo.DeleteFarm(playerID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // HarvestCrop 收获作物 func (s *PlantService) HarvestCrop(ctx context.Context, playerID string, cropID string) (*HarvestResultDTO, error) { // 获取作物信息 // TODO: 修复FindByID方法调用 // crop, err := s.cropRepo.FindByID(cropID) // if err != nil { // return nil, fmt.Errorf("failed to get crop info: %w", err) // } // crop := &plant.Crop{} // TODO: 修复GetPlayerID方法调用 // if crop.GetPlayerID() != playerID { // return nil, plant.ErrUnauthorized // } // 收获作物 // TODO: 修复HarvestCrop方法调用 // harvestResult, err := s.plantService.HarvestCrop(crop) harvestResult := &plant.HarvestResult{} // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to harvest crop: %w", err) // } // 保存收获记录 // TODO: 修复Save方法调用 // if err := s.harvestRepo.Save(harvestResult); err != nil { // return nil, fmt.Errorf("failed to save harvest record: %w", err) // } // 删除作物(已收获) // TODO: 修复Delete方法调用 // if err := s.cropRepo.Delete(cropID); err != nil { // return nil, fmt.Errorf("failed to delete harvested crop: %w", err) // } // 更新统计数据 // TODO: 修复updateHarvestStatistics方法调用 // if err := s.updateHarvestStatistics(ctx, playerID, harvestResult); err != nil { // // 统计更新失败不影响主流程 // // TODO: 添加日志记录 // } // 清除相关缓存 // TODO: 修复DeleteFarm方法调用 // if err := s.cacheRepo.DeleteFarm(playerID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildHarvestResultDTO(harvestResult), nil } // GetCropInfo 获取作物信息 func (s *PlantService) GetCropInfo(ctx context.Context, playerID string, cropID string) (*CropDTO, error) { // 获取作物信息 // TODO: 修复FindByID方法调用 // crop, err := s.cropRepo.FindByID(cropID) // if err != nil { // return nil, fmt.Errorf("failed to get crop info: %w", err) // } crop := &plant.Crop{} // TODO: 修复GetPlayerID方法调用 // if crop.GetPlayerID() != playerID { // return nil, plant.ErrUnauthorized // } return s.buildCropDTO(crop), nil } // GetPlayerCrops 获取玩家所有作物 func (s *PlantService) GetPlayerCrops(ctx context.Context, playerID string) ([]*CropDTO, error) { // 获取玩家所有作物 // TODO: 修复FindByPlayer方法调用 // crops, err := s.cropRepo.FindByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get player crops: %w", err) // } crops := []*plant.Crop{} return s.buildCropDTOs(crops), nil } // GetAvailableSeeds 获取可用种子 func (s *PlantService) GetAvailableSeeds(ctx context.Context, playerID string) ([]*SeedDTO, error) { // 先从缓存获取 // TODO: 修复GetAvailableSeeds方法调用 // cachedSeeds, err := s.cacheRepo.GetAvailableSeeds(playerID) // if err == nil && len(cachedSeeds) > 0 { // return s.buildSeedDTOs(cachedSeeds), nil // } // 从数据库获取 // TODO: 修复FindAvailableForPlayer方法调用 // seeds, err := s.seedRepo.FindAvailableForPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get available seeds: %w", err) // } // seeds := []interface{}{} // TODO: 修复plant.Seed类型 // 更新缓存 // TODO: 修复SetAvailableSeeds方法调用 // if err := s.cacheRepo.SetAvailableSeeds(playerID, seeds, time.Hour*2); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildSeedDTOs([]plant.SeedType{}), nil // TODO: 修复seeds类型 } // GetHarvestHistory 获取收获历史 func (s *PlantService) GetHarvestHistory(ctx context.Context, playerID string, limit int) ([]*HarvestHistoryDTO, error) { // TODO: 修复FindByPlayer方法调用 // history, err := s.harvestRepo.FindByPlayer(playerID, limit) // if err != nil { // return nil, fmt.Errorf("failed to get harvest history: %w", err) // } history := []*plant.HarvestResult{} return s.buildHarvestHistoryDTOs(history), nil } // GetPlantingStatistics 获取种植统计 func (s *PlantService) GetPlantingStatistics(ctx context.Context, playerID string) (*PlantingStatisticsDTO, error) { // TODO: 修复statisticsRepo字段 // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get planting statistics: %w", err) // } // stats := &struct{}{} // TODO: 修复plant.PlantingStatistics类型 return s.buildStatisticsDTO(&plant.FarmStatistics{}), nil // TODO: 修复stats类型 } // UpdateCropGrowth 更新作物成长(系统调用) func (s *PlantService) UpdateCropGrowth(ctx context.Context) error { // 获取所有需要更新的作物 // TODO: 修复FindGrowingCrops方法调用 // crops, err := s.cropRepo.FindGrowingCrops() // if err != nil { // return fmt.Errorf("failed to get growing crops: %w", err) // } crops := []*plant.Crop{} for range crops { // 更新作物成长 // TODO: 修复UpdateCropGrowth方法调用 // if err := s.plantService.UpdateCropGrowth(crop); err != nil { // // 单个作物更新失败不影响其他作物 // // TODO: 添加日志记录 // continue // } // 保存更新后的作物 // TODO: 修复Update方法调用 // if err := s.cropRepo.Update(crop); err != nil { // // TODO: 添加日志记录 // continue // } } return nil } // UpgradeFarmPlot 升级农场地块 func (s *PlantService) UpgradeFarmPlot(ctx context.Context, playerID string, plotID string) error { // 获取农场信息 // TODO: 修复FindByPlayer方法调用 // farm, err := s.farmRepo.FindByPlayer(playerID) // if err != nil { // return fmt.Errorf("failed to get farm info: %w", err) // } // farm := &plant.FarmAggregate{} // 升级地块 // TODO: 修复UpgradePlot方法调用 // if err := farm.UpgradePlot(plotID); err != nil { // return fmt.Errorf("failed to upgrade plot: %w", err) // } // 更新农场 // TODO: 修复Update方法调用 // if err := s.farmRepo.Update(farm); err != nil { // return fmt.Errorf("failed to update farm: %w", err) // } // 清除缓存 // TODO: 修复DeleteFarm方法调用 // if err := s.cacheRepo.DeleteFarm(playerID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // 私有方法 // updatePlantingStatistics 更新种植统计 func (s *PlantService) updatePlantingStatistics(ctx context.Context, playerID string, seedID string) error { // TODO: 修复statisticsRepo字段 // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil && !plant.IsNotFoundError(err) { // return err // } // if stats == nil { // stats = plant.NewPlantingStatistics(playerID) // } // stats := &plant.PlantingStatistics{} // 更新统计数据 // TODO: 修复AddPlantedSeed方法调用 // stats.AddPlantedSeed(seedID) // stats.UpdateLastPlantTime(time.Now()) // 保存统计数据 // TODO: 修复Save方法调用 // return s.statisticsRepo.Save(stats) return nil } // updateHarvestStatistics 更新收获统计 func (s *PlantService) updateHarvestStatistics(ctx context.Context, playerID string, harvestResult *plant.HarvestResult) error { // TODO: 修复statisticsRepo字段 // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil && !plant.IsNotFoundError(err) { // return err // } // if stats == nil { // stats = plant.NewPlantingStatistics(playerID) // } // stats := &plant.PlantingStatistics{} // 更新统计数据 // TODO: 修复AddHarvestResult方法调用 // stats.AddHarvestResult(harvestResult.GetCropType(), harvestResult.GetQuantity(), harvestResult.GetQuality()) // stats.UpdateLastHarvestTime(harvestResult.GetHarvestTime()) // 保存统计数据 // TODO: 修复Save方法调用 // return s.statisticsRepo.Save(stats) return nil } // buildFarmDTO 构建农场DTO func (s *PlantService) buildFarmDTO(farm *plant.FarmAggregate) *FarmDTO { plots := make([]*PlotDTO, 0) for range farm.GetPlots() { plots = append(plots, &PlotDTO{ ID: "", // TODO: plot.GetID(), Level: 0, // TODO: plot.GetLevel(), SoilType: "", // TODO: string(plot.GetSoilType()), Fertility: 0, // TODO: plot.GetFertility(), Moisture: 0, // TODO: plot.GetMoisture(), IsOccupied: false, // TODO: plot.IsOccupied(), CropID: "", // TODO: plot.GetCropID(), }) } return &FarmDTO{ PlayerID: "", // TODO: farm.GetPlayerID(), Level: 0, // TODO: farm.GetLevel(), Experience: 0, // TODO: farm.GetExperience(), Plots: plots, TotalPlots: 0, // TODO: farm.GetTotalPlots(), UsedPlots: 0, // TODO: farm.GetUsedPlots(), CreatedAt: time.Now(), // TODO: farm.GetCreatedAt(), UpdatedAt: time.Now(), // TODO: farm.GetUpdatedAt(), } } // buildCropDTO 构建作物DTO func (s *PlantService) buildCropDTO(crop *plant.Crop) *CropDTO { return &CropDTO{ ID: crop.GetID(), PlayerID: crop.GetPlayerID(), PlotID: crop.GetPlotID(), SeedID: crop.GetSeedID(), CropType: string(crop.GetCropType()), CurrentStage: string(crop.GetCurrentStage()), GrowthProgress: crop.GetGrowthProgress(), Health: 0, // TODO: crop.GetHealth(), Moisture: 0, // TODO: crop.GetMoisture(), Nutrition: 0, // TODO: crop.GetNutrition(), Quality: 0, // TODO: crop.GetQuality(), PlantedAt: time.Now(), // TODO: crop.GetPlantedAt(), LastWatered: time.Now(), // TODO: crop.GetLastWatered(), LastFertilized: time.Now(), // TODO: crop.GetLastFertilized(), EstimatedHarvestTime: time.Now(), // TODO: crop.GetEstimatedHarvestTime(), IsReadyToHarvest: false, // TODO: crop.IsReadyToHarvest(), } } // buildCropDTOs 构建作物DTO列表 func (s *PlantService) buildCropDTOs(crops []*plant.Crop) []*CropDTO { dtos := make([]*CropDTO, len(crops)) for i, crop := range crops { dtos[i] = s.buildCropDTO(crop) } return dtos } // buildSeedDTOs 构建种子DTO列表 func (s *PlantService) buildSeedDTOs(seeds []plant.SeedType) []*SeedDTO { dtos := make([]*SeedDTO, len(seeds)) for i, _ := range seeds { dtos[i] = &SeedDTO{ ID: "", // TODO: seed.GetID(), Name: "", // TODO: seed.GetName(), CropType: "", // TODO: string(seed.GetCropType()), GrowthTime: 0, // TODO: seed.GetGrowthTime(), RequiredLevel: 0, // TODO: seed.GetRequiredLevel(), Price: 0, // TODO: seed.GetPrice(), Yield: 0, // TODO: seed.GetYield(), Quality: 0, // TODO: seed.GetQuality(), Description: "", // TODO: seed.GetDescription(), } } return dtos } // buildHarvestResultDTO 构建收获结果DTO func (s *PlantService) buildHarvestResultDTO(harvestResult *plant.HarvestResult) *HarvestResultDTO { return &HarvestResultDTO{ CropID: "", // TODO: harvestResult.GetCropID(), CropType: "", // TODO: string(harvestResult.GetCropType()), Quantity: 0, // TODO: harvestResult.GetQuantity(), Quality: 0, // TODO: harvestResult.GetQuality(), Experience: 0, // TODO: harvestResult.GetExperience(), HarvestTime: time.Now(), // TODO: harvestResult.GetHarvestTime(), Items: map[string]int{}, // TODO: harvestResult.GetItems(), } } // buildHarvestHistoryDTOs 构建收获历史DTO列表 func (s *PlantService) buildHarvestHistoryDTOs(history []*plant.HarvestResult) []*HarvestHistoryDTO { dtos := make([]*HarvestHistoryDTO, len(history)) for i, _ := range history { dtos[i] = &HarvestHistoryDTO{ ID: "", // TODO: record.GetID(), CropType: "", // TODO: string(record.GetCropType()), Quantity: 0, // TODO: record.GetQuantity(), Quality: 0, // TODO: record.GetQuality(), Experience: 0, // TODO: record.GetExperience(), HarvestTime: time.Now(), // TODO: record.GetHarvestTime(), } } return dtos } // buildStatisticsDTO 构建统计DTO func (s *PlantService) buildStatisticsDTO(stats *plant.FarmStatistics) *PlantingStatisticsDTO { return &PlantingStatisticsDTO{ PlayerID: "", // TODO: stats.GetPlayerID(), TotalPlanted: 0, // TODO: stats.GetTotalPlanted(), TotalHarvested: 0, // TODO: stats.GetTotalHarvested(), TotalExperience: 0, // TODO: stats.GetTotalExperience(), CropTypeStats: map[string]*CropTypeStats{}, // TODO: stats.GetCropTypeStats(), AverageQuality: 0, // TODO: stats.GetAverageQuality(), FavoriteCrop: "", // TODO: string(stats.GetFavoriteCrop()), LastPlantTime: time.Now(), // TODO: stats.GetLastPlantTime(), LastHarvestTime: time.Now(), // TODO: stats.GetLastHarvestTime(), } } // DTO 定义 // FarmDTO 农场DTO type FarmDTO struct { PlayerID string `json:"player_id"` Level int `json:"level"` Experience int64 `json:"experience"` Plots []*PlotDTO `json:"plots"` TotalPlots int `json:"total_plots"` UsedPlots int `json:"used_plots"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PlotDTO 地块DTO type PlotDTO struct { ID string `json:"id"` Level int `json:"level"` SoilType string `json:"soil_type"` Fertility float64 `json:"fertility"` Moisture float64 `json:"moisture"` IsOccupied bool `json:"is_occupied"` CropID string `json:"crop_id,omitempty"` } // CropDTO 作物DTO type CropDTO struct { ID string `json:"id"` PlayerID string `json:"player_id"` PlotID string `json:"plot_id"` SeedID string `json:"seed_id"` CropType string `json:"crop_type"` CurrentStage string `json:"current_stage"` GrowthProgress float64 `json:"growth_progress"` Health float64 `json:"health"` Moisture float64 `json:"moisture"` Nutrition float64 `json:"nutrition"` Quality float64 `json:"quality"` PlantedAt time.Time `json:"planted_at"` LastWatered time.Time `json:"last_watered"` LastFertilized time.Time `json:"last_fertilized"` EstimatedHarvestTime time.Time `json:"estimated_harvest_time"` IsReadyToHarvest bool `json:"is_ready_to_harvest"` } // SeedDTO 种子DTO type SeedDTO struct { ID string `json:"id"` Name string `json:"name"` CropType string `json:"crop_type"` GrowthTime time.Duration `json:"growth_time"` RequiredLevel int `json:"required_level"` Price int64 `json:"price"` Yield int `json:"yield"` Quality float64 `json:"quality"` Description string `json:"description"` } // HarvestResultDTO 收获结果DTO type HarvestResultDTO struct { CropID string `json:"crop_id"` CropType string `json:"crop_type"` Quantity int `json:"quantity"` Quality float64 `json:"quality"` Experience int64 `json:"experience"` HarvestTime time.Time `json:"harvest_time"` Items map[string]int `json:"items"` } // HarvestHistoryDTO 收获历史DTO type HarvestHistoryDTO struct { ID string `json:"id"` CropType string `json:"crop_type"` Quantity int `json:"quantity"` Quality float64 `json:"quality"` Experience int64 `json:"experience"` HarvestTime time.Time `json:"harvest_time"` } // PlantingStatisticsDTO 种植统计DTO type PlantingStatisticsDTO struct { PlayerID string `json:"player_id"` TotalPlanted int64 `json:"total_planted"` TotalHarvested int64 `json:"total_harvested"` TotalExperience int64 `json:"total_experience"` CropTypeStats map[string]*CropTypeStats `json:"crop_type_stats"` AverageQuality float64 `json:"average_quality"` FavoriteCrop string `json:"favorite_crop"` LastPlantTime time.Time `json:"last_plant_time"` LastHarvestTime time.Time `json:"last_harvest_time"` } // CropTypeStats 作物类型统计 type CropTypeStats struct { CropType string `json:"crop_type"` TotalPlanted int64 `json:"total_planted"` TotalHarvested int64 `json:"total_harvested"` AverageQuality float64 `json:"average_quality"` TotalYield int64 `json:"total_yield"` SuccessRate float64 `json:"success_rate"` } ================================================ FILE: internal/application/services/player_service.go ================================================ // Package services 应用服务层 package services import ( "context" "fmt" "log" "greatestworks/internal/domain/player" ) // PlayerService 玩家应用服务 type PlayerService struct { playerRepo player.Repository } // NewPlayerService 创建新的玩家应用服务 func NewPlayerService(playerRepo player.Repository) *PlayerService { return &PlayerService{ playerRepo: playerRepo, } } // CreatePlayerCommand 创建玩家命令 type CreatePlayerCommand struct { Name string `json:"name" validate:"required,min=2,max=20"` } // CreatePlayerResult 创建玩家结果 type CreatePlayerResult struct { PlayerID string `json:"player_id"` Name string `json:"name"` Level int `json:"level"` } // CreatePlayer 创建玩家 func (s *PlayerService) CreatePlayer(ctx context.Context, cmd *CreatePlayerCommand) (*CreatePlayerResult, error) { // 验证玩家名称是否已存在 exists := s.playerRepo.ExistsByName(ctx, cmd.Name) if exists { return nil, player.ErrPlayerAlreadyExists } // 创建新玩家 newPlayer := player.NewPlayer(cmd.Name) // 保存玩家 if err := s.playerRepo.Save(ctx, newPlayer); err != nil { return nil, fmt.Errorf("保存玩家失败: %w", err) } log.Printf("创建玩家成功: %s (ID: %s)", newPlayer.Name(), newPlayer.ID().String()) return &CreatePlayerResult{ PlayerID: newPlayer.ID().String(), Name: newPlayer.Name(), Level: newPlayer.Level(), }, nil } // MovePlayer 移动玩家 func (s *PlayerService) MovePlayer(ctx context.Context, playerID string, position player.Position) error { // 获取玩家 playerIDObj := player.PlayerIDFromString(playerID) p, err := s.playerRepo.FindByID(ctx, playerIDObj) if err != nil { return fmt.Errorf("获取玩家失败: %w", err) } if p == nil { return player.ErrPlayerNotFound } // 更新玩家位置 if err := p.MoveTo(position); err != nil { return fmt.Errorf("移动玩家失败: %w", err) } // 保存玩家 if err := s.playerRepo.Save(ctx, p); err != nil { return fmt.Errorf("保存玩家失败: %w", err) } return nil } // LoginPlayerCommand 玩家登录命令 type LoginPlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` } // LoginPlayerResult 玩家登录结果 type LoginPlayerResult struct { PlayerID string `json:"player_id"` Name string `json:"name"` Level int `json:"level"` Status player.PlayerStatus `json:"status"` Position player.Position `json:"position"` Stats player.PlayerStats `json:"stats"` } // LoginPlayer 玩家登录 func (s *PlayerService) LoginPlayer(ctx context.Context, cmd *LoginPlayerCommand) (*LoginPlayerResult, error) { // 解析玩家ID playerID := player.PlayerID{} // 注意:这里需要实现PlayerID的解析逻辑 // 查找玩家 p, err := s.playerRepo.FindByID(ctx, playerID) if err != nil { return nil, fmt.Errorf("查找玩家失败: %w", err) } // 设置玩家上线 p.SetOnline() // 更新玩家状态 if err := s.playerRepo.Update(ctx, p); err != nil { return nil, fmt.Errorf("更新玩家状态失败: %w", err) } log.Printf("玩家登录成功: %s (ID: %s)", p.Name(), p.ID().String()) return &LoginPlayerResult{ PlayerID: p.ID().String(), Name: p.Name(), Level: p.Level(), Status: p.Status(), Position: p.GetPosition(), Stats: p.Stats(), }, nil } // LogoutPlayerCommand 玩家登出命令 type LogoutPlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` } // LogoutPlayer 玩家登出 func (s *PlayerService) LogoutPlayer(ctx context.Context, cmd *LogoutPlayerCommand) error { // 解析玩家ID playerID := player.PlayerID{} // 注意:这里需要实现PlayerID的解析逻辑 // 查找玩家 p, err := s.playerRepo.FindByID(ctx, playerID) if err != nil { return fmt.Errorf("查找玩家失败: %w", err) } // 设置玩家下线 p.SetOffline() // 更新玩家状态 if err := s.playerRepo.Update(ctx, p); err != nil { return fmt.Errorf("更新玩家状态失败: %w", err) } log.Printf("玩家登出成功: %s (ID: %s)", p.Name(), p.ID().String()) return nil } // MovePlayerCommand 玩家移动命令 type MovePlayerCommand struct { PlayerID string `json:"player_id" validate:"required"` Position player.Position `json:"position" validate:"required"` } // MovePlayerWithCommand 使用命令移动玩家 func (s *PlayerService) MovePlayerWithCommand(ctx context.Context, cmd *MovePlayerCommand) error { // 解析玩家ID playerID := player.PlayerID{} // 注意:这里需要实现PlayerID的解析逻辑 // 查找玩家 p, err := s.playerRepo.FindByID(ctx, playerID) if err != nil { return fmt.Errorf("查找玩家失败: %w", err) } // 移动玩家 if err := p.MoveTo(cmd.Position); err != nil { return fmt.Errorf("移动玩家失败: %w", err) } // 更新玩家位置 if err := s.playerRepo.Update(ctx, p); err != nil { return fmt.Errorf("更新玩家位置失败: %w", err) } return nil } // GetPlayerQuery 获取玩家查询 type GetPlayerQuery struct { PlayerID string `json:"player_id" validate:"required"` } // DeletePlayer 删除玩家 func (s *PlayerService) DeletePlayer(ctx context.Context, playerID string) error { // 解析玩家ID pid := player.PlayerIDFromString(playerID) // 查找玩家 p, err := s.playerRepo.FindByID(ctx, pid) if err != nil { return fmt.Errorf("查找玩家失败: %w", err) } if p == nil { return player.ErrPlayerNotFound } // 删除玩家 if err := s.playerRepo.Delete(ctx, pid); err != nil { return fmt.Errorf("删除玩家失败: %w", err) } log.Printf("删除玩家成功: %s (ID: %s)", p.Name(), p.ID().String()) return nil } // GetPlayerResult 获取玩家结果 type GetPlayerResult struct { PlayerID string `json:"player_id"` Name string `json:"name"` Level int `json:"level"` Exp int64 `json:"exp"` Status player.PlayerStatus `json:"status"` Position player.Position `json:"position"` Stats player.PlayerStats `json:"stats"` } // GetPlayer 获取玩家信息 func (s *PlayerService) GetPlayer(ctx context.Context, query *GetPlayerQuery) (*GetPlayerResult, error) { // 解析玩家ID playerID := player.PlayerID{} // 注意:这里需要实现PlayerID的解析逻辑 // 查找玩家 p, err := s.playerRepo.FindByID(ctx, playerID) if err != nil { return nil, fmt.Errorf("查找玩家失败: %w", err) } return &GetPlayerResult{ PlayerID: p.ID().String(), Name: p.Name(), Level: p.Level(), Status: p.Status(), Position: p.GetPosition(), Stats: p.Stats(), }, nil } // GetOnlinePlayersQuery 获取在线玩家查询 type GetOnlinePlayersQuery struct { Limit int `json:"limit" validate:"min=1,max=100"` } // GetOnlinePlayersResult 获取在线玩家结果 type GetOnlinePlayersResult struct { Players []*GetPlayerResult `json:"players"` Total int `json:"total"` } // GetOnlinePlayers 获取在线玩家列表 func (s *PlayerService) GetOnlinePlayers(ctx context.Context, query *GetOnlinePlayersQuery) (*GetOnlinePlayersResult, error) { if query.Limit <= 0 { query.Limit = 10 } // 查找在线玩家 players, err := s.playerRepo.FindOnlinePlayers(ctx, query.Limit) if err != nil { return nil, fmt.Errorf("查找在线玩家失败: %w", err) } // 转换结果 results := make([]*GetPlayerResult, 0, len(players)) for _, p := range players { results = append(results, &GetPlayerResult{ PlayerID: p.ID().String(), Name: p.Name(), Level: p.Level(), Status: p.Status(), Position: p.GetPosition(), Stats: p.Stats(), }) } return &GetOnlinePlayersResult{ Players: results, Total: len(results), }, nil } // Login 玩家登录 func (s *PlayerService) Login(ctx context.Context, playerID string) (*LoginPlayerResult, error) { // 解析玩家ID pid := player.PlayerID{} // 查找玩家 p, err := s.playerRepo.FindByID(ctx, pid) if err != nil { return nil, fmt.Errorf("获取玩家失败: %w", err) } if p == nil { return nil, player.ErrPlayerNotFound } // 更新玩家状态为在线 p.SetOnline() // 保存玩家 if err := s.playerRepo.Save(ctx, p); err != nil { return nil, fmt.Errorf("保存玩家失败: %w", err) } return &LoginPlayerResult{ PlayerID: p.ID().String(), Name: p.Name(), Level: p.Level(), Status: p.Status(), Position: p.GetPosition(), Stats: p.Stats(), }, nil } // Logout 玩家登出 func (s *PlayerService) Logout(ctx context.Context, playerID string) error { // 解析玩家ID pid := player.PlayerID{} // 查找玩家 p, err := s.playerRepo.FindByID(ctx, pid) if err != nil { return fmt.Errorf("获取玩家失败: %w", err) } if p == nil { return player.ErrPlayerNotFound } // 更新玩家状态为离线 p.SetOffline() // 保存玩家 if err := s.playerRepo.Save(ctx, p); err != nil { return fmt.Errorf("保存玩家失败: %w", err) } return nil } // GetPlayerInfo 获取玩家信息 func (s *PlayerService) GetPlayerInfo(ctx context.Context, playerID string) (*LoginPlayerResult, error) { // 解析玩家ID pid := player.PlayerID{} // 查找玩家 p, err := s.playerRepo.FindByID(ctx, pid) if err != nil { return nil, fmt.Errorf("获取玩家失败: %w", err) } if p == nil { return nil, player.ErrPlayerNotFound } return &LoginPlayerResult{ PlayerID: p.ID().String(), Name: p.Name(), Level: p.Level(), Status: p.Status(), Position: p.GetPosition(), Stats: p.Stats(), }, nil } // UpdatePlayer 更新玩家信息 func (s *PlayerService) UpdatePlayer(ctx context.Context, playerID string, updates map[string]interface{}) error { // 解析玩家ID pid := player.PlayerID{} // 查找玩家 p, err := s.playerRepo.FindByID(ctx, pid) if err != nil { return fmt.Errorf("获取玩家失败: %w", err) } if p == nil { return player.ErrPlayerNotFound } // 应用更新 for key, value := range updates { switch key { case "name": // TODO: 实现名称更新逻辑 _ = value case "level": // TODO: 实现等级更新逻辑 _ = value // 可以添加更多字段的更新逻辑 } } // 保存玩家 if err := s.playerRepo.Save(ctx, p); err != nil { return fmt.Errorf("保存玩家失败: %w", err) } return nil } ================================================ FILE: internal/application/services/quest_service.go ================================================ package services import ( "context" "errors" "fmt" "greatestworks/internal/domain/quest" "greatestworks/internal/infrastructure/datamanager" "greatestworks/internal/infrastructure/persistence" ) // QuestService 任务服务 type QuestService struct { questRepo *persistence.QuestRepository } // NewQuestService 创建任务服务 func NewQuestService(questRepo *persistence.QuestRepository) *QuestService { return &QuestService{ questRepo: questRepo, } } // AcceptQuest 接受任务 func (s *QuestService) AcceptQuest(ctx context.Context, characterID int64, questID int32) error { // 获取任务配置 questDefine := datamanager.GetInstance().GetQuest(questID) if questDefine == nil { return errors.New("quest not found") } // 检查前置任务 // TODO: 实现前置任务检查 // 创建任务进度 objectives := make([]persistence.DbObjective, len(questDefine.Objectives)) for i, obj := range questDefine.Objectives { objectives[i] = persistence.DbObjective{ Type: obj.Type, TargetID: obj.TargetID, Required: obj.Required, Current: 0, } } dbQuest := &persistence.DbQuest{ CharacterID: characterID, QuestID: questID, Status: 0, // 进行中 Objectives: objectives, } if err := s.questRepo.Create(ctx, dbQuest); err != nil { return fmt.Errorf("failed to accept quest: %w", err) } return nil } // GetQuests 获取角色的任务列表 func (s *QuestService) GetQuests(ctx context.Context, characterID int64) ([]*persistence.DbQuest, error) { return s.questRepo.FindByCharacterID(ctx, characterID) } // UpdateObjective 更新任务目标 func (s *QuestService) UpdateObjective(ctx context.Context, characterID int64, questID, objType, targetID, progress int32) error { quests, err := s.questRepo.FindByCharacterID(ctx, characterID) if err != nil { return err } // 查找对应任务 var targetQuest *persistence.DbQuest for _, q := range quests { if q.QuestID == questID && q.Status == 0 { targetQuest = q break } } if targetQuest == nil { return errors.New("quest not found or already completed") } // 更新目标进度 updated := false for i := range targetQuest.Objectives { obj := &targetQuest.Objectives[i] if obj.Type == objType && obj.TargetID == targetID { obj.Current += progress if obj.Current > obj.Required { obj.Current = obj.Required } updated = true } } if !updated { return errors.New("objective not found") } // 检查是否完成 allComplete := true for _, obj := range targetQuest.Objectives { if obj.Current < obj.Required { allComplete = false break } } if allComplete { targetQuest.Status = 1 // 已完成 } return s.questRepo.Update(ctx, targetQuest) } // SubmitQuest 提交任务 func (s *QuestService) SubmitQuest(ctx context.Context, characterID int64, questID int32) error { quests, err := s.questRepo.FindByCharacterID(ctx, characterID) if err != nil { return err } // 查找对应任务 var targetQuest *persistence.DbQuest for _, q := range quests { if q.QuestID == questID && q.Status == 1 { targetQuest = q break } } if targetQuest == nil { return errors.New("quest not completed") } // 获取任务配置 questDefine := datamanager.GetInstance().GetQuest(questID) if questDefine == nil { return errors.New("quest not found") } // TODO: 发放奖励 // - 经验 // - 金币 // - 物品 // 标记为已领取 targetQuest.Status = 2 return s.questRepo.Update(ctx, targetQuest) } // AbandonQuest 放弃任务 func (s *QuestService) AbandonQuest(ctx context.Context, characterID int64, questID int32) error { quests, err := s.questRepo.FindByCharacterID(ctx, characterID) if err != nil { return err } // 查找对应任务 var targetQuest *persistence.DbQuest for _, q := range quests { if q.QuestID == questID && q.Status == 0 { targetQuest = q break } } if targetQuest == nil { return errors.New("quest not found or already completed") } // 删除任务进度(这里简化处理,实际可能需要软删除) // TODO: 实现任务删除 _ = targetQuest return nil } // OnKill 击杀事件处理 func (s *QuestService) OnKill(ctx context.Context, characterID int64, targetID int32) error { return s.UpdateObjective(ctx, characterID, 0, int32(quest.ObjectiveTypeKill), targetID, 1) } // OnCollect 收集事件处理 func (s *QuestService) OnCollect(ctx context.Context, characterID int64, itemID, count int32) error { return s.UpdateObjective(ctx, characterID, 0, int32(quest.ObjectiveTypeCollect), itemID, count) } // OnReach 到达事件处理 func (s *QuestService) OnReach(ctx context.Context, characterID int64, locationID int32) error { return s.UpdateObjective(ctx, characterID, 0, int32(quest.ObjectiveTypeReach), locationID, 1) } // OnTalk 对话事件处理 func (s *QuestService) OnTalk(ctx context.Context, characterID int64, npcID int32) error { return s.UpdateObjective(ctx, characterID, 0, int32(quest.ObjectiveTypeTalk), npcID, 1) } ================================================ FILE: internal/application/services/ranking_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/domain/ranking" ) // RankingApplicationService 排行榜应用服务 type RankingApplicationService struct { rankingRepo ranking.RankingRepository rankEntryRepo ranking.RankEntryRepository rankingService *ranking.RankingService eventBus ranking.RankingEventBus } // NewRankingApplicationService 创建排行榜应用服务 func NewRankingApplicationService( rankingRepo ranking.RankingRepository, rankEntryRepo ranking.RankEntryRepository, rankingService *ranking.RankingService, eventBus ranking.RankingEventBus, ) *RankingApplicationService { return &RankingApplicationService{ rankingRepo: rankingRepo, rankEntryRepo: rankEntryRepo, rankingService: rankingService, eventBus: eventBus, } } // CreateRankingRequest 创建排行榜请求 type CreateRankingRequest struct { Name string `json:"name"` Description string `json:"description"` RankType string `json:"rank_type"` PeriodType string `json:"period_type"` MaxEntries int32 `json:"max_entries"` IsActive bool `json:"is_active"` } // CreateRankingResponse 创建排行榜响应 type CreateRankingResponse struct { RankingID string `json:"ranking_id"` Name string `json:"name"` Description string `json:"description"` RankType string `json:"rank_type"` PeriodType string `json:"period_type"` MaxEntries int32 `json:"max_entries"` IsActive bool `json:"is_active"` CreatedAt time.Time `json:"created_at"` } // CreateRanking 创建排行榜 func (s *RankingApplicationService) CreateRanking(ctx context.Context, req *CreateRankingRequest) (*CreateRankingResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateCreateRankingRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 转换排行榜类型 // rankType, err := s.parseRankType(req.RankType) // if err != nil { // return nil, fmt.Errorf("invalid rank type: %w", err) // } // 转换周期类型 // periodType, err := s.parsePeriodType(req.PeriodType) // if err != nil { // return nil, fmt.Errorf("invalid period type: %w", err) // } // 创建排行榜聚合根 // TODO: 修复NewRankingAggregate方法调用 // rankingAggregate := ranking.NewRankingAggregate(req.Name, rankType, periodType) // rankingAggregate := &ranking.RankingAggregate{} // TODO: 修复SetDescription方法调用 // rankingAggregate.SetDescription(req.Description) // TODO: 修复SetMaxEntries方法调用 // rankingAggregate.SetMaxEntries(req.MaxEntries) // TODO: 修复Activate方法调用 // if req.IsActive { // rankingAggregate.Activate() // } // 保存排行榜 // TODO: 修复Save方法调用 // if err := s.rankingRepo.Save(ctx, rankingAggregate); err != nil { // return nil, fmt.Errorf("failed to save ranking: %w", err) // } // 发布事件 // TODO: 修复NewRankingCreatedEvent方法调用 // event := ranking.NewRankingCreatedEvent(rankingAggregate.GetID(), req.Name, rankType, periodType) // event := &ranking.RankingCreatedEvent{} // TODO: 修复Publish方法调用 // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish ranking created event: %v\n", err) // } return &CreateRankingResponse{ RankingID: "", // TODO: rankingAggregate.GetID(), Name: "", // TODO: rankingAggregate.GetName(), Description: "", // TODO: rankingAggregate.GetDescription(), RankType: "", // TODO: rankingAggregate.GetRankType().String(), PeriodType: "", // TODO: rankingAggregate.GetPeriodType().String(), MaxEntries: 0, // TODO: rankingAggregate.GetMaxEntries(), IsActive: false, // TODO: rankingAggregate.IsActive(), CreatedAt: time.Now(), // TODO: rankingAggregate.GetCreatedAt(), }, nil } // UpdatePlayerScoreRequest 更新玩家分数请求 type UpdatePlayerScoreRequest struct { RankingID string `json:"ranking_id"` PlayerID uint64 `json:"player_id"` Score int64 `json:"score"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // UpdatePlayerScoreResponse 更新玩家分数响应 type UpdatePlayerScoreResponse struct { RankingID string `json:"ranking_id"` PlayerID uint64 `json:"player_id"` OldScore int64 `json:"old_score"` NewScore int64 `json:"new_score"` OldRank int32 `json:"old_rank"` NewRank int32 `json:"new_rank"` RankChanged bool `json:"rank_changed"` ScoreChanged bool `json:"score_changed"` } // UpdatePlayerScore 更新玩家分数 func (s *RankingApplicationService) UpdatePlayerScore(ctx context.Context, req *UpdatePlayerScoreRequest) (*UpdatePlayerScoreResponse, error) { if req == nil { return nil, fmt.Errorf("request cannot be nil") } if err := s.validateUpdatePlayerScoreRequest(req); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } // 获取排行榜 // rankingAggregate, err := s.rankingRepo.FindByID(ctx, req.RankingID) // if err != nil { // return nil, fmt.Errorf("failed to find ranking: %w", err) // } // if rankingAggregate == nil { // return nil, fmt.Errorf("ranking not found") // } // rankingAggregate := &ranking.RankingAggregate{} // 检查排行榜是否激活 // TODO: 修复IsActive方法调用 // if !rankingAggregate.IsActive() { // return nil, fmt.Errorf("ranking is not active") // } // 获取当前玩家排名条目 // TODO: 修复FindByRankingAndPlayer方法调用 // currentEntry, err := s.rankEntryRepo.FindByRankingAndPlayer(ctx, req.RankingID, req.PlayerID) // TODO: 修复err变量 // if err != nil { // return nil, fmt.Errorf("failed to find current entry: %w", err) // } oldScore := int64(0) oldRank := int64(0) // 更新分数 // entry, err := s.rankingService.UpdatePlayerScore(ctx, req.RankingID, req.PlayerID, req.Score) // if err != nil { // return nil, fmt.Errorf("failed to update player score: %w", err) // } entry := &ranking.RankEntry{} // 设置元数据 // if req.Metadata != nil { // for key, value := range req.Metadata { // entry.SetMetadata(key, value) // } // if err := s.rankEntryRepo.Save(ctx, entry); err != nil { // return nil, fmt.Errorf("failed to save entry metadata: %w", err) // } // } newScore := entry.GetScore() newRank := entry.GetRank() rankChanged := oldRank != newRank scoreChanged := oldScore != newScore // 发布事件 // if scoreChanged { // event := ranking.NewPlayerScoreUpdatedEvent(req.RankingID, req.PlayerID, oldScore, newScore) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish score updated event: %v\n", err) // } // } // if rankChanged { // event := ranking.NewPlayerRankChangedEvent(req.RankingID, req.PlayerID, oldRank, newRank) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish rank changed event: %v\n", err) // } // } return &UpdatePlayerScoreResponse{ RankingID: req.RankingID, PlayerID: req.PlayerID, OldScore: oldScore, NewScore: newScore, OldRank: int32(oldRank), NewRank: int32(newRank), RankChanged: rankChanged, ScoreChanged: scoreChanged, }, nil } // GetRankingRequest 获取排行榜请求 type GetRankingRequest struct { RankingID string `json:"ranking_id"` Page int `json:"page"` PageSize int `json:"page_size"` } // RankEntryResponse 排名条目响应 type RankEntryResponse struct { PlayerID uint64 `json:"player_id"` Rank int32 `json:"rank"` Score int64 `json:"score"` Metadata map[string]interface{} `json:"metadata,omitempty"` UpdatedAt time.Time `json:"updated_at"` } // GetRankingResponse 获取排行榜响应 type GetRankingResponse struct { RankingID string `json:"ranking_id"` Name string `json:"name"` Description string `json:"description"` RankType string `json:"rank_type"` PeriodType string `json:"period_type"` Entries []*RankEntryResponse `json:"entries"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int64 `json:"total_pages"` UpdatedAt time.Time `json:"updated_at"` } // GetRanking 获取排行榜 func (s *RankingApplicationService) GetRanking(ctx context.Context, req *GetRankingRequest) (*GetRankingResponse, error) { if req == nil || req.RankingID == "" { return nil, fmt.Errorf("ranking ID is required") } // 设置默认值 if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 50 } // 获取排行榜 // rankingAggregate, err := s.rankingRepo.FindByID(ctx, req.RankingID) // if err != nil { // return nil, fmt.Errorf("failed to find ranking: %w", err) // } // if rankingAggregate == nil { // return nil, fmt.Errorf("ranking not found") // } // 构建查询 // query := ranking.NewRankEntryQuery(). // WithRanking(req.RankingID). // WithSort("rank", "asc"). // WithPagination(req.Page, req.PageSize) // 查询排名条目 // entries, total, err := s.rankEntryRepo.FindByQuery(ctx, query) // if err != nil { // return nil, fmt.Errorf("failed to find rank entries: %w", err) // } entries := []*ranking.RankEntry{} total := 0 // 转换响应 entryResponses := make([]*RankEntryResponse, len(entries)) for i, entry := range entries { entryResponses[i] = &RankEntryResponse{ PlayerID: entry.GetPlayerID(), Rank: int32(entry.GetRank()), Score: entry.GetScore(), Metadata: entry.GetMetadata(), UpdatedAt: entry.GetUpdatedAt(), } } totalPages := (int64(total) + int64(req.PageSize) - 1) / int64(req.PageSize) return &GetRankingResponse{ RankingID: "", // TODO: rankingAggregate.GetID(), Name: "", // TODO: rankingAggregate.GetName(), Description: "", // TODO: rankingAggregate.GetDescription(), RankType: "", // TODO: rankingAggregate.GetRankType().String(), PeriodType: "", // TODO: rankingAggregate.GetPeriodType().String(), Entries: entryResponses, Total: int64(total), Page: req.Page, PageSize: req.PageSize, TotalPages: totalPages, UpdatedAt: time.Now(), // TODO: rankingAggregate.GetUpdatedAt(), }, nil } // GetPlayerRankRequest 获取玩家排名请求 type GetPlayerRankRequest struct { RankingID string `json:"ranking_id"` PlayerID uint64 `json:"player_id"` } // GetPlayerRankResponse 获取玩家排名响应 type GetPlayerRankResponse struct { RankingID string `json:"ranking_id"` PlayerID uint64 `json:"player_id"` Rank int32 `json:"rank"` Score int64 `json:"score"` Metadata map[string]interface{} `json:"metadata,omitempty"` UpdatedAt time.Time `json:"updated_at"` Found bool `json:"found"` } // GetPlayerRank 获取玩家排名 func (s *RankingApplicationService) GetPlayerRank(ctx context.Context, req *GetPlayerRankRequest) (*GetPlayerRankResponse, error) { if req == nil || req.RankingID == "" || req.PlayerID == 0 { return nil, fmt.Errorf("ranking ID and player ID are required") } // 获取玩家排名条目 // entry, err := s.rankEntryRepo.FindByRankingAndPlayer(ctx, req.RankingID, req.PlayerID) // if err != nil { // return nil, fmt.Errorf("failed to find player rank: %w", err) // } // 占位实现:未接入存储,默认未找到 return &GetPlayerRankResponse{ RankingID: req.RankingID, PlayerID: req.PlayerID, Found: false, }, nil // 以下为真实实现路径,待存储接入后启用 // return &GetPlayerRankResponse{ // RankingID: req.RankingID, // PlayerID: req.PlayerID, // Rank: int32(entry.GetRank()), // Score: entry.GetScore(), // Metadata: entry.GetMetadata(), // UpdatedAt: entry.GetUpdatedAt(), // Found: true, // }, nil } // GetTopPlayersRequest 获取排行榜前N名请求 type GetTopPlayersRequest struct { RankingID string `json:"ranking_id"` Limit int `json:"limit"` } // GetTopPlayersResponse 获取排行榜前N名响应 type GetTopPlayersResponse struct { RankingID string `json:"ranking_id"` Entries []*RankEntryResponse `json:"entries"` UpdatedAt time.Time `json:"updated_at"` } // GetTopPlayers 获取排行榜前N名 func (s *RankingApplicationService) GetTopPlayers(ctx context.Context, req *GetTopPlayersRequest) (*GetTopPlayersResponse, error) { if req == nil || req.RankingID == "" { return nil, fmt.Errorf("ranking ID is required") } // 设置默认值 if req.Limit <= 0 { req.Limit = 10 } if req.Limit > 100 { req.Limit = 100 // 限制最大数量 } // 获取排行榜 // rankingAggregate, err := s.rankingRepo.FindByID(ctx, req.RankingID) // if err != nil { // return nil, fmt.Errorf("failed to find ranking: %w", err) // } // if rankingAggregate == nil { // return nil, fmt.Errorf("ranking not found") // } // 获取前N名 // entries, err := s.rankingService.GetTopPlayers(ctx, req.RankingID, req.Limit) // if err != nil { // return nil, fmt.Errorf("failed to get top players: %w", err) // } entries := []*ranking.RankEntry{} // 转换响应 entryResponses := make([]*RankEntryResponse, len(entries)) for i, entry := range entries { entryResponses[i] = &RankEntryResponse{ PlayerID: entry.GetPlayerID(), Rank: int32(entry.GetRank()), Score: entry.GetScore(), Metadata: entry.GetMetadata(), UpdatedAt: entry.GetUpdatedAt(), } } return &GetTopPlayersResponse{ RankingID: req.RankingID, Entries: entryResponses, UpdatedAt: time.Now(), // TODO: rankingAggregate.GetUpdatedAt(), }, nil } // ResetRankingRequest 重置排行榜请求 type ResetRankingRequest struct { RankingID string `json:"ranking_id"` Reason string `json:"reason"` } // ResetRankingResponse 重置排行榜响应 type ResetRankingResponse struct { RankingID string `json:"ranking_id"` EntriesCleared int64 `json:"entries_cleared"` ResetAt time.Time `json:"reset_at"` } // ResetRanking 重置排行榜 func (s *RankingApplicationService) ResetRanking(ctx context.Context, req *ResetRankingRequest) (*ResetRankingResponse, error) { if req == nil || req.RankingID == "" { return nil, fmt.Errorf("ranking ID is required") } // 获取排行榜 // rankingAggregate, err := s.rankingRepo.FindByID(ctx, req.RankingID) // if err != nil { // return nil, fmt.Errorf("failed to find ranking: %w", err) // } // if rankingAggregate == nil { // return nil, fmt.Errorf("ranking not found") // } // 重置排行榜 // entriesCleared, err := s.rankingService.ResetRanking(ctx, req.RankingID) // if err != nil { // return nil, fmt.Errorf("failed to reset ranking: %w", err) // } // entriesCleared := &ranking.RankingOperationResult{} // 更新排行榜状态 // rankingAggregate.Reset() // if err := s.rankingRepo.Save(ctx, rankingAggregate); err != nil { // return nil, fmt.Errorf("failed to save ranking: %w", err) // } // 发布事件 // event := ranking.NewRankingResetEvent(req.RankingID, req.Reason, entriesCleared) // if err := s.eventBus.Publish(ctx, event); err != nil { // fmt.Printf("failed to publish ranking reset event: %v\n", err) // } return &ResetRankingResponse{ RankingID: req.RankingID, EntriesCleared: 0, // TODO: entriesCleared, ResetAt: time.Now(), }, nil } // 私有方法 // validateCreateRankingRequest 验证创建排行榜请求 func (s *RankingApplicationService) validateCreateRankingRequest(req *CreateRankingRequest) error { if req.Name == "" { return fmt.Errorf("name is required") } if len(req.Name) > 100 { return fmt.Errorf("name too long (max 100 characters)") } if req.RankType == "" { return fmt.Errorf("rank type is required") } if req.PeriodType == "" { return fmt.Errorf("period type is required") } if req.MaxEntries <= 0 { return fmt.Errorf("max entries must be positive") } if req.MaxEntries > 10000 { return fmt.Errorf("max entries too large (max 10000)") } return nil } // validateUpdatePlayerScoreRequest 验证更新玩家分数请求 func (s *RankingApplicationService) validateUpdatePlayerScoreRequest(req *UpdatePlayerScoreRequest) error { if req.RankingID == "" { return fmt.Errorf("ranking ID is required") } if req.PlayerID == 0 { return fmt.Errorf("player ID is required") } return nil } // parseRankType 解析排行榜类型 func (s *RankingApplicationService) parseRankType(rankTypeStr string) (ranking.RankType, error) { switch rankTypeStr { case "level": return ranking.RankTypeLevel, nil case "exp": return ranking.RankTypeLevel, nil // TODO: 修复RankTypeExp case "power": return ranking.RankTypePower, nil case "wealth": return ranking.RankTypeWealth, nil case "achievement": return ranking.RankTypeAchievement, nil case "pvp": return ranking.RankTypeLevel, nil // TODO: 修复RankTypePvP case "guild": return ranking.RankTypeGuild, nil default: return ranking.RankTypeLevel, fmt.Errorf("unknown rank type: %s", rankTypeStr) } } // parsePeriodType 解析周期类型 func (s *RankingApplicationService) parsePeriodType(periodTypeStr string) (ranking.RankPeriod, error) { switch periodTypeStr { case "permanent": return ranking.RankPeriodPermanent, nil case "daily": return ranking.RankPeriodDaily, nil case "weekly": return ranking.RankPeriodWeekly, nil case "monthly": return ranking.RankPeriodMonthly, nil case "seasonal": return ranking.RankPeriodSeasonal, nil default: return ranking.RankPeriodPermanent, fmt.Errorf("unknown period type: %s", periodTypeStr) } } ================================================ FILE: internal/application/services/replication_service.go ================================================ // Package services 应用服务层 // ReplicationService 编排副本实例的创建、管理和销毁 package services import ( "context" "fmt" "time" "greatestworks/internal/domain/replication" "greatestworks/internal/infrastructure/logging" "github.com/google/uuid" ) // ReplicationService 副本应用服务 type ReplicationService struct { replicationRepo replication.Repository eventBus EventPublisher logger logging.Logger } // NewReplicationService 创建副本应用服务 func NewReplicationService( replicationRepo replication.Repository, eventBus EventPublisher, logger logging.Logger, ) *ReplicationService { return &ReplicationService{ replicationRepo: replicationRepo, eventBus: eventBus, logger: logger, } } // CreateInstanceCommand 创建实例命令 type CreateInstanceCommand struct { TemplateID string InstanceType int OwnerPlayerID string OwnerName string OwnerLevel int MaxPlayers int Difficulty int Lifetime time.Duration } // JoinInstanceCommand 加入实例命令 type JoinInstanceCommand struct { InstanceID string PlayerID string PlayerName string Level int Role string } // LeaveInstanceCommand 离开实例命令 type LeaveInstanceCommand struct { InstanceID string PlayerID string } // UpdateInstanceProgressCommand 更新实例进度命令 type UpdateInstanceProgressCommand struct { InstanceID string Progress int CompletedTask string } // InstanceInfoDTO 实例信息DTO type InstanceInfoDTO struct { InstanceID string TemplateID string InstanceType int Status int PlayerCount int MaxPlayers int Progress int SceneID string CreatedAt time.Time OwnerID string Difficulty int } // CreateInstance 创建副本实例 func (s *ReplicationService) CreateInstance(ctx context.Context, cmd *CreateInstanceCommand) (*InstanceInfoDTO, error) { // 基础校验 if cmd == nil { return nil, fmt.Errorf("invalid command") } if cmd.TemplateID == "" || cmd.OwnerPlayerID == "" { return nil, fmt.Errorf("template_id and owner_player_id are required") } if cmd.MaxPlayers <= 0 { return nil, fmt.Errorf("max_players must be > 0") } if cmd.Lifetime <= 0 { return nil, fmt.Errorf("lifetime must be > 0") } s.logger.Info("创建副本实例", logging.Fields{ "template_id": cmd.TemplateID, "owner_id": cmd.OwnerPlayerID, }) // 生成实例ID instanceID := uuid.New().String() // 创建领域对象 instance := replication.NewInstance( instanceID, cmd.TemplateID, replication.InstanceType(cmd.InstanceType), cmd.OwnerPlayerID, cmd.MaxPlayers, cmd.Lifetime, ) // 添加创建者作为第一个玩家 if err := instance.AddPlayer(cmd.OwnerPlayerID, cmd.OwnerName, cmd.OwnerLevel, ""); err != nil { return nil, fmt.Errorf("添加创建者失败: %w", err) } // 保存到仓储 if err := s.replicationRepo.Save(ctx, instance); err != nil { return nil, fmt.Errorf("保存实例失败: %w", err) } // 发布领域事件 s.publishEvents(ctx, instance) s.logger.Info("副本实例创建成功", logging.Fields{ "instance_id": instanceID, "template_id": cmd.TemplateID, }) return &InstanceInfoDTO{ InstanceID: instance.ID(), TemplateID: instance.TemplateID(), InstanceType: int(instance.Type()), Status: int(instance.Status()), PlayerCount: instance.PlayerCount(), MaxPlayers: instance.MaxPlayers(), Progress: instance.Progress(), SceneID: instance.SceneID(), CreatedAt: instance.CreatedAt(), Difficulty: instance.Difficulty(), }, nil } // JoinInstance 加入副本实例 func (s *ReplicationService) JoinInstance(ctx context.Context, cmd *JoinInstanceCommand) error { if cmd == nil || cmd.InstanceID == "" || cmd.PlayerID == "" { return fmt.Errorf("instance_id and player_id are required") } s.logger.Info("玩家加入实例", logging.Fields{ "instance_id": cmd.InstanceID, "player_id": cmd.PlayerID, }) // 查找实例 instance, err := s.replicationRepo.FindByID(ctx, cmd.InstanceID) if err != nil { return fmt.Errorf("查找实例失败: %w", err) } if instance == nil { return fmt.Errorf("实例不存在: %s", cmd.InstanceID) } // 添加玩家 if err := instance.AddPlayer(cmd.PlayerID, cmd.PlayerName, cmd.Level, cmd.Role); err != nil { return fmt.Errorf("添加玩家失败: %w", err) } // 保存 if err := s.replicationRepo.Save(ctx, instance); err != nil { return fmt.Errorf("保存实例失败: %w", err) } // 发布事件 s.publishEvents(ctx, instance) s.logger.Info("玩家加入实例成功", logging.Fields{ "instance_id": cmd.InstanceID, "player_id": cmd.PlayerID, }) return nil } // LeaveInstance 离开副本实例 func (s *ReplicationService) LeaveInstance(ctx context.Context, cmd *LeaveInstanceCommand) error { if cmd == nil || cmd.InstanceID == "" || cmd.PlayerID == "" { return fmt.Errorf("instance_id and player_id are required") } s.logger.Info("玩家离开实例", logging.Fields{ "instance_id": cmd.InstanceID, "player_id": cmd.PlayerID, }) // 查找实例 instance, err := s.replicationRepo.FindByID(ctx, cmd.InstanceID) if err != nil { return fmt.Errorf("查找实例失败: %w", err) } if instance == nil { return fmt.Errorf("实例不存在: %s", cmd.InstanceID) } // 移除玩家 if err := instance.RemovePlayer(cmd.PlayerID); err != nil { return fmt.Errorf("移除玩家失败: %w", err) } // 保存 if err := s.replicationRepo.Save(ctx, instance); err != nil { return fmt.Errorf("保存实例失败: %w", err) } // 发布事件 s.publishEvents(ctx, instance) s.logger.Info("玩家离开实例成功", logging.Fields{ "instance_id": cmd.InstanceID, "player_id": cmd.PlayerID, }) return nil } // UpdateInstanceProgress 更新实例进度 func (s *ReplicationService) UpdateInstanceProgress(ctx context.Context, cmd *UpdateInstanceProgressCommand) error { if cmd == nil || cmd.InstanceID == "" { return fmt.Errorf("instance_id is required") } if cmd.Progress < 0 || cmd.Progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } s.logger.Info("更新实例进度", logging.Fields{ "instance_id": cmd.InstanceID, "progress": cmd.Progress, }) // 查找实例 instance, err := s.replicationRepo.FindByID(ctx, cmd.InstanceID) if err != nil { return fmt.Errorf("查找实例失败: %w", err) } if instance == nil { return fmt.Errorf("实例不存在: %s", cmd.InstanceID) } // 更新进度 instance.UpdateProgress(cmd.Progress, cmd.CompletedTask) // 保存 if err := s.replicationRepo.Save(ctx, instance); err != nil { return fmt.Errorf("保存实例失败: %w", err) } // 发布事件 s.publishEvents(ctx, instance) return nil } // GetInstanceInfo 获取实例信息 func (s *ReplicationService) GetInstanceInfo(ctx context.Context, instanceID string) (*InstanceInfoDTO, error) { instance, err := s.replicationRepo.FindByID(ctx, instanceID) if err != nil { return nil, fmt.Errorf("查找实例失败: %w", err) } if instance == nil { return nil, fmt.Errorf("实例不存在: %s", instanceID) } return &InstanceInfoDTO{ InstanceID: instance.ID(), TemplateID: instance.TemplateID(), InstanceType: int(instance.Type()), Status: int(instance.Status()), PlayerCount: instance.PlayerCount(), MaxPlayers: instance.MaxPlayers(), Progress: instance.Progress(), SceneID: instance.SceneID(), CreatedAt: instance.CreatedAt(), Difficulty: instance.Difficulty(), }, nil } // ListActiveInstances 列出所有活跃实例 func (s *ReplicationService) ListActiveInstances(ctx context.Context) ([]*InstanceInfoDTO, error) { instances, err := s.replicationRepo.FindActiveInstances(ctx) if err != nil { return nil, fmt.Errorf("查找活跃实例失败: %w", err) } result := make([]*InstanceInfoDTO, 0, len(instances)) for _, instance := range instances { result = append(result, &InstanceInfoDTO{ InstanceID: instance.ID(), TemplateID: instance.TemplateID(), InstanceType: int(instance.Type()), Status: int(instance.Status()), PlayerCount: instance.PlayerCount(), MaxPlayers: instance.MaxPlayers(), Progress: instance.Progress(), SceneID: instance.SceneID(), CreatedAt: instance.CreatedAt(), Difficulty: instance.Difficulty(), }) } return result, nil } // CleanupExpiredInstances 清理过期实例 func (s *ReplicationService) CleanupExpiredInstances(ctx context.Context) (int, error) { s.logger.Info("清理过期实例") instances, err := s.replicationRepo.FindExpiredInstances(ctx) if err != nil { return 0, fmt.Errorf("查找过期实例失败: %w", err) } count := 0 for _, instance := range instances { instance.Close() if err := s.replicationRepo.Save(ctx, instance); err != nil { s.logger.Error("保存关闭实例失败", err, logging.Fields{ "instance_id": instance.ID(), }) continue } // 发布事件 s.publishEvents(ctx, instance) count++ } s.logger.Info("过期实例清理完成", logging.Fields{ "count": count, }) return count, nil } // publishEvents 发布领域事件 func (s *ReplicationService) publishEvents(ctx context.Context, instance *replication.Instance) { events := instance.GetEvents() for _, event := range events { if err := s.eventBus.Publish(ctx, event); err != nil { s.logger.Error("发布事件失败", err, logging.Fields{ "event_type": fmt.Sprintf("%T", event), }) } } } ================================================ FILE: internal/application/services/sacred_service.go ================================================ package services import ( "context" "fmt" "time" "greatestworks/internal/domain/scene/sacred" ) // SacredService 圣地应用服务 type SacredService struct { sacredRepo sacred.SacredPlaceRepository challengeRepo sacred.ChallengeRepository blessingRepo sacred.BlessingRepository // TODO: 实现这些仓储接口 // artifactRepo sacred.ArtifactRepository // statisticsRepo sacred.StatisticsRepository // cacheRepo sacred.CacheRepository sacredService *sacred.SacredService } // NewSacredService 创建圣地应用服务 func NewSacredService( sacredRepo sacred.SacredPlaceRepository, challengeRepo sacred.ChallengeRepository, blessingRepo sacred.BlessingRepository, // TODO: 实现这些仓储接口 // artifactRepo sacred.ArtifactRepository, // statisticsRepo sacred.StatisticsRepository, // cacheRepo sacred.CacheRepository, sacredService *sacred.SacredService, ) *SacredService { return &SacredService{ sacredRepo: sacredRepo, challengeRepo: challengeRepo, blessingRepo: blessingRepo, // artifactRepo: artifactRepo, // statisticsRepo: statisticsRepo, // cacheRepo: cacheRepo, sacredService: sacredService, } } // GetSacredPlace 获取圣地信息 func (s *SacredService) GetSacredPlace(ctx context.Context, sacredID string) (*SacredPlaceDTO, error) { // 先从缓存获取 // cachedSacred, err := s.cacheRepo.GetSacredPlace(sacredID) // if err == nil && cachedSacred != nil { // return s.buildSacredPlaceDTO(cachedSacred), nil // } // 从数据库获取 sacredPlace, err := s.sacredRepo.FindByID(sacredID) if err != nil { return nil, fmt.Errorf("failed to get sacred place: %w", err) } // 更新缓存 // if err := s.cacheRepo.SetSacredPlace(sacredID, sacredPlace, time.Hour); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildSacredPlaceDTO(sacredPlace), nil } // GetAvailableSacredPlaces 获取可用圣地列表 func (s *SacredService) GetAvailableSacredPlaces(ctx context.Context, playerID string) ([]*SacredPlaceDTO, error) { // 先从缓存获取 // cachedPlaces, err := s.cacheRepo.GetAvailableSacredPlaces(playerID) // if err == nil && len(cachedPlaces) > 0 { // return s.buildSacredPlaceDTOs(cachedPlaces), nil // } // 从数据库获取 // sacredPlaces, err := s.sacredRepo.FindAvailableForPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get available sacred places: %w", err) // } // sacredPlaces := []interface{}{} // TODO: 修复sacred.SacredPlace类型 // 更新缓存 // if err := s.cacheRepo.SetAvailableSacredPlaces(playerID, sacredPlaces, time.Hour*2); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildSacredPlaceDTOs([]*sacred.SacredPlaceAggregate{}), nil // TODO: 修复sacredPlaces类型 } // EnterSacredPlace 进入圣地 func (s *SacredService) EnterSacredPlace(ctx context.Context, playerID string, sacredID string) error { // 获取圣地信息 // sacredPlace, err := s.sacredRepo.FindByID(sacredID) // if err != nil { // return fmt.Errorf("failed to get sacred place: %w", err) // } // 检查进入条件 // if err := s.sacredService.CheckEnterConditions(playerID, sacredPlace); err != nil { // return fmt.Errorf("failed to check enter conditions: %w", err) // } // 记录进入 // if err := sacredPlace.AddVisitor(playerID); err != nil { // return fmt.Errorf("failed to add visitor: %w", err) // } // 更新圣地 // if err := s.sacredRepo.Update(sacredPlace); err != nil { // return fmt.Errorf("failed to update sacred place: %w", err) // } // 清除相关缓存 // if err := s.cacheRepo.DeleteSacredPlace(sacredID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // StartChallenge 开始挑战 func (s *SacredService) StartChallenge(ctx context.Context, playerID string, challengeID string) (*ChallengeInstanceDTO, error) { // 获取挑战信息 // challenge, err := s.challengeRepo.FindByID(challengeID) // if err != nil { // return nil, fmt.Errorf("failed to get challenge: %w", err) // } // 开始挑战 // challengeInstance, err := s.sacredService.StartChallenge(playerID, challenge) // if err != nil { // return nil, fmt.Errorf("failed to start challenge: %w", err) // } challengeInstance := &sacred.Challenge{} // TODO: 修复sacred.ChallengeInstance类型 // 保存挑战实例 // if err := s.challengeRepo.SaveInstance(challengeInstance); err != nil { // return nil, fmt.Errorf("failed to save challenge instance: %w", err) // } return s.buildChallengeInstanceDTO(challengeInstance), nil } // CompleteChallenge 完成挑战 func (s *SacredService) CompleteChallenge(ctx context.Context, playerID string, instanceID string, result *ChallengeResultData) (*ChallengeRewardDTO, error) { // 获取挑战实例 // instance, err := s.challengeRepo.FindInstanceByID(instanceID) // if err != nil { // return nil, fmt.Errorf("failed to get challenge instance: %w", err) // } instance := &sacred.Challenge{} // TODO: 修复sacred.ChallengeInstance类型 // if instance.GetPlayerID() != playerID { // return nil, sacred.ErrUnauthorized // } // 完成挑战 // reward, err := s.sacredService.CompleteChallenge(instance, result) // if err != nil { // return nil, fmt.Errorf("failed to complete challenge: %w", err) // } reward := &sacred.ChallengeReward{} // 更新挑战实例 // if err := s.challengeRepo.UpdateInstance(instance); err != nil { // return nil, fmt.Errorf("failed to update challenge instance: %w", err) // } // 更新统计数据 if err := s.updateChallengeStatistics(ctx, playerID, instance, reward); err != nil { // 统计更新失败不影响主流程 // TODO: 添加日志记录 } return s.buildChallengeRewardDTO(reward), nil } // ActivateBlessing 激活祝福 func (s *SacredService) ActivateBlessing(ctx context.Context, playerID string, blessingID string) error { // 获取祝福信息 // blessing, err := s.blessingRepo.FindByID(blessingID) // if err != nil { // return fmt.Errorf("failed to get blessing: %w", err) // } // 激活祝福 // if err := s.sacredService.ActivateBlessing(playerID, blessing); err != nil { // return fmt.Errorf("failed to activate blessing: %w", err) // } // 保存祝福状态 // if err := s.blessingRepo.SavePlayerBlessing(playerID, blessing); err != nil { // return fmt.Errorf("failed to save player blessing: %w", err) // } return nil } // GetPlayerBlessings 获取玩家祝福 func (s *SacredService) GetPlayerBlessings(ctx context.Context, playerID string) ([]*PlayerBlessingDTO, error) { // blessings, err := s.blessingRepo.FindByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get player blessings: %w", err) // } blessings := []interface{}{} // TODO: 修复sacred.Blessing类型 return s.buildPlayerBlessingDTOs(blessings), nil } // GetAvailableArtifacts 获取可用圣物 func (s *SacredService) GetAvailableArtifacts(ctx context.Context, sacredID string) ([]*ArtifactDTO, error) { // artifacts, err := s.artifactRepo.FindBySacredPlace(sacredID) // if err != nil { // return nil, fmt.Errorf("failed to get artifacts: %w", err) // } artifacts := []interface{}{} // TODO: 修复sacred.Artifact类型 return s.buildArtifactDTOs(artifacts), nil } // UseArtifact 使用圣物 func (s *SacredService) UseArtifact(ctx context.Context, playerID string, artifactID string) (*ArtifactEffectDTO, error) { // 获取圣物信息 // artifact, err := s.artifactRepo.FindByID(artifactID) // if err != nil { // return nil, fmt.Errorf("failed to get artifact: %w", err) // } // artifact := &sacred.Artifact{} // 使用圣物 // effect, err := s.sacredService.UseArtifact(playerID, artifact) // if err != nil { // return nil, fmt.Errorf("failed to use artifact: %w", err) // } effect := &struct{}{} // TODO: 修复sacred.ArtifactEffect类型 // 更新圣物状态 // if err := s.artifactRepo.Update(artifact); err != nil { // return nil, fmt.Errorf("failed to update artifact: %w", err) // } return s.buildArtifactEffectDTO(effect), nil } // GetSacredStatistics 获取圣地统计 func (s *SacredService) GetSacredStatistics(ctx context.Context, playerID string) (*SacredStatisticsDTO, error) { // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil { // return nil, fmt.Errorf("failed to get sacred statistics: %w", err) // } // stats := &struct{}{} // TODO: 修复statisticsRepo字段 return s.buildStatisticsDTO(&sacred.SacredStatistics{}), nil // TODO: 修复stats类型 } // GetChallengeHistory 获取挑战历史 func (s *SacredService) GetChallengeHistory(ctx context.Context, playerID string, limit int) ([]*ChallengeHistoryDTO, error) { // history, err := s.challengeRepo.FindHistoryByPlayer(playerID, limit) // if err != nil { // return nil, fmt.Errorf("failed to get challenge history: %w", err) // } // history := []interface{}{} // TODO: 修复sacred.ChallengeInstance类型 return s.buildChallengeHistoryDTOs([]*sacred.Challenge{}), nil // TODO: 修复history类型 } // 私有方法 // updateChallengeStatistics 更新挑战统计 func (s *SacredService) updateChallengeStatistics(ctx context.Context, playerID string, instance *sacred.Challenge, reward *sacred.ChallengeReward) error { // stats, err := s.statisticsRepo.FindByPlayer(playerID) // if err != nil && !sacred.IsNotFoundError(err) { // return err // } // if stats == nil { // stats = sacred.NewSacredStatistics(playerID) // } // 更新统计数据 // stats.AddChallengeResult(instance.GetChallengeID(), instance.IsCompleted(), reward.GetTotalValue()) // stats.UpdateLastChallengeTime(instance.GetCompletedAt()) // 保存统计数据 // return s.statisticsRepo.Save(stats) return nil // TODO: 修复statisticsRepo字段 } // buildSacredPlaceDTO 构建圣地DTO func (s *SacredService) buildSacredPlaceDTO(sacredPlace *sacred.SacredPlaceAggregate) *SacredPlaceDTO { return &SacredPlaceDTO{ ID: "", // TODO: sacredPlace.GetID(), Name: "", // TODO: sacredPlace.GetName(), Description: "", // TODO: sacredPlace.GetDescription(), Level: 0, // TODO: sacredPlace.GetLevel(), Status: "", // TODO: string(sacredPlace.GetStatus()), RequiredLevel: 0, // TODO: sacredPlace.GetRequiredLevel(), VisitorCount: 0, // TODO: sacredPlace.GetVisitorCount(), MaxVisitors: 0, // TODO: sacredPlace.GetMaxVisitors(), Challenges: []string{}, // TODO: sacredPlace.GetChallengeIDs(), Blessings: []string{}, // TODO: sacredPlace.GetBlessingIDs(), Artifacts: []string{}, // TODO: sacredPlace.GetArtifactIDs(), CreatedAt: time.Now(), // TODO: sacredPlace.GetCreatedAt(), UpdatedAt: time.Now(), // TODO: sacredPlace.GetUpdatedAt(), } } // buildSacredPlaceDTOs 构建圣地DTO列表 func (s *SacredService) buildSacredPlaceDTOs(sacredPlaces []*sacred.SacredPlaceAggregate) []*SacredPlaceDTO { dtos := make([]*SacredPlaceDTO, len(sacredPlaces)) for i, place := range sacredPlaces { dtos[i] = s.buildSacredPlaceDTO(place) } return dtos } // buildChallengeInstanceDTO 构建挑战实例DTO func (s *SacredService) buildChallengeInstanceDTO(instance *sacred.Challenge) *ChallengeInstanceDTO { return &ChallengeInstanceDTO{ ID: "", // TODO: instance.GetID(), PlayerID: "", // TODO: instance.GetPlayerID(), ChallengeID: "", // TODO: instance.GetChallengeID(), Status: "", // TODO: string(instance.GetStatus()), StartTime: time.Now(), // TODO: instance.GetStartTime(), EndTime: time.Now(), // TODO: instance.GetEndTime(), Duration: 0, // TODO: instance.GetDuration(), Difficulty: 0.0, // TODO: instance.GetDifficulty(), Progress: 0, // TODO: instance.GetProgress(), IsCompleted: false, // TODO: instance.IsCompleted(), } } // buildChallengeRewardDTO 构建挑战奖励DTO func (s *SacredService) buildChallengeRewardDTO(reward *sacred.ChallengeReward) *ChallengeRewardDTO { return &ChallengeRewardDTO{ Experience: 0, // TODO: reward.GetExperience(), Items: map[string]int{}, // TODO: reward.GetItems(), Blessings: []string{}, // TODO: reward.GetBlessings(), TotalValue: 0, // TODO: reward.GetTotalValue(), } } // buildPlayerBlessingDTOs 构建玩家祝福DTO列表 // TODO: 实现PlayerBlessing类型 func (s *SacredService) buildPlayerBlessingDTOs(blessings []interface{}) []*PlayerBlessingDTO { dtos := make([]*PlayerBlessingDTO, len(blessings)) for i, _ := range blessings { dtos[i] = &PlayerBlessingDTO{ BlessingID: "", // TODO: blessing.GetBlessingID(), Name: "", // TODO: blessing.GetName(), Description: "", // TODO: blessing.GetDescription(), EffectType: "", // TODO: string(blessing.GetEffectType()), EffectValue: 0, // TODO: blessing.GetEffectValue(), Duration: 0, // TODO: blessing.GetDuration(), RemainingTime: 0, // TODO: blessing.GetRemainingTime(), IsActive: false, // TODO: blessing.IsActive(), ActivatedAt: time.Now(), // TODO: blessing.GetActivatedAt(), } } return dtos } // buildArtifactDTOs 构建圣物DTO列表 // TODO: 实现Artifact类型 func (s *SacredService) buildArtifactDTOs(artifacts []interface{}) []*ArtifactDTO { dtos := make([]*ArtifactDTO, len(artifacts)) for i, _ := range artifacts { dtos[i] = &ArtifactDTO{ ID: "", // TODO: artifact.GetID(), Name: "", // TODO: artifact.GetName(), Description: "", // TODO: artifact.GetDescription(), Type: "", // TODO: string(artifact.GetType()), Rarity: "", // TODO: string(artifact.GetRarity()), Power: 0, // TODO: artifact.GetPower(), Cooldown: 0, // TODO: artifact.GetCooldown(), UsageCount: 0, // TODO: artifact.GetUsageCount(), MaxUsage: 0, // TODO: artifact.GetMaxUsage(), IsAvailable: false, // TODO: artifact.IsAvailable(), } } return dtos } // buildArtifactEffectDTO 构建圣物效果DTO // TODO: 实现ArtifactEffect类型 func (s *SacredService) buildArtifactEffectDTO(effect interface{}) *ArtifactEffectDTO { return &ArtifactEffectDTO{ EffectType: "", // TODO: string(effect.GetEffectType()), Value: 0, // TODO: effect.GetValue(), Duration: 0, // TODO: effect.GetDuration(), Description: "", // TODO: effect.GetDescription(), } } // buildChallengeHistoryDTOs 构建挑战历史DTO列表 func (s *SacredService) buildChallengeHistoryDTOs(history []*sacred.Challenge) []*ChallengeHistoryDTO { dtos := make([]*ChallengeHistoryDTO, len(history)) for i, _ := range history { dtos[i] = &ChallengeHistoryDTO{ ID: "", // TODO: instance.GetID(), ChallengeID: "", // TODO: instance.GetChallengeID(), StartTime: time.Now(), // TODO: instance.GetStartTime(), EndTime: time.Now(), // TODO: instance.GetEndTime(), Duration: 0, // TODO: instance.GetDuration(), Difficulty: 0.0, // TODO: instance.GetDifficulty(), IsCompleted: false, // TODO: instance.IsCompleted(), Reward: 0, // TODO: instance.GetRewardValue(), } } return dtos } // buildStatisticsDTO 构建统计DTO func (s *SacredService) buildStatisticsDTO(stats *sacred.SacredStatistics) *SacredStatisticsDTO { return &SacredStatisticsDTO{ PlayerID: "", // TODO: stats.GetPlayerID(), TotalChallenges: 0, // TODO: stats.GetTotalChallenges(), CompletedChallenges: 0, // TODO: stats.GetCompletedChallenges(), FailedChallenges: 0, // TODO: stats.GetFailedChallenges(), CompletionRate: 0.0, // TODO: stats.GetCompletionRate(), TotalReward: 0, // TODO: stats.GetTotalReward(), AverageReward: 0.0, // TODO: stats.GetAverageReward(), FavoriteChallenge: "", // TODO: stats.GetFavoriteChallenge(), ActiveBlessings: 0, // TODO: stats.GetActiveBlessings(), LastChallengeTime: time.Now(), // TODO: stats.GetLastChallengeTime(), } } // DTO 定义 // SacredPlaceDTO 圣地DTO type SacredPlaceDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Level int `json:"level"` Status string `json:"status"` RequiredLevel int `json:"required_level"` VisitorCount int `json:"visitor_count"` MaxVisitors int `json:"max_visitors"` Challenges []string `json:"challenges"` Blessings []string `json:"blessings"` Artifacts []string `json:"artifacts"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ChallengeInstanceDTO 挑战实例DTO type ChallengeInstanceDTO struct { ID string `json:"id"` PlayerID string `json:"player_id"` ChallengeID string `json:"challenge_id"` Status string `json:"status"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` Difficulty float64 `json:"difficulty"` Progress float64 `json:"progress"` IsCompleted bool `json:"is_completed"` } // ChallengeRewardDTO 挑战奖励DTO type ChallengeRewardDTO struct { Experience int64 `json:"experience"` Items map[string]int `json:"items"` Blessings []string `json:"blessings"` TotalValue int64 `json:"total_value"` } // PlayerBlessingDTO 玩家祝福DTO type PlayerBlessingDTO struct { BlessingID string `json:"blessing_id"` Name string `json:"name"` Description string `json:"description"` EffectType string `json:"effect_type"` EffectValue float64 `json:"effect_value"` Duration time.Duration `json:"duration"` RemainingTime time.Duration `json:"remaining_time"` IsActive bool `json:"is_active"` ActivatedAt time.Time `json:"activated_at"` } // ArtifactDTO 圣物DTO type ArtifactDTO struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` Rarity string `json:"rarity"` Power float64 `json:"power"` Cooldown time.Duration `json:"cooldown"` UsageCount int `json:"usage_count"` MaxUsage int `json:"max_usage"` IsAvailable bool `json:"is_available"` } // ArtifactEffectDTO 圣物效果DTO type ArtifactEffectDTO struct { EffectType string `json:"effect_type"` Value float64 `json:"value"` Duration time.Duration `json:"duration"` Description string `json:"description"` } // ChallengeHistoryDTO 挑战历史DTO type ChallengeHistoryDTO struct { ID string `json:"id"` ChallengeID string `json:"challenge_id"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` Difficulty float64 `json:"difficulty"` IsCompleted bool `json:"is_completed"` Reward int64 `json:"reward"` } // SacredStatisticsDTO 圣地统计DTO type SacredStatisticsDTO struct { PlayerID string `json:"player_id"` TotalChallenges int64 `json:"total_challenges"` CompletedChallenges int64 `json:"completed_challenges"` FailedChallenges int64 `json:"failed_challenges"` CompletionRate float64 `json:"completion_rate"` TotalReward int64 `json:"total_reward"` AverageReward float64 `json:"average_reward"` FavoriteChallenge string `json:"favorite_challenge"` ActiveBlessings int `json:"active_blessings"` LastChallengeTime time.Time `json:"last_challenge_time"` } // ChallengeResultData 挑战结果数据 type ChallengeResultData struct { Score int64 `json:"score"` TimeUsed time.Duration `json:"time_used"` Actions []string `json:"actions"` Performance map[string]float64 `json:"performance"` } ================================================ FILE: internal/application/services/scene_service.go ================================================ // Package services 应用服务层 - 场景服务 package services import ( "context" "fmt" "time" "greatestworks/internal/domain/scene" "greatestworks/internal/infrastructure/logging" ) // SceneService 场景应用服务 // 编排场景聚合根,协调仓储、事件总线等基础设施 type SceneService struct { sceneRepo scene.Repository eventBus EventPublisher logger logging.Logger } // EventPublisher 事件发布器接口 type EventPublisher interface { Publish(ctx context.Context, event interface{}) error } // NewSceneService 创建场景应用服务 func NewSceneService( sceneRepo scene.Repository, eventBus EventPublisher, logger logging.Logger, ) *SceneService { return &SceneService{ sceneRepo: sceneRepo, eventBus: eventBus, logger: logger, } } // CreateSceneCommand 创建场景命令 type CreateSceneCommand struct { ID string `json:"id" validate:"required"` Name string `json:"name" validate:"required,min=2,max=50"` SceneType scene.SceneType `json:"scene_type" validate:"required"` Width float64 `json:"width" validate:"required,gt=0"` Height float64 `json:"height" validate:"required,gt=0"` MaxPlayers int `json:"max_players" validate:"required,gt=0"` } // CreateSceneResult 创建场景结果 type CreateSceneResult struct { SceneID string `json:"scene_id"` Name string `json:"name"` SceneType scene.SceneType `json:"scene_type"` Status scene.SceneStatus `json:"status"` } // CreateScene 创建场景 func (s *SceneService) CreateScene(ctx context.Context, cmd *CreateSceneCommand) (*CreateSceneResult, error) { // 检查场景是否已存在 exists, err := s.sceneRepo.Exists(ctx, cmd.ID) if err != nil { return nil, fmt.Errorf("检查场景是否存在失败: %w", err) } if exists { return nil, scene.ErrSceneAlreadyExists } // 创建场景聚合根 newScene := scene.NewScene( cmd.ID, cmd.Name, cmd.SceneType, cmd.Width, cmd.Height, cmd.MaxPlayers, ) // 保存场景 if err := s.sceneRepo.Save(ctx, newScene); err != nil { return nil, fmt.Errorf("保存场景失败: %w", err) } s.logger.Info("创建场景成功", logging.Fields{ "scene_id": newScene.ID(), "scene_name": newScene.Name(), "scene_type": cmd.SceneType, }) return &CreateSceneResult{ SceneID: newScene.ID(), Name: newScene.Name(), SceneType: cmd.SceneType, Status: newScene.Status(), }, nil } // PlayerEnterSceneCommand 玩家进入场景命令 type PlayerEnterSceneCommand struct { SceneID string `json:"scene_id" validate:"required"` PlayerID string `json:"player_id" validate:"required"` PlayerName string `json:"player_name" validate:"required"` Level int `json:"level" validate:"required,gt=0"` Position *scene.Position `json:"position"` } // PlayerEnterScene 玩家进入场景 func (s *SceneService) PlayerEnterScene(ctx context.Context, cmd *PlayerEnterSceneCommand) error { // 获取场景 sceneObj, err := s.sceneRepo.FindByID(ctx, cmd.SceneID) if err != nil { return fmt.Errorf("获取场景失败: %w", err) } if sceneObj == nil { return scene.ErrSceneNotFound } // 创建玩家实体(这里简化,实际应从玩家服务获取) player := &scene.Player{ // 填充玩家数据 } // 添加玩家到场景 if err := sceneObj.AddPlayer(player); err != nil { return fmt.Errorf("玩家进入场景失败: %w", err) } // 保存场景状态 if err := s.sceneRepo.Save(ctx, sceneObj); err != nil { return fmt.Errorf("保存场景状态失败: %w", err) } // 发布领域事件 events := sceneObj.GetEvents() for _, event := range events { if err := s.eventBus.Publish(ctx, event); err != nil { s.logger.Error("发布事件失败", err, logging.Fields{ "event_type": event.EventType(), "scene_id": event.SceneID(), }) } } sceneObj.ClearEvents() s.logger.Info("玩家进入场景", logging.Fields{ "scene_id": cmd.SceneID, "player_id": cmd.PlayerID, "player_name": cmd.PlayerName, }) return nil } // PlayerLeaveSceneCommand 玩家离开场景命令 type PlayerLeaveSceneCommand struct { SceneID string `json:"scene_id" validate:"required"` PlayerID string `json:"player_id" validate:"required"` } // PlayerLeaveScene 玩家离开场景 func (s *SceneService) PlayerLeaveScene(ctx context.Context, cmd *PlayerLeaveSceneCommand) error { // 获取场景 sceneObj, err := s.sceneRepo.FindByID(ctx, cmd.SceneID) if err != nil { return fmt.Errorf("获取场景失败: %w", err) } if sceneObj == nil { return scene.ErrSceneNotFound } // 移除玩家 if err := sceneObj.RemovePlayer(cmd.PlayerID); err != nil { return fmt.Errorf("玩家离开场景失败: %w", err) } // 保存场景状态 if err := s.sceneRepo.Save(ctx, sceneObj); err != nil { return fmt.Errorf("保存场景状态失败: %w", err) } // 发布领域事件 events := sceneObj.GetEvents() for _, event := range events { if err := s.eventBus.Publish(ctx, event); err != nil { s.logger.Error("发布事件失败", err, logging.Fields{ "event_type": event.EventType(), "scene_id": event.SceneID(), }) } } sceneObj.ClearEvents() s.logger.Info("玩家离开场景", logging.Fields{ "scene_id": cmd.SceneID, "player_id": cmd.PlayerID, }) return nil } // UpdateSceneCommand 更新场景命令 type UpdateSceneCommand struct { SceneID string `json:"scene_id" validate:"required"` DeltaTime int64 `json:"delta_time" validate:"required,gt=0"` // 毫秒 } // UpdateScene 更新场景(tick) func (s *SceneService) UpdateScene(ctx context.Context, cmd *UpdateSceneCommand) error { // 获取场景 sceneObj, err := s.sceneRepo.FindByID(ctx, cmd.SceneID) if err != nil { return fmt.Errorf("获取场景失败: %w", err) } if sceneObj == nil { return scene.ErrSceneNotFound } // 更新场景逻辑(AI、刷怪、AOI等) deltaTime := time.Duration(cmd.DeltaTime) * time.Millisecond sceneObj.Update(deltaTime) // 保存场景状态 if err := s.sceneRepo.Save(ctx, sceneObj); err != nil { return fmt.Errorf("保存场景状态失败: %w", err) } // 发布领域事件 events := sceneObj.GetEvents() for _, event := range events { if err := s.eventBus.Publish(ctx, event); err != nil { s.logger.Error("发布事件失败", err, logging.Fields{ "event_type": event.EventType(), "scene_id": event.SceneID(), }) } } sceneObj.ClearEvents() return nil } // GetSceneInfoQuery 获取场景信息查询 type GetSceneInfoQuery struct { SceneID string `json:"scene_id" validate:"required"` } // SceneInfoDTO 场景信息DTO type SceneInfoDTO struct { SceneID string `json:"scene_id"` Name string `json:"name"` SceneType scene.SceneType `json:"scene_type"` Status scene.SceneStatus `json:"status"` CurrentPlayers int `json:"current_players"` MaxPlayers int `json:"max_players"` } // GetSceneInfo 获取场景信息 func (s *SceneService) GetSceneInfo(ctx context.Context, query *GetSceneInfoQuery) (*SceneInfoDTO, error) { // 获取场景 sceneObj, err := s.sceneRepo.FindByID(ctx, query.SceneID) if err != nil { return nil, fmt.Errorf("获取场景失败: %w", err) } if sceneObj == nil { return nil, scene.ErrSceneNotFound } return &SceneInfoDTO{ SceneID: sceneObj.ID(), Name: sceneObj.Name(), SceneType: sceneObj.Type(), Status: sceneObj.Status(), CurrentPlayers: sceneObj.PlayerCount(), MaxPlayers: sceneObj.GetMaxPlayers(), }, nil } // ListAvailableScenesQuery 列出可用场景查询 type ListAvailableScenesQuery struct { Limit int `json:"limit"` Offset int `json:"offset"` } // ListAvailableScenes 列出可用场景 func (s *SceneService) ListAvailableScenes(ctx context.Context, query *ListAvailableScenesQuery) ([]*SceneInfoDTO, error) { // 获取可用场景列表 scenes, err := s.sceneRepo.FindAvailableScenes(ctx) if err != nil { return nil, fmt.Errorf("获取可用场景列表失败: %w", err) } // 转换为DTO result := make([]*SceneInfoDTO, 0, len(scenes)) for _, sceneObj := range scenes { result = append(result, &SceneInfoDTO{ SceneID: sceneObj.ID(), Name: sceneObj.Name(), SceneType: sceneObj.Type(), Status: sceneObj.Status(), CurrentPlayers: sceneObj.PlayerCount(), MaxPlayers: sceneObj.GetMaxPlayers(), }) } return result, nil } ================================================ FILE: internal/application/services/service_registry.go ================================================ // Package services 应用层服务注册器 package services import ( "context" "fmt" "greatestworks/internal/application/interfaces" "greatestworks/internal/infrastructure/container" "greatestworks/internal/infrastructure/logging" ) // ServiceRegistry 服务注册器 type ServiceRegistry struct { container *container.SimpleContainer commandBus interfaces.CommandBus queryBus interfaces.QueryBus eventBus interfaces.EventBus logger logging.Logger } // NewServiceRegistry 创建新的服务注册器 func NewServiceRegistry() *ServiceRegistry { return &ServiceRegistry{ container: container.NewSimpleContainer(), } } // RegisterCoreServices 注册核心服务 func (r *ServiceRegistry) RegisterCoreServices() error { // 注册日志服务 r.container.RegisterSingleton("logger", func() (interface{}, error) { config := &logging.Config{ Level: logging.InfoLevel, Format: "json", Output: "stdout", } return logging.NewLogger(config) }) // 注册命令总线 r.container.RegisterSingleton("command_bus", func() (interface{}, error) { // 这里应该创建实际的命令总线实现 return &mockCommandBus{}, nil }) // 注册查询总线 r.container.RegisterSingleton("query_bus", func() (interface{}, error) { // 这里应该创建实际的查询总线实现 return &mockQueryBus{}, nil }) // 注册事件总线 r.container.RegisterSingleton("event_bus", func() (interface{}, error) { // 这里应该创建实际的事件总线实现 return &mockEventBus{}, nil }) return nil } // RegisterDomainServices 注册领域服务 func (r *ServiceRegistry) RegisterDomainServices() error { // 注册玩家服务 r.container.RegisterSingleton("player_service", func() (interface{}, error) { // 这里应该创建实际的玩家服务实现 return &mockPlayerService{}, nil }) // 注册战斗服务 r.container.RegisterSingleton("battle_service", func() (interface{}, error) { // 这里应该创建实际的战斗服务实现 return &mockBattleService{}, nil }) return nil } // RegisterApplicationServices 注册应用服务 func (r *ServiceRegistry) RegisterApplicationServices() error { // 注册命令处理器 if err := r.registerCommandHandlers(); err != nil { return fmt.Errorf("failed to register command handlers: %w", err) } // 注册查询处理器 if err := r.registerQueryHandlers(); err != nil { return fmt.Errorf("failed to register query handlers: %w", err) } return nil } // registerCommandHandlers 注册命令处理器 func (r *ServiceRegistry) registerCommandHandlers() error { // 这里应该注册所有的命令处理器 // 例如:创建玩家命令处理器、移动玩家命令处理器等 return nil } // registerQueryHandlers 注册查询处理器 func (r *ServiceRegistry) registerQueryHandlers() error { // 这里应该注册所有的查询处理器 // 例如:获取玩家信息查询处理器、获取战斗状态查询处理器等 return nil } // Start 启动所有服务 func (r *ServiceRegistry) Start(ctx context.Context) error { // 启动容器中的服务 if err := r.container.StartServices(ctx); err != nil { return fmt.Errorf("failed to start services: %w", err) } // 获取日志记录器 logger, err := container.ResolveTyped[logging.Logger](r.container, "logger") if err != nil { return fmt.Errorf("failed to resolve logger: %w", err) } r.logger = logger // 获取总线 commandBus, err := container.ResolveTyped[interfaces.CommandBus](r.container, "command_bus") if err != nil { return fmt.Errorf("failed to resolve command bus: %w", err) } r.commandBus = commandBus queryBus, err := container.ResolveTyped[interfaces.QueryBus](r.container, "query_bus") if err != nil { return fmt.Errorf("failed to resolve query bus: %w", err) } r.queryBus = queryBus eventBus, err := container.ResolveTyped[interfaces.EventBus](r.container, "event_bus") if err != nil { return fmt.Errorf("failed to resolve event bus: %w", err) } r.eventBus = eventBus r.logger.Info("服务注册器启动成功") return nil } // Stop 停止所有服务 func (r *ServiceRegistry) Stop(ctx context.Context) error { if err := r.container.StopServices(ctx); err != nil { return fmt.Errorf("failed to stop services: %w", err) } if r.logger != nil { r.logger.Info("服务注册器已停止") } return nil } // GetContainer 获取容器 func (r *ServiceRegistry) GetContainer() *container.SimpleContainer { return r.container } // GetCommandBus 获取命令总线 func (r *ServiceRegistry) GetCommandBus() interfaces.CommandBus { return r.commandBus } // GetQueryBus 获取查询总线 func (r *ServiceRegistry) GetQueryBus() interfaces.QueryBus { return r.queryBus } // GetEventBus 获取事件总线 func (r *ServiceRegistry) GetEventBus() interfaces.EventBus { return r.eventBus } // GetLogger 获取日志记录器 func (r *ServiceRegistry) GetLogger() logging.Logger { return r.logger } // 模拟实现(实际项目中应该替换为真实实现) type mockCommandBus struct{} func (m *mockCommandBus) RegisterHandler(commandType string, handler interface{}) {} func (m *mockCommandBus) Execute(ctx context.Context, cmd interfaces.Command) (interface{}, error) { return nil, fmt.Errorf("not implemented") } type mockQueryBus struct{} func (m *mockQueryBus) RegisterHandler(queryType string, handler interface{}) {} func (m *mockQueryBus) Execute(ctx context.Context, query interfaces.Query) (interface{}, error) { return nil, fmt.Errorf("not implemented") } type mockEventBus struct{} func (m *mockEventBus) Publish(ctx context.Context, event interfaces.Event) error { return fmt.Errorf("not implemented") } func (m *mockEventBus) Subscribe(eventType string, handler interfaces.EventHandler[interfaces.Event]) error { return fmt.Errorf("not implemented") } func (m *mockEventBus) Unsubscribe(eventType string, handler interfaces.EventHandler[interfaces.Event]) error { return fmt.Errorf("not implemented") } type mockPlayerService struct{} type mockBattleService struct{} ================================================ FILE: internal/application/services/spawn_manager.go ================================================ package services import ( "context" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // SpawnTask is a unit of spawning work executed asynchronously. type SpawnTask func(ctx context.Context) // SpawnManager provides a simple asynchronous queue to run spawn tasks. type SpawnManager struct { logger logging.Logger ch chan SpawnTask wg sync.WaitGroup cancel context.CancelFunc } func NewSpawnManager(logger logging.Logger, queueSize int) *SpawnManager { if queueSize <= 0 { queueSize = 1024 } return &SpawnManager{ logger: logger, ch: make(chan SpawnTask, queueSize), } } func (m *SpawnManager) Start(parent context.Context, workers int) { if workers <= 0 { workers = 2 } ctx, cancel := context.WithCancel(parent) m.cancel = cancel m.logger.Info("SpawnManager starting", logging.Fields{"workers": workers}) for i := 0; i < workers; i++ { m.wg.Add(1) go func(id int) { defer m.wg.Done() for { select { case <-ctx.Done(): return case task := <-m.ch: safeRun(ctx, task, m.logger) } } }(i) } } func (m *SpawnManager) Stop() { if m.cancel != nil { m.cancel() } close(m.ch) done := make(chan struct{}) go func() { m.wg.Wait(); close(done) }() select { case <-done: case <-time.After(2 * time.Second): // timeout best-effort } } func (m *SpawnManager) Enqueue(task SpawnTask) { select { case m.ch <- task: default: m.logger.Warn("Spawn queue full, dropping task", logging.Fields{}) } } func safeRun(ctx context.Context, task SpawnTask, logger logging.Logger) { defer func() { if r := recover(); r != nil { logger.Error("spawn task panic", nil, logging.Fields{"recover": r}) } }() task(ctx) } ================================================ FILE: internal/application/services/update_manager.go ================================================ package services import ( "context" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // Updatable represents any object that can be updated on each tick. type Updatable interface { Update(ctx context.Context, delta time.Duration) error } // UpdateFunc is a functional adapter to register plain functions. type UpdateFunc func(ctx context.Context, delta time.Duration) error func (f UpdateFunc) Update(ctx context.Context, delta time.Duration) error { return f(ctx, delta) } // UpdateManager runs a game-loop style tick and calls registered updatables. type UpdateManager struct { logger logging.Logger interval time.Duration mu sync.RWMutex running bool cancel context.CancelFunc updaters map[string]Updatable } // NewUpdateManager creates an update manager with a default interval (50ms ~ 20 TPS) when interval<=0. func NewUpdateManager(logger logging.Logger, interval time.Duration) *UpdateManager { if interval <= 0 { interval = 50 * time.Millisecond } return &UpdateManager{ logger: logger, interval: interval, updaters: make(map[string]Updatable), } } // Register adds an updatable by id (last write wins). func (m *UpdateManager) Register(id string, u Updatable) { m.mu.Lock() defer m.mu.Unlock() m.updaters[id] = u } // Unregister removes an updatable. func (m *UpdateManager) Unregister(id string) { m.mu.Lock() defer m.mu.Unlock() delete(m.updaters, id) } // Start begins the tick loop in a goroutine; calling Start again has no effect. func (m *UpdateManager) Start(parent context.Context) { m.mu.Lock() if m.running { m.mu.Unlock() return } ctx, cancel := context.WithCancel(parent) m.cancel = cancel m.running = true m.mu.Unlock() go m.loop(ctx) } // Stop cancels the loop and waits for it to end. func (m *UpdateManager) Stop() { m.mu.Lock() if !m.running { m.mu.Unlock() return } cancel := m.cancel m.running = false m.cancel = nil m.mu.Unlock() if cancel != nil { cancel() } } func (m *UpdateManager) loop(ctx context.Context) { ticker := time.NewTicker(m.interval) defer ticker.Stop() last := time.Now() m.logger.Info("UpdateManager loop started", logging.Fields{"interval_ms": m.interval.Milliseconds()}) for { select { case <-ctx.Done(): m.logger.Info("UpdateManager loop stopped") return case <-ticker.C: now := time.Now() delta := now.Sub(last) last = now m.tick(ctx, delta) } } } func (m *UpdateManager) tick(ctx context.Context, delta time.Duration) { m.mu.RLock() // copy to avoid holding lock during updates updaters := make([]Updatable, 0, len(m.updaters)) for _, u := range m.updaters { updaters = append(updaters, u) } m.mu.RUnlock() for _, u := range updaters { // Best-effort: isolate panics per updater func() { defer func() { if r := recover(); r != nil { m.logger.Error("updater panic", nil, logging.Fields{"recover": r}) } }() if err := u.Update(ctx, delta); err != nil { m.logger.Warn("updater error", logging.Fields{"error": err.Error()}) } }() } } ================================================ FILE: internal/application/services/user_service.go ================================================ package services import ( "context" "errors" "fmt" "time" "greatestworks/internal/infrastructure/persistence" "golang.org/x/crypto/bcrypt" ) // UserService 用户服务 type UserService struct { userRepo *persistence.UserRepository } // NewUserService 创建用户服务 func NewUserService(userRepo *persistence.UserRepository) *UserService { return &UserService{ userRepo: userRepo, } } // Register 注册新用户 func (s *UserService) Register(ctx context.Context, username, password string) (int64, error) { // 检查用户名是否存在 existingUser, err := s.userRepo.FindByUsername(ctx, username) if err == nil && existingUser != nil { return 0, errors.New("username already exists") } // 密码哈希 passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return 0, fmt.Errorf("failed to hash password: %w", err) } // 生成用户ID(实际应用中应使用分布式ID生成器) userID := time.Now().UnixNano() // 创建用户 user := &persistence.DbUser{ UserID: userID, Username: username, PasswordHash: string(passwordHash), Status: 0, } if err := s.userRepo.Create(ctx, user); err != nil { return 0, fmt.Errorf("failed to create user: %w", err) } return userID, nil } // Login 用户登录 func (s *UserService) Login(ctx context.Context, username, password string) (int64, error) { // 查找用户 user, err := s.userRepo.FindByUsername(ctx, username) if err != nil { return 0, errors.New("invalid username or password") } // 检查用户状态 if user.Status != 0 { return 0, errors.New("user account is banned") } // 验证密码 if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { return 0, errors.New("invalid username or password") } // 更新最后登录时间 if err := s.userRepo.UpdateLastLogin(ctx, user.UserID); err != nil { // 记录日志但不影响登录 } return user.UserID, nil } // GetUserInfo 获取用户信息 func (s *UserService) GetUserInfo(ctx context.Context, userID int64) (*persistence.DbUser, error) { return s.userRepo.FindByID(ctx, userID) } // BanUser 封禁用户 func (s *UserService) BanUser(ctx context.Context, userID int64) error { user, err := s.userRepo.FindByID(ctx, userID) if err != nil { return fmt.Errorf("user not found: %w", err) } user.Status = 1 return s.userRepo.Update(ctx, user) } // UnbanUser 解封用户 func (s *UserService) UnbanUser(ctx context.Context, userID int64) error { user, err := s.userRepo.FindByID(ctx, userID) if err != nil { return fmt.Errorf("user not found: %w", err) } user.Status = 0 return s.userRepo.Update(ctx, user) } ================================================ FILE: internal/application/services/weather_service.go ================================================ package services import ( "context" "time" "greatestworks/internal/domain/scene/weather" ) // WeatherService 天气应用服务 type WeatherService struct { weatherRepo weather.WeatherRepository forecastRepo weather.WeatherForecastRepository effectRepo weather.WeatherEffectRepository // statisticsRepo weather.StatisticsRepository // TODO: Define StatisticsRepository cacheRepo weather.WeatherCacheRepository weatherService *weather.WeatherService } // NewWeatherService 创建天气应用服务 func NewWeatherService( weatherRepo weather.WeatherRepository, forecastRepo weather.WeatherForecastRepository, effectRepo weather.WeatherEffectRepository, // statisticsRepo weather.StatisticsRepository, cacheRepo weather.WeatherCacheRepository, weatherService *weather.WeatherService, ) *WeatherService { return &WeatherService{ weatherRepo: weatherRepo, forecastRepo: forecastRepo, effectRepo: effectRepo, // statisticsRepo: statisticsRepo, cacheRepo: cacheRepo, weatherService: weatherService, } } // GetCurrentWeather 获取当前天气 func (s *WeatherService) GetCurrentWeather(ctx context.Context, regionID string) (*WeatherDTO, error) { // 先从缓存获取 // cachedWeather, err := s.cacheRepo.GetCurrentWeather(regionID) // if err == nil && cachedWeather != nil { // return s.buildWeatherDTO(cachedWeather), nil // } // 从数据库获取 // currentWeather, err := s.weatherRepo.FindCurrentByRegion(regionID) // if err != nil { // return nil, fmt.Errorf("failed to get current weather: %w", err) // } // currentWeather := &weather.WeatherState{} // 更新缓存 // if err := s.cacheRepo.SetCurrentWeather(regionID, currentWeather, time.Minute*10); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildWeatherDTO(&weather.WeatherAggregate{}), nil // TODO: 修复currentWeather类型 } // GetWeatherForecast 获取天气预报 func (s *WeatherService) GetWeatherForecast(ctx context.Context, regionID string, days int) ([]*WeatherForecastDTO, error) { // 先从缓存获取 // cachedForecast, err := s.cacheRepo.GetWeatherForecast(regionID, days) // if err == nil && len(cachedForecast) > 0 { // return s.buildForecastDTOs(cachedForecast), nil // } // 生成天气预报 // forecasts, err := s.weatherService.GenerateForecast(regionID, days) // if err != nil { // return nil, fmt.Errorf("failed to generate weather forecast: %w", err) // } forecasts := []*weather.WeatherForecast{} // 保存预报数据 // for _, forecast := range forecasts { // if err := s.forecastRepo.Save(forecast); err != nil { // // 保存失败不影响返回结果 // // TODO: 添加日志记录 // } // } // 更新缓存 // if err := s.cacheRepo.SetWeatherForecast(regionID, forecasts, time.Hour); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return s.buildForecastDTOs(forecasts), nil } // GetWeatherEffects 获取天气影响 // TODO: 实现EffectTargetType类型 func (s *WeatherService) GetWeatherEffects(ctx context.Context, regionID string, targetType string) ([]*WeatherEffectDTO, error) { // 获取当前天气 // currentWeather, err := s.weatherRepo.FindCurrentByRegion(regionID) // if err != nil { // return nil, fmt.Errorf("failed to get current weather: %w", err) // } // currentWeather := &weather.WeatherState{} // 获取天气影响 // effects, err := s.weatherService.CalculateEffects(currentWeather, targetType) // if err != nil { // return nil, fmt.Errorf("failed to calculate weather effects: %w", err) // } effects := []*weather.WeatherEffect{} return s.buildEffectDTOs(effects), nil } // UpdateWeather 更新天气(系统调用) func (s *WeatherService) UpdateWeather(ctx context.Context, regionID string) error { // 获取当前天气 // currentWeather, err := s.weatherRepo.FindCurrentByRegion(regionID) // if err != nil && !weather.IsNotFoundError(err) { // return fmt.Errorf("failed to get current weather: %w", err) // } // currentWeather := &weather.WeatherAggregate{} // 生成新天气 // newWeather, err := s.weatherService.GenerateNextWeather(regionID, currentWeather) // if err != nil { // return fmt.Errorf("failed to generate new weather: %w", err) // } // newWeather := &weather.WeatherAggregate{} // 保存新天气 // if err := s.weatherRepo.Save(newWeather); err != nil { // return fmt.Errorf("failed to save new weather: %w", err) // } // 更新统计数据 // if err := s.updateStatistics(ctx, regionID, newWeather); err != nil { // // 统计更新失败不影响主流程 // // TODO: 添加日志记录 // } // 清除相关缓存 // if err := s.cacheRepo.DeleteCurrentWeather(regionID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // GetWeatherHistory 获取天气历史 func (s *WeatherService) GetWeatherHistory(ctx context.Context, regionID string, startTime, endTime time.Time) ([]*WeatherHistoryDTO, error) { // weatherHistory, err := s.weatherRepo.FindByRegionAndTimeRange(regionID, startTime, endTime) // if err != nil { // return nil, fmt.Errorf("failed to get weather history: %w", err) // } weatherHistory := []*weather.WeatherAggregate{} return s.buildHistoryDTOs(weatherHistory), nil } // GetWeatherStatistics 获取天气统计 func (s *WeatherService) GetWeatherStatistics(ctx context.Context, regionID string) (*WeatherStatisticsDTO, error) { // stats, err := s.statisticsRepo.FindByRegion(regionID) // if err != nil { // return nil, fmt.Errorf("failed to get weather statistics: %w", err) // } return s.buildStatisticsDTO(&weather.WeatherStatistics{}), nil // TODO: 修复stats类型 } // GetGlobalWeatherInfo 获取全局天气信息 func (s *WeatherService) GetGlobalWeatherInfo(ctx context.Context) (*GlobalWeatherDTO, error) { // 先从缓存获取 // cachedGlobal, err := s.cacheRepo.GetGlobalWeatherInfo() // if err == nil && cachedGlobal != nil { // return cachedGlobal, nil // } // 获取所有区域的当前天气 // allWeather, err := s.weatherRepo.FindAllCurrent() // if err != nil { // return nil, fmt.Errorf("failed to get all current weather: %w", err) // } allWeather := []*weather.WeatherAggregate{} // 构建全局天气信息 globalInfo := s.buildGlobalWeatherDTO(allWeather) // 更新缓存 // if err := s.cacheRepo.SetGlobalWeatherInfo(globalInfo, time.Minute*5); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return globalInfo, nil } // TriggerSpecialWeather 触发特殊天气事件 func (s *WeatherService) TriggerSpecialWeather(ctx context.Context, regionID string, weatherType weather.WeatherType, duration time.Duration) error { // 创建特殊天气事件 // specialWeather, err := s.weatherService.CreateSpecialWeather(regionID, weatherType, duration) // if err != nil { // return fmt.Errorf("failed to create special weather: %w", err) // } // specialWeather := &weather.WeatherAggregate{} // 保存特殊天气 // if err := s.weatherRepo.Save(specialWeather); err != nil { // return fmt.Errorf("failed to save special weather: %w", err) // } // 清除相关缓存 // if err := s.cacheRepo.DeleteCurrentWeather(regionID); err != nil { // // 缓存清除失败不影响主流程 // // TODO: 添加日志记录 // } return nil } // GetSeasonInfo 获取季节信息 func (s *WeatherService) GetSeasonInfo(ctx context.Context, regionID string) (*SeasonInfoDTO, error) { // 先从缓存获取 // cachedSeason, err := s.cacheRepo.GetSeasonInfo(regionID) // if err == nil && cachedSeason != nil { // return cachedSeason, nil // } // 计算当前季节信息 // seasonInfo, err := s.weatherService.GetCurrentSeason(regionID) // if err != nil { // return nil, fmt.Errorf("failed to get season info: %w", err) // } // seasonInfo := &weather.SeasonInfo{} seasonDTO := s.buildSeasonInfoDTO(&struct{}{}) // TODO: 修复weather.SeasonInfo类型 // 更新缓存 // if err := s.cacheRepo.SetSeasonInfo(regionID, seasonDTO, time.Hour*6); err != nil { // // 缓存更新失败不影响主流程 // // TODO: 添加日志记录 // } return seasonDTO, nil } // 私有方法 // updateStatistics 更新统计数据 func (s *WeatherService) updateStatistics(ctx context.Context, regionID string, newWeather *weather.WeatherAggregate) error { // TODO: 修复statisticsRepo字段 // stats, err := s.statisticsRepo.FindByRegion(regionID) // if err != nil && !weather.IsNotFoundError(err) { // return err // } // if stats == nil { // stats = weather.NewWeatherStatistics(regionID) // } // 更新统计数据 // stats.AddWeatherRecord(newWeather.GetWeatherType(), newWeather.GetIntensity()) // stats.UpdateLastWeatherTime(newWeather.GetStartTime()) // 保存统计数据 // return s.statisticsRepo.Save(stats) return nil // TODO: 修复updateStatistics方法 } // buildWeatherDTO 构建天气DTO func (s *WeatherService) buildWeatherDTO(weatherAggregate *weather.WeatherAggregate) *WeatherDTO { return &WeatherDTO{ RegionID: weatherAggregate.GetRegionID(), WeatherType: weatherAggregate.GetWeatherType().String(), Intensity: float64(weatherAggregate.GetIntensity()), Temperature: weatherAggregate.GetTemperature(), Humidity: weatherAggregate.GetHumidity(), WindSpeed: weatherAggregate.GetWindSpeed(), Visibility: weatherAggregate.GetVisibility(), StartTime: weatherAggregate.GetStartTime(), EndTime: weatherAggregate.GetEndTime(), Duration: weatherAggregate.GetDuration(), IsSpecial: weatherAggregate.IsSpecialWeather(), Description: weatherAggregate.GetDescription(), } } // buildForecastDTOs 构建预报DTO列表 func (s *WeatherService) buildForecastDTOs(forecasts []*weather.WeatherForecast) []*WeatherForecastDTO { dtos := make([]*WeatherForecastDTO, len(forecasts)) for i, _ := range forecasts { dtos[i] = &WeatherForecastDTO{ RegionID: "", // TODO: forecast.GetRegionID(), ForecastDate: time.Now(), // TODO: forecast.GetForecastDate(), WeatherType: "", // TODO: string(forecast.GetWeatherType()), Intensity: 0.0, // TODO: float64(forecast.GetIntensity()), Temperature: 0.0, // TODO: forecast.GetTemperature(), Humidity: 0.0, // TODO: forecast.GetHumidity(), WindSpeed: 0.0, // TODO: forecast.GetWindSpeed(), Probability: 0.0, // TODO: forecast.GetProbability(), Description: "", // TODO: forecast.GetDescription(), } } return dtos } // buildEffectDTOs 构建影响DTO列表 func (s *WeatherService) buildEffectDTOs(effects []*weather.WeatherEffect) []*WeatherEffectDTO { dtos := make([]*WeatherEffectDTO, len(effects)) for i, _ := range effects { dtos[i] = &WeatherEffectDTO{ EffectType: "", // TODO: string(effect.GetEffectType()), TargetType: "", // TODO: string(effect.GetTargetType()), Modifier: 0.0, // TODO: effect.GetModifier(), Duration: 0, // TODO: effect.GetDuration(), IsPositive: false, // TODO: effect.IsPositive(), Description: "", // TODO: effect.GetDescription(), } } return dtos } // buildHistoryDTOs 构建历史DTO列表 func (s *WeatherService) buildHistoryDTOs(history []*weather.WeatherAggregate) []*WeatherHistoryDTO { dtos := make([]*WeatherHistoryDTO, len(history)) for i, record := range history { dtos[i] = &WeatherHistoryDTO{ RegionID: record.GetRegionID(), WeatherType: record.GetWeatherType().String(), Intensity: float64(record.GetIntensity()), Temperature: record.GetTemperature(), StartTime: record.GetStartTime(), EndTime: record.GetEndTime(), Duration: record.GetDuration(), IsSpecial: record.IsSpecialWeather(), } } return dtos } // buildStatisticsDTO 构建统计DTO func (s *WeatherService) buildStatisticsDTO(stats *weather.WeatherStatistics) *WeatherStatisticsDTO { return &WeatherStatisticsDTO{ RegionID: "", // TODO: stats.GetRegionID(), TotalRecords: 0, // TODO: stats.GetTotalRecords(), WeatherTypeStats: map[string]int64{}, // TODO: stats.GetWeatherTypeStats(), AverageTemperature: 0.0, // TODO: stats.GetAverageTemperature(), AverageHumidity: 0.0, // TODO: stats.GetAverageHumidity(), AverageWindSpeed: 0.0, // TODO: stats.GetAverageWindSpeed(), MostCommonWeather: "", // TODO: string(stats.GetMostCommonWeather()), SpecialWeatherCount: 0, // TODO: stats.GetSpecialWeatherCount(), LastWeatherTime: time.Now(), // TODO: stats.GetLastWeatherTime(), } } // buildGlobalWeatherDTO 构建全局天气DTO func (s *WeatherService) buildGlobalWeatherDTO(allWeather []*weather.WeatherAggregate) *GlobalWeatherDTO { regionWeather := make(map[string]*WeatherDTO) weatherTypeCount := make(map[string]int) totalRegions := len(allWeather) specialWeatherCount := 0 for _, w := range allWeather { regionWeather[w.GetRegionID()] = s.buildWeatherDTO(w) weatherType := w.GetWeatherType().String() weatherTypeCount[weatherType]++ if w.IsSpecialWeather() { specialWeatherCount++ } } return &GlobalWeatherDTO{ RegionWeather: regionWeather, TotalRegions: totalRegions, WeatherTypeCount: weatherTypeCount, SpecialWeatherCount: specialWeatherCount, LastUpdateTime: time.Now(), } } // buildSeasonInfoDTO 构建季节信息DTO // TODO: 实现SeasonInfo类型 func (s *WeatherService) buildSeasonInfoDTO(seasonInfo interface{}) *SeasonInfoDTO { return &SeasonInfoDTO{ CurrentSeason: "", // TODO: string(seasonInfo.GetCurrentSeason()), SeasonProgress: 0.0, // TODO: seasonInfo.GetSeasonProgress(), DaysRemaining: 0, // TODO: seasonInfo.GetDaysRemaining(), NextSeason: "", // TODO: string(seasonInfo.GetNextSeason()), SeasonEffects: map[string]float64{}, // TODO: seasonInfo.GetSeasonEffects(), TemperatureRange: map[string]float64{}, // TODO: seasonInfo.GetTemperatureRange(), WeatherTendency: map[string]float64{}, // TODO: seasonInfo.GetWeatherTendency(), } } // DTO 定义 // WeatherDTO 天气DTO type WeatherDTO struct { RegionID string `json:"region_id"` WeatherType string `json:"weather_type"` Intensity float64 `json:"intensity"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` WindSpeed float64 `json:"wind_speed"` Visibility float64 `json:"visibility"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` IsSpecial bool `json:"is_special"` Description string `json:"description"` } // WeatherForecastDTO 天气预报DTO type WeatherForecastDTO struct { RegionID string `json:"region_id"` ForecastDate time.Time `json:"forecast_date"` WeatherType string `json:"weather_type"` Intensity float64 `json:"intensity"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` WindSpeed float64 `json:"wind_speed"` Probability float64 `json:"probability"` Description string `json:"description"` } // WeatherEffectDTO 天气影响DTO type WeatherEffectDTO struct { EffectType string `json:"effect_type"` TargetType string `json:"target_type"` Modifier float64 `json:"modifier"` Duration time.Duration `json:"duration"` IsPositive bool `json:"is_positive"` Description string `json:"description"` } // WeatherHistoryDTO 天气历史DTO type WeatherHistoryDTO struct { RegionID string `json:"region_id"` WeatherType string `json:"weather_type"` Intensity float64 `json:"intensity"` Temperature float64 `json:"temperature"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` IsSpecial bool `json:"is_special"` } // WeatherStatisticsDTO 天气统计DTO type WeatherStatisticsDTO struct { RegionID string `json:"region_id"` TotalRecords int64 `json:"total_records"` WeatherTypeStats map[string]int64 `json:"weather_type_stats"` AverageTemperature float64 `json:"average_temperature"` AverageHumidity float64 `json:"average_humidity"` AverageWindSpeed float64 `json:"average_wind_speed"` MostCommonWeather string `json:"most_common_weather"` SpecialWeatherCount int64 `json:"special_weather_count"` LastWeatherTime time.Time `json:"last_weather_time"` } // GlobalWeatherDTO 全局天气DTO type GlobalWeatherDTO struct { RegionWeather map[string]*WeatherDTO `json:"region_weather"` TotalRegions int `json:"total_regions"` WeatherTypeCount map[string]int `json:"weather_type_count"` SpecialWeatherCount int `json:"special_weather_count"` LastUpdateTime time.Time `json:"last_update_time"` } // SeasonInfoDTO 季节信息DTO type SeasonInfoDTO struct { CurrentSeason string `json:"current_season"` SeasonProgress float64 `json:"season_progress"` DaysRemaining int `json:"days_remaining"` NextSeason string `json:"next_season"` SeasonEffects map[string]float64 `json:"season_effects"` TemperatureRange map[string]float64 `json:"temperature_range"` WeatherTendency map[string]float64 `json:"weather_tendency"` } ================================================ FILE: internal/auth/jwt.go ================================================ // Package auth JWT认证相关功能 // Author: MMO Server Team // Created: 2024 package auth import ( "errors" "time" "github.com/golang-jwt/jwt/v5" ) // JWTManager JWT管理器 type JWTManager struct { secretKey []byte tokenDuration time.Duration } // UserClaims 用户声明 type UserClaims struct { UserID string `json:"user_id"` Username string `json:"username"` Role string `json:"role"` jwt.RegisteredClaims } // NewJWTManager 创建JWT管理器 func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager { return &JWTManager{ secretKey: []byte(secretKey), tokenDuration: tokenDuration, } } // Generate 生成JWT令牌 func (manager *JWTManager) Generate(userID, username, role string) (string, error) { claims := UserClaims{ UserID: userID, Username: username, Role: role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(manager.tokenDuration)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(manager.secretKey) } // Verify 验证JWT令牌 func (manager *JWTManager) Verify(tokenString string) (*UserClaims, error) { token, err := jwt.ParseWithClaims( tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) { _, ok := token.Method.(*jwt.SigningMethodHMAC) if !ok { return nil, errors.New("unexpected token signing method") } return manager.secretKey, nil }, ) if err != nil { return nil, err } claims, ok := token.Claims.(*UserClaims) if !ok { return nil, errors.New("invalid token claims") } return claims, nil } // Refresh 刷新JWT令牌 func (manager *JWTManager) Refresh(tokenString string) (string, error) { claims, err := manager.Verify(tokenString) if err != nil { return "", err } // 检查令牌是否即将过期(在过期前30分钟内可以刷新) if time.Until(claims.ExpiresAt.Time) > 30*time.Minute { return "", errors.New("token is not eligible for refresh") } return manager.Generate(claims.UserID, claims.Username, claims.Role) } ================================================ FILE: internal/base_module.go ================================================ package internal import ( // "greatestworks/internal/infrastructure/module_router" // TODO: 实现模块路由 // "greatestworks/internal/infrastructure/net/call" // TODO: 实现网络调用 // "greatestworks/internal/note/event" // TODO: 实现事件系统 "go.opentelemetry.io/otel/trace" ) type BaseModule struct { ModuleName string activeEventCategory map[int]bool tracer trace.Tracer // methods []call.MethodKey // TODO: 实现call包 } func (b *BaseModule) OnEvent(c Character, event interface{}) { // TODO: 实现event处理 } func (b *BaseModule) SetEventCategoryActive(eventCategory int) { b.activeEventCategory[eventCategory] = true } func NewBaseModule() *BaseModule { return &BaseModule{} } func (b *BaseModule) Get(id uint32) interface{} { return nil } func (b *BaseModule) Load() { } func (b *BaseModule) Save() { } func (b *BaseModule) GetName() string { return b.ModuleName } func (b *BaseModule) Description() string { return "" } func (b *BaseModule) SetName(str string) { b.ModuleName = str } func (b *BaseModule) OnStart() { } func (b *BaseModule) AfterStart() { } func (b *BaseModule) OnStop() { } func (b *BaseModule) AfterStop() { } func (b *BaseModule) RegisterHandler() { // TODO: 实现module_router // module_router.RegisterModuleMessageHandler(0, 0, nil) } ================================================ FILE: internal/bootstrap/auth_bootstrap.go ================================================ package bootstrap import ( "context" "fmt" "sync/atomic" "time" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" "greatestworks/internal/config" "greatestworks/internal/database" "greatestworks/internal/events" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/infrastructure/messaging" "greatestworks/internal/infrastructure/monitoring" httpiface "greatestworks/internal/interfaces/http" ) // AuthBootstrap wires infrastructure for the auth service type AuthBootstrap struct { config atomic.Pointer[config.Config] logger logging.Logger httpServer *httpiface.Server profiler *monitoring.Profiler // infra mongoClient *mongo.Client redisClient *redis.Client eventBus *events.EventBus ctx context.Context cancel context.CancelFunc } func NewAuthBootstrap(cfg *config.Config, logger logging.Logger) *AuthBootstrap { ctx, cancel := context.WithCancel(context.Background()) b := &AuthBootstrap{logger: logger, ctx: ctx, cancel: cancel} if cfg != nil { b.config.Store(cfg) } return b } func (s *AuthBootstrap) UpdateConfig(cfg *config.Config) { if cfg != nil { s.config.Store(cfg) } } func (s *AuthBootstrap) Start() error { cfg := s.config.Load() if cfg == nil { return fmt.Errorf("auth service configuration not loaded") } s.logger.Info("Starting auth service", logging.Fields{ "service": cfg.Service.Name, "version": cfg.Service.Version, "node_id": cfg.Service.NodeID, }) if err := s.initializeInfrastructure(cfg); err != nil { return fmt.Errorf("初始化基础设施失败: %w", err) } if err := s.initializeHTTPServer(cfg); err != nil { return fmt.Errorf("初始化HTTP服务器失败: %w", err) } go func() { if err := s.httpServer.Start(); err != nil { s.logger.Error("HTTP server start failed", err) } }() s.profiler = monitoring.NewProfiler(s.logger) if cfg.Monitoring.Profiling.Enabled { host := cfg.Monitoring.Profiling.Host if host == "" { host = cfg.Server.HTTP.Host } if cfg.Monitoring.Profiling.Port == 0 { s.logger.Warn("pprof未启动: 未配置端口") } else if host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.logger.Info("pprof routes enabled on primary HTTP server", logging.Fields{"addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "path": "/debug/pprof/"}) } else if err := s.profiler.Start(host, cfg.Monitoring.Profiling.Port); err != nil { s.logger.Error("Failed to start pprof server", err, logging.Fields{"host": host, "port": cfg.Monitoring.Profiling.Port}) } } s.logger.Info("Auth service started successfully", logging.Fields{ "http_addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), }) return nil } func (s *AuthBootstrap) Stop() error { s.logger.Info("停止认证服务") s.cancel() if s.httpServer != nil { if err := s.httpServer.Stop(); err != nil { s.logger.Error("Failed to stop HTTP server", err) return err } } if s.profiler != nil { if err := s.profiler.Stop(context.Background()); err != nil { s.logger.Error("Failed to stop pprof server", err) return err } } if s.mongoClient != nil { if err := s.mongoClient.Disconnect(s.ctx); err != nil { s.logger.Error("Failed to disconnect MongoDB", err) } } if s.redisClient != nil { if err := s.redisClient.Close(); err != nil { s.logger.Error("Failed to close Redis", err) } } if s.eventBus != nil { s.eventBus.Close() } s.logger.Info("认证服务已停止") return nil } func (s *AuthBootstrap) initializeInfrastructure(cfg *config.Config) error { s.logger.Info("初始化基础设施层") // Mongo mongoConfig := &database.MongoConfig{ URI: cfg.Database.MongoDB.URI, Database: cfg.Database.MongoDB.Database, MaxPoolSize: uint64(cfg.Database.MongoDB.MaxPoolSize), MinPoolSize: uint64(cfg.Database.MongoDB.MinPoolSize), MaxIdleTime: int(cfg.Database.MongoDB.MaxIdleTime / time.Second), ConnectTimeout: int(cfg.Database.MongoDB.ConnectTimeout / time.Second), SocketTimeout: int(cfg.Database.MongoDB.SocketTimeout / time.Second), } mongoDB := database.NewMongoDB(mongoConfig) if err := mongoDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接MongoDB失败: %w", err) } s.mongoClient = mongoDB.GetClient() s.logger.Info("MongoDB连接成功", logging.Fields{"database": mongoConfig.Database}) // Redis redisConfig := &database.RedisConfig{ Addr: cfg.Database.Redis.Addr, Password: cfg.Database.Redis.Password, DB: cfg.Database.Redis.DB, PoolSize: cfg.Database.Redis.PoolSize, MinIdleConns: cfg.Database.Redis.MinIdleConns, DialTimeout: int(cfg.Database.Redis.DialTimeout / time.Second), ReadTimeout: int(cfg.Database.Redis.ReadTimeout / time.Second), WriteTimeout: int(cfg.Database.Redis.WriteTimeout / time.Second), } redisDB := database.NewRedis(redisConfig) if err := redisDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接Redis失败: %w", err) } s.redisClient = redisDB.GetClient() s.logger.Info("Redis连接成功", logging.Fields{"addr": redisConfig.Addr, "db": redisConfig.DB}) // Event bus (optional) eventLogger := messaging.NewEventLoggerAdapter(s.logger) s.eventBus = events.NewEventBus(eventLogger) if cfg.Messaging.NATS.URL != "" { if err := s.eventBus.ConnectNATS(cfg.Messaging.NATS.URL); err != nil { s.logger.Error("连接NATS失败", err, logging.Fields{"url": cfg.Messaging.NATS.URL}) } else { s.logger.Info("NATS连接成功", logging.Fields{"url": cfg.Messaging.NATS.URL}) } } s.logger.Info("基础设施层初始化完成") return nil } func (s *AuthBootstrap) initializeHTTPServer(cfg *config.Config) error { s.logger.Info("初始化HTTP服务器") httpConfig := &httpiface.ServerConfig{ Host: cfg.Server.HTTP.Host, Port: cfg.Server.HTTP.Port, ReadTimeout: cfg.Server.HTTP.ReadTimeout, WriteTimeout: cfg.Server.HTTP.WriteTimeout, IdleTimeout: cfg.Server.HTTP.IdleTimeout, } s.httpServer = httpiface.NewServer(httpConfig, s.logger) if cfg.Monitoring.Profiling.Enabled && cfg.Monitoring.Profiling.Host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.httpServer.EnableProfiling() } s.logger.Info("HTTP服务器初始化完成") return nil } // Done returns a channel that's closed when the service context is canceled. func (s *AuthBootstrap) Done() <-chan struct{} { return s.ctx.Done() } ================================================ FILE: internal/bootstrap/game_bootstrap.go ================================================ package bootstrap import ( "context" "fmt" "sync/atomic" "time" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" "greatestworks/internal/application/handlers" "greatestworks/internal/config" "greatestworks/internal/database" "greatestworks/internal/events" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/infrastructure/messaging" "greatestworks/internal/infrastructure/monitoring" httpiface "greatestworks/internal/interfaces/http" "greatestworks/internal/interfaces/rpc" ) // GameBootstrap wires infrastructure and app layers for the game service type GameBootstrap struct { config atomic.Pointer[config.Config] logger logging.Logger httpServer *httpiface.Server rpcServer *rpc.RPCServer profiler *monitoring.Profiler // infra mongoClient *mongo.Client redisClient *redis.Client eventBus *events.EventBus // buses commandBus *handlers.CommandBus queryBus *handlers.QueryBus ctx context.Context cancel context.CancelFunc } func NewGameBootstrap(cfg *config.Config, logger logging.Logger) *GameBootstrap { ctx, cancel := context.WithCancel(context.Background()) b := &GameBootstrap{logger: logger, ctx: ctx, cancel: cancel} if cfg != nil { b.config.Store(cfg) } return b } func (s *GameBootstrap) UpdateConfig(cfg *config.Config) { if cfg != nil { s.config.Store(cfg) } } func (s *GameBootstrap) Start() error { cfg := s.config.Load() if cfg == nil { return fmt.Errorf("game service configuration not loaded") } s.logger.Info("Starting game service", logging.Fields{"service": cfg.Service.Name, "version": cfg.Service.Version, "node_id": cfg.Service.NodeID}) if err := s.initializeInfrastructure(cfg); err != nil { return fmt.Errorf("初始化基础设施失败: %w", err) } if err := s.initializeApplicationLayer(cfg); err != nil { return fmt.Errorf("初始化应用服务层失败: %w", err) } if err := s.initializeHTTPServer(cfg); err != nil { return fmt.Errorf("初始化HTTP服务器失败: %w", err) } if err := s.initializeRPCServer(cfg); err != nil { return fmt.Errorf("初始化RPC服务器失败: %w", err) } go func() { if err := s.httpServer.Start(); err != nil { s.logger.Error("HTTP server start failed", err) } }() go func() { if err := s.rpcServer.Start(); err != nil { s.logger.Error("RPC server start failed", err) } }() s.profiler = monitoring.NewProfiler(s.logger) if cfg.Monitoring.Profiling.Enabled { host := cfg.Monitoring.Profiling.Host if host == "" { host = cfg.Server.HTTP.Host } if cfg.Monitoring.Profiling.Port == 0 { s.logger.Warn("pprof未启动: 未配置端口") } else if host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.logger.Info("pprof routes enabled on primary HTTP server", logging.Fields{"addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "path": "/debug/pprof/"}) } else if err := s.profiler.Start(host, cfg.Monitoring.Profiling.Port); err != nil { s.logger.Error("Failed to start pprof server", err, logging.Fields{"host": host, "port": cfg.Monitoring.Profiling.Port}) } } s.logger.Info("Game service started successfully", logging.Fields{"http_addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "rpc_addr": fmt.Sprintf("%s:%d", cfg.Server.RPC.Host, cfg.Server.RPC.Port)}) return nil } func (s *GameBootstrap) Stop() error { s.logger.Info("停止游戏服务") s.cancel() if s.httpServer != nil { if err := s.httpServer.Stop(); err != nil { s.logger.Error("Failed to stop HTTP server", err) return err } } if s.rpcServer != nil { if err := s.rpcServer.Stop(); err != nil { s.logger.Error("Failed to stop RPC server", err) return err } } if s.profiler != nil { if err := s.profiler.Stop(context.Background()); err != nil { s.logger.Error("Failed to stop pprof server", err) return err } } if s.mongoClient != nil { if err := s.mongoClient.Disconnect(s.ctx); err != nil { s.logger.Error("Failed to disconnect MongoDB", err) } } if s.redisClient != nil { if err := s.redisClient.Close(); err != nil { s.logger.Error("Failed to close Redis", err) } } if s.eventBus != nil { s.eventBus.Close() } s.logger.Info("游戏服务已停止") return nil } func (s *GameBootstrap) initializeInfrastructure(cfg *config.Config) error { s.logger.Info("初始化基础设施层") // Mongo mongoConfig := &database.MongoConfig{URI: cfg.Database.MongoDB.URI, Database: cfg.Database.MongoDB.Database, MaxPoolSize: uint64(cfg.Database.MongoDB.MaxPoolSize), MinPoolSize: uint64(cfg.Database.MongoDB.MinPoolSize), MaxIdleTime: int(cfg.Database.MongoDB.MaxIdleTime / time.Second), ConnectTimeout: int(cfg.Database.MongoDB.ConnectTimeout / time.Second), SocketTimeout: int(cfg.Database.MongoDB.SocketTimeout / time.Second)} mongoDB := database.NewMongoDB(mongoConfig) if err := mongoDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接MongoDB失败: %w", err) } s.mongoClient = mongoDB.GetClient() s.logger.Info("MongoDB连接成功", logging.Fields{"database": mongoConfig.Database}) // Redis redisConfig := &database.RedisConfig{Addr: cfg.Database.Redis.Addr, Password: cfg.Database.Redis.Password, DB: cfg.Database.Redis.DB, PoolSize: cfg.Database.Redis.PoolSize, MinIdleConns: cfg.Database.Redis.MinIdleConns, DialTimeout: int(cfg.Database.Redis.DialTimeout / time.Second), ReadTimeout: int(cfg.Database.Redis.ReadTimeout / time.Second), WriteTimeout: int(cfg.Database.Redis.WriteTimeout / time.Second)} redisDB := database.NewRedis(redisConfig) if err := redisDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接Redis失败: %w", err) } s.redisClient = redisDB.GetClient() s.logger.Info("Redis连接成功", logging.Fields{"addr": redisConfig.Addr, "db": redisConfig.DB}) // Event bus eventLogger := messaging.NewEventLoggerAdapter(s.logger) s.eventBus = events.NewEventBus(eventLogger) if cfg.Messaging.NATS.URL != "" { if err := s.eventBus.ConnectNATS(cfg.Messaging.NATS.URL); err != nil { s.logger.Error("连接NATS失败", err, logging.Fields{"url": cfg.Messaging.NATS.URL}) } else { s.logger.Info("NATS连接成功", logging.Fields{"url": cfg.Messaging.NATS.URL}) } } s.logger.Info("基础设施层初始化完成") return nil } func (s *GameBootstrap) initializeApplicationLayer(cfg *config.Config) error { _ = cfg s.logger.Info("初始化应用服务层") s.commandBus = handlers.NewCommandBus() s.queryBus = handlers.NewQueryBus() s.logger.Info("应用服务层初始化完成") return nil } func (s *GameBootstrap) initializeHTTPServer(cfg *config.Config) error { s.logger.Info("初始化HTTP服务器") httpConfig := &httpiface.ServerConfig{Host: cfg.Server.HTTP.Host, Port: cfg.Server.HTTP.Port, ReadTimeout: cfg.Server.HTTP.ReadTimeout, WriteTimeout: cfg.Server.HTTP.WriteTimeout, IdleTimeout: cfg.Server.HTTP.IdleTimeout} s.httpServer = httpiface.NewServer(httpConfig, s.logger) if cfg.Monitoring.Profiling.Enabled && cfg.Monitoring.Profiling.Host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.httpServer.EnableProfiling() } s.logger.Info("HTTP服务器初始化完成") return nil } func (s *GameBootstrap) initializeRPCServer(cfg *config.Config) error { s.logger.Info("初始化RPC服务器") rpcConfig := &rpc.RPCServerConfig{Host: cfg.Server.RPC.Host, Port: cfg.Server.RPC.Port, MaxConnections: cfg.Server.RPC.MaxConnections, Timeout: cfg.Server.RPC.Timeout, KeepAlive: cfg.Server.RPC.KeepAlive, KeepAlivePeriod: cfg.Server.RPC.KeepAlivePeriod, ReadTimeout: cfg.Server.RPC.ReadTimeout, WriteTimeout: cfg.Server.RPC.WriteTimeout} s.rpcServer = rpc.NewRPCServer(rpcConfig, s.commandBus, s.queryBus, s.logger) s.logger.Info("RPC服务器初始化完成") return nil } // Done returns a channel that's closed when the service context is canceled. func (s *GameBootstrap) Done() <-chan struct{} { return s.ctx.Done() } ================================================ FILE: internal/bootstrap/gateway_bootstrap.go ================================================ package bootstrap import ( "context" "encoding/json" "fmt" "sync/atomic" "time" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" "greatestworks/internal/application/handlers" appServices "greatestworks/internal/application/services" "greatestworks/internal/config" "greatestworks/internal/database" "greatestworks/internal/domain/character" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/infrastructure/monitoring" "greatestworks/internal/infrastructure/persistence" "greatestworks/internal/interfaces/tcp" tcpProtocol "greatestworks/internal/interfaces/tcp/protocol" ) // GatewayBootstrap wires infrastructure for the gateway service type GatewayBootstrap struct { config atomic.Pointer[config.Config] logger logging.Logger tcpServer *tcp.TCPServer profiler *monitoring.Profiler // infra mongoClient *mongo.Client redisClient *redis.Client // buses commandBus *handlers.CommandBus queryBus *handlers.QueryBus // app services mapService *appServices.MapService fightService *appServices.FightService characterService *appServices.CharacterService updateMgr *appServices.UpdateManager spawnMgr *appServices.SpawnManager ctx context.Context cancel context.CancelFunc } func NewGatewayBootstrap(cfg *config.Config, logger logging.Logger) *GatewayBootstrap { ctx, cancel := context.WithCancel(context.Background()) b := &GatewayBootstrap{logger: logger, ctx: ctx, cancel: cancel} if cfg != nil { b.config.Store(cfg) } return b } func (s *GatewayBootstrap) UpdateConfig(cfg *config.Config) { if cfg != nil { s.config.Store(cfg) } } func (s *GatewayBootstrap) Start() error { cfg := s.config.Load() if cfg == nil { return fmt.Errorf("gateway service configuration not loaded") } s.logger.Info("Starting gateway service", logging.Fields{"service": cfg.Service.Name, "version": cfg.Service.Version, "node_id": cfg.Service.NodeID}) if err := s.initializeInfrastructure(cfg); err != nil { return fmt.Errorf("初始化基础设施失败: %w", err) } if err := s.initializeApplicationLayer(cfg); err != nil { return fmt.Errorf("初始化应用服务层失败: %w", err) } if err := s.initializeTCPServer(cfg); err != nil { return fmt.Errorf("初始化TCP服务器失败: %w", err) } // Start runtime managers if s.updateMgr != nil { // Register map tick into update loop if s.mapService != nil { s.updateMgr.Register("map.tick", appServices.UpdateFunc(func(ctx context.Context, d time.Duration) error { s.mapService.Tick(ctx, d) return nil })) } s.updateMgr.Start(s.ctx) } if s.spawnMgr != nil { // default 2 workers; can be made configurable later s.spawnMgr.Start(s.ctx, 2) } go func() { if err := s.tcpServer.Start(); err != nil { s.logger.Error("TCP server start failed", err) } }() s.profiler = monitoring.NewProfiler(s.logger) if cfg.Monitoring.Profiling.Enabled { host := cfg.Monitoring.Profiling.Host if host == "" { host = cfg.Server.TCP.Host } if cfg.Monitoring.Profiling.Port == 0 { s.logger.Warn("pprof未启动: 未配置端口") } else if err := s.profiler.Start(host, cfg.Monitoring.Profiling.Port); err != nil { s.logger.Error("Failed to start pprof server", err, logging.Fields{"host": host, "port": cfg.Monitoring.Profiling.Port}) } } s.logger.Info("Gateway service started successfully", logging.Fields{"tcp_addr": fmt.Sprintf("%s:%d", cfg.Server.TCP.Host, cfg.Server.TCP.Port)}) return nil } func (s *GatewayBootstrap) Stop() error { s.logger.Info("停止网关服务") s.cancel() if s.updateMgr != nil { s.updateMgr.Stop() } if s.spawnMgr != nil { s.spawnMgr.Stop() } if s.tcpServer != nil { if err := s.tcpServer.Stop(); err != nil { s.logger.Error("Failed to stop TCP server", err) return err } } if s.profiler != nil { if err := s.profiler.Stop(context.Background()); err != nil { s.logger.Error("Failed to stop pprof server", err) return err } } if s.redisClient != nil { if err := s.redisClient.Close(); err != nil { s.logger.Error("Failed to close Redis", err) } } s.logger.Info("网关服务已停止") return nil } func (s *GatewayBootstrap) initializeInfrastructure(cfg *config.Config) error { s.logger.Info("初始化基础设施层") // Mongo mongoConfig := &database.MongoConfig{ URI: cfg.Database.MongoDB.URI, Database: cfg.Database.MongoDB.Database, MaxPoolSize: uint64(cfg.Database.MongoDB.MaxPoolSize), MinPoolSize: uint64(cfg.Database.MongoDB.MinPoolSize), MaxIdleTime: int(cfg.Database.MongoDB.MaxIdleTime / time.Second), ConnectTimeout: int(cfg.Database.MongoDB.ConnectTimeout / time.Second), SocketTimeout: int(cfg.Database.MongoDB.SocketTimeout / time.Second), } mongoDB := database.NewMongoDB(mongoConfig) if err := mongoDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接MongoDB失败: %w", err) } s.mongoClient = mongoDB.GetClient() s.logger.Info("MongoDB连接成功", logging.Fields{"database": mongoConfig.Database}) // Redis redisConfig := &database.RedisConfig{ Addr: cfg.Database.Redis.Addr, Password: cfg.Database.Redis.Password, DB: cfg.Database.Redis.DB, PoolSize: cfg.Database.Redis.PoolSize, MinIdleConns: cfg.Database.Redis.MinIdleConns, DialTimeout: int(cfg.Database.Redis.DialTimeout / time.Second), ReadTimeout: int(cfg.Database.Redis.ReadTimeout / time.Second), WriteTimeout: int(cfg.Database.Redis.WriteTimeout / time.Second), } redisDB := database.NewRedis(redisConfig) if err := redisDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接Redis失败: %w", err) } s.redisClient = redisDB.GetClient() s.logger.Info("Redis连接成功", logging.Fields{"addr": redisConfig.Addr, "db": redisConfig.DB}) s.logger.Info("基础设施层初始化完成") return nil } func (s *GatewayBootstrap) initializeApplicationLayer(cfg *config.Config) error { _ = cfg s.logger.Info("初始化应用服务层") s.commandBus = handlers.NewCommandBus() s.queryBus = handlers.NewQueryBus() // 创建仓储 db := s.mongoClient.Database(cfg.Database.MongoDB.Database) characterRepo := persistence.NewCharacterRepository(db) itemRepo := persistence.NewItemRepository(db) questRepo := persistence.NewQuestRepository(db) // Instantiate application services s.mapService = appServices.NewMapService() s.fightService = appServices.NewFightService(nil) s.characterService = appServices.NewCharacterService(characterRepo, itemRepo, questRepo) s.updateMgr = appServices.NewUpdateManager(s.logger, 50*time.Millisecond) s.spawnMgr = appServices.NewSpawnManager(s.logger, 1024) // Wiring: map service uses spawn manager for async tasks s.mapService.SetSpawnManager(s.spawnMgr) s.logger.Info("应用服务层初始化完成") return nil } func (s *GatewayBootstrap) initializeTCPServer(cfg *config.Config) error { s.logger.Info("初始化TCP服务器") tcpCfg := &tcp.ServerConfig{Addr: fmt.Sprintf("%s:%d", cfg.Server.TCP.Host, cfg.Server.TCP.Port), MaxConnections: cfg.Server.TCP.MaxConnections, ReadTimeout: cfg.Server.TCP.ReadTimeout, WriteTimeout: cfg.Server.TCP.WriteTimeout, EnableCompression: cfg.Server.TCP.CompressionEnabled, BufferSize: cfg.Server.TCP.BufferSize} s.tcpServer = tcp.NewTCPServer(tcpCfg, s.commandBus, s.queryBus, s.logger) // Provide services to TCP server for handlers s.tcpServer.SetMapService(s.mapService) s.tcpServer.SetFightService(s.fightService) s.tcpServer.SetCharacterService(s.characterService) // Inject broadcaster from TCP server into MapService if s.mapService != nil { connMgr := s.tcpServer.GetConnectionManager() s.mapService.SetBroadcaster(func(recipients []character.EntityID, topic string, payload interface{}) { // Choose a message type based on topic var msgType uint32 switch topic { case "entity_move": msgType = uint32(tcpProtocol.MsgPlayerMove) case "entity_appear", "entity_disappear": msgType = uint32(tcpProtocol.MsgPlayerStatusSync) case "skill_cast": msgType = uint32(tcpProtocol.MsgBattleSkill) default: msgType = uint32(tcpProtocol.MsgPlayerStatus) } msg := &tcpProtocol.Message{ Header: tcpProtocol.MessageHeader{ Magic: tcpProtocol.MessageMagic, MessageID: 0, MessageType: msgType, Flags: tcpProtocol.FlagBroadcast | tcpProtocol.FlagAsync, PlayerID: 0, Timestamp: time.Now().Unix(), Sequence: 0, }, Payload: map[string]interface{}{ "topic": topic, "payload": payload, }, } if data, err := json.Marshal(msg); err == nil { for _, id := range recipients { if session, ok := connMgr.GetSessionByPlayer(int32(id)); ok { _ = session.Send(data) } } } else { s.logger.Error("广播消息序列化失败", err, logging.Fields{"topic": topic}) } }) } s.logger.Info("TCP服务器初始化完成") return nil } // Done returns a channel that's closed when the service context is canceled. func (s *GatewayBootstrap) Done() <-chan struct{} { return s.ctx.Done() } ================================================ FILE: internal/bootstrap/replication_bootstrap.go ================================================ package bootstrap import ( "context" "fmt" "sync/atomic" "time" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" "greatestworks/internal/application/handlers" appsvc "greatestworks/internal/application/services" "greatestworks/internal/config" "greatestworks/internal/database" "greatestworks/internal/events" "greatestworks/internal/infrastructure/cache" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/infrastructure/messaging" "greatestworks/internal/infrastructure/monitoring" "greatestworks/internal/infrastructure/persistence" httpiface "greatestworks/internal/interfaces/http" "greatestworks/internal/interfaces/rpc" ) // ReplicationBootstrap wires infrastructure and app layers for replication service type ReplicationBootstrap struct { config atomic.Pointer[config.Config] logger logging.Logger httpServer *httpiface.Server rpcServer *rpc.RPCServer profiler *monitoring.Profiler // DDD components replicationService *appsvc.ReplicationService replicationRepo *persistence.MongoReplicationRepository eventBus *events.EventBus mongoClient *mongo.Client redisClient *redis.Client ctx context.Context cancel context.CancelFunc } func NewReplicationBootstrap(cfg *config.Config, logger logging.Logger) *ReplicationBootstrap { ctx, cancel := context.WithCancel(context.Background()) b := &ReplicationBootstrap{logger: logger, ctx: ctx, cancel: cancel} if cfg != nil { b.config.Store(cfg) } return b } func (s *ReplicationBootstrap) UpdateConfig(cfg *config.Config) { if cfg != nil { s.config.Store(cfg) } } func (s *ReplicationBootstrap) Start() error { cfg := s.config.Load() if cfg == nil { return fmt.Errorf("replication service configuration not loaded") } s.logger.Info("Starting replication service", logging.Fields{ "service": cfg.Service.Name, "version": cfg.Service.Version, "node_id": cfg.Service.NodeID, }) if err := s.initializeInfrastructure(cfg); err != nil { return fmt.Errorf("初始化基础设施失败: %w", err) } if err := s.initializeApplicationLayer(cfg); err != nil { return fmt.Errorf("初始化应用服务层失败: %w", err) } if err := s.initializeHTTPServer(cfg); err != nil { return fmt.Errorf("初始化HTTP服务器失败: %w", err) } if err := s.initializeRPCServer(cfg); err != nil { return fmt.Errorf("初始化RPC服务器失败: %w", err) } go func() { if err := s.httpServer.Start(); err != nil { s.logger.Error("HTTP server start failed", err) } }() go func() { if err := s.rpcServer.Start(); err != nil { s.logger.Error("RPC server start failed", err) } }() s.profiler = monitoring.NewProfiler(s.logger) if cfg.Monitoring.Profiling.Enabled { host := cfg.Monitoring.Profiling.Host if host == "" { host = cfg.Server.HTTP.Host } if cfg.Monitoring.Profiling.Port == 0 { s.logger.Warn("pprof未启动: 未配置端口") } else if host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.logger.Info("pprof routes enabled on primary HTTP server", logging.Fields{"addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "path": "/debug/pprof/"}) } else if err := s.profiler.Start(host, cfg.Monitoring.Profiling.Port); err != nil { s.logger.Error("Failed to start pprof server", err, logging.Fields{"host": host, "port": cfg.Monitoring.Profiling.Port}) } } s.logger.Info("Replication service started successfully", logging.Fields{ "http_addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "rpc_addr": fmt.Sprintf("%s:%d", cfg.Server.RPC.Host, cfg.Server.RPC.Port), }) return nil } func (s *ReplicationBootstrap) Stop() error { s.logger.Info("停止副本服务") s.cancel() if s.httpServer != nil { if err := s.httpServer.Stop(); err != nil { s.logger.Error("Failed to stop HTTP server", err) return err } } if s.rpcServer != nil { if err := s.rpcServer.Stop(); err != nil { s.logger.Error("Failed to stop RPC server", err) return err } } if s.profiler != nil { if err := s.profiler.Stop(context.Background()); err != nil { s.logger.Error("Failed to stop pprof server", err) return err } } if s.mongoClient != nil { if err := s.mongoClient.Disconnect(s.ctx); err != nil { s.logger.Error("Failed to disconnect MongoDB", err) } } if s.redisClient != nil { if err := s.redisClient.Close(); err != nil { s.logger.Error("Failed to close Redis", err) } } if s.eventBus != nil { s.eventBus.Close() } s.logger.Info("副本服务已停止") return nil } // Done returns a channel that's closed when the service context is canceled. func (s *ReplicationBootstrap) Done() <-chan struct{} { return s.ctx.Done() } func (s *ReplicationBootstrap) initializeInfrastructure(cfg *config.Config) error { s.logger.Info("初始化基础设施层") // Mongo mongoConfig := &database.MongoConfig{ URI: cfg.Database.MongoDB.URI, Database: cfg.Database.MongoDB.Database, MaxPoolSize: uint64(cfg.Database.MongoDB.MaxPoolSize), MinPoolSize: uint64(cfg.Database.MongoDB.MinPoolSize), MaxIdleTime: int(cfg.Database.MongoDB.MaxIdleTime / time.Second), ConnectTimeout: int(cfg.Database.MongoDB.ConnectTimeout / time.Second), SocketTimeout: int(cfg.Database.MongoDB.SocketTimeout / time.Second), } mongoDB := database.NewMongoDB(mongoConfig) if err := mongoDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接MongoDB失败: %w", err) } s.mongoClient = mongoDB.GetClient() s.logger.Info("MongoDB连接成功", logging.Fields{"database": mongoConfig.Database}) // Redis redisConfig := &database.RedisConfig{ Addr: cfg.Database.Redis.Addr, Password: cfg.Database.Redis.Password, DB: cfg.Database.Redis.DB, PoolSize: cfg.Database.Redis.PoolSize, MinIdleConns: cfg.Database.Redis.MinIdleConns, DialTimeout: int(cfg.Database.Redis.DialTimeout / time.Second), ReadTimeout: int(cfg.Database.Redis.ReadTimeout / time.Second), WriteTimeout: int(cfg.Database.Redis.WriteTimeout / time.Second), } redisDB := database.NewRedis(redisConfig) if err := redisDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接Redis失败: %w", err) } s.redisClient = redisDB.GetClient() s.logger.Info("Redis连接成功", logging.Fields{"addr": redisConfig.Addr, "db": redisConfig.DB}) // Cache and repo redisCache := cache.NewRedisCache(s.redisClient, s.logger) db := mongoDB.GetDatabase() s.replicationRepo = persistence.NewMongoReplicationRepository(db, redisCache, s.logger) // Event bus eventLogger := messaging.NewEventLoggerAdapter(s.logger) s.eventBus = events.NewEventBus(eventLogger) if cfg.Messaging.NATS.URL != "" { if err := s.eventBus.ConnectNATS(cfg.Messaging.NATS.URL); err != nil { s.logger.Error("连接NATS失败", err, logging.Fields{"url": cfg.Messaging.NATS.URL}) } else { s.logger.Info("NATS连接成功", logging.Fields{"url": cfg.Messaging.NATS.URL}) } } s.logger.Info("基础设施层初始化完成") return nil } func (s *ReplicationBootstrap) initializeApplicationLayer(cfg *config.Config) error { _ = cfg s.logger.Info("初始化应用服务层") publisher := messaging.NewEventBusPublisher(s.eventBus) s.replicationService = appsvc.NewReplicationService(s.replicationRepo, publisher, s.logger) // register subscribers handlers.RegisterReplicationSubscribers(s.eventBus, s.logger) s.logger.Info("应用服务层初始化完成") return nil } func (s *ReplicationBootstrap) initializeHTTPServer(cfg *config.Config) error { s.logger.Info("初始化HTTP服务器") httpConfig := &httpiface.ServerConfig{ Host: cfg.Server.HTTP.Host, Port: cfg.Server.HTTP.Port, ReadTimeout: cfg.Server.HTTP.ReadTimeout, WriteTimeout: cfg.Server.HTTP.WriteTimeout, IdleTimeout: cfg.Server.HTTP.IdleTimeout, } s.httpServer = httpiface.NewServer(httpConfig, s.logger) // register routes handlers := httpiface.NewReplicationHTTPHandlers(s.replicationService, s.logger) httpiface.RegisterReplicationRoutes(s.httpServer, handlers) if cfg.Monitoring.Profiling.Enabled && cfg.Monitoring.Profiling.Host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.httpServer.EnableProfiling() } s.logger.Info("HTTP服务器初始化完成") return nil } func (s *ReplicationBootstrap) initializeRPCServer(cfg *config.Config) error { s.logger.Info("初始化RPC服务器") commandBus := handlers.NewCommandBus() queryBus := handlers.NewQueryBus() rpcConfig := &rpc.RPCServerConfig{ Host: cfg.Server.RPC.Host, Port: cfg.Server.RPC.Port, MaxConnections: cfg.Server.RPC.MaxConnections, Timeout: cfg.Server.RPC.Timeout, KeepAlive: cfg.Server.RPC.KeepAlive, KeepAlivePeriod: cfg.Server.RPC.KeepAlivePeriod, ReadTimeout: cfg.Server.RPC.ReadTimeout, WriteTimeout: cfg.Server.RPC.WriteTimeout, } s.rpcServer = rpc.NewRPCServer(rpcConfig, commandBus, queryBus, s.logger) // register replication RPC service s.rpcServer.RegisterService(rpc.NewReplicationRPCService(s.replicationService, s.logger)) s.logger.Info("RPC服务器初始化完成") return nil } ================================================ FILE: internal/bootstrap/scene_bootstrap.go ================================================ package bootstrap import ( "context" "fmt" "sync/atomic" "time" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" "greatestworks/internal/application/handlers" "greatestworks/internal/application/services" "greatestworks/internal/config" "greatestworks/internal/database" "greatestworks/internal/events" "greatestworks/internal/infrastructure/cache" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/infrastructure/messaging" "greatestworks/internal/infrastructure/monitoring" "greatestworks/internal/infrastructure/persistence" httpiface "greatestworks/internal/interfaces/http" "greatestworks/internal/interfaces/rpc" ) // SceneBootstrap wires infrastructure and app layers for the scene service type SceneBootstrap struct { config atomic.Pointer[config.Config] logger logging.Logger httpServer *httpiface.Server rpcServer *rpc.RPCServer profiler *monitoring.Profiler // DDD components sceneService *services.SceneService sceneRepo *persistence.MongoSceneRepository eventBus *events.EventBus mongoClient *mongo.Client redisClient *redis.Client ctx context.Context cancel context.CancelFunc } func NewSceneBootstrap(cfg *config.Config, logger logging.Logger) *SceneBootstrap { ctx, cancel := context.WithCancel(context.Background()) b := &SceneBootstrap{logger: logger, ctx: ctx, cancel: cancel} if cfg != nil { b.config.Store(cfg) } return b } func (s *SceneBootstrap) UpdateConfig(cfg *config.Config) { if cfg != nil { s.config.Store(cfg) } } // Done returns a channel that's closed when the service context is canceled. func (s *SceneBootstrap) Done() <-chan struct{} { return s.ctx.Done() } func (s *SceneBootstrap) Start() error { cfg := s.config.Load() if cfg == nil { return fmt.Errorf("scene service configuration not loaded") } s.logger.Info("Starting scene service", logging.Fields{ "service": cfg.Service.Name, "version": cfg.Service.Version, "node_id": cfg.Service.NodeID, }) if err := s.initializeInfrastructure(cfg); err != nil { return fmt.Errorf("初始化基础设施失败: %w", err) } if err := s.initializeApplicationLayer(cfg); err != nil { return fmt.Errorf("初始化应用服务层失败: %w", err) } if err := s.initializeHTTPServer(cfg); err != nil { return fmt.Errorf("初始化HTTP服务器失败: %w", err) } if err := s.initializeRPCServer(cfg); err != nil { return fmt.Errorf("初始化RPC服务器失败: %w", err) } go func() { if err := s.httpServer.Start(); err != nil { s.logger.Error("HTTP server start failed", err) } }() go func() { if err := s.rpcServer.Start(); err != nil { s.logger.Error("RPC server start failed", err) } }() s.profiler = monitoring.NewProfiler(s.logger) if cfg.Monitoring.Profiling.Enabled { host := cfg.Monitoring.Profiling.Host if host == "" { host = cfg.Server.HTTP.Host } if cfg.Monitoring.Profiling.Port == 0 { s.logger.Warn("pprof未启动: 未配置端口") } else if host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.logger.Info("pprof routes enabled on primary HTTP server", logging.Fields{"addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "path": "/debug/pprof/"}) } else if err := s.profiler.Start(host, cfg.Monitoring.Profiling.Port); err != nil { s.logger.Error("Failed to start pprof server", err, logging.Fields{"host": host, "port": cfg.Monitoring.Profiling.Port}) } } s.logger.Info("Scene service started successfully", logging.Fields{ "http_addr": fmt.Sprintf("%s:%d", cfg.Server.HTTP.Host, cfg.Server.HTTP.Port), "rpc_addr": fmt.Sprintf("%s:%d", cfg.Server.RPC.Host, cfg.Server.RPC.Port), }) return nil } func (s *SceneBootstrap) Stop() error { s.logger.Info("停止场景服务") s.cancel() if s.httpServer != nil { if err := s.httpServer.Stop(); err != nil { s.logger.Error("Failed to stop HTTP server", err) return err } } if s.rpcServer != nil { if err := s.rpcServer.Stop(); err != nil { s.logger.Error("Failed to stop RPC server", err) return err } } if s.profiler != nil { if err := s.profiler.Stop(context.Background()); err != nil { s.logger.Error("Failed to stop pprof server", err) return err } } if s.mongoClient != nil { if err := s.mongoClient.Disconnect(s.ctx); err != nil { s.logger.Error("Failed to disconnect MongoDB", err) } } if s.redisClient != nil { if err := s.redisClient.Close(); err != nil { s.logger.Error("Failed to close Redis", err) } } if s.eventBus != nil { s.eventBus.Close() } s.logger.Info("场景服务已停止") return nil } func (s *SceneBootstrap) initializeInfrastructure(cfg *config.Config) error { s.logger.Info("初始化基础设施层") // Mongo mongoConfig := &database.MongoConfig{ URI: cfg.Database.MongoDB.URI, Database: cfg.Database.MongoDB.Database, MaxPoolSize: uint64(cfg.Database.MongoDB.MaxPoolSize), MinPoolSize: uint64(cfg.Database.MongoDB.MinPoolSize), MaxIdleTime: int(cfg.Database.MongoDB.MaxIdleTime / time.Second), ConnectTimeout: int(cfg.Database.MongoDB.ConnectTimeout / time.Second), SocketTimeout: int(cfg.Database.MongoDB.SocketTimeout / time.Second), } mongoDB := database.NewMongoDB(mongoConfig) if err := mongoDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接MongoDB失败: %w", err) } s.mongoClient = mongoDB.GetClient() s.logger.Info("MongoDB连接成功", logging.Fields{"database": mongoConfig.Database}) // Redis redisConfig := &database.RedisConfig{ Addr: cfg.Database.Redis.Addr, Password: cfg.Database.Redis.Password, DB: cfg.Database.Redis.DB, PoolSize: cfg.Database.Redis.PoolSize, MinIdleConns: cfg.Database.Redis.MinIdleConns, DialTimeout: int(cfg.Database.Redis.DialTimeout / time.Second), ReadTimeout: int(cfg.Database.Redis.ReadTimeout / time.Second), WriteTimeout: int(cfg.Database.Redis.WriteTimeout / time.Second), } redisDB := database.NewRedis(redisConfig) if err := redisDB.Connect(s.ctx); err != nil { return fmt.Errorf("连接Redis失败: %w", err) } s.redisClient = redisDB.GetClient() s.logger.Info("Redis连接成功", logging.Fields{"addr": redisConfig.Addr, "db": redisConfig.DB}) // Cache and repo redisCache := cache.NewRedisCache(s.redisClient, s.logger) db := mongoDB.GetDatabase() s.sceneRepo = persistence.NewMongoSceneRepository(db, redisCache, s.logger) // Event bus eventLogger := messaging.NewEventLoggerAdapter(s.logger) s.eventBus = events.NewEventBus(eventLogger) if cfg.Messaging.NATS.URL != "" { if err := s.eventBus.ConnectNATS(cfg.Messaging.NATS.URL); err != nil { s.logger.Error("连接NATS失败", err, logging.Fields{"url": cfg.Messaging.NATS.URL}) } else { s.logger.Info("NATS连接成功", logging.Fields{"url": cfg.Messaging.NATS.URL}) } } s.logger.Info("基础设施层初始化完成") return nil } func (s *SceneBootstrap) initializeApplicationLayer(cfg *config.Config) error { _ = cfg s.logger.Info("初始化应用服务层") publisher := messaging.NewEventBusPublisher(s.eventBus) s.sceneService = services.NewSceneService(s.sceneRepo, publisher, s.logger) s.logger.Info("应用服务层初始化完成") return nil } func (s *SceneBootstrap) initializeHTTPServer(cfg *config.Config) error { s.logger.Info("初始化HTTP服务器") httpConfig := &httpiface.ServerConfig{ Host: cfg.Server.HTTP.Host, Port: cfg.Server.HTTP.Port, ReadTimeout: cfg.Server.HTTP.ReadTimeout, WriteTimeout: cfg.Server.HTTP.WriteTimeout, IdleTimeout: cfg.Server.HTTP.IdleTimeout, } s.httpServer = httpiface.NewServer(httpConfig, s.logger) if cfg.Monitoring.Profiling.Enabled && cfg.Monitoring.Profiling.Host == cfg.Server.HTTP.Host && cfg.Monitoring.Profiling.Port == cfg.Server.HTTP.Port { s.httpServer.EnableProfiling() } s.logger.Info("HTTP服务器初始化完成") return nil } func (s *SceneBootstrap) initializeRPCServer(cfg *config.Config) error { s.logger.Info("初始化RPC服务器") commandBus := handlers.NewCommandBus() queryBus := handlers.NewQueryBus() rpcConfig := &rpc.RPCServerConfig{ Host: cfg.Server.RPC.Host, Port: cfg.Server.RPC.Port, MaxConnections: cfg.Server.RPC.MaxConnections, Timeout: cfg.Server.RPC.Timeout, KeepAlive: cfg.Server.RPC.KeepAlive, KeepAlivePeriod: cfg.Server.RPC.KeepAlivePeriod, ReadTimeout: cfg.Server.RPC.ReadTimeout, WriteTimeout: cfg.Server.RPC.WriteTimeout, } s.rpcServer = rpc.NewRPCServer(rpcConfig, commandBus, queryBus, s.logger) s.logger.Info("RPC服务器初始化完成") return nil } ================================================ FILE: internal/config/config.go ================================================ //go:build ignore // +build ignore package config import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "sync" "time" ) // Deprecated: this file intentionally left blank. The new configuration loader, // manager and typed schema live in loader.go, manager.go and types.go. // Logger 简单的日志接口 type Logger interface { Info(msg string, args ...interface{}) Error(msg string, args ...interface{}) Debug(msg string, args ...interface{}) } // Config 全局配置结构 type Config struct { Server ServerConfig `json:"server"` Database DatabaseConfig `json:"database"` Redis RedisConfig `json:"redis"` NATS NATSConfig `json:"nats"` Gateway GatewayConfig `json:"gateway"` Scene SceneConfig `json:"scene"` Battle BattleConfig `json:"battle"` Activity ActivityConfig `json:"activity"` Login LoginConfig `json:"login"` Log LogConfig `json:"log"` } // ServerConfig 服务器配置 type ServerConfig struct { Host string `json:"host"` Port int `json:"port"` ReadTimeout int `json:"read_timeout"` WriteTimeout int `json:"write_timeout"` MaxConns int `json:"max_conns"` } // DatabaseConfig 数据库配置 type DatabaseConfig struct { Host string `json:"host"` Port int `json:"port"` Username string `json:"username"` Password string `json:"password"` Database string `json:"database"` MaxOpenConns int `json:"max_open_conns"` MaxIdleConns int `json:"max_idle_conns"` MaxLifetime int `json:"max_lifetime"` } // RedisConfig Redis配置 type RedisConfig struct { Host string `json:"host"` Port int `json:"port"` Password string `json:"password"` DB int `json:"db"` PoolSize int `json:"pool_size"` MinIdleConn int `json:"min_idle_conn"` } // NATSConfig NATS配置 type NATSConfig struct { URL string `json:"url"` ClusterID string `json:"cluster_id"` ClientID string `json:"client_id"` MaxReconnect int `json:"max_reconnect"` ReconnectWait int `json:"reconnect_wait"` MaxReconnects int `json:"max_reconnects"` ConnectionName string `json:"connection_name"` DrainTimeout int `json:"drain_timeout"` } // GatewayConfig 网关配置 type GatewayConfig struct { Host string `json:"host"` Port int `json:"port"` MaxConnections int `json:"max_connections"` Servers []string `json:"servers"` } // SceneConfig 场景服务器配置 type SceneConfig struct { SceneID string `json:"scene_id"` Host string `json:"host"` Port int `json:"port"` GatewayAddr string `json:"gateway_addr"` MaxPlayers int `json:"max_players"` } // BattleConfig 战斗服务器配置 type BattleConfig struct { ServerID string `json:"server_id"` Host string `json:"host"` Port int `json:"port"` GatewayAddr string `json:"gateway_addr"` MaxBattles int `json:"max_battles"` } // ActivityConfig 活动服务器配置 type ActivityConfig struct { ServerID string `json:"server_id"` Host string `json:"host"` Port int `json:"port"` GatewayAddr string `json:"gateway_addr"` } // LoginConfig 登录服务器配置 type LoginConfig struct { ServerID string `json:"server_id"` Host string `json:"host"` Port int `json:"port"` GatewayAddr string `json:"gateway_addr"` JWTSecret string `json:"jwt_secret"` } // LogConfig 日志配置 type LogConfig struct { Level string `json:"level"` Format string `json:"format"` Output string `json:"output"` MaxSize int `json:"max_size"` MaxBackups int `json:"max_backups"` MaxAge int `json:"max_age"` Compress bool `json:"compress"` } // Manager 配置管理器 type Manager struct { config *Config configPath string mutex sync.RWMutex watchers []func(*Config) logger Logger stopChan chan struct{} } // NewManager 创建配置管理器 func NewManager(configPath string, logger Logger) *Manager { return &Manager{ configPath: configPath, watchers: make([]func(*Config), 0), logger: logger, stopChan: make(chan struct{}), } } // Load 加载配置 func (m *Manager) Load() error { m.mutex.Lock() defer m.mutex.Unlock() data, err := ioutil.ReadFile(m.configPath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } var config Config if err := json.Unmarshal(data, &config); err != nil { return fmt.Errorf("failed to parse config: %w", err) } // 设置默认值 m.setDefaults(&config) m.config = &config m.logger.Info("Configuration loaded", "path", m.configPath) // 通知观察者 for _, watcher := range m.watchers { go watcher(&config) } return nil } // Get 获取配置 func (m *Manager) Get() *Config { m.mutex.RLock() defer m.mutex.RUnlock() return m.config } // Watch 监听配置变化 func (m *Manager) Watch(callback func(*Config)) { m.mutex.Lock() m.watchers = append(m.watchers, callback) m.mutex.Unlock() } // StartWatching 开始监听配置文件变化 func (m *Manager) StartWatching() { go m.watchConfigFile() } // StopWatching 停止监听 func (m *Manager) StopWatching() { close(m.stopChan) } // watchConfigFile 监听配置文件变化 func (m *Manager) watchConfigFile() { ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() var lastModTime time.Time if stat, err := os.Stat(m.configPath); err == nil { lastModTime = stat.ModTime() } for { select { case <-m.stopChan: return case <-ticker.C: stat, err := os.Stat(m.configPath) if err != nil { continue } if stat.ModTime().After(lastModTime) { lastModTime = stat.ModTime() m.logger.Info("Config file changed, reloading...") if err := m.Load(); err != nil { m.logger.Error("Failed to reload config", "error", err) } } } } } // setDefaults 设置默认值 func (m *Manager) setDefaults(config *Config) { // 服务器默认配置 if config.Server.Host == "" { config.Server.Host = "0.0.0.0" } if config.Server.Port == 0 { config.Server.Port = 8080 } if config.Server.ReadTimeout == 0 { config.Server.ReadTimeout = 30 } if config.Server.WriteTimeout == 0 { config.Server.WriteTimeout = 30 } if config.Server.MaxConns == 0 { config.Server.MaxConns = 10000 } // 数据库默认配置 if config.Database.Host == "" { config.Database.Host = "localhost" } if config.Database.Port == 0 { config.Database.Port = 27017 } if config.Database.MaxOpenConns == 0 { config.Database.MaxOpenConns = 100 } if config.Database.MaxIdleConns == 0 { config.Database.MaxIdleConns = 10 } if config.Database.MaxLifetime == 0 { config.Database.MaxLifetime = 3600 } // Redis默认配置 if config.Redis.Host == "" { config.Redis.Host = "localhost" } if config.Redis.Port == 0 { config.Redis.Port = 6379 } if config.Redis.PoolSize == 0 { config.Redis.PoolSize = 10 } if config.Redis.MinIdleConn == 0 { config.Redis.MinIdleConn = 5 } // 日志默认配置 if config.Log.Level == "" { config.Log.Level = "info" } if config.Log.Format == "" { config.Log.Format = "json" } if config.Log.Output == "" { config.Log.Output = "stdout" } if config.Log.MaxSize == 0 { config.Log.MaxSize = 100 } if config.Log.MaxBackups == 0 { config.Log.MaxBackups = 3 } if config.Log.MaxAge == 0 { config.Log.MaxAge = 7 } } // LoadFromEnv 从环境变量加载配置 func (m *Manager) LoadFromEnv() { m.mutex.Lock() defer m.mutex.Unlock() if m.config == nil { m.config = &Config{} } // 从环境变量覆盖配置 if host := os.Getenv("SERVER_HOST"); host != "" { m.config.Server.Host = host } if dbHost := os.Getenv("DB_HOST"); dbHost != "" { m.config.Database.Host = dbHost } if redisHost := os.Getenv("REDIS_HOST"); redisHost != "" { m.config.Redis.Host = redisHost } m.logger.Info("Environment variables loaded") } // Save 保存配置到文件 func (m *Manager) Save() error { m.mutex.RLock() config := m.config m.mutex.RUnlock() if config == nil { return fmt.Errorf("no config to save") } data, err := json.MarshalIndent(config, "", " ") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } // 确保目录存在 dir := filepath.Dir(m.configPath) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create config directory: %w", err) } if err := ioutil.WriteFile(m.configPath, data, 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } m.logger.Info("Configuration saved", "path", m.configPath) return nil } ================================================ FILE: internal/config/loader.go ================================================ package config import ( "errors" "fmt" "os" "path/filepath" "sort" "strconv" "strings" "gopkg.in/yaml.v3" ) // Loader merges configuration files for a service. type Loader struct { baseDir string env string service string explicitFiles []string } // Option customises a Loader instance. type Option func(*Loader) // NewLoader constructs a Loader with optional overrides. func NewLoader(opts ...Option) *Loader { loader := &Loader{ baseDir: "configs", env: normalizeEnv(os.Getenv("APP_ENV")), } if loader.env == "" { loader.env = "development" } for _, opt := range opts { opt(loader) } if loader.service == "" { loader.service = strings.TrimSpace(os.Getenv("SERVICE_NAME")) } if path := strings.TrimSpace(os.Getenv("CONFIG_PATH")); path != "" { loader.explicitFiles = []string{path} } else if path := strings.TrimSpace(os.Getenv("CONFIG_FILE")); path != "" { loader.explicitFiles = []string{path} } if loader.baseDir == "" { loader.baseDir = "." } return loader } // WithBaseDir sets the base search directory for configuration files. func WithBaseDir(dir string) Option { return func(l *Loader) { if dir == "" { return } l.baseDir = filepath.Clean(dir) } } // WithEnvironment sets the active environment (e.g. development, production). func WithEnvironment(env string) Option { return func(l *Loader) { l.env = normalizeEnv(env) } } // WithService sets the logical service name whose configuration should be loaded. func WithService(service string) Option { return func(l *Loader) { l.service = strings.TrimSpace(service) } } // WithExplicitFiles supplies explicit configuration files and bypasses discovery. func WithExplicitFiles(files ...string) Option { return func(l *Loader) { cleaned := make([]string, 0, len(files)) for _, file := range files { file = strings.TrimSpace(file) if file != "" { cleaned = append(cleaned, file) } } if len(cleaned) > 0 { l.explicitFiles = cleaned } } } // Load gathers and merges configuration files into a Config instance. func (l *Loader) Load() (*Config, []string, error) { candidates := l.resolveCandidates() if len(candidates) == 0 { return nil, nil, fmt.Errorf("config: no configuration candidates resolved") } var ( cfg Config used []string errs []error ) for _, path := range candidates { data, err := os.ReadFile(path) if err != nil { if errors.Is(err, os.ErrNotExist) { continue } errs = append(errs, fmt.Errorf("config: read %s: %w", path, err)) continue } if err := yaml.Unmarshal(data, &cfg); err != nil { errs = append(errs, fmt.Errorf("config: parse %s: %w", path, err)) continue } used = append(used, path) } if len(used) == 0 { if len(errs) > 0 { return nil, nil, errors.Join(errs...) } return nil, nil, fmt.Errorf( "config: no configuration files found for service %q (env=%s) under %s", l.service, l.env, l.baseDir, ) } cfg.ApplyDefaults() l.applyEnvOverrides(&cfg) if l.service != "" && cfg.Service.Name == "" { cfg.Service.Name = l.service } if cfg.App.Environment == "" { cfg.App.Environment = l.env } if err := cfg.Validate(); err != nil { return nil, nil, err } return &cfg, used, nil } // LoadInto hydrates the supplied target structure with the merged configuration. func (l *Loader) LoadInto(target any) ([]string, error) { cfg, files, err := l.Load() if err != nil { return nil, err } // Marshal then unmarshal to allow the caller to provide a tailored struct shape. data, err := yaml.Marshal(cfg) if err != nil { return nil, fmt.Errorf("config: marshal combined config: %w", err) } if err := yaml.Unmarshal(data, target); err != nil { return nil, fmt.Errorf("config: hydrate target: %w", err) } return files, nil } // Environment exposes the current loader environment value. func (l *Loader) Environment() string { return l.env } // Service exposes the service name for the loader. func (l *Loader) Service() string { return l.service } // BaseDir exposes the root search directory. func (l *Loader) BaseDir() string { return l.baseDir } func (l *Loader) resolveCandidates() []string { if len(l.explicitFiles) > 0 { return normalizePaths(l.explicitFiles) } names := []string{ "config.base.yaml", "config.yaml", } if l.env != "" { names = append(names, fmt.Sprintf("config.%s.yaml", l.env)) } if l.service != "" { names = append(names, fmt.Sprintf("%s.yaml", l.service)) if l.env != "" { names = append(names, fmt.Sprintf("%s.%s.yaml", l.service, l.env)) } } paths := make([]string, 0, len(names)) for _, name := range names { if name == "" { continue } paths = append(paths, filepath.Join(l.baseDir, name)) } return normalizePaths(paths) } func (l *Loader) applyEnvOverrides(cfg *Config) { overrideString := func(target *string, keys ...string) { for _, key := range keys { if value := strings.TrimSpace(os.Getenv(key)); value != "" { *target = value return } } } overrideInt := func(target *int, keys ...string) { for _, key := range keys { if value := strings.TrimSpace(os.Getenv(key)); value != "" { if v, err := strconv.Atoi(value); err == nil { *target = v return } } } } overrideString(&cfg.Server.HTTP.Host, "SERVER_HTTP_HOST", "SERVER_HOST") overrideInt(&cfg.Server.HTTP.Port, "SERVER_HTTP_PORT", "SERVER_PORT") overrideString(&cfg.Database.MongoDB.URI, "MONGODB_URI") overrideString(&cfg.Database.MongoDB.Database, "MONGODB_DATABASE") overrideString(&cfg.Database.Redis.Addr, "REDIS_ADDR") overrideString(&cfg.Database.Redis.Password, "REDIS_PASSWORD") overrideString(&cfg.Security.JWT.Secret, "JWT_SECRET") overrideString(&cfg.Logging.Level, "LOG_LEVEL") overrideString(&cfg.Messaging.NATS.URL, "NATS_URL") overrideString(&cfg.Messaging.NATS.ClusterID, "NATS_CLUSTER_ID") overrideString(&cfg.Messaging.NATS.ClientID, "NATS_CLIENT_ID") overrideString(&cfg.Service.NodeID, "SERVICE_NODE_ID", "POD_NAME") } func normalizePaths(paths []string) []string { seen := make(map[string]struct{}, len(paths)) normalized := make([]string, 0, len(paths)) for _, path := range paths { if path == "" { continue } cleaned := filepath.Clean(path) if !filepath.IsAbs(cleaned) { abs, err := filepath.Abs(cleaned) if err == nil { cleaned = abs } } if _, exists := seen[cleaned]; exists { continue } seen[cleaned] = struct{}{} normalized = append(normalized, cleaned) } sort.Strings(normalized) return normalized } func normalizeEnv(env string) string { return strings.ToLower(strings.TrimSpace(env)) } ================================================ FILE: internal/config/loader_test.go ================================================ package config import ( "os" "path/filepath" "testing" ) func TestLoaderMergesFilesAndAppliesDefaults(t *testing.T) { dir := t.TempDir() base := `app: name: GreatestWorks Test version: 0.1.0 logging: level: warn ` service := `server: http: port: 9090 rpc: port: 18080 ` if err := os.WriteFile(filepath.Join(dir, "config.base.yaml"), []byte(base), 0o644); err != nil { t.Fatalf("write base config: %v", err) } if err := os.WriteFile(filepath.Join(dir, "game-service.yaml"), []byte(service), 0o644); err != nil { t.Fatalf("write service config: %v", err) } loader := NewLoader( WithBaseDir(dir), WithService("game-service"), WithEnvironment("development"), ) cfg, files, err := loader.Load() if err != nil { t.Fatalf("load config: %v", err) } if len(files) != 2 { t.Fatalf("expected 2 config files to be used, got %d", len(files)) } if cfg.App.Name != "GreatestWorks Test" { t.Fatalf("unexpected app name: %s", cfg.App.Name) } if cfg.Server.HTTP.Port != 9090 { t.Fatalf("expected HTTP port override to be 9090, got %d", cfg.Server.HTTP.Port) } if cfg.Server.RPC.Port != 18080 { t.Fatalf("expected RPC port override to be 18080, got %d", cfg.Server.RPC.Port) } if cfg.Logging.Format != "json" { t.Fatalf("expected logging format default json, got %s", cfg.Logging.Format) } if cfg.Security.JWT.Secret == "" { t.Fatalf("expected JWT secret to have default value") } } func TestLoaderRespectsEnvironmentOverrides(t *testing.T) { dir := t.TempDir() fileContent := `server: http: port: 9090 ` if err := os.WriteFile(filepath.Join(dir, "game-service.yaml"), []byte(fileContent), 0o644); err != nil { t.Fatalf("write config: %v", err) } t.Setenv("SERVER_HTTP_PORT", "8088") t.Setenv("MONGODB_URI", "mongodb://example:27017") loader := NewLoader( WithBaseDir(dir), WithService("game-service"), ) cfg, _, err := loader.Load() if err != nil { t.Fatalf("load config: %v", err) } if cfg.Server.HTTP.Port != 8088 { t.Fatalf("expected env override for HTTP port, got %d", cfg.Server.HTTP.Port) } if cfg.Database.MongoDB.URI != "mongodb://example:27017" { t.Fatalf("expected env override for mongo uri, got %s", cfg.Database.MongoDB.URI) } } ================================================ FILE: internal/config/manager.go ================================================ package config import ( "context" "sync" "time" "github.com/fsnotify/fsnotify" ) // WatcherFunc receives an updated configuration snapshot. type WatcherFunc func(*Config) // Manager caches configuration and optionally hot-reloads when files change. type Manager struct { loader *Loader mu sync.RWMutex cfg *Config watchers []WatcherFunc fsWatcher *fsnotify.Watcher watchedFiles map[string]struct{} debounceInterval time.Duration } // ManagerOption configures a Manager instance. type ManagerOption func(*Manager) // WithDebounce sets the debounce interval applied before reloading after file events. func WithDebounce(interval time.Duration) ManagerOption { return func(m *Manager) { if interval > 0 { m.debounceInterval = interval } } } // NewManager constructs a Manager using the provided loader and options. func NewManager(loader *Loader, opts ...ManagerOption) (*Manager, error) { if loader == nil { loader = NewLoader() } cfg, files, err := loader.Load() if err != nil { return nil, err } manager := &Manager{ loader: loader, cfg: cfg, watchers: make([]WatcherFunc, 0), watchedFiles: make(map[string]struct{}, len(files)), debounceInterval: 250 * time.Millisecond, } for _, opt := range opts { opt(manager) } for _, file := range files { manager.watchedFiles[file] = struct{}{} } return manager, nil } // Config returns a clone of the current configuration for safe concurrent use. func (m *Manager) Config() *Config { m.mu.RLock() defer m.mu.RUnlock() return m.cfg.Clone() } // OnChange registers a watcher callback and immediately invokes it asynchronously with the current config. func (m *Manager) OnChange(callback WatcherFunc) { if callback == nil { return } m.mu.Lock() m.watchers = append(m.watchers, callback) snapshot := m.cfg.Clone() m.mu.Unlock() go callback(snapshot) } // Reload forces the manager to reload configuration files and notify watchers on success. func (m *Manager) Reload() error { cfg, files, err := m.loader.Load() if err != nil { return err } newSet := make(map[string]struct{}, len(files)) for _, file := range files { newSet[file] = struct{}{} } m.mu.Lock() oldSet := m.watchedFiles m.cfg = cfg m.watchedFiles = newSet watchers := append([]WatcherFunc(nil), m.watchers...) watcher := m.fsWatcher m.mu.Unlock() if watcher != nil { for file := range oldSet { if _, ok := newSet[file]; !ok { _ = watcher.Remove(file) } } for file := range newSet { if _, ok := oldSet[file]; !ok { _ = watcher.Add(file) } } } for _, cb := range watchers { if cb != nil { cb(cfg.Clone()) } } return nil } // StartWatching begins watching configuration files for changes until the context is cancelled. func (m *Manager) StartWatching(ctx context.Context) error { m.mu.Lock() if m.fsWatcher != nil { m.mu.Unlock() return nil } watcher, err := fsnotify.NewWatcher() if err != nil { m.mu.Unlock() return err } for file := range m.watchedFiles { _ = watcher.Add(file) } m.fsWatcher = watcher debounce := m.debounceInterval m.mu.Unlock() go m.watchLoop(ctx, watcher, debounce) return nil } // Close stops file watching if it is active. func (m *Manager) Close() error { m.mu.Lock() watcher := m.fsWatcher m.fsWatcher = nil m.mu.Unlock() if watcher != nil { return watcher.Close() } return nil } func (m *Manager) watchLoop(ctx context.Context, watcher *fsnotify.Watcher, debounce time.Duration) { defer watcher.Close() var ( pending bool timer *time.Timer ) if debounce > 0 { timer = time.NewTimer(debounce) if !timer.Stop() { <-timer.C } } triggerReload := func() { _ = m.Reload() } for { var timerCh <-chan time.Time if timer != nil { timerCh = timer.C } select { case <-ctx.Done(): return case event, ok := <-watcher.Events: if !ok { return } if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Remove|fsnotify.Rename) == 0 { continue } if debounce <= 0 { triggerReload() continue } if !pending { pending = true timer.Reset(debounce) continue } if !timer.Stop() { select { case <-timer.C: default: } } timer.Reset(debounce) case <-timerCh: pending = false triggerReload() case <-watcher.Errors: // Errors are ignored; loader reload will surface issues when necessary. } } } ================================================ FILE: internal/config/types.go ================================================ package config import ( "fmt" "strings" "time" ) // Config coordinates all configuration sections for services. type Config struct { App AppConfig `yaml:"app"` Service ServiceConfig `yaml:"service"` Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` Messaging MessagingConfig `yaml:"messaging"` Logging LoggingConfig `yaml:"logging"` Security SecurityConfig `yaml:"security"` Monitoring MonitoringConfig `yaml:"monitoring"` Game GameConfig `yaml:"game"` Domain DomainConfig `yaml:"domain"` Application ApplicationConfig `yaml:"application"` Performance PerformanceConfig `yaml:"performance"` ThirdParty ThirdPartyConfig `yaml:"third_party"` Session SessionConfig `yaml:"session"` Gateway GatewayConfig `yaml:"gateway"` Environment EnvironmentConfig `yaml:"environment"` Observability ObservabilityConfig `yaml:"observability"` } // AppConfig contains global metadata. type AppConfig struct { Name string `yaml:"name"` Version string `yaml:"version"` Environment string `yaml:"environment"` Debug bool `yaml:"debug"` } // ServiceConfig captures per-service identifiers. type ServiceConfig struct { Name string `yaml:"name"` Version string `yaml:"version"` Environment string `yaml:"environment"` NodeID string `yaml:"node_id"` Region string `yaml:"region"` Cluster string `yaml:"cluster"` } // ServerConfig aggregates protocols served by the process. type ServerConfig struct { HTTP HTTPServerConfig `yaml:"http"` RPC RPCServerConfig `yaml:"rpc"` TCP TCPServerConfig `yaml:"tcp"` GRPC GRPCServerConfig `yaml:"grpc"` Metrics MetricsServerConfig `yaml:"metrics"` } // HTTPServerConfig holds HTTP server details. type HTTPServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` MaxHeaderBytes int `yaml:"max_header_bytes"` EnableCORS bool `yaml:"enable_cors"` EnableMetrics bool `yaml:"enable_metrics"` EnableRequestID bool `yaml:"enable_request_id"` EnableLogging bool `yaml:"enable_logging"` EnableRecovery bool `yaml:"enable_recovery"` EnableSwagger bool `yaml:"enable_swagger"` RateLimitEnabled bool `yaml:"rate_limit_enabled"` RateLimitRequests int `yaml:"rate_limit_requests"` RateLimitWindow time.Duration `yaml:"rate_limit_window"` } // RPCServerConfig configures internal RPC endpoints. type RPCServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` MaxConnections int `yaml:"max_connections"` Timeout time.Duration `yaml:"timeout"` KeepAlive bool `yaml:"keep_alive"` KeepAlivePeriod time.Duration `yaml:"keep_alive_period"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` TLS TLSConfig `yaml:"tls"` } // TCPServerConfig configures raw TCP listeners. type TCPServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` MaxConnections int `yaml:"max_connections"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` HeartbeatEnabled bool `yaml:"heartbeat_enabled"` HeartbeatInterval time.Duration `yaml:"heartbeat_interval"` HeartbeatTimeout time.Duration `yaml:"heartbeat_timeout"` HeartbeatMaxMissed int `yaml:"heartbeat_max_missed"` KeepAlive bool `yaml:"keep_alive"` KeepAliveInterval time.Duration `yaml:"keep_alive_interval"` NoDelay bool `yaml:"no_delay"` MaxPacketSize int `yaml:"max_packet_size"` CompressionEnabled bool `yaml:"compression_enabled"` EncryptionEnabled bool `yaml:"encryption_enabled"` BufferSize int `yaml:"buffer_size"` } // GRPCServerConfig configures gRPC endpoints. type GRPCServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` TLS TLSConfig `yaml:"tls"` } // MetricsServerConfig configures /metrics endpoint. type MetricsServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Path string `yaml:"path"` } // DatabaseConfig contains persistence providers. type DatabaseConfig struct { MongoDB MongoDBConfig `yaml:"mongodb"` Redis RedisConfig `yaml:"redis"` SQL SQLConfig `yaml:"sql"` } // MongoDBConfig defines Mongo connection. type MongoDBConfig struct { URI string `yaml:"uri"` Database string `yaml:"database"` Username string `yaml:"username"` Password string `yaml:"password"` AuthSource string `yaml:"auth_source"` MaxPoolSize int `yaml:"max_pool_size"` MinPoolSize int `yaml:"min_pool_size"` MaxIdleTime time.Duration `yaml:"max_idle_time"` ConnectTimeout time.Duration `yaml:"connect_timeout"` SocketTimeout time.Duration `yaml:"socket_timeout"` ReplicaSet string `yaml:"replica_set"` RetryWrites bool `yaml:"retry_writes"` } // RedisConfig defines redis connection pool. type RedisConfig struct { Addr string `yaml:"addr"` Password string `yaml:"password"` DB int `yaml:"db"` PoolSize int `yaml:"pool_size"` MinIdleConns int `yaml:"min_idle_conns"` MaxRetries int `yaml:"max_retries"` DialTimeout time.Duration `yaml:"dial_timeout"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` PoolTimeout time.Duration `yaml:"pool_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` TLS TLSConfig `yaml:"tls"` Cluster RedisClusterConfig `yaml:"cluster"` } // RedisClusterConfig holds redis cluster endpoints. type RedisClusterConfig struct { Enabled bool `yaml:"enabled"` Addresses []string `yaml:"addresses"` } // SQLConfig describes relational database. type SQLConfig struct { Driver string `yaml:"driver"` DSN string `yaml:"dsn"` MaxOpenConns int `yaml:"max_open_conns"` MaxIdleConns int `yaml:"max_idle_conns"` ConnMaxLifetime time.Duration `yaml:"conn_max_lifetime"` } // MessagingConfig contains message bus details. type MessagingConfig struct { NATS NATSConfig `yaml:"nats"` Kafka KafkaConfig `yaml:"kafka"` SQS SQSConfig `yaml:"sqs"` Scheduler SchedulerConfig `yaml:"scheduler"` } // NATSConfig describes JetStream. type NATSConfig struct { URL string `yaml:"url"` ClusterID string `yaml:"cluster_id"` ClientID string `yaml:"client_id"` MaxReconnect int `yaml:"max_reconnect"` ReconnectWait time.Duration `yaml:"reconnect_wait"` Timeout time.Duration `yaml:"timeout"` Credentials string `yaml:"credentials"` TLS TLSConfig `yaml:"tls"` JetStream JetStreamConfig `yaml:"jetstream"` Subjects SubjectsConfig `yaml:"subjects"` } // JetStreamConfig toggles JetStream-specific options. type JetStreamConfig struct { Enabled bool `yaml:"enabled"` Domain string `yaml:"domain"` } // SubjectsConfig enumerates subject keys. type SubjectsConfig struct { PlayerEvents string `yaml:"player_events"` GameEvents string `yaml:"game_events"` SystemEvents string `yaml:"system_events"` DomainEvents string `yaml:"domain_events"` } // KafkaConfig placeholder for future. type KafkaConfig struct { Brokers []string `yaml:"brokers"` Topic string `yaml:"topic"` } // SQSConfig placeholder for AWS SQS. type SQSConfig struct { QueueURL string `yaml:"queue_url"` Timeout time.Duration `yaml:"timeout"` } // SchedulerConfig defines internal scheduler defaults. type SchedulerConfig struct { TickInterval time.Duration `yaml:"tick_interval"` MaxWorkers int `yaml:"max_workers"` } // LoggingConfig controls service logging. type LoggingConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` Output string `yaml:"output"` File FileLogConfig `yaml:"file"` Fields map[string]string `yaml:"fields"` Sensitive []string `yaml:"sensitive_fields"` } // FileLogConfig used when outputting to file. type FileLogConfig struct { Path string `yaml:"path"` MaxSize int `yaml:"max_size"` MaxBackups int `yaml:"max_backups"` MaxAge int `yaml:"max_age"` Compress bool `yaml:"compress"` } // SecurityConfig holds JWT, TLS, rate limiting. type SecurityConfig struct { JWT JWTConfig `yaml:"jwt"` RateLimit RateLimitConfig `yaml:"rate_limit"` Encryption EncryptionConfig `yaml:"encryption"` PasswordPolicy PasswordPolicyConfig `yaml:"password_policy"` DDoSProtection DDoSProtectionConfig `yaml:"ddos_protection"` TLS TLSConfig `yaml:"tls"` CORS CORSConfig `yaml:"cors"` } // JWTConfig for auth tokens. type JWTConfig struct { Secret string `yaml:"secret"` Issuer string `yaml:"issuer"` Audience string `yaml:"audience"` AccessTokenTTL time.Duration `yaml:"access_token_ttl"` RefreshTokenTTL time.Duration `yaml:"refresh_token_ttl"` } // RateLimitConfig describes throttling. type RateLimitConfig struct { Enabled bool `yaml:"enabled"` RequestsPerMinute int `yaml:"requests_per_minute"` Burst int `yaml:"burst"` Interval time.Duration `yaml:"interval"` GlobalLimit int `yaml:"global_limit"` PerIPLimit int `yaml:"per_ip_limit"` } // EncryptionConfig generic symmetric/asymmetric options. type EncryptionConfig struct { Enabled bool `yaml:"enabled"` Key string `yaml:"key"` Algorithm string `yaml:"algorithm"` } // PasswordPolicyConfig defines password complexity requirements. type PasswordPolicyConfig struct { MinLength int `yaml:"min_length"` RequireUppercase bool `yaml:"require_uppercase"` RequireLowercase bool `yaml:"require_lowercase"` RequireNumbers bool `yaml:"require_numbers"` RequireSymbols bool `yaml:"require_symbols"` MaxAttempts int `yaml:"max_attempts"` LockoutDuration time.Duration `yaml:"lockout_duration"` } // DDoSProtectionConfig captures advanced network protection thresholds. type DDoSProtectionConfig struct { Enabled bool `yaml:"enabled"` Threshold int `yaml:"threshold"` BanDuration time.Duration `yaml:"ban_duration"` IPWhitelist []string `yaml:"ip_whitelist"` IPBlacklist []string `yaml:"ip_blacklist"` RateLimitKey string `yaml:"rate_limit_key"` } // TLSConfig reused across sections. type TLSConfig struct { Enabled bool `yaml:"enabled"` CertFile string `yaml:"cert_file"` KeyFile string `yaml:"key_file"` CAFile string `yaml:"ca_file"` Insecure bool `yaml:"insecure"` MinVersion string `yaml:"min_version"` } // CORSConfig toggles HTTP cross-origin. type CORSConfig struct { AllowedOrigins []string `yaml:"allowed_origins"` AllowedMethods []string `yaml:"allowed_methods"` AllowedHeaders []string `yaml:"allowed_headers"` ExposeHeaders []string `yaml:"expose_headers"` AllowCredentials bool `yaml:"allow_credentials"` MaxAge int `yaml:"max_age"` } // MonitoringConfig collects metrics/tracing options. type MonitoringConfig struct { Health HealthConfig `yaml:"health"` Metrics MetricsConfig `yaml:"metrics"` Tracing TracingConfig `yaml:"tracing"` Profiling ProfilingConfig `yaml:"profiling"` Alerting AlertingConfig `yaml:"alerting"` Audit AuditConfig `yaml:"audit"` } // HealthConfig toggles health endpoint. type HealthConfig struct { Enabled bool `yaml:"enabled"` Path string `yaml:"path"` } // MetricsConfig represents legacy Prometheus settings. Deprecated: Prometheus // metrics have been removed; keep for backward compatibility in configuration // files only. type MetricsConfig struct { Enabled bool `yaml:"enabled"` Namespace string `yaml:"namespace"` } // TracingConfig configures distributed tracing. type TracingConfig struct { Enabled bool `yaml:"enabled"` Endpoint string `yaml:"endpoint"` SampleRate float64 `yaml:"sample_rate"` ServiceName string `yaml:"service_name"` } // ProfilingConfig toggles pprof. type ProfilingConfig struct { Enabled bool `yaml:"enabled"` Host string `yaml:"host"` Port int `yaml:"port"` } // AlertingConfig external alert endpoints. type AlertingConfig struct { Enabled bool `yaml:"enabled"` WebhookURL string `yaml:"webhook_url"` } // AuditConfig audit logs. type AuditConfig struct { Enabled bool `yaml:"enabled"` LogFile string `yaml:"log_file"` RetentionDays int `yaml:"retention_days"` } // GameConfig domain-specific. type GameConfig struct { Player PlayerConfig `yaml:"player"` Battle BattleConfig `yaml:"battle"` Experience ExperienceConfig `yaml:"experience"` Chat ChatConfig `yaml:"chat"` Ranking RankingConfig `yaml:"ranking"` Weather WeatherConfig `yaml:"weather"` Plant PlantConfig `yaml:"plant"` } // PlayerConfig domain defaults. type PlayerConfig struct { MaxLevel int `yaml:"max_level"` InitialGold int `yaml:"initial_gold"` InitialExperience int `yaml:"initial_experience"` MaxInventorySlots int `yaml:"max_inventory_slots"` MaxFriends int `yaml:"max_friends"` SessionTimeout time.Duration `yaml:"session_timeout"` } // BattleConfig domain defaults. type BattleConfig struct { MaxBattleTime time.Duration `yaml:"max_battle_time"` DamageVariance float64 `yaml:"damage_variance"` CriticalRateBase float64 `yaml:"critical_rate_base"` CriticalDamageBase float64 `yaml:"critical_damage_base"` MaxParticipants int `yaml:"max_participants"` TurnTimeout time.Duration `yaml:"turn_timeout"` } // ExperienceConfig domain defaults. type ExperienceConfig struct { BaseExpPerLevel int `yaml:"base_exp_per_level"` ExpMultiplier float64 `yaml:"exp_multiplier"` MaxExpBonus float64 `yaml:"max_exp_bonus"` } // ChatConfig domain defaults. type ChatConfig struct { MaxMessageLength int `yaml:"max_message_length"` RateLimit int `yaml:"rate_limit"` BannedWords []string `yaml:"banned_words"` SpamProtection bool `yaml:"spam_protection"` } // RankingConfig domain defaults. type RankingConfig struct { MaxEntries int `yaml:"max_entries"` UpdateInterval time.Duration `yaml:"update_interval"` CacheTTL time.Duration `yaml:"cache_ttl"` } // WeatherConfig domain defaults. type WeatherConfig struct { UpdateInterval time.Duration `yaml:"update_interval"` ForecastDays int `yaml:"forecast_days"` SeasonalEffects bool `yaml:"seasonal_effects"` } // PlantConfig domain defaults. type PlantConfig struct { GrowthSpeed float64 `yaml:"growth_speed"` HarvestBonus float64 `yaml:"harvest_bonus"` MaxFarmSize int `yaml:"max_farm_size"` } // DomainConfig placeholder for more domain-level sections. type DomainConfig struct { EnabledFeatures []string `yaml:"enabled_features"` } // ApplicationConfig cross-cutting app service settings. type ApplicationConfig struct { CommandBus BusConfig `yaml:"command_bus"` QueryBus BusConfig `yaml:"query_bus"` EventBus BusConfig `yaml:"event_bus"` } // BusConfig for command/query/event bus. type BusConfig struct { Timeout time.Duration `yaml:"timeout"` RetryAttempts int `yaml:"retry_attempts"` RetryDelay time.Duration `yaml:"retry_delay"` CacheTTL time.Duration `yaml:"cache_ttl"` DeadLetter bool `yaml:"dead_letter_queue"` } // PerformanceConfig runtime performance knobs. type PerformanceConfig struct { WorkerPool WorkerPoolConfig `yaml:"worker_pool"` Cache CacheConfig `yaml:"cache"` RateLimit RateLimitConfig `yaml:"rate_limit"` ConnectionPool ConnectionPoolConfig `yaml:"connection_pool"` } // WorkerPoolConfig concurrency settings. type WorkerPoolConfig struct { Size int `yaml:"size"` QueueSize int `yaml:"queue_size"` } // CacheConfig general caching defaults. type CacheConfig struct { DefaultTTL time.Duration `yaml:"default_ttl"` MaxEntries int `yaml:"max_entries"` CleanupInterval time.Duration `yaml:"cleanup_interval"` EvictionPolicy string `yaml:"eviction_policy"` } // ConnectionPoolConfig defines shared connection pool tuning parameters. type ConnectionPoolConfig struct { MaxIdle int `yaml:"max_idle"` MaxOpen int `yaml:"max_open"` MaxLifetime time.Duration `yaml:"max_lifetime"` } // ThirdPartyConfig external integration toggles. type ThirdPartyConfig struct { Payment PaymentConfig `yaml:"payment"` PushNotification PushNotificationConfig `yaml:"push_notification"` Email EmailConfig `yaml:"email"` OAuth OAuthConfig `yaml:"oauth"` } // PaymentConfig payments provider config. type PaymentConfig struct { Stripe StripeConfig `yaml:"stripe"` } // StripeConfig for Stripe integration. type StripeConfig struct { PublicKey string `yaml:"public_key"` SecretKey string `yaml:"secret_key"` WebhookSecret string `yaml:"webhook_secret"` } // PushNotificationConfig push provider config. type PushNotificationConfig struct { Firebase FirebaseConfig `yaml:"firebase"` } // FirebaseConfig details. type FirebaseConfig struct { ServerKey string `yaml:"server_key"` } // EmailConfig email provider config. type EmailConfig struct { SMTP SMTPConfig `yaml:"smtp"` } // OAuthConfig stores OAuth provider credentials. type OAuthConfig struct { Providers map[string]OAuthProviderConfig `yaml:"providers"` } // OAuthProviderConfig contains individual provider credentials. type OAuthProviderConfig struct { ClientID string `yaml:"client_id"` ClientSecret string `yaml:"client_secret"` } // SMTPConfig SMTP transport. type SMTPConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` } // EnvironmentConfig environment toggles. type EnvironmentConfig struct { HotReload bool `yaml:"hot_reload"` MockData bool `yaml:"mock_data"` TestMode bool `yaml:"test_mode"` } // ObservabilityConfig aggregator for metrics/tracing/logging combos. type ObservabilityConfig struct { Enabled bool `yaml:"enabled"` Backend string `yaml:"backend"` } // SessionConfig governs session lifecycle parameters. type SessionConfig struct { MaxSessionsPerUser int `yaml:"max_sessions_per_user"` SessionTimeout time.Duration `yaml:"session_timeout"` CleanupInterval time.Duration `yaml:"cleanup_interval"` StoreType string `yaml:"store_type"` } // GatewayConfig aggregates gateway-specific knobs used by the edge service. type GatewayConfig struct { GameServices GatewayGameServicesConfig `yaml:"game_services"` AuthService GatewayExternalServiceConfig `yaml:"auth_service"` Connection GatewayConnectionConfig `yaml:"connection"` Protocol GatewayProtocolConfig `yaml:"protocol"` Routing GatewayRoutingConfig `yaml:"routing"` } // GatewayGameServicesConfig captures dependencies on downstream game services. type GatewayGameServicesConfig struct { Discovery GatewayDiscoveryConfig `yaml:"discovery"` RPC GatewayRPCConfig `yaml:"rpc"` LoadBalancer GatewayLoadBalancerConfig `yaml:"load_balancer"` } // GatewayDiscoveryConfig defines discovery backends. type GatewayDiscoveryConfig struct { Type string `yaml:"type"` Consul GatewayConsulConfig `yaml:"consul"` Etcd GatewayEtcdConfig `yaml:"etcd"` Static GatewayStaticConfig `yaml:"static"` } // GatewayConsulConfig configures Consul discovery. type GatewayConsulConfig struct { Address string `yaml:"address"` Datacenter string `yaml:"datacenter"` ServiceName string `yaml:"service_name"` } // GatewayEtcdConfig configures Etcd discovery. type GatewayEtcdConfig struct { Endpoints []string `yaml:"endpoints"` } // GatewayStaticConfig provides static endpoints. type GatewayStaticConfig struct { Endpoints []string `yaml:"endpoints"` } // GatewayRPCConfig describes outbound RPC connectivity. type GatewayRPCConfig struct { Protocol string `yaml:"protocol"` Timeout time.Duration `yaml:"timeout"` RetryAttempts int `yaml:"retry_attempts"` RetryDelay time.Duration `yaml:"retry_delay"` CircuitBreaker GatewayCircuitBreakerConfig `yaml:"circuit_breaker"` } // GatewayCircuitBreakerConfig contains circuit breaker knobs shared between rpc and auth. type GatewayCircuitBreakerConfig struct { Enabled bool `yaml:"enabled"` FailureThreshold int `yaml:"failure_threshold"` Timeout time.Duration `yaml:"timeout"` MaxRequests int `yaml:"max_requests"` } // GatewayLoadBalancerConfig configures gateway load balancing strategy. type GatewayLoadBalancerConfig struct { Strategy string `yaml:"strategy"` HealthCheck GatewayHealthCheckConfig `yaml:"health_check"` } // GatewayHealthCheckConfig defines health probes for downstream services. type GatewayHealthCheckConfig struct { Enabled bool `yaml:"enabled"` Interval time.Duration `yaml:"interval"` Timeout time.Duration `yaml:"timeout"` Path string `yaml:"path"` } // GatewayExternalServiceConfig represents HTTP dependencies such as auth service. type GatewayExternalServiceConfig struct { BaseURL string `yaml:"base_url"` Timeout time.Duration `yaml:"timeout"` RetryAttempts int `yaml:"retry_attempts"` RetryDelay time.Duration `yaml:"retry_delay"` CircuitBreaker GatewayCircuitBreakerConfig `yaml:"circuit_breaker"` } // GatewayConnectionConfig covers local connection pool and messaging details. type GatewayConnectionConfig struct { MaxConnections int `yaml:"max_connections"` ConnectionTimeout time.Duration `yaml:"connection_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` CleanupInterval time.Duration `yaml:"cleanup_interval"` Session GatewaySessionConfig `yaml:"session"` MessageQueue GatewayMessageQueueConfig `yaml:"message_queue"` } // GatewaySessionConfig describes gateway session caches. type GatewaySessionConfig struct { Timeout time.Duration `yaml:"timeout"` CleanupInterval time.Duration `yaml:"cleanup_interval"` StoreType string `yaml:"store_type"` } // GatewayMessageQueueConfig configures message queue integration. type GatewayMessageQueueConfig struct { Enabled bool `yaml:"enabled"` Provider string `yaml:"provider"` Topics GatewayMessageTopicsConfig `yaml:"topics"` } // GatewayMessageTopicsConfig enumerates queue topics. type GatewayMessageTopicsConfig struct { PlayerEvents string `yaml:"player_events"` GameEvents string `yaml:"game_events"` SystemEvents string `yaml:"system_events"` } // GatewayProtocolConfig defines protocol bridging. type GatewayProtocolConfig struct { Client GatewayProtocolEndpointConfig `yaml:"client"` Game GatewayProtocolEndpointConfig `yaml:"game"` } // GatewayProtocolEndpointConfig captures per endpoint protocol settings. type GatewayProtocolEndpointConfig struct { Type string `yaml:"type"` Codec string `yaml:"codec"` Compression bool `yaml:"compression"` Encryption bool `yaml:"encryption"` } // GatewayRoutingConfig configures message routing rules. type GatewayRoutingConfig struct { Rules []GatewayRoutingRule `yaml:"rules"` LoadBalancer GatewayRoutingLoadBalancerConfig `yaml:"load_balancer"` } // GatewayRoutingRule describes a single routing entry. type GatewayRoutingRule struct { Pattern string `yaml:"pattern"` Target string `yaml:"target"` Method string `yaml:"method"` } // GatewayRoutingLoadBalancerConfig tunes routing load balancing. type GatewayRoutingLoadBalancerConfig struct { Strategy string `yaml:"strategy"` HealthCheck bool `yaml:"health_check"` Failover bool `yaml:"failover"` } // ApplyDefaults populates zero-value fields with opinionated defaults. func (c *Config) ApplyDefaults() { if c.App.Name == "" { c.App.Name = "GreatestWorks" } if c.App.Version == "" { c.App.Version = "1.0.0" } if c.App.Environment == "" { c.App.Environment = "development" } if c.Service.Name == "" { c.Service.Name = c.App.Name } if c.Service.Version == "" { c.Service.Version = c.App.Version } if c.Service.Environment == "" { c.Service.Environment = c.App.Environment } if c.Service.Cluster == "" { c.Service.Cluster = c.App.Environment } if c.Server.HTTP.Host == "" { c.Server.HTTP.Host = "0.0.0.0" } if c.Server.HTTP.Port == 0 { c.Server.HTTP.Port = 8080 } if c.Server.HTTP.ReadTimeout == 0 { c.Server.HTTP.ReadTimeout = 30 * time.Second } if c.Server.HTTP.WriteTimeout == 0 { c.Server.HTTP.WriteTimeout = 30 * time.Second } if c.Server.HTTP.IdleTimeout == 0 { c.Server.HTTP.IdleTimeout = 60 * time.Second } if c.Server.RPC.Host == "" { c.Server.RPC.Host = c.Server.HTTP.Host } if c.Server.RPC.Port == 0 { c.Server.RPC.Port = 8081 } if c.Server.RPC.Timeout == 0 { c.Server.RPC.Timeout = 30 * time.Second } if c.Server.RPC.KeepAlivePeriod == 0 { c.Server.RPC.KeepAlivePeriod = 30 * time.Second } if c.Server.TCP.Host == "" { c.Server.TCP.Host = c.Server.HTTP.Host } if c.Server.TCP.Port == 0 { c.Server.TCP.Port = 9090 } if c.Server.TCP.MaxConnections == 0 { c.Server.TCP.MaxConnections = 10000 } if c.Server.TCP.HeartbeatInterval == 0 { c.Server.TCP.HeartbeatInterval = 30 * time.Second } if c.Server.TCP.HeartbeatTimeout == 0 { c.Server.TCP.HeartbeatTimeout = 10 * time.Second } if c.Server.TCP.HeartbeatMaxMissed == 0 { c.Server.TCP.HeartbeatMaxMissed = 3 } if c.Server.TCP.KeepAliveInterval == 0 { c.Server.TCP.KeepAliveInterval = 30 * time.Second } if c.Server.Metrics.Host == "" { c.Server.Metrics.Host = "0.0.0.0" } if c.Server.Metrics.Port == 0 { c.Server.Metrics.Port = 9000 } if c.Server.Metrics.Path == "" { c.Server.Metrics.Path = "/metrics" } if c.Database.MongoDB.URI == "" { c.Database.MongoDB.URI = "mongodb://localhost:27017" } if c.Database.MongoDB.Database == "" { c.Database.MongoDB.Database = "mmo_game" } if c.Database.MongoDB.MaxPoolSize == 0 { c.Database.MongoDB.MaxPoolSize = 100 } if c.Database.MongoDB.MinPoolSize == 0 { c.Database.MongoDB.MinPoolSize = 5 } if c.Database.MongoDB.ConnectTimeout == 0 { c.Database.MongoDB.ConnectTimeout = 10 * time.Second } if c.Database.MongoDB.SocketTimeout == 0 { c.Database.MongoDB.SocketTimeout = 30 * time.Second } if c.Database.Redis.Addr == "" { c.Database.Redis.Addr = "localhost:6379" } if c.Database.Redis.PoolSize == 0 { c.Database.Redis.PoolSize = 100 } if c.Database.Redis.MinIdleConns == 0 { c.Database.Redis.MinIdleConns = 10 } if c.Database.Redis.DialTimeout == 0 { c.Database.Redis.DialTimeout = 5 * time.Second } if c.Database.Redis.ReadTimeout == 0 { c.Database.Redis.ReadTimeout = 3 * time.Second } if c.Database.Redis.WriteTimeout == 0 { c.Database.Redis.WriteTimeout = 3 * time.Second } if c.Database.Redis.PoolTimeout == 0 { c.Database.Redis.PoolTimeout = 4 * time.Second } if c.Database.Redis.IdleTimeout == 0 { c.Database.Redis.IdleTimeout = 5 * time.Minute } if c.Logging.Level == "" { c.Logging.Level = "info" } if c.Logging.Format == "" { c.Logging.Format = "json" } if c.Logging.Output == "" { c.Logging.Output = "stdout" } if c.Logging.Fields == nil { c.Logging.Fields = make(map[string]string) } if c.Security.JWT.Secret == "" { c.Security.JWT.Secret = "dev-secret-change-me" } if c.Security.JWT.AccessTokenTTL == 0 { c.Security.JWT.AccessTokenTTL = 15 * time.Minute } if c.Security.JWT.RefreshTokenTTL == 0 { c.Security.JWT.RefreshTokenTTL = 168 * time.Hour } if c.Security.RateLimit.RequestsPerMinute == 0 { c.Security.RateLimit.RequestsPerMinute = 1000 } if c.Security.RateLimit.Burst == 0 { c.Security.RateLimit.Burst = 100 } if c.Security.RateLimit.Interval == 0 { c.Security.RateLimit.Interval = time.Minute } if c.Security.RateLimit.GlobalLimit == 0 { c.Security.RateLimit.GlobalLimit = 1000 } if c.Security.RateLimit.PerIPLimit == 0 { c.Security.RateLimit.PerIPLimit = 100 } if c.Security.Encryption.Algorithm == "" { c.Security.Encryption.Algorithm = "AES-256-GCM" } if c.Security.PasswordPolicy.MinLength == 0 { c.Security.PasswordPolicy.MinLength = 8 } if c.Security.PasswordPolicy.MaxAttempts == 0 { c.Security.PasswordPolicy.MaxAttempts = 5 } if c.Security.PasswordPolicy.LockoutDuration == 0 { c.Security.PasswordPolicy.LockoutDuration = 15 * time.Minute } if c.Security.DDoSProtection.Threshold == 0 { c.Security.DDoSProtection.Threshold = 1000 } if c.Security.DDoSProtection.BanDuration == 0 { c.Security.DDoSProtection.BanDuration = time.Hour } if c.Security.CORS.AllowedMethods == nil { c.Security.CORS.AllowedMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"} } if c.Monitoring.Health.Path == "" { c.Monitoring.Health.Path = "/healthz" } if c.Monitoring.Metrics.Namespace == "" { c.Monitoring.Metrics.Namespace = "greatestworks" } if c.Monitoring.Profiling.Host == "" { c.Monitoring.Profiling.Host = "0.0.0.0" } if c.Monitoring.Profiling.Enabled && c.Monitoring.Profiling.Port == 0 { c.Monitoring.Profiling.Port = 6060 } if c.Messaging.NATS.MaxReconnect == 0 { c.Messaging.NATS.MaxReconnect = 10 } if c.Messaging.NATS.ReconnectWait == 0 { c.Messaging.NATS.ReconnectWait = 2 * time.Second } if c.Messaging.NATS.Timeout == 0 { c.Messaging.NATS.Timeout = 5 * time.Second } if c.Messaging.NATS.Subjects.PlayerEvents == "" { c.Messaging.NATS.Subjects.PlayerEvents = "player.events.>" } if c.Messaging.NATS.Subjects.GameEvents == "" { c.Messaging.NATS.Subjects.GameEvents = "game.events.>" } if c.Messaging.NATS.Subjects.SystemEvents == "" { c.Messaging.NATS.Subjects.SystemEvents = "system.events.>" } if c.Game.Player.MaxLevel == 0 { c.Game.Player.MaxLevel = 100 } if c.Game.Player.MaxInventorySlots == 0 { c.Game.Player.MaxInventorySlots = 100 } if c.Game.Battle.MaxBattleTime == 0 { c.Game.Battle.MaxBattleTime = 10 * time.Minute } if c.Game.Battle.TurnTimeout == 0 { c.Game.Battle.TurnTimeout = 30 * time.Second } if c.Game.Experience.BaseExpPerLevel == 0 { c.Game.Experience.BaseExpPerLevel = 100 } if c.Game.Experience.ExpMultiplier == 0 { c.Game.Experience.ExpMultiplier = 1.2 } if c.Game.Chat.MaxMessageLength == 0 { c.Game.Chat.MaxMessageLength = 500 } if c.Application.CommandBus.Timeout == 0 { c.Application.CommandBus.Timeout = 5 * time.Second } if c.Application.QueryBus.Timeout == 0 { c.Application.QueryBus.Timeout = 5 * time.Second } if c.Application.EventBus.Timeout == 0 { c.Application.EventBus.Timeout = 5 * time.Second } if c.Performance.WorkerPool.Size == 0 { c.Performance.WorkerPool.Size = 100 } if c.Performance.WorkerPool.QueueSize == 0 { c.Performance.WorkerPool.QueueSize = 1000 } if c.Performance.Cache.DefaultTTL == 0 { c.Performance.Cache.DefaultTTL = time.Hour } if c.Performance.Cache.CleanupInterval == 0 { c.Performance.Cache.CleanupInterval = 10 * time.Minute } if c.Performance.Cache.EvictionPolicy == "" { c.Performance.Cache.EvictionPolicy = "lfu" } if c.Performance.ConnectionPool.MaxIdle == 0 { c.Performance.ConnectionPool.MaxIdle = 100 } if c.Performance.ConnectionPool.MaxOpen == 0 { c.Performance.ConnectionPool.MaxOpen = 200 } if c.Performance.ConnectionPool.MaxLifetime == 0 { c.Performance.ConnectionPool.MaxLifetime = time.Hour } if c.Session.MaxSessionsPerUser == 0 { c.Session.MaxSessionsPerUser = 3 } if c.Session.SessionTimeout == 0 { c.Session.SessionTimeout = 24 * time.Hour } if c.Session.CleanupInterval == 0 { c.Session.CleanupInterval = time.Hour } if c.Session.StoreType == "" { c.Session.StoreType = "memory" } if c.Gateway.Connection.Session.Timeout == 0 { c.Gateway.Connection.Session.Timeout = 24 * time.Hour } if c.Gateway.Connection.Session.CleanupInterval == 0 { c.Gateway.Connection.Session.CleanupInterval = time.Hour } } // Validate ensures essential configuration values are present and acceptable. func (c *Config) Validate() error { var problems []string if c.Database.MongoDB.URI == "" { problems = append(problems, "database.mongodb.uri is required") } if c.Database.MongoDB.Database == "" { problems = append(problems, "database.mongodb.database is required") } if c.Security.JWT.Secret == "" { problems = append(problems, "security.jwt.secret is required") } if !validPort(c.Server.HTTP.Port) { problems = append(problems, fmt.Sprintf("server.http.port out of range: %d", c.Server.HTTP.Port)) } if !validPort(c.Server.RPC.Port) { problems = append(problems, fmt.Sprintf("server.rpc.port out of range: %d", c.Server.RPC.Port)) } if !validPort(c.Server.TCP.Port) { problems = append(problems, fmt.Sprintf("server.tcp.port out of range: %d", c.Server.TCP.Port)) } if !validPort(c.Server.Metrics.Port) { problems = append(problems, fmt.Sprintf("server.metrics.port out of range: %d", c.Server.Metrics.Port)) } if len(problems) > 0 { return fmt.Errorf("config validation failed: %s", strings.Join(problems, "; ")) } return nil } // Clone produces a shallow copy of the configuration with safe duplicated slices and maps. func (c *Config) Clone() *Config { if c == nil { return nil } clone := *c clone.Logging.Fields = copyStringMap(c.Logging.Fields) clone.Logging.Sensitive = copyStringSlice(c.Logging.Sensitive) clone.Security.CORS.AllowedOrigins = copyStringSlice(c.Security.CORS.AllowedOrigins) clone.Security.CORS.AllowedMethods = copyStringSlice(c.Security.CORS.AllowedMethods) clone.Security.CORS.AllowedHeaders = copyStringSlice(c.Security.CORS.AllowedHeaders) clone.Security.CORS.ExposeHeaders = copyStringSlice(c.Security.CORS.ExposeHeaders) clone.Security.DDoSProtection.IPWhitelist = copyStringSlice(c.Security.DDoSProtection.IPWhitelist) clone.Security.DDoSProtection.IPBlacklist = copyStringSlice(c.Security.DDoSProtection.IPBlacklist) clone.Game.Chat.BannedWords = copyStringSlice(c.Game.Chat.BannedWords) clone.Domain.EnabledFeatures = copyStringSlice(c.Domain.EnabledFeatures) clone.ThirdParty.OAuth.Providers = copyOAuthProviders(c.ThirdParty.OAuth.Providers) clone.Gateway.GameServices.Discovery.Etcd.Endpoints = copyStringSlice(c.Gateway.GameServices.Discovery.Etcd.Endpoints) clone.Gateway.GameServices.Discovery.Static.Endpoints = copyStringSlice(c.Gateway.GameServices.Discovery.Static.Endpoints) clone.Gateway.Routing.Rules = copyGatewayRoutingRules(c.Gateway.Routing.Rules) return &clone } func validPort(port int) bool { return port >= 0 && port <= 65535 } func copyStringSlice(src []string) []string { if len(src) == 0 { return nil } dst := make([]string, len(src)) copy(dst, src) return dst } func copyStringMap(src map[string]string) map[string]string { if len(src) == 0 { return nil } dst := make(map[string]string, len(src)) for k, v := range src { dst[k] = v } return dst } func copyOAuthProviders(src map[string]OAuthProviderConfig) map[string]OAuthProviderConfig { if len(src) == 0 { return nil } dst := make(map[string]OAuthProviderConfig, len(src)) for k, v := range src { dst[k] = v } return dst } func copyGatewayRoutingRules(src []GatewayRoutingRule) []GatewayRoutingRule { if len(src) == 0 { return nil } dst := make([]GatewayRoutingRule, len(src)) copy(dst, src) return dst } ================================================ FILE: internal/config_manager_base.go ================================================ package internal // ConfigManagerBase 配置管理器基类 type ConfigManagerBase struct { } // Load 加载配置 func (c *ConfigManagerBase) Load() { } // Get 获取配置项 func (c *ConfigManagerBase) Get(id uint32) interface{} { return nil } ================================================ FILE: internal/data_base.go ================================================ package internal // "greatestworks/internal/note/event" // TODO: 实现事件系统 type DataAsPublisher struct { // event.BasePublisher // TODO: 实现事件系统 } type DataAsSubscriber struct { // event.BaseSubscriber // TODO: 实现事件系统 } ================================================ FILE: internal/database/mongodb.go ================================================ // Package database 数据库连接池和操作封装 // Author: MMO Server Team // Created: 2024 package database import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" ) // MongoDB MongoDB数据库管理器 type MongoDB struct { client *mongo.Client database *mongo.Database config *MongoConfig } // MongoConfig MongoDB配置 type MongoConfig struct { URI string `json:"uri"` Database string `json:"database"` MaxPoolSize uint64 `json:"max_pool_size"` MinPoolSize uint64 `json:"min_pool_size"` MaxIdleTime int `json:"max_idle_time"` ConnectTimeout int `json:"connect_timeout"` SocketTimeout int `json:"socket_timeout"` } // NewMongoDB 创建MongoDB管理器 func NewMongoDB(config *MongoConfig) *MongoDB { return &MongoDB{ config: config, } } // Connect 连接到MongoDB func (m *MongoDB) Connect(ctx context.Context) error { // 设置连接选项 clientOptions := options.Client().ApplyURI(m.config.URI) if m.config.MaxPoolSize > 0 { clientOptions.SetMaxPoolSize(m.config.MaxPoolSize) } if m.config.MinPoolSize > 0 { clientOptions.SetMinPoolSize(m.config.MinPoolSize) } if m.config.MaxIdleTime > 0 { clientOptions.SetMaxConnIdleTime(time.Duration(m.config.MaxIdleTime) * time.Second) } if m.config.ConnectTimeout > 0 { clientOptions.SetConnectTimeout(time.Duration(m.config.ConnectTimeout) * time.Second) } if m.config.SocketTimeout > 0 { clientOptions.SetSocketTimeout(time.Duration(m.config.SocketTimeout) * time.Second) } // 创建客户端 client, err := mongo.Connect(ctx, clientOptions) if err != nil { return fmt.Errorf("failed to connect to MongoDB: %w", err) } // 测试连接 if err := client.Ping(ctx, readpref.Primary()); err != nil { return fmt.Errorf("failed to ping MongoDB: %w", err) } m.client = client m.database = client.Database(m.config.Database) return nil } // Disconnect 断开连接 func (m *MongoDB) Disconnect(ctx context.Context) error { if m.client != nil { if err := m.client.Disconnect(ctx); err != nil { return fmt.Errorf("failed to disconnect from MongoDB: %w", err) } } return nil } // GetCollection 获取集合 func (m *MongoDB) GetCollection(name string) *mongo.Collection { return m.database.Collection(name) } // GetDatabase 获取数据库 func (m *MongoDB) GetDatabase() *mongo.Database { return m.database } // GetClient 获取客户端 func (m *MongoDB) GetClient() *mongo.Client { return m.client } ================================================ FILE: internal/database/redis.go ================================================ // Package database Redis缓存操作封装 // Author: MMO Server Team // Created: 2024 package database import ( "context" "fmt" "time" "github.com/redis/go-redis/v9" ) // Redis Redis缓存管理器 type Redis struct { client *redis.Client config *RedisConfig } // RedisConfig Redis配置 type RedisConfig struct { Addr string `json:"addr"` Password string `json:"password"` DB int `json:"db"` PoolSize int `json:"pool_size"` MinIdleConns int `json:"min_idle_conns"` MaxIdleConns int `json:"max_idle_conns"` ConnMaxAge int `json:"conn_max_age"` DialTimeout int `json:"dial_timeout"` ReadTimeout int `json:"read_timeout"` WriteTimeout int `json:"write_timeout"` } // NewRedis 创建Redis管理器 func NewRedis(config *RedisConfig) *Redis { return &Redis{ config: config, } } // Connect 连接到Redis func (r *Redis) Connect(ctx context.Context) error { // 设置连接选项 opts := &redis.Options{ Addr: r.config.Addr, Password: r.config.Password, DB: r.config.DB, } if r.config.PoolSize > 0 { opts.PoolSize = r.config.PoolSize } if r.config.MinIdleConns > 0 { opts.MinIdleConns = r.config.MinIdleConns } if r.config.MaxIdleConns > 0 { opts.MaxIdleConns = r.config.MaxIdleConns } if r.config.ConnMaxAge > 0 { opts.ConnMaxLifetime = time.Duration(r.config.ConnMaxAge) * time.Second } if r.config.DialTimeout > 0 { opts.DialTimeout = time.Duration(r.config.DialTimeout) * time.Second } if r.config.ReadTimeout > 0 { opts.ReadTimeout = time.Duration(r.config.ReadTimeout) * time.Second } if r.config.WriteTimeout > 0 { opts.WriteTimeout = time.Duration(r.config.WriteTimeout) * time.Second } // 创建客户端 r.client = redis.NewClient(opts) // 测试连接 if err := r.client.Ping(ctx).Err(); err != nil { return fmt.Errorf("failed to connect to Redis: %w", err) } return nil } // Disconnect 断开连接 func (r *Redis) Disconnect() error { if r.client != nil { if err := r.client.Close(); err != nil { return fmt.Errorf("failed to disconnect from Redis: %w", err) } } return nil } // GetClient 获取客户端 func (r *Redis) GetClient() *redis.Client { return r.client } // Set 设置键值 func (r *Redis) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { return r.client.Set(ctx, key, value, expiration).Err() } // Get 获取值 func (r *Redis) Get(ctx context.Context, key string) (string, error) { return r.client.Get(ctx, key).Result() } // Del 删除键 func (r *Redis) Del(ctx context.Context, keys ...string) error { return r.client.Del(ctx, keys...).Err() } // Exists 检查键是否存在 func (r *Redis) Exists(ctx context.Context, keys ...string) (int64, error) { return r.client.Exists(ctx, keys...).Result() } // Expire 设置过期时间 func (r *Redis) Expire(ctx context.Context, key string, expiration time.Duration) error { return r.client.Expire(ctx, key, expiration).Err() } ================================================ FILE: internal/domain/ai/monster_ai.go ================================================ package ai import ( "context" "greatestworks/internal/domain/character" "math" ) // AIState AI状态 type AIState int32 const ( AIStateIdle AIState = 0 // 空闲 AIStateWalk AIState = 1 // 巡逻 AIStateChase AIState = 2 // 追击 AIStateCast AIState = 3 // 施法 AIStateGoback AIState = 4 // 返回 AIStateHurt AIState = 5 // 受伤 AIStateDeath AIState = 6 // 死亡 ) // MonsterAI 怪物AI type MonsterAI struct { owner *character.Monster state AIState stateTime float32 // 当前状态持续时间 target *character.Actor patrolRadius float32 // 巡逻半径 chaseRadius float32 // 追击半径 attackRadius float32 // 攻击半径 initPosition character.Vector3 currentSkillID int32 } // NewMonsterAI 创建怪物AI func NewMonsterAI(owner *character.Monster, patrolRadius, chaseRadius, attackRadius float32) *MonsterAI { return &MonsterAI{ owner: owner, state: AIStateIdle, patrolRadius: patrolRadius, chaseRadius: chaseRadius, attackRadius: attackRadius, } } // Start 初始化AI func (ai *MonsterAI) Start(ctx context.Context, initPos character.Vector3) error { ai.initPosition = initPos ai.state = AIStateIdle ai.stateTime = 0 return nil } // Update AI更新 func (ai *MonsterAI) Update(ctx context.Context, deltaTime float32) error { if ai.owner == nil || ai.owner.IsDeath() { ai.state = AIStateDeath return nil } ai.stateTime += deltaTime switch ai.state { case AIStateIdle: ai.updateIdle(ctx, deltaTime) case AIStateWalk: ai.updateWalk(ctx, deltaTime) case AIStateChase: ai.updateChase(ctx, deltaTime) case AIStateCast: ai.updateCast(ctx, deltaTime) case AIStateGoback: ai.updateGoback(ctx, deltaTime) case AIStateHurt: ai.updateHurt(ctx, deltaTime) } return nil } // updateIdle 更新空闲状态 func (ai *MonsterAI) updateIdle(ctx context.Context, deltaTime float32) { // 检测周围目标 if ai.detectTarget() { ai.changeState(AIStateChase) return } // 空闲一段时间后开始巡逻 if ai.stateTime > 3.0 { ai.changeState(AIStateWalk) } } // updateWalk 更新巡逻状态 func (ai *MonsterAI) updateWalk(ctx context.Context, deltaTime float32) { // 检测目标 if ai.detectTarget() { ai.changeState(AIStateChase) return } // 检查是否离出生点过远 dist := ai.owner.Position().Distance(ai.initPosition) if dist > ai.patrolRadius { ai.changeState(AIStateGoback) return } // 巡逻一段时间后停止 if ai.stateTime > 5.0 { ai.changeState(AIStateIdle) } // TODO: 实际移动逻辑 } // updateChase 更新追击状态 func (ai *MonsterAI) updateChase(ctx context.Context, deltaTime float32) { if ai.target == nil || ai.target.IsDeath() { ai.target = nil ai.changeState(AIStateIdle) return } // 检查目标是否超出追击范围 dist := ai.owner.DistanceTo(ai.target.Entity) if dist > ai.chaseRadius { ai.target = nil ai.changeState(AIStateGoback) return } // 检查是否在攻击范围内 if dist <= ai.attackRadius { ai.changeState(AIStateCast) return } // TODO: 追击移动逻辑 } // updateCast 更新施法状态 func (ai *MonsterAI) updateCast(ctx context.Context, deltaTime float32) { if ai.target == nil || ai.target.IsDeath() { ai.target = nil ai.changeState(AIStateIdle) return } // 检查目标距离 dist := ai.owner.DistanceTo(ai.target.Entity) if dist > ai.attackRadius { ai.changeState(AIStateChase) return } // 尝试释放技能 if ai.stateTime > 0.5 { // 施法间隔 ai.trySkillCast() ai.changeState(AIStateChase) } } // updateGoback 更新返回状态 func (ai *MonsterAI) updateGoback(ctx context.Context, deltaTime float32) { dist := ai.owner.Position().Distance(ai.initPosition) if dist < 1.0 { // 到达出生点,回满血 ai.owner.Revive(ctx) ai.changeState(AIStateIdle) return } // TODO: 返回移动逻辑 } // updateHurt 更新受伤状态 func (ai *MonsterAI) updateHurt(ctx context.Context, deltaTime float32) { if ai.stateTime > 0.5 { ai.changeState(AIStateChase) } } // OnHurt 受到伤害回调 func (ai *MonsterAI) OnHurt(attacker *character.Actor) { if attacker != nil && !attacker.IsDeath() { ai.target = attacker ai.changeState(AIStateHurt) } } // detectTarget 检测目标 func (ai *MonsterAI) detectTarget() bool { // TODO: 从地图获取附近的玩家 // 这里需要地图系统支持 return false } // trySkillCast 尝试释放技能 func (ai *MonsterAI) trySkillCast() { if ai.target == nil { return } // 获取第一个可用技能 sm := ai.owner.GetSkillManager() // TODO: 从技能管理器选择合适的技能 _ = sm } // changeState 切换状态 func (ai *MonsterAI) changeState(newState AIState) { ai.state = newState ai.stateTime = 0 } // GetState 获取当前状态 func (ai *MonsterAI) GetState() AIState { return ai.state } // MissileAI 投射物AI type MissileAI struct { owner *character.Missile target *character.Actor speed float32 maxRange float32 travelDist float32 } // NewMissileAI 创建投射物AI func NewMissileAI(owner *character.Missile, target *character.Actor, speed, maxRange float32) *MissileAI { return &MissileAI{ owner: owner, target: target, speed: speed, maxRange: maxRange, } } // Update 更新投射物 func (mai *MissileAI) Update(ctx context.Context, deltaTime float32) error { if mai.owner == nil { return nil } // 检查目标 if mai.target == nil || mai.target.IsDeath() { // 目标消失,销毁投射物 mai.owner.Destroy(ctx) return nil } // 计算移动 currentPos := mai.owner.Position() targetPos := mai.target.Position() dx := targetPos.X - currentPos.X dy := targetPos.Y - currentPos.Y dz := targetPos.Z - currentPos.Z dist := float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz))) if dist < 0.5 { // 命中目标 mai.onHit() mai.owner.Destroy(ctx) return nil } // 移动 moveSpeed := mai.speed * deltaTime if moveSpeed > dist { moveSpeed = dist } ratio := moveSpeed / dist newPos := character.NewVector3( currentPos.X+dx*ratio, currentPos.Y+dy*ratio, currentPos.Z+dz*ratio, ) mai.owner.SetPosition(newPos) mai.travelDist += moveSpeed // 检查是否超出最大射程 if mai.travelDist >= mai.maxRange { mai.owner.Destroy(ctx) } return nil } // onHit 命中处理 func (mai *MissileAI) onHit() { // TODO: 应用伤害或效果到目标 } ================================================ FILE: internal/domain/battle/battle.go ================================================ // Package battle 战斗领域 package battle import ( "github.com/google/uuid" "greatestworks/internal/domain/player" "time" ) // BattleID 战斗ID值对象 type BattleID struct { value string } // NewBattleID 创建新的战斗ID func NewBattleID() BattleID { return BattleID{value: uuid.New().String()} } // String 返回字符串表示 func (id BattleID) String() string { return id.value } // BattleStatus 战斗状态枚举 type BattleStatus int const ( BattleStatusWaiting BattleStatus = iota BattleStatusInProgress BattleStatusFinished BattleStatusCancelled ) // BattleType 战斗类型枚举 type BattleType int const ( BattleTypePvP BattleType = iota BattleTypePvE BattleTypeTeamPvP BattleTypeRaid ) // Battle 战斗聚合根 type Battle struct { id BattleID battleType BattleType status BattleStatus participants []*BattleParticipant rounds []*BattleRound winner *player.PlayerID startTime time.Time endTime *time.Time createdAt time.Time updatedAt time.Time version int64 } // BattleParticipant 战斗参与者 type BattleParticipant struct { PlayerID player.PlayerID `json:"player_id"` Team int `json:"team"` CurrentHP int `json:"current_hp"` CurrentMP int `json:"current_mp"` IsAlive bool `json:"is_alive"` DamageDealt int `json:"damage_dealt"` DamageTaken int `json:"damage_taken"` JoinedAt time.Time `json:"joined_at"` } // BattleRound 战斗回合 type BattleRound struct { RoundNumber int `json:"round_number"` Actions []*BattleAction `json:"actions"` StartTime time.Time `json:"start_time"` EndTime *time.Time `json:"end_time"` } // BattleAction 战斗行动 type BattleAction struct { ActionID string `json:"action_id"` ActorID player.PlayerID `json:"actor_id"` TargetID *player.PlayerID `json:"target_id,omitempty"` ActionType ActionType `json:"action_type"` SkillID *string `json:"skill_id,omitempty"` Damage int `json:"damage"` Healing int `json:"healing"` Critical bool `json:"critical"` Timestamp time.Time `json:"timestamp"` } // ActionType 行动类型枚举 type ActionType int const ( ActionTypeAttack ActionType = iota ActionTypeSkill ActionTypeDefend ActionTypeHeal ActionTypeEscape ) // NewBattle 创建新战斗 func NewBattle(battleType BattleType) *Battle { now := time.Now() return &Battle{ id: NewBattleID(), battleType: battleType, status: BattleStatusWaiting, participants: make([]*BattleParticipant, 0), rounds: make([]*BattleRound, 0), createdAt: now, updatedAt: now, version: 1, } } // ID 获取战斗ID func (b *Battle) ID() BattleID { return b.id } // Status 获取战斗状态 func (b *Battle) Status() BattleStatus { return b.status } // BattleType 获取战斗类型 func (b *Battle) GetBattleType() BattleType { return b.battleType } // Participants 获取参与者 func (b *Battle) Participants() []*BattleParticipant { return b.participants } // AddParticipant 添加参与者 func (b *Battle) AddParticipant(playerID player.PlayerID, team int, hp, mp int) error { if b.status != BattleStatusWaiting { return ErrBattleAlreadyStarted } // 检查玩家是否已经参与 for _, p := range b.participants { if p.PlayerID == playerID { return ErrPlayerAlreadyInBattle } } participant := &BattleParticipant{ PlayerID: playerID, Team: team, CurrentHP: hp, CurrentMP: mp, IsAlive: true, DamageDealt: 0, DamageTaken: 0, JoinedAt: time.Now(), } b.participants = append(b.participants, participant) b.updatedAt = time.Now() b.version++ return nil } // Start 开始战斗 func (b *Battle) Start() error { if b.status != BattleStatusWaiting { return ErrBattleAlreadyStarted } if len(b.participants) < 2 { return ErrInsufficientParticipants } b.status = BattleStatusInProgress b.startTime = time.Now() b.updatedAt = time.Now() b.version++ return nil } // ExecuteAction 执行战斗行动 func (b *Battle) ExecuteAction(actorID player.PlayerID, targetID *player.PlayerID, actionType ActionType, skillID *string) (*BattleAction, error) { if b.status != BattleStatusInProgress { return nil, ErrBattleNotInProgress } // 查找行动者 actor := b.findParticipant(actorID) if actor == nil { return nil, ErrPlayerNotInBattle } if !actor.IsAlive { return nil, ErrPlayerDead } // 创建行动 action := &BattleAction{ ActionID: uuid.New().String(), ActorID: actorID, TargetID: targetID, ActionType: actionType, SkillID: skillID, Timestamp: time.Now(), } // 执行行动逻辑 switch actionType { case ActionTypeAttack: b.executeAttack(action, actor) case ActionTypeDefend: b.executeDefend(action, actor) case ActionTypeHeal: b.executeHeal(action, actor) } // 添加到当前回合 b.addActionToCurrentRound(action) // 检查战斗是否结束 b.checkBattleEnd() b.updatedAt = time.Now() b.version++ return action, nil } // executeAttack 执行攻击 func (b *Battle) executeAttack(action *BattleAction, actor *BattleParticipant) { if action.TargetID == nil { return } target := b.findParticipant(*action.TargetID) if target == nil || !target.IsAlive { return } // 计算伤害(简化版本) baseDamage := 20 // 基础攻击力 damage := baseDamage // 暴击判断 if b.rollCritical() { damage *= 2 action.Critical = true } action.Damage = damage target.CurrentHP -= damage target.DamageTaken += damage actor.DamageDealt += damage if target.CurrentHP <= 0 { target.CurrentHP = 0 target.IsAlive = false } } // executeDefend 执行防御 func (b *Battle) executeDefend(action *BattleAction, actor *BattleParticipant) { // 防御状态,下次受到伤害减半(简化实现) } // executeHeal 执行治疗 func (b *Battle) executeHeal(action *BattleAction, actor *BattleParticipant) { healAmount := 30 // 基础治疗量 actor.CurrentHP += healAmount action.Healing = healAmount } // rollCritical 暴击判断 func (b *Battle) rollCritical() bool { // 简化版本:20%暴击率 return time.Now().UnixNano()%5 == 0 } // findParticipant 查找参与者 func (b *Battle) findParticipant(playerID player.PlayerID) *BattleParticipant { for _, p := range b.participants { if p.PlayerID == playerID { return p } } return nil } // addActionToCurrentRound 添加行动到当前回合 func (b *Battle) addActionToCurrentRound(action *BattleAction) { if len(b.rounds) == 0 { // 创建第一个回合 round := &BattleRound{ RoundNumber: 1, Actions: make([]*BattleAction, 0), StartTime: time.Now(), } b.rounds = append(b.rounds, round) } currentRound := b.rounds[len(b.rounds)-1] currentRound.Actions = append(currentRound.Actions, action) } // checkBattleEnd 检查战斗是否结束 func (b *Battle) checkBattleEnd() { // 统计各队伍存活人数 teamAlive := make(map[int]int) for _, p := range b.participants { if p.IsAlive { teamAlive[p.Team]++ } } // 如果只有一个队伍有存活者,战斗结束 aliveTeams := 0 winnerTeam := -1 for team, count := range teamAlive { if count > 0 { aliveTeams++ winnerTeam = team } } if aliveTeams <= 1 { b.endBattle(winnerTeam) } } // endBattle 结束战斗 func (b *Battle) endBattle(winnerTeam int) { b.status = BattleStatusFinished now := time.Now() b.endTime = &now // 设置获胜者(简化版本:取获胜队伍第一个存活玩家) for _, p := range b.participants { if p.Team == winnerTeam && p.IsAlive { b.winner = &p.PlayerID break } } // 结束当前回合 if len(b.rounds) > 0 { currentRound := b.rounds[len(b.rounds)-1] if currentRound.EndTime == nil { currentRound.EndTime = &now } } } // Winner 获取获胜者 func (b *Battle) Winner() *player.PlayerID { return b.winner } // IsFinished 是否已结束 func (b *Battle) IsFinished() bool { return b.status == BattleStatusFinished || b.status == BattleStatusCancelled } // Version 获取版本号 func (b *Battle) Version() int64 { return b.version } // StartTime 获取开始时间 func (b *Battle) StartTime() time.Time { return b.startTime } // EndTime 获取结束时间 func (b *Battle) EndTime() *time.Time { return b.endTime } // CreatedAt 获取创建时间 func (b *Battle) CreatedAt() time.Time { return b.createdAt } // UpdatedAt 获取更新时间 func (b *Battle) UpdatedAt() time.Time { return b.updatedAt } // Rounds 获取回合列表 func (b *Battle) Rounds() []*BattleRound { return b.rounds } ================================================ FILE: internal/domain/battle/errors.go ================================================ package battle import "greatestworks/internal/errors" // 战斗领域错误定义 - 使用统一的错误处理机制 var ( ErrBattleNotFound = errors.ErrBattleNotFound ErrBattleAlreadyStarted = errors.ErrBattleAlreadyStarted ErrBattleNotInProgress = errors.ErrBattleNotInProgress ErrPlayerNotInBattle = errors.ErrPlayerNotInBattle ErrPlayerAlreadyInBattle = errors.ErrPlayerAlreadyInBattle ErrInsufficientParticipants = errors.ErrInsufficientParticipants ErrInsufficientMana = errors.ErrInsufficientMana ErrInvalidTarget = errors.ErrInvalidTarget ErrBattleFinished = errors.ErrBattleFinished ErrPlayerDead = errors.NewDomainError("PLAYER_DEAD", "玩家已死亡") ErrInvalidAction = errors.NewDomainError("INVALID_ACTION", "无效的行动") ErrActionOnCooldown = errors.NewDomainError("ACTION_ON_COOLDOWN", "行动冷却中") ErrBattleAlreadyFinished = errors.NewDomainError("BATTLE_ALREADY_FINISHED", "战斗已结束") ErrBattleNotFinished = errors.NewDomainError("BATTLE_NOT_FINISHED", "战斗未结束") ) ================================================ FILE: internal/domain/battle/repository.go ================================================ package battle import ( "context" "greatestworks/internal/domain/player" ) // Repository 战斗仓储接口 type Repository interface { // Save 保存战斗 Save(ctx context.Context, battle *Battle) error // FindByID 根据ID查找战斗 FindByID(ctx context.Context, id BattleID) (*Battle, error) // Update 更新战斗 Update(ctx context.Context, battle *Battle) error // Delete 删除战斗 Delete(ctx context.Context, id BattleID) error // FindByPlayerID 根据玩家ID查找战斗 FindByPlayerID(ctx context.Context, playerID player.PlayerID, limit int) ([]*Battle, error) // FindActiveBattles 查找进行中的战斗 FindActiveBattles(ctx context.Context, limit int) ([]*Battle, error) // FindByStatus 根据状态查找战斗 FindByStatus(ctx context.Context, status BattleStatus, limit int) ([]*Battle, error) // FindByType 根据类型查找战斗 FindByType(ctx context.Context, battleType BattleType, limit int) ([]*Battle, error) // CountByPlayerID 统计玩家参与的战斗数量 CountByPlayerID(ctx context.Context, playerID player.PlayerID) (int64, error) } ================================================ FILE: internal/domain/battle/service.go ================================================ package battle import ( "context" "fmt" "greatestworks/internal/domain/player" "math/rand" "time" ) // Service 战斗领域服务 type Service struct { battleRepository Repository skillRegistry *SkillRegistry } // NewService 创建战斗领域服务 func NewService(battleRepository Repository) *Service { skillRegistry := NewSkillRegistry() skillRegistry.InitializeDefaultSkills() return &Service{ battleRepository: battleRepository, skillRegistry: skillRegistry, } } // CreateBattle 创建战斗 func (s *Service) CreateBattle(ctx context.Context, battleType BattleType) (*Battle, error) { battle := NewBattle(battleType) if err := s.battleRepository.Save(ctx, battle); err != nil { return nil, fmt.Errorf("save battle: %w", err) } return battle, nil } // JoinBattle 加入战斗 func (s *Service) JoinBattle(ctx context.Context, battleID BattleID, playerID player.PlayerID, team int, hp, mp int) error { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return fmt.Errorf("find battle: %w", err) } if err := battle.AddParticipant(playerID, team, hp, mp); err != nil { return err } if err := s.battleRepository.Update(ctx, battle); err != nil { return fmt.Errorf("update battle: %w", err) } return nil } // StartBattle 开始战斗 func (s *Service) StartBattle(ctx context.Context, battleID BattleID) error { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return fmt.Errorf("find battle: %w", err) } if err := battle.Start(); err != nil { return err } if err := s.battleRepository.Update(ctx, battle); err != nil { return fmt.Errorf("update battle: %w", err) } return nil } // ExecuteAttack 执行攻击 func (s *Service) ExecuteAttack(ctx context.Context, battleID BattleID, actorID player.PlayerID, targetID player.PlayerID) (*BattleAction, error) { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return nil, fmt.Errorf("find battle: %w", err) } action, err := battle.ExecuteAction(actorID, &targetID, ActionTypeAttack, nil) if err != nil { return nil, err } if err := s.battleRepository.Update(ctx, battle); err != nil { return nil, fmt.Errorf("update battle: %w", err) } return action, nil } // ExecuteSkill 执行技能 func (s *Service) ExecuteSkill(ctx context.Context, battleID BattleID, actorID player.PlayerID, targetID *player.PlayerID, skillID string) (*BattleAction, error) { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return nil, fmt.Errorf("find battle: %w", err) } // 验证技能是否存在 skill, err := s.skillRegistry.GetSkill(skillID) if err != nil { return nil, fmt.Errorf("get skill: %w", err) } // 检查行动者是否有足够的魔法值 actor := battle.findParticipant(actorID) if actor == nil { return nil, ErrPlayerNotInBattle } if actor.CurrentMP < skill.ManaCost() { return nil, ErrInsufficientMana } // 消耗魔法值 actor.CurrentMP -= skill.ManaCost() // 执行技能 action, err := s.executeSkillAction(battle, actorID, targetID, skill) if err != nil { return nil, err } if err := s.battleRepository.Update(ctx, battle); err != nil { return nil, fmt.Errorf("update battle: %w", err) } return action, nil } // executeSkillAction 执行技能行动 func (s *Service) executeSkillAction(battle *Battle, actorID player.PlayerID, targetID *player.PlayerID, skill *Skill) (*BattleAction, error) { action := &BattleAction{ ActionID: fmt.Sprintf("action_%d", time.Now().UnixNano()), ActorID: actorID, TargetID: targetID, ActionType: ActionTypeSkill, SkillID: &skill.id.value, Timestamp: time.Now(), } switch skill.GetSkillType() { case SkillTypeAttack: s.executeSkillAttack(battle, action, skill) case SkillTypeHeal: s.executeSkillHeal(battle, action, skill) case SkillTypeDefense: s.executeSkillDefense(battle, action, skill) case SkillTypeBuff: s.executeSkillBuff(battle, action, skill) case SkillTypeDebuff: s.executeSkillDebuff(battle, action, skill) } // 添加到当前回合 battle.addActionToCurrentRound(action) // 检查战斗是否结束 battle.checkBattleEnd() return action, nil } // executeSkillAttack 执行攻击技能 func (s *Service) executeSkillAttack(battle *Battle, action *BattleAction, skill *Skill) { if action.TargetID == nil { return } target := battle.findParticipant(*action.TargetID) if target == nil || !target.IsAlive { return } actor := battle.findParticipant(action.ActorID) if actor == nil { return } // 计算技能伤害 damage := skill.Damage() // 暴击判断 if s.rollCritical() { damage = int(float64(damage) * 1.5) action.Critical = true } action.Damage = damage target.CurrentHP -= damage target.DamageTaken += damage actor.DamageDealt += damage if target.CurrentHP <= 0 { target.CurrentHP = 0 target.IsAlive = false } // 应用技能效果 for _, effect := range skill.Effects() { s.applySkillEffect(target, effect) } } // executeSkillHeal 执行治疗技能 func (s *Service) executeSkillHeal(battle *Battle, action *BattleAction, skill *Skill) { var target *BattleParticipant if action.TargetID != nil { target = battle.findParticipant(*action.TargetID) } else { // 如果没有指定目标,治疗自己 target = battle.findParticipant(action.ActorID) } if target == nil || !target.IsAlive { return } healAmount := skill.Healing() target.CurrentHP += healAmount action.Healing = healAmount // 不能超过最大生命值(这里简化处理,假设最大生命值为初始生命值) // 实际项目中应该从玩家数据中获取最大生命值 } // executeSkillDefense 执行防御技能 func (s *Service) executeSkillDefense(battle *Battle, action *BattleAction, skill *Skill) { target := battle.findParticipant(action.ActorID) if target == nil { return } // 应用防御效果 for _, effect := range skill.Effects() { s.applySkillEffect(target, effect) } } // executeSkillBuff 执行增益技能 func (s *Service) executeSkillBuff(battle *Battle, action *BattleAction, skill *Skill) { var target *BattleParticipant if action.TargetID != nil { target = battle.findParticipant(*action.TargetID) } else { target = battle.findParticipant(action.ActorID) } if target == nil { return } // 应用增益效果 for _, effect := range skill.Effects() { s.applySkillEffect(target, effect) } } // executeSkillDebuff 执行减益技能 func (s *Service) executeSkillDebuff(battle *Battle, action *BattleAction, skill *Skill) { if action.TargetID == nil { return } target := battle.findParticipant(*action.TargetID) if target == nil || !target.IsAlive { return } // 应用减益效果 for _, effect := range skill.Effects() { s.applySkillEffect(target, effect) } } // applySkillEffect 应用技能效果 func (s *Service) applySkillEffect(target *BattleParticipant, effect *SkillEffect) { // 这里简化处理,实际项目中应该有完整的效果系统 switch effect.GetEffectType() { case EffectTypePoison: // 中毒效果,持续伤害 target.CurrentHP -= effect.Value() case EffectTypeBurn: // 燃烧效果,持续伤害 target.CurrentHP -= effect.Value() case EffectTypeAttackBoost: // 攻击力提升(这里简化处理) case EffectTypeDefenseBoost: // 防御力提升(这里简化处理) } if target.CurrentHP < 0 { target.CurrentHP = 0 target.IsAlive = false } } // rollCritical 暴击判断 func (s *Service) rollCritical() bool { // 20% 暴击率 return rand.Intn(100) < 20 } // GetBattleStatus 获取战斗状态 func (s *Service) GetBattleStatus(ctx context.Context, battleID BattleID) (*Battle, error) { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return nil, fmt.Errorf("find battle: %w", err) } return battle, nil } // GetPlayerBattles 获取玩家的战斗列表 func (s *Service) GetPlayerBattles(ctx context.Context, playerID player.PlayerID, limit int) ([]*Battle, error) { battles, err := s.battleRepository.FindByPlayerID(ctx, playerID, limit) if err != nil { return nil, fmt.Errorf("find player battles: %w", err) } return battles, nil } // EndBattle 结束战斗 func (s *Service) EndBattle(ctx context.Context, battleID BattleID) error { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return fmt.Errorf("find battle: %w", err) } if battle.IsFinished() { return ErrBattleAlreadyFinished } // 强制结束战斗 battle.status = BattleStatusCancelled now := time.Now() battle.endTime = &now battle.updatedAt = now battle.version++ if err := s.battleRepository.Update(ctx, battle); err != nil { return fmt.Errorf("update battle: %w", err) } return nil } // CalculateBattleRewards 计算战斗奖励 func (s *Service) CalculateBattleRewards(ctx context.Context, battleID BattleID) (map[player.PlayerID]*BattleReward, error) { battle, err := s.battleRepository.FindByID(ctx, battleID) if err != nil { return nil, fmt.Errorf("find battle: %w", err) } if !battle.IsFinished() { return nil, ErrBattleNotFinished } rewards := make(map[player.PlayerID]*BattleReward) for _, participant := range battle.Participants() { reward := &BattleReward{ PlayerID: participant.PlayerID, Exp: s.calculateExpReward(participant, battle), Gold: s.calculateGoldReward(participant, battle), Items: s.calculateItemRewards(participant, battle), } // 获胜者额外奖励 if battle.Winner() != nil && *battle.Winner() == participant.PlayerID { reward.Exp = int64(float64(reward.Exp) * 1.5) reward.Gold = int64(float64(reward.Gold) * 1.2) } rewards[participant.PlayerID] = reward } return rewards, nil } // calculateExpReward 计算经验奖励 func (s *Service) calculateExpReward(participant *BattleParticipant, battle *Battle) int64 { baseExp := int64(100) // 根据造成的伤害调整 damageBonus := int64(participant.DamageDealt / 10) // 根据战斗时长调整 duration := battle.endTime.Sub(battle.startTime) timeBonus := int64(duration.Minutes() * 5) return baseExp + damageBonus + timeBonus } // calculateGoldReward 计算金币奖励 func (s *Service) calculateGoldReward(participant *BattleParticipant, battle *Battle) int64 { baseGold := int64(50) // 根据造成的伤害调整 damageBonus := int64(participant.DamageDealt / 20) return baseGold + damageBonus } // calculateItemRewards 计算物品奖励 func (s *Service) calculateItemRewards(participant *BattleParticipant, battle *Battle) []string { items := make([]string, 0) // 简单的随机掉落系统 if rand.Intn(100) < 30 { // 30% 概率掉落物品 items = append(items, "health_potion") } if rand.Intn(100) < 20 { // 20% 概率掉落装备 items = append(items, "basic_sword") } return items } // BattleReward 战斗奖励 type BattleReward struct { PlayerID player.PlayerID `json:"player_id"` Exp int64 `json:"exp"` Gold int64 `json:"gold"` Items []string `json:"items"` } ================================================ FILE: internal/domain/battle/skill.go ================================================ package battle import ( "errors" "time" ) // SkillID 技能ID值对象 type SkillID struct { value string } // NewSkillID 创建技能ID func NewSkillID(id string) SkillID { return SkillID{value: id} } // String 返回字符串表示 func (id SkillID) String() string { return id.value } // SkillType 技能类型枚举 type SkillType int const ( SkillTypeAttack SkillType = iota // 攻击技能 SkillTypeDefense // 防御技能 SkillTypeHeal // 治疗技能 SkillTypeBuff // 增益技能 SkillTypeDebuff // 减益技能 SkillTypeUtility // 辅助技能 ) // SkillTarget 技能目标类型 type SkillTarget int const ( SkillTargetSelf SkillTarget = iota // 自己 SkillTargetEnemy // 敌人 SkillTargetAlly // 队友 SkillTargetAll // 所有人 SkillTargetEnemyAll // 所有敌人 SkillTargetAllyAll // 所有队友 ) // Skill 技能实体 type Skill struct { id SkillID name string description string skillType SkillType targetType SkillTarget manaCost int cooldown time.Duration damage int healing int effects []*SkillEffect range_ float64 level int maxLevel int } // NewSkill 创建新技能 func NewSkill(id, name, description string, skillType SkillType, targetType SkillTarget) *Skill { return &Skill{ id: NewSkillID(id), name: name, description: description, skillType: skillType, targetType: targetType, manaCost: 10, cooldown: time.Second * 3, damage: 0, healing: 0, effects: make([]*SkillEffect, 0), range_: 5.0, level: 1, maxLevel: 10, } } // ID 获取技能ID func (s *Skill) ID() SkillID { return s.id } // Name 获取技能名称 func (s *Skill) Name() string { return s.name } // Description 获取技能描述 func (s *Skill) Description() string { return s.description } // SkillType 获取技能类型 func (s *Skill) GetSkillType() SkillType { return s.skillType } // TargetType 获取目标类型 func (s *Skill) TargetType() SkillTarget { return s.targetType } // ManaCost 获取魔法消耗 func (s *Skill) ManaCost() int { return s.manaCost } // Cooldown 获取冷却时间 func (s *Skill) Cooldown() time.Duration { return s.cooldown } // Damage 获取伤害值 func (s *Skill) Damage() int { return s.damage } // Healing 获取治疗值 func (s *Skill) Healing() int { return s.healing } // Effects 获取技能效果 func (s *Skill) Effects() []*SkillEffect { return s.effects } // Range 获取技能范围 func (s *Skill) Range() float64 { return s.range_ } // Level 获取技能等级 func (s *Skill) Level() int { return s.level } // MaxLevel 获取最大等级 func (s *Skill) MaxLevel() int { return s.maxLevel } // CanUpgrade 是否可以升级 func (s *Skill) CanUpgrade() bool { return s.level < s.maxLevel } // Upgrade 升级技能 func (s *Skill) Upgrade() error { if !s.CanUpgrade() { return errors.New("skill already at max level") } s.level++ // 升级时提升技能属性 s.damage = int(float64(s.damage) * 1.1) s.healing = int(float64(s.healing) * 1.1) return nil } // SkillEffect 技能效果 type SkillEffect struct { effectType EffectType value int duration time.Duration stackable bool } // EffectType 效果类型枚举 type EffectType int const ( EffectTypePoison EffectType = iota // 中毒 EffectTypeBurn // 燃烧 EffectTypeFreeze // 冰冻 EffectTypeStun // 眩晕 EffectTypeAttackBoost // 攻击力提升 EffectTypeDefenseBoost // 防御力提升 EffectTypeSpeedBoost // 速度提升 EffectTypeAttackReduction // 攻击力降低 EffectTypeDefenseReduction // 防御力降低 EffectTypeSpeedReduction // 速度降低 ) // NewSkillEffect 创建技能效果 func NewSkillEffect(effectType EffectType, value int, duration time.Duration, stackable bool) *SkillEffect { return &SkillEffect{ effectType: effectType, value: value, duration: duration, stackable: stackable, } } // EffectType 获取效果类型 func (e *SkillEffect) GetEffectType() EffectType { return e.effectType } // Value 获取效果值 func (e *SkillEffect) Value() int { return e.value } // Duration 获取持续时间 func (e *SkillEffect) Duration() time.Duration { return e.duration } // IsStackable 是否可叠加 func (e *SkillEffect) IsStackable() bool { return e.stackable } // PlayerSkill 玩家技能 type PlayerSkill struct { skill *Skill lastUsedTime time.Time uses int } // NewPlayerSkill 创建玩家技能 func NewPlayerSkill(skill *Skill) *PlayerSkill { return &PlayerSkill{ skill: skill, lastUsedTime: time.Time{}, uses: 0, } } // Skill 获取技能 func (ps *PlayerSkill) GetSkill() *Skill { return ps.skill } // CanUse 是否可以使用 func (ps *PlayerSkill) CanUse(currentMana int) bool { // 检查魔法值是否足够 if currentMana < ps.skill.ManaCost() { return false } // 检查冷却时间 if time.Since(ps.lastUsedTime) < ps.skill.Cooldown() { return false } return true } // Use 使用技能 func (ps *PlayerSkill) Use() error { if time.Since(ps.lastUsedTime) < ps.skill.Cooldown() { return errors.New("skill is on cooldown") } ps.lastUsedTime = time.Now() ps.uses++ return nil } // GetCooldownRemaining 获取剩余冷却时间 func (ps *PlayerSkill) GetCooldownRemaining() time.Duration { elapsed := time.Since(ps.lastUsedTime) if elapsed >= ps.skill.Cooldown() { return 0 } return ps.skill.Cooldown() - elapsed } // Uses 获取使用次数 func (ps *PlayerSkill) Uses() int { return ps.uses } // SkillRegistry 技能注册表 type SkillRegistry struct { skills map[string]*Skill } // NewSkillRegistry 创建技能注册表 func NewSkillRegistry() *SkillRegistry { return &SkillRegistry{ skills: make(map[string]*Skill), } } // RegisterSkill 注册技能 func (sr *SkillRegistry) RegisterSkill(skill *Skill) { sr.skills[skill.ID().String()] = skill } // GetSkill 获取技能 func (sr *SkillRegistry) GetSkill(skillID string) (*Skill, error) { skill, exists := sr.skills[skillID] if !exists { return nil, errors.New("skill not found") } return skill, nil } // GetAllSkills 获取所有技能 func (sr *SkillRegistry) GetAllSkills() []*Skill { skills := make([]*Skill, 0, len(sr.skills)) for _, skill := range sr.skills { skills = append(skills, skill) } return skills } // InitializeDefaultSkills 初始化默认技能 func (sr *SkillRegistry) InitializeDefaultSkills() { // 基础攻击技能 basicAttack := NewSkill("basic_attack", "基础攻击", "普通的物理攻击", SkillTypeAttack, SkillTargetEnemy) basicAttack.damage = 20 basicAttack.manaCost = 0 basicAttack.cooldown = time.Second * 1 sr.RegisterSkill(basicAttack) // 火球术 fireball := NewSkill("fireball", "火球术", "发射一个火球攻击敌人", SkillTypeAttack, SkillTargetEnemy) fireball.damage = 35 fireball.manaCost = 15 fireball.cooldown = time.Second * 3 fireball.effects = append(fireball.effects, NewSkillEffect(EffectTypeBurn, 5, time.Second*3, false)) sr.RegisterSkill(fireball) // 治疗术 heal := NewSkill("heal", "治疗术", "恢复目标的生命值", SkillTypeHeal, SkillTargetAlly) heal.healing = 30 heal.manaCost = 20 heal.cooldown = time.Second * 2 sr.RegisterSkill(heal) // 防御姿态 defense := NewSkill("defense_stance", "防御姿态", "提高防御力", SkillTypeDefense, SkillTargetSelf) defense.manaCost = 10 defense.cooldown = time.Second * 5 defense.effects = append(defense.effects, NewSkillEffect(EffectTypeDefenseBoost, 10, time.Second*10, false)) sr.RegisterSkill(defense) } ================================================ FILE: internal/domain/building/aggregate.go ================================================ package building import ( "fmt" "time" ) // BuildingAggregate 建筑聚合根 type BuildingAggregate struct { ID string `json:"id" bson:"_id"` PlayerID uint64 `json:"player_id" bson:"player_id"` BuildingTypeID string `json:"building_type_id" bson:"building_type_id"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` Level int32 `json:"level" bson:"level"` MaxLevel int32 `json:"max_level" bson:"max_level"` Status BuildingStatus `json:"status" bson:"status"` Category BuildingCategory `json:"category" bson:"category"` Position *Position `json:"position,omitempty" bson:"position,omitempty"` Size *Size `json:"size" bson:"size"` Orientation Orientation `json:"orientation" bson:"orientation"` Health int32 `json:"health" bson:"health"` MaxHealth int32 `json:"max_health" bson:"max_health"` Durability int32 `json:"durability" bson:"durability"` MaxDurability int32 `json:"max_durability" bson:"max_durability"` Effects []*BuildingEffect `json:"effects" bson:"effects"` Requirements []*Requirement `json:"requirements" bson:"requirements"` UpgradeCosts []*ResourceCost `json:"upgrade_costs" bson:"upgrade_costs"` MaintenanceCosts []*ResourceCost `json:"maintenance_costs" bson:"maintenance_costs"` Production *ProductionInfo `json:"production,omitempty" bson:"production,omitempty"` Storage *StorageInfo `json:"storage,omitempty" bson:"storage,omitempty"` Defense *DefenseInfo `json:"defense,omitempty" bson:"defense,omitempty"` Construction *ConstructionInfo `json:"construction,omitempty" bson:"construction,omitempty"` Upgrade *UpgradeInfo `json:"upgrade,omitempty" bson:"upgrade,omitempty"` Maintenance *MaintenanceInfo `json:"maintenance,omitempty" bson:"maintenance,omitempty"` Workers []*WorkerInfo `json:"workers" bson:"workers"` Visitors []*VisitorInfo `json:"visitors" bson:"visitors"` Tags []string `json:"tags" bson:"tags"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` LastActiveAt time.Time `json:"last_active_at" bson:"last_active_at"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewBuildingAggregate 创建新的建筑聚合 func NewBuildingAggregate(playerID uint64, buildingTypeID, name string, category BuildingCategory) *BuildingAggregate { now := time.Now() return &BuildingAggregate{ ID: generateBuildingID(), PlayerID: playerID, BuildingTypeID: buildingTypeID, Name: name, Description: "", Level: 1, MaxLevel: 10, Status: BuildingStatusPlanning, Category: category, Size: &Size{Width: 1, Height: 1, Depth: 1}, Orientation: OrientationNorth, Health: 100, MaxHealth: 100, Durability: 100, MaxDurability: 100, Effects: make([]*BuildingEffect, 0), Requirements: make([]*Requirement, 0), UpgradeCosts: make([]*ResourceCost, 0), MaintenanceCosts: make([]*ResourceCost, 0), Workers: make([]*WorkerInfo, 0), Visitors: make([]*VisitorInfo, 0), Tags: make([]string, 0), Metadata: make(map[string]interface{}), LastActiveAt: now, CreatedAt: now, UpdatedAt: now, } } // StartConstruction 开始建造 func (b *BuildingAggregate) StartConstruction(duration time.Duration, costs []*ResourceCost) error { if b.Status != BuildingStatusPlanning { return fmt.Errorf("building must be in planning status to start construction") } // 检查建造要求 if err := b.checkRequirements(); err != nil { return fmt.Errorf("construction requirements not met: %w", err) } // 创建建造信息 now := time.Now() b.Construction = &ConstructionInfo{ StartedAt: now, Duration: duration, CompletedAt: nil, Progress: 0.0, Costs: costs, Workers: make([]*WorkerAssignment, 0), Materials: make([]*MaterialUsage, 0), Status: ConstructionStatusInProgress, CreatedAt: now, UpdatedAt: now, } b.Status = BuildingStatusUnderConstruction b.UpdatedAt = now return nil } // UpdateConstructionProgress 更新建造进度 func (b *BuildingAggregate) UpdateConstructionProgress(progress float64) error { if b.Status != BuildingStatusUnderConstruction { return fmt.Errorf("building is not under construction") } if b.Construction == nil { return fmt.Errorf("construction info not found") } if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } b.Construction.Progress = progress b.Construction.UpdatedAt = time.Now() b.UpdatedAt = time.Now() // 检查是否完成建造 if progress >= 100 { return b.CompleteConstruction() } return nil } // CompleteConstruction 完成建造 func (b *BuildingAggregate) CompleteConstruction() error { if b.Status != BuildingStatusUnderConstruction { return fmt.Errorf("building is not under construction") } if b.Construction == nil { return fmt.Errorf("construction info not found") } now := time.Now() b.Construction.CompletedAt = &now b.Construction.Progress = 100.0 b.Construction.Status = ConstructionStatusCompleted b.Construction.UpdatedAt = now b.Status = BuildingStatusActive b.UpdatedAt = now return nil } // CancelConstruction 取消建造 func (b *BuildingAggregate) CancelConstruction(reason string) error { if b.Status != BuildingStatusUnderConstruction { return fmt.Errorf("building is not under construction") } if b.Construction == nil { return fmt.Errorf("construction info not found") } now := time.Now() b.Construction.Status = ConstructionStatusCancelled b.Construction.UpdatedAt = now b.Construction.SetMetadata("cancel_reason", reason) b.Status = BuildingStatusCancelled b.UpdatedAt = now return nil } // StartUpgrade 开始升级 func (b *BuildingAggregate) StartUpgrade(targetLevel int32, duration time.Duration, costs []*ResourceCost) error { if b.Status != BuildingStatusActive { return fmt.Errorf("building must be active to start upgrade") } if targetLevel <= b.Level { return fmt.Errorf("target level must be higher than current level") } if targetLevel > b.MaxLevel { return fmt.Errorf("target level exceeds maximum level") } // 检查升级要求 if err := b.checkUpgradeRequirements(targetLevel); err != nil { return fmt.Errorf("upgrade requirements not met: %w", err) } // 创建升级信息 now := time.Now() b.Upgrade = &UpgradeInfo{ FromLevel: b.Level, ToLevel: targetLevel, StartedAt: now, Duration: duration, CompletedAt: nil, Progress: 0.0, Costs: costs, Workers: make([]*WorkerAssignment, 0), Materials: make([]*MaterialUsage, 0), Status: UpgradeStatusInProgress, CreatedAt: now, UpdatedAt: now, } b.Status = BuildingStatusUpgrading b.UpdatedAt = now return nil } // UpdateUpgradeProgress 更新升级进度 func (b *BuildingAggregate) UpdateUpgradeProgress(progress float64) error { if b.Status != BuildingStatusUpgrading { return fmt.Errorf("building is not upgrading") } if b.Upgrade == nil { return fmt.Errorf("upgrade info not found") } if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } b.Upgrade.Progress = progress b.Upgrade.UpdatedAt = time.Now() b.UpdatedAt = time.Now() // 检查是否完成升级 if progress >= 100 { return b.CompleteUpgrade() } return nil } // CompleteUpgrade 完成升级 func (b *BuildingAggregate) CompleteUpgrade() error { if b.Status != BuildingStatusUpgrading { return fmt.Errorf("building is not upgrading") } if b.Upgrade == nil { return fmt.Errorf("upgrade info not found") } now := time.Now() b.Upgrade.CompletedAt = &now b.Upgrade.Progress = 100.0 b.Upgrade.Status = UpgradeStatusCompleted b.Upgrade.UpdatedAt = now // 更新建筑等级 b.Level = b.Upgrade.ToLevel // 应用升级效果 b.applyUpgradeEffects() b.Status = BuildingStatusActive b.UpdatedAt = now return nil } // CancelUpgrade 取消升级 func (b *BuildingAggregate) CancelUpgrade(reason string) error { if b.Status != BuildingStatusUpgrading { return fmt.Errorf("building is not upgrading") } if b.Upgrade == nil { return fmt.Errorf("upgrade info not found") } now := time.Now() b.Upgrade.Status = UpgradeStatusCancelled b.Upgrade.UpdatedAt = now b.Upgrade.SetMetadata("cancel_reason", reason) b.Status = BuildingStatusActive b.UpdatedAt = now return nil } // Repair 修理建筑 func (b *BuildingAggregate) Repair(amount int32, costs []*ResourceCost) error { if b.Status != BuildingStatusActive && b.Status != BuildingStatusDamaged { return fmt.Errorf("building cannot be repaired in current status") } if b.Health >= b.MaxHealth { return fmt.Errorf("building is already at full health") } // 修理建筑 b.Health += amount if b.Health > b.MaxHealth { b.Health = b.MaxHealth } // 如果完全修复,更新状态 if b.Health >= b.MaxHealth && b.Status == BuildingStatusDamaged { b.Status = BuildingStatusActive } b.UpdatedAt = time.Now() return nil } // TakeDamage 受到伤害 func (b *BuildingAggregate) TakeDamage(damage int32, damageType DamageType) error { if b.Status == BuildingStatusDestroyed { return fmt.Errorf("building is already destroyed") } // 计算实际伤害(考虑防御) actualDamage := b.calculateActualDamage(damage, damageType) // 扣除生命值 b.Health -= actualDamage if b.Health < 0 { b.Health = 0 } // 扣除耐久度 b.Durability -= actualDamage / 2 if b.Durability < 0 { b.Durability = 0 } // 更新状态 if b.Health <= 0 { b.Status = BuildingStatusDestroyed } else if b.Health < b.MaxHealth/2 { b.Status = BuildingStatusDamaged } b.UpdatedAt = time.Now() return nil } // Demolish 拆除建筑 func (b *BuildingAggregate) Demolish(reason string) error { if b.Status == BuildingStatusDestroyed { return fmt.Errorf("building is already destroyed") } // 停止所有进行中的操作 if b.Construction != nil && b.Construction.Status == ConstructionStatusInProgress { b.Construction.Status = ConstructionStatusCancelled } if b.Upgrade != nil && b.Upgrade.Status == UpgradeStatusInProgress { b.Upgrade.Status = UpgradeStatusCancelled } // 清空工人和访客 b.Workers = make([]*WorkerInfo, 0) b.Visitors = make([]*VisitorInfo, 0) b.Status = BuildingStatusDemolished b.SetMetadata("demolish_reason", reason) b.UpdatedAt = time.Now() return nil } // SetPosition 设置位置 func (b *BuildingAggregate) SetPosition(position *Position) error { if position == nil { return fmt.Errorf("position cannot be nil") } if err := position.Validate(); err != nil { return fmt.Errorf("invalid position: %w", err) } b.Position = position b.UpdatedAt = time.Now() return nil } // SetOrientation 设置朝向 func (b *BuildingAggregate) SetOrientation(orientation Orientation) error { if !orientation.IsValid() { return fmt.Errorf("invalid orientation: %v", orientation) } b.Orientation = orientation b.UpdatedAt = time.Now() return nil } // AddWorker 添加工人 func (b *BuildingAggregate) AddWorker(workerID uint64, role WorkerRole, efficiency float64) error { if b.Status != BuildingStatusActive { return fmt.Errorf("building must be active to add workers") } // 检查工人是否已存在 for _, worker := range b.Workers { if worker.WorkerID == workerID { return fmt.Errorf("worker %d is already assigned to this building", workerID) } } // 检查工人容量 maxWorkers := b.getMaxWorkers() if len(b.Workers) >= maxWorkers { return fmt.Errorf("building has reached maximum worker capacity: %d", maxWorkers) } // 添加工人 now := time.Now() worker := &WorkerInfo{ WorkerID: workerID, Role: role, Efficiency: efficiency, AssignedAt: now, Status: WorkerStatusActive, CreatedAt: now, UpdatedAt: now, } b.Workers = append(b.Workers, worker) b.UpdatedAt = now return nil } // RemoveWorker 移除工人 func (b *BuildingAggregate) RemoveWorker(workerID uint64, reason string) error { for i, worker := range b.Workers { if worker.WorkerID == workerID { // 移除工人 b.Workers = append(b.Workers[:i], b.Workers[i+1:]...) b.UpdatedAt = time.Now() return nil } } return fmt.Errorf("worker %d not found in building", workerID) } // AddEffect 添加效果 func (b *BuildingAggregate) AddEffect(effect *BuildingEffect) error { if effect == nil { return fmt.Errorf("effect cannot be nil") } if err := effect.Validate(); err != nil { return fmt.Errorf("invalid effect: %w", err) } // 检查是否已存在相同效果 for _, existing := range b.Effects { if existing.Type == effect.Type && existing.Target == effect.Target { // 更新现有效果 existing.Value = effect.Value existing.Duration = effect.Duration existing.UpdatedAt = time.Now() b.UpdatedAt = time.Now() return nil } } // 添加新效果 effect.CreatedAt = time.Now() effect.UpdatedAt = time.Now() b.Effects = append(b.Effects, effect) b.UpdatedAt = time.Now() return nil } // RemoveEffect 移除效果 func (b *BuildingAggregate) RemoveEffect(effectType EffectType, target string) error { for i, effect := range b.Effects { if effect.Type == effectType && effect.Target == target { // 移除效果 b.Effects = append(b.Effects[:i], b.Effects[i+1:]...) b.UpdatedAt = time.Now() return nil } } return fmt.Errorf("effect not found: type=%v, target=%s", effectType, target) } // UpdateProduction 更新生产信息 func (b *BuildingAggregate) UpdateProduction(production *ProductionInfo) error { if production == nil { return fmt.Errorf("production info cannot be nil") } if err := production.Validate(); err != nil { return fmt.Errorf("invalid production info: %w", err) } b.Production = production b.UpdatedAt = time.Now() return nil } // UpdateStorage 更新存储信息 func (b *BuildingAggregate) UpdateStorage(storage *StorageInfo) error { if storage == nil { return fmt.Errorf("storage info cannot be nil") } if err := storage.Validate(); err != nil { return fmt.Errorf("invalid storage info: %w", err) } b.Storage = storage b.UpdatedAt = time.Now() return nil } // UpdateDefense 更新防御信息 func (b *BuildingAggregate) UpdateDefense(defense *DefenseInfo) error { if defense == nil { return fmt.Errorf("defense info cannot be nil") } if err := defense.Validate(); err != nil { return fmt.Errorf("invalid defense info: %w", err) } b.Defense = defense b.UpdatedAt = time.Now() return nil } // PerformMaintenance 执行维护 func (b *BuildingAggregate) PerformMaintenance(maintenanceType MaintenanceType, costs []*ResourceCost) error { if b.Status != BuildingStatusActive { return fmt.Errorf("building must be active to perform maintenance") } // 创建或更新维护信息 if b.Maintenance == nil { now := time.Now() b.Maintenance = &MaintenanceInfo{ LastMaintenanceAt: &now, NextMaintenanceAt: now.Add(24 * time.Hour), // 默认24小时后需要维护 MaintenanceLevel: 100, Costs: make([]*ResourceCost, 0), History: make([]*MaintenanceRecord, 0), CreatedAt: now, UpdatedAt: now, } } // 执行维护 now := time.Now() record := &MaintenanceRecord{ Type: maintenanceType, PerformedAt: now, Costs: costs, Result: "success", Notes: fmt.Sprintf("Performed %s maintenance", maintenanceType.String()), } b.Maintenance.History = append(b.Maintenance.History, record) b.Maintenance.LastMaintenanceAt = &now b.Maintenance.NextMaintenanceAt = now.Add(24 * time.Hour) b.Maintenance.MaintenanceLevel = 100 // 重置维护等级 b.Maintenance.UpdatedAt = now // 恢复耐久度 if b.Durability < b.MaxDurability { b.Durability += int32(float64(b.MaxDurability) * 0.1) // 恢复10%耐久度 if b.Durability > b.MaxDurability { b.Durability = b.MaxDurability } } b.UpdatedAt = now return nil } // SetMetadata 设置元数据 func (b *BuildingAggregate) SetMetadata(key string, value interface{}) { if b.Metadata == nil { b.Metadata = make(map[string]interface{}) } b.Metadata[key] = value b.UpdatedAt = time.Now() } // GetMetadata 获取元数据 func (b *BuildingAggregate) GetMetadata(key string) (interface{}, bool) { if b.Metadata == nil { return nil, false } value, exists := b.Metadata[key] return value, exists } // AddTag 添加标签 func (b *BuildingAggregate) AddTag(tag string) { // 检查标签是否已存在 for _, existing := range b.Tags { if existing == tag { return // 已存在,不重复添加 } } b.Tags = append(b.Tags, tag) b.UpdatedAt = time.Now() } // RemoveTag 移除标签 func (b *BuildingAggregate) RemoveTag(tag string) { for i, existing := range b.Tags { if existing == tag { b.Tags = append(b.Tags[:i], b.Tags[i+1:]...) b.UpdatedAt = time.Now() return } } } // HasTag 检查是否有标签 func (b *BuildingAggregate) HasTag(tag string) bool { for _, existing := range b.Tags { if existing == tag { return true } } return false } // 查询方法 // IsActive 检查是否活跃 func (b *BuildingAggregate) IsActive() bool { return b.Status == BuildingStatusActive } // IsUnderConstruction 检查是否在建造中 func (b *BuildingAggregate) IsUnderConstruction() bool { return b.Status == BuildingStatusUnderConstruction } // IsUpgrading 检查是否在升级中 func (b *BuildingAggregate) IsUpgrading() bool { return b.Status == BuildingStatusUpgrading } // IsDamaged 检查是否受损 func (b *BuildingAggregate) IsDamaged() bool { return b.Status == BuildingStatusDamaged } // IsDestroyed 检查是否被摧毁 func (b *BuildingAggregate) IsDestroyed() bool { return b.Status == BuildingStatusDestroyed } // CanUpgrade 检查是否可以升级 func (b *BuildingAggregate) CanUpgrade() bool { return b.Status == BuildingStatusActive && b.Level < b.MaxLevel } // CanRepair 检查是否可以修理 func (b *BuildingAggregate) CanRepair() bool { return (b.Status == BuildingStatusActive || b.Status == BuildingStatusDamaged) && b.Health < b.MaxHealth } // NeedsMaintenance 检查是否需要维护 func (b *BuildingAggregate) NeedsMaintenance() bool { if b.Maintenance == nil { return true } return time.Now().After(b.Maintenance.NextMaintenanceAt) } // GetEfficiency 获取效率 func (b *BuildingAggregate) GetEfficiency() float64 { if b.Status != BuildingStatusActive { return 0.0 } // 基础效率 baseEfficiency := 1.0 // 健康度影响 healthFactor := float64(b.Health) / float64(b.MaxHealth) // 耐久度影响 durabilityFactor := float64(b.Durability) / float64(b.MaxDurability) // 维护影响 maintenanceFactor := 1.0 if b.Maintenance != nil { maintenanceFactor = float64(b.Maintenance.MaintenanceLevel) / 100.0 } // 工人效率影响 workerFactor := b.getWorkerEfficiencyFactor() // 效果影响 effectFactor := b.getEffectFactor(EffectTypeEfficiency) return baseEfficiency * healthFactor * durabilityFactor * maintenanceFactor * workerFactor * effectFactor } // GetTotalUpgradeCost 获取升级总成本 func (b *BuildingAggregate) GetTotalUpgradeCost(targetLevel int32) []*ResourceCost { totalCosts := make(map[string]int64) for level := b.Level + 1; level <= targetLevel; level++ { levelCosts := b.getUpgradeCostForLevel(level) for _, cost := range levelCosts { totalCosts[cost.ResourceType] += cost.Amount } } result := make([]*ResourceCost, 0, len(totalCosts)) for resourceType, amount := range totalCosts { result = append(result, &ResourceCost{ ResourceType: resourceType, Amount: amount, }) } return result } // GetOccupiedArea 获取占用面积 func (b *BuildingAggregate) GetOccupiedArea() int32 { if b.Size == nil { return 1 } return b.Size.Width * b.Size.Height } // GetBoundingBox 获取边界框 func (b *BuildingAggregate) GetBoundingBox() *BoundingBox { if b.Position == nil || b.Size == nil { return nil } return &BoundingBox{ MinX: b.Position.X, MinY: b.Position.Y, MinZ: b.Position.Z, MaxX: b.Position.X + b.Size.Width - 1, MaxY: b.Position.Y + b.Size.Height - 1, MaxZ: b.Position.Z + b.Size.Depth - 1, } } // 私有方法 // checkRequirements 检查建造要求 func (b *BuildingAggregate) checkRequirements() error { for _, req := range b.Requirements { if !req.IsMet() { return fmt.Errorf("requirement not met: %s", req.Description) } } return nil } // checkUpgradeRequirements 检查升级要求 func (b *BuildingAggregate) checkUpgradeRequirements(targetLevel int32) error { // 检查基础要求 if err := b.checkRequirements(); err != nil { return err } // 检查等级特定要求 // TODO: 实现等级特定要求检查 return nil } // applyUpgradeEffects 应用升级效果 func (b *BuildingAggregate) applyUpgradeEffects() { // 提升最大生命值 b.MaxHealth = int32(float64(b.MaxHealth) * 1.1) b.Health = b.MaxHealth // 提升最大耐久度 b.MaxDurability = int32(float64(b.MaxDurability) * 1.1) b.Durability = b.MaxDurability // 应用等级相关的效果 // TODO: 根据建筑类型和等级应用特定效果 } // calculateActualDamage 计算实际伤害 func (b *BuildingAggregate) calculateActualDamage(damage int32, damageType DamageType) int32 { actualDamage := damage // 应用防御 if b.Defense != nil { defenseValue := b.Defense.GetDefenseValue(damageType) actualDamage -= defenseValue if actualDamage < 0 { actualDamage = 0 } } // 应用效果 effectFactor := b.getEffectFactor(EffectTypeDefense) actualDamage = int32(float64(actualDamage) * (1.0 - effectFactor)) return actualDamage } // getMaxWorkers 获取最大工人数 func (b *BuildingAggregate) getMaxWorkers() int { baseWorkers := 2 levelBonus := int(b.Level - 1) sizeBonus := int(b.GetOccupiedArea() / 4) return baseWorkers + levelBonus + sizeBonus } // getWorkerEfficiencyFactor 获取工人效率因子 func (b *BuildingAggregate) getWorkerEfficiencyFactor() float64 { if len(b.Workers) == 0 { return 0.5 // 没有工人时效率降低 } totalEfficiency := 0.0 for _, worker := range b.Workers { if worker.Status == WorkerStatusActive { totalEfficiency += worker.Efficiency } } averageEfficiency := totalEfficiency / float64(len(b.Workers)) return averageEfficiency } // getEffectFactor 获取效果因子 func (b *BuildingAggregate) getEffectFactor(effectType EffectType) float64 { factor := 0.0 for _, effect := range b.Effects { if effect.Type == effectType && effect.IsActive() { factor += effect.Value } } return factor } // getUpgradeCostForLevel 获取指定等级的升级成本 func (b *BuildingAggregate) getUpgradeCostForLevel(level int32) []*ResourceCost { // 基础成本随等级增长 baseCost := int64(100 * level * level) return []*ResourceCost{ {ResourceType: "wood", Amount: baseCost}, {ResourceType: "stone", Amount: baseCost / 2}, {ResourceType: "metal", Amount: baseCost / 4}, } } // generateBuildingID 生成建筑ID func generateBuildingID() string { return fmt.Sprintf("building_%d", time.Now().UnixNano()) } // 常量定义 const ( // 建筑相关常量 DefaultMaxLevel = int32(10) // 默认最大等级 DefaultMaxHealth = int32(100) // 默认最大生命值 DefaultMaxDurability = int32(100) // 默认最大耐久度 // 维护相关常量 MaintenanceInterval = 24 * time.Hour // 维护间隔 MaintenanceCost = int64(50) // 基础维护成本 // 建造相关常量 DefaultConstructionTime = 1 * time.Hour // 默认建造时间 DefaultUpgradeTime = 30 * time.Minute // 默认升级时间 // 效率相关常量 MinEfficiency = 0.1 // 最小效率 MaxEfficiency = 2.0 // 最大效率 BaseEfficiency = 1.0 // 基础效率 NoWorkerEfficiency = 0.5 // 无工人时的效率 ) // 验证函数 // ReconstructBuildingAggregate 从持久化数据重建建筑聚合根 func ReconstructBuildingAggregate( id string, playerID uint64, buildingTypeID string, name string, description string, level int32, maxLevel int32, status BuildingStatus, category BuildingCategory, position *Position, size *Size, orientation Orientation, health int32, maxHealth int32, durability int32, maxDurability int32, effects []*BuildingEffect, requirements []*Requirement, upgradeCosts []*ResourceCost, maintenanceCosts []*ResourceCost, tags []string, metadata map[string]interface{}, lastActiveAt time.Time, createdAt time.Time, updatedAt time.Time, ) *BuildingAggregate { return &BuildingAggregate{ ID: id, PlayerID: playerID, BuildingTypeID: buildingTypeID, Name: name, Description: description, Level: level, MaxLevel: maxLevel, Status: status, Category: category, Position: position, Size: size, Orientation: orientation, Health: health, MaxHealth: maxHealth, Durability: durability, MaxDurability: maxDurability, Effects: effects, Requirements: requirements, UpgradeCosts: upgradeCosts, MaintenanceCosts: maintenanceCosts, Workers: make([]*WorkerInfo, 0), Visitors: make([]*VisitorInfo, 0), Tags: tags, Metadata: metadata, LastActiveAt: lastActiveAt, CreatedAt: createdAt, UpdatedAt: updatedAt, } } // Validate 验证建筑聚合 func (b *BuildingAggregate) Validate() error { if b.ID == "" { return fmt.Errorf("building ID cannot be empty") } if b.PlayerID == 0 { return fmt.Errorf("player ID cannot be zero") } if b.BuildingTypeID == "" { return fmt.Errorf("building type ID cannot be empty") } if b.Name == "" { return fmt.Errorf("building name cannot be empty") } if !b.Status.IsValid() { return fmt.Errorf("invalid building status: %v", b.Status) } if !b.Category.IsValid() { return fmt.Errorf("invalid building category: %v", b.Category) } if b.Level < 1 { return fmt.Errorf("building level must be at least 1") } if b.Level > b.MaxLevel { return fmt.Errorf("building level cannot exceed max level") } if b.Health < 0 || b.Health > b.MaxHealth { return fmt.Errorf("building health must be between 0 and max health") } if b.Durability < 0 || b.Durability > b.MaxDurability { return fmt.Errorf("building durability must be between 0 and max durability") } if b.Size != nil { if err := b.Size.Validate(); err != nil { return fmt.Errorf("invalid building size: %w", err) } } if b.Position != nil { if err := b.Position.Validate(); err != nil { return fmt.Errorf("invalid building position: %w", err) } } if !b.Orientation.IsValid() { return fmt.Errorf("invalid building orientation: %v", b.Orientation) } // 验证效果 for _, effect := range b.Effects { if err := effect.Validate(); err != nil { return fmt.Errorf("invalid building effect: %w", err) } } // 验证要求 for _, req := range b.Requirements { if err := req.Validate(); err != nil { return fmt.Errorf("invalid building requirement: %w", err) } } // 验证成本 for _, cost := range b.UpgradeCosts { if err := cost.Validate(); err != nil { return fmt.Errorf("invalid upgrade cost: %w", err) } } for _, cost := range b.MaintenanceCosts { if err := cost.Validate(); err != nil { return fmt.Errorf("invalid maintenance cost: %w", err) } } // 验证生产信息 if b.Production != nil { if err := b.Production.Validate(); err != nil { return fmt.Errorf("invalid production info: %w", err) } } // 验证存储信息 if b.Storage != nil { if err := b.Storage.Validate(); err != nil { return fmt.Errorf("invalid storage info: %w", err) } } // 验证防御信息 if b.Defense != nil { if err := b.Defense.Validate(); err != nil { return fmt.Errorf("invalid defense info: %w", err) } } return nil } ================================================ FILE: internal/domain/building/entity.go ================================================ package building import ( "fmt" "time" ) // ConstructionInfo 建造信息实体 type ConstructionInfo struct { ID string `json:"id" bson:"_id"` BuildingID string `json:"building_id" bson:"building_id"` StartedAt time.Time `json:"started_at" bson:"started_at"` Duration time.Duration `json:"duration" bson:"duration"` CompletedAt *time.Time `json:"completed_at,omitempty" bson:"completed_at,omitempty"` Progress float64 `json:"progress" bson:"progress"` Costs []*ResourceCost `json:"costs" bson:"costs"` Workers []*WorkerAssignment `json:"workers" bson:"workers"` Materials []*MaterialUsage `json:"materials" bson:"materials"` Status ConstructionStatus `json:"status" bson:"status"` Blueprint *Blueprint `json:"blueprint,omitempty" bson:"blueprint,omitempty"` Phases []*ConstructionPhase `json:"phases" bson:"phases"` CurrentPhase *ConstructionPhase `json:"current_phase,omitempty" bson:"current_phase,omitempty"` QualityScore float64 `json:"quality_score" bson:"quality_score"` SafetyScore float64 `json:"safety_score" bson:"safety_score"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // ConstructionStatus 建造状态 type ConstructionStatus int32 const ( ConstructionStatusPlanning ConstructionStatus = iota + 1 // 规划中 ConstructionStatusInProgress // 进行中 ConstructionStatusPaused // 暂停 ConstructionStatusCompleted // 已完成 ConstructionStatusCancelled // 已取消 ConstructionStatusFailed // 失败 ) // String 返回建造状态的字符串表示 func (cs ConstructionStatus) String() string { switch cs { case ConstructionStatusPlanning: return "planning" case ConstructionStatusInProgress: return "in_progress" case ConstructionStatusPaused: return "paused" case ConstructionStatusCompleted: return "completed" case ConstructionStatusCancelled: return "cancelled" case ConstructionStatusFailed: return "failed" default: return "unknown" } } // IsValid 检查建造状态是否有效 func (cs ConstructionStatus) IsValid() bool { return cs >= ConstructionStatusPlanning && cs <= ConstructionStatusFailed } // NewConstructionInfo 创建新建造信息 func NewConstructionInfo(buildingID string, duration time.Duration) *ConstructionInfo { now := time.Now() return &ConstructionInfo{ ID: generateConstructionID(), BuildingID: buildingID, StartedAt: now, Duration: duration, Progress: 0.0, Costs: make([]*ResourceCost, 0), Workers: make([]*WorkerAssignment, 0), Materials: make([]*MaterialUsage, 0), Status: ConstructionStatusPlanning, Phases: make([]*ConstructionPhase, 0), QualityScore: 100.0, SafetyScore: 100.0, Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // AddWorker 添加工人 func (ci *ConstructionInfo) AddWorker(assignment *WorkerAssignment) error { if assignment == nil { return fmt.Errorf("worker assignment cannot be nil") } // 检查工人是否已分配 for _, existing := range ci.Workers { if existing.WorkerID == assignment.WorkerID { return fmt.Errorf("worker %d is already assigned", assignment.WorkerID) } } ci.Workers = append(ci.Workers, assignment) ci.UpdatedAt = time.Now() return nil } // RemoveWorker 移除工人 func (ci *ConstructionInfo) RemoveWorker(workerID uint64) error { for i, worker := range ci.Workers { if worker.WorkerID == workerID { ci.Workers = append(ci.Workers[:i], ci.Workers[i+1:]...) ci.UpdatedAt = time.Now() return nil } } return fmt.Errorf("worker %d not found", workerID) } // AddMaterial 添加材料 func (ci *ConstructionInfo) AddMaterial(material *MaterialUsage) error { if material == nil { return fmt.Errorf("material usage cannot be nil") } ci.Materials = append(ci.Materials, material) ci.UpdatedAt = time.Now() return nil } // AddPhase 添加阶段 func (ci *ConstructionInfo) AddPhase(phase *ConstructionPhase) error { if phase == nil { return fmt.Errorf("construction phase cannot be nil") } ci.Phases = append(ci.Phases, phase) ci.UpdatedAt = time.Now() return nil } // StartNextPhase 开始下一阶段 func (ci *ConstructionInfo) StartNextPhase() *ConstructionPhase { for _, phase := range ci.Phases { if phase.Status == PhaseStatusPending { phase.Start() ci.CurrentPhase = phase ci.UpdatedAt = time.Now() return phase } } return nil } // CompleteCurrentPhase 完成当前阶段 func (ci *ConstructionInfo) CompleteCurrentPhase() error { if ci.CurrentPhase == nil { return fmt.Errorf("no current phase to complete") } ci.CurrentPhase.Complete() ci.CurrentPhase = nil ci.UpdatedAt = time.Now() // 检查是否所有阶段都完成 allCompleted := true for _, phase := range ci.Phases { if phase.Status != PhaseStatusCompleted { allCompleted = false break } } if allCompleted { ci.Status = ConstructionStatusCompleted now := time.Now() ci.CompletedAt = &now ci.Progress = 100.0 } return nil } // UpdateProgress 更新进度 func (ci *ConstructionInfo) UpdateProgress(progress float64) error { if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } ci.Progress = progress ci.UpdatedAt = time.Now() if progress >= 100 { ci.Status = ConstructionStatusCompleted now := time.Now() ci.CompletedAt = &now } return nil } // SetMetadata 设置元数据 func (ci *ConstructionInfo) SetMetadata(key string, value interface{}) { if ci.Metadata == nil { ci.Metadata = make(map[string]interface{}) } ci.Metadata[key] = value ci.UpdatedAt = time.Now() } // GetMetadata 获取元数据 func (ci *ConstructionInfo) GetMetadata(key string) (interface{}, bool) { if ci.Metadata == nil { return nil, false } value, exists := ci.Metadata[key] return value, exists } // GetEstimatedCompletionTime 获取预计完成时间 func (ci *ConstructionInfo) GetEstimatedCompletionTime() time.Time { if ci.Progress <= 0 { return ci.StartedAt.Add(ci.Duration) } elapsed := time.Since(ci.StartedAt) estimatedTotal := time.Duration(float64(elapsed) / (ci.Progress / 100.0)) return ci.StartedAt.Add(estimatedTotal) } // GetEfficiency 获取建造效率 func (ci *ConstructionInfo) GetEfficiency() float64 { if ci.Progress <= 0 { return 0.0 } elapsed := time.Since(ci.StartedAt) expectedProgress := float64(elapsed) / float64(ci.Duration) * 100.0 if expectedProgress <= 0 { return 0.0 } return ci.Progress / expectedProgress } // UpgradeInfo 升级信息实体 type UpgradeInfo struct { ID string `json:"id" bson:"_id"` BuildingID string `json:"building_id" bson:"building_id"` FromLevel int32 `json:"from_level" bson:"from_level"` ToLevel int32 `json:"to_level" bson:"to_level"` StartedAt time.Time `json:"started_at" bson:"started_at"` Duration time.Duration `json:"duration" bson:"duration"` CompletedAt *time.Time `json:"completed_at,omitempty" bson:"completed_at,omitempty"` Progress float64 `json:"progress" bson:"progress"` Costs []*ResourceCost `json:"costs" bson:"costs"` Workers []*WorkerAssignment `json:"workers" bson:"workers"` Materials []*MaterialUsage `json:"materials" bson:"materials"` Status UpgradeStatus `json:"status" bson:"status"` Benefits []*UpgradeBenefit `json:"benefits" bson:"benefits"` Requirements []*Requirement `json:"requirements" bson:"requirements"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // UpgradeStatus 升级状态 type UpgradeStatus int32 const ( UpgradeStatusPlanning UpgradeStatus = iota + 1 // 规划中 UpgradeStatusInProgress // 进行中 UpgradeStatusPaused // 暂停 UpgradeStatusCompleted // 已完成 UpgradeStatusCancelled // 已取消 UpgradeStatusFailed // 失败 ) // String 返回升级状态的字符串表示 func (us UpgradeStatus) String() string { switch us { case UpgradeStatusPlanning: return "planning" case UpgradeStatusInProgress: return "in_progress" case UpgradeStatusPaused: return "paused" case UpgradeStatusCompleted: return "completed" case UpgradeStatusCancelled: return "cancelled" case UpgradeStatusFailed: return "failed" default: return "unknown" } } // IsValid 检查升级状态是否有效 func (us UpgradeStatus) IsValid() bool { return us >= UpgradeStatusPlanning && us <= UpgradeStatusFailed } // NewUpgradeInfo 创建新升级信息 func NewUpgradeInfo(buildingID string, fromLevel, toLevel int32, duration time.Duration) *UpgradeInfo { now := time.Now() return &UpgradeInfo{ ID: generateUpgradeID(), BuildingID: buildingID, FromLevel: fromLevel, ToLevel: toLevel, StartedAt: now, Duration: duration, Progress: 0.0, Costs: make([]*ResourceCost, 0), Workers: make([]*WorkerAssignment, 0), Materials: make([]*MaterialUsage, 0), Status: UpgradeStatusPlanning, Benefits: make([]*UpgradeBenefit, 0), Requirements: make([]*Requirement, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // AddBenefit 添加升级收益 func (ui *UpgradeInfo) AddBenefit(benefit *UpgradeBenefit) error { if benefit == nil { return fmt.Errorf("upgrade benefit cannot be nil") } ui.Benefits = append(ui.Benefits, benefit) ui.UpdatedAt = time.Now() return nil } // AddRequirement 添加升级要求 func (ui *UpgradeInfo) AddRequirement(requirement *Requirement) error { if requirement == nil { return fmt.Errorf("requirement cannot be nil") } ui.Requirements = append(ui.Requirements, requirement) ui.UpdatedAt = time.Now() return nil } // CheckRequirements 检查升级要求 func (ui *UpgradeInfo) CheckRequirements() bool { for _, req := range ui.Requirements { if !req.IsMet() { return false } } return true } // UpdateProgress 更新升级进度 func (ui *UpgradeInfo) UpdateProgress(progress float64) error { if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } ui.Progress = progress ui.UpdatedAt = time.Now() if progress >= 100 { ui.Status = UpgradeStatusCompleted now := time.Now() ui.CompletedAt = &now } return nil } // SetMetadata 设置元数据 func (ui *UpgradeInfo) SetMetadata(key string, value interface{}) { if ui.Metadata == nil { ui.Metadata = make(map[string]interface{}) } ui.Metadata[key] = value ui.UpdatedAt = time.Now() } // GetMetadata 获取元数据 func (ui *UpgradeInfo) GetMetadata(key string) (interface{}, bool) { if ui.Metadata == nil { return nil, false } value, exists := ui.Metadata[key] return value, exists } // WorkerAssignment 工人分配实体 type WorkerAssignment struct { ID string `json:"id" bson:"_id"` WorkerID uint64 `json:"worker_id" bson:"worker_id"` Role WorkerRole `json:"role" bson:"role"` Task string `json:"task" bson:"task"` Efficiency float64 `json:"efficiency" bson:"efficiency"` StartTime time.Time `json:"start_time" bson:"start_time"` EndTime *time.Time `json:"end_time,omitempty" bson:"end_time,omitempty"` Status WorkerAssignmentStatus `json:"status" bson:"status"` Progress float64 `json:"progress" bson:"progress"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // WorkerAssignmentStatus 工人分配状态 type WorkerAssignmentStatus int32 const ( WorkerAssignmentStatusAssigned WorkerAssignmentStatus = iota + 1 // 已分配 WorkerAssignmentStatusWorking // 工作中 WorkerAssignmentStatusPaused // 暂停 WorkerAssignmentStatusCompleted // 已完成 WorkerAssignmentStatusCancelled // 已取消 ) // String 返回工人分配状态的字符串表示 func (was WorkerAssignmentStatus) String() string { switch was { case WorkerAssignmentStatusAssigned: return "assigned" case WorkerAssignmentStatusWorking: return "working" case WorkerAssignmentStatusPaused: return "paused" case WorkerAssignmentStatusCompleted: return "completed" case WorkerAssignmentStatusCancelled: return "cancelled" default: return "unknown" } } // IsValid 检查工人分配状态是否有效 func (was WorkerAssignmentStatus) IsValid() bool { return was >= WorkerAssignmentStatusAssigned && was <= WorkerAssignmentStatusCancelled } // NewWorkerAssignment 创建新工人分配 func NewWorkerAssignment(workerID uint64, role WorkerRole, task string) *WorkerAssignment { now := time.Now() return &WorkerAssignment{ ID: generateWorkerAssignmentID(), WorkerID: workerID, Role: role, Task: task, Efficiency: 1.0, StartTime: now, Status: WorkerAssignmentStatusAssigned, Progress: 0.0, Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // Start 开始工作 func (wa *WorkerAssignment) Start() { wa.Status = WorkerAssignmentStatusWorking wa.UpdatedAt = time.Now() } // Pause 暂停工作 func (wa *WorkerAssignment) Pause() { wa.Status = WorkerAssignmentStatusPaused wa.UpdatedAt = time.Now() } // Resume 恢复工作 func (wa *WorkerAssignment) Resume() { wa.Status = WorkerAssignmentStatusWorking wa.UpdatedAt = time.Now() } // Complete 完成工作 func (wa *WorkerAssignment) Complete() { now := time.Now() wa.Status = WorkerAssignmentStatusCompleted wa.EndTime = &now wa.Progress = 100.0 wa.UpdatedAt = now } // Cancel 取消工作 func (wa *WorkerAssignment) Cancel() { now := time.Now() wa.Status = WorkerAssignmentStatusCancelled wa.EndTime = &now wa.UpdatedAt = now } // UpdateProgress 更新进度 func (wa *WorkerAssignment) UpdateProgress(progress float64) error { if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } wa.Progress = progress wa.UpdatedAt = time.Now() if progress >= 100 { wa.Complete() } return nil } // GetDuration 获取工作持续时间 func (wa *WorkerAssignment) GetDuration() time.Duration { if wa.EndTime != nil { return wa.EndTime.Sub(wa.StartTime) } return time.Since(wa.StartTime) } // MaterialUsage 材料使用实体 type MaterialUsage struct { ID string `json:"id" bson:"_id"` MaterialType string `json:"material_type" bson:"material_type"` Quantity int64 `json:"quantity" bson:"quantity"` Used int64 `json:"used" bson:"used"` Wasted int64 `json:"wasted" bson:"wasted"` Quality float64 `json:"quality" bson:"quality"` Cost int64 `json:"cost" bson:"cost"` Supplier string `json:"supplier" bson:"supplier"` DeliveredAt time.Time `json:"delivered_at" bson:"delivered_at"` UsedAt *time.Time `json:"used_at,omitempty" bson:"used_at,omitempty"` Status MaterialUsageStatus `json:"status" bson:"status"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // MaterialUsageStatus 材料使用状态 type MaterialUsageStatus int32 const ( MaterialUsageStatusOrdered MaterialUsageStatus = iota + 1 // 已订购 MaterialUsageStatusDelivered // 已交付 MaterialUsageStatusInUse // 使用中 MaterialUsageStatusUsed // 已使用 MaterialUsageStatusWasted // 已浪费 MaterialUsageStatusReturned // 已退回 ) // String 返回材料使用状态的字符串表示 func (mus MaterialUsageStatus) String() string { switch mus { case MaterialUsageStatusOrdered: return "ordered" case MaterialUsageStatusDelivered: return "delivered" case MaterialUsageStatusInUse: return "in_use" case MaterialUsageStatusUsed: return "used" case MaterialUsageStatusWasted: return "wasted" case MaterialUsageStatusReturned: return "returned" default: return "unknown" } } // IsValid 检查材料使用状态是否有效 func (mus MaterialUsageStatus) IsValid() bool { return mus >= MaterialUsageStatusOrdered && mus <= MaterialUsageStatusReturned } // NewMaterialUsage 创建新材料使用 func NewMaterialUsage(materialType string, quantity int64, cost int64) *MaterialUsage { now := time.Now() return &MaterialUsage{ ID: generateMaterialUsageID(), MaterialType: materialType, Quantity: quantity, Used: 0, Wasted: 0, Quality: 1.0, Cost: cost, Supplier: "", DeliveredAt: now, Status: MaterialUsageStatusOrdered, Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // Use 使用材料 func (mu *MaterialUsage) Use(amount int64) error { if amount <= 0 { return fmt.Errorf("use amount must be positive") } if mu.Used+amount > mu.Quantity { return fmt.Errorf("insufficient material: have %d, need %d", mu.Quantity-mu.Used, amount) } mu.Used += amount mu.Status = MaterialUsageStatusInUse if mu.Used >= mu.Quantity { mu.Status = MaterialUsageStatusUsed now := time.Now() mu.UsedAt = &now } mu.UpdatedAt = time.Now() return nil } // Waste 浪费材料 func (mu *MaterialUsage) Waste(amount int64) error { if amount <= 0 { return fmt.Errorf("waste amount must be positive") } if mu.Used+mu.Wasted+amount > mu.Quantity { return fmt.Errorf("waste amount exceeds available material") } mu.Wasted += amount mu.UpdatedAt = time.Now() return nil } // GetRemaining 获取剩余材料 func (mu *MaterialUsage) GetRemaining() int64 { return mu.Quantity - mu.Used - mu.Wasted } // GetUsageRate 获取使用率 func (mu *MaterialUsage) GetUsageRate() float64 { if mu.Quantity == 0 { return 0.0 } return float64(mu.Used) / float64(mu.Quantity) * 100.0 } // GetWasteRate 获取浪费率 func (mu *MaterialUsage) GetWasteRate() float64 { if mu.Quantity == 0 { return 0.0 } return float64(mu.Wasted) / float64(mu.Quantity) * 100.0 } // ConstructionPhase 建造阶段实体 type ConstructionPhase struct { ID string `json:"id" bson:"_id"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` Order int32 `json:"order" bson:"order"` Duration time.Duration `json:"duration" bson:"duration"` StartedAt *time.Time `json:"started_at,omitempty" bson:"started_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty" bson:"completed_at,omitempty"` Progress float64 `json:"progress" bson:"progress"` Status PhaseStatus `json:"status" bson:"status"` Requirements []*Requirement `json:"requirements" bson:"requirements"` Tasks []*PhaseTask `json:"tasks" bson:"tasks"` Workers []*WorkerAssignment `json:"workers" bson:"workers"` Materials []*MaterialUsage `json:"materials" bson:"materials"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // PhaseStatus 阶段状态 type PhaseStatus int32 const ( PhaseStatusPending PhaseStatus = iota + 1 // 等待中 PhaseStatusInProgress // 进行中 PhaseStatusPaused // 暂停 PhaseStatusCompleted // 已完成 PhaseStatusCancelled // 已取消 PhaseStatusFailed // 失败 ) // String 返回阶段状态的字符串表示 func (ps PhaseStatus) String() string { switch ps { case PhaseStatusPending: return "pending" case PhaseStatusInProgress: return "in_progress" case PhaseStatusPaused: return "paused" case PhaseStatusCompleted: return "completed" case PhaseStatusCancelled: return "cancelled" case PhaseStatusFailed: return "failed" default: return "unknown" } } // IsValid 检查阶段状态是否有效 func (ps PhaseStatus) IsValid() bool { return ps >= PhaseStatusPending && ps <= PhaseStatusFailed } // NewConstructionPhase 创建新建造阶段 func NewConstructionPhase(name, description string, order int32, duration time.Duration) *ConstructionPhase { now := time.Now() return &ConstructionPhase{ ID: generatePhaseID(), Name: name, Description: description, Order: order, Duration: duration, Progress: 0.0, Status: PhaseStatusPending, Requirements: make([]*Requirement, 0), Tasks: make([]*PhaseTask, 0), Workers: make([]*WorkerAssignment, 0), Materials: make([]*MaterialUsage, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // Start 开始阶段 func (cp *ConstructionPhase) Start() { now := time.Now() cp.StartedAt = &now cp.Status = PhaseStatusInProgress cp.UpdatedAt = now } // Complete 完成阶段 func (cp *ConstructionPhase) Complete() { now := time.Now() cp.CompletedAt = &now cp.Progress = 100.0 cp.Status = PhaseStatusCompleted cp.UpdatedAt = now } // Pause 暂停阶段 func (cp *ConstructionPhase) Pause() { cp.Status = PhaseStatusPaused cp.UpdatedAt = time.Now() } // Resume 恢复阶段 func (cp *ConstructionPhase) Resume() { cp.Status = PhaseStatusInProgress cp.UpdatedAt = time.Now() } // Cancel 取消阶段 func (cp *ConstructionPhase) Cancel() { cp.Status = PhaseStatusCancelled cp.UpdatedAt = time.Now() } // UpdateProgress 更新进度 func (cp *ConstructionPhase) UpdateProgress(progress float64) error { if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } cp.Progress = progress cp.UpdatedAt = time.Now() if progress >= 100 { cp.Complete() } return nil } // AddTask 添加任务 func (cp *ConstructionPhase) AddTask(task *PhaseTask) error { if task == nil { return fmt.Errorf("phase task cannot be nil") } cp.Tasks = append(cp.Tasks, task) cp.UpdatedAt = time.Now() return nil } // PhaseTask 阶段任务实体 type PhaseTask struct { ID string `json:"id" bson:"_id"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` Type TaskType `json:"type" bson:"type"` Priority TaskPriority `json:"priority" bson:"priority"` Duration time.Duration `json:"duration" bson:"duration"` StartedAt *time.Time `json:"started_at,omitempty" bson:"started_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty" bson:"completed_at,omitempty"` Progress float64 `json:"progress" bson:"progress"` Status TaskStatus `json:"status" bson:"status"` AssignedTo *uint64 `json:"assigned_to,omitempty" bson:"assigned_to,omitempty"` DependsOn []string `json:"depends_on" bson:"depends_on"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // TaskType 任务类型 type TaskType int32 const ( TaskTypeFoundation TaskType = iota + 1 // 地基 TaskTypeFramework // 框架 TaskTypeWalls // 墙体 TaskTypeRoof // 屋顶 TaskTypeElectrical // 电气 TaskTypePlumbing // 管道 TaskTypeInterior // 内装 TaskTypeExterior // 外装 TaskTypeLandscaping // 景观 TaskTypeCustom // 自定义 ) // String 返回任务类型的字符串表示 func (tt TaskType) String() string { switch tt { case TaskTypeFoundation: return "foundation" case TaskTypeFramework: return "framework" case TaskTypeWalls: return "walls" case TaskTypeRoof: return "roof" case TaskTypeElectrical: return "electrical" case TaskTypePlumbing: return "plumbing" case TaskTypeInterior: return "interior" case TaskTypeExterior: return "exterior" case TaskTypeLandscaping: return "landscaping" case TaskTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查任务类型是否有效 func (tt TaskType) IsValid() bool { return tt >= TaskTypeFoundation && tt <= TaskTypeCustom } // TaskPriority 任务优先级 type TaskPriority int32 const ( TaskPriorityLow TaskPriority = iota + 1 // 低优先级 TaskPriorityNormal // 普通优先级 TaskPriorityHigh // 高优先级 TaskPriorityCritical // 关键优先级 ) // String 返回任务优先级的字符串表示 func (tp TaskPriority) String() string { switch tp { case TaskPriorityLow: return "low" case TaskPriorityNormal: return "normal" case TaskPriorityHigh: return "high" case TaskPriorityCritical: return "critical" default: return "unknown" } } // IsValid 检查任务优先级是否有效 func (tp TaskPriority) IsValid() bool { return tp >= TaskPriorityLow && tp <= TaskPriorityCritical } // TaskStatus 任务状态 type TaskStatus int32 const ( TaskStatusPending TaskStatus = iota + 1 // 等待中 TaskStatusInProgress // 进行中 TaskStatusPaused // 暂停 TaskStatusCompleted // 已完成 TaskStatusCancelled // 已取消 TaskStatusFailed // 失败 ) // String 返回任务状态的字符串表示 func (ts TaskStatus) String() string { switch ts { case TaskStatusPending: return "pending" case TaskStatusInProgress: return "in_progress" case TaskStatusPaused: return "paused" case TaskStatusCompleted: return "completed" case TaskStatusCancelled: return "cancelled" case TaskStatusFailed: return "failed" default: return "unknown" } } // IsValid 检查任务状态是否有效 func (ts TaskStatus) IsValid() bool { return ts >= TaskStatusPending && ts <= TaskStatusFailed } // NewPhaseTask 创建新阶段任务 func NewPhaseTask(name, description string, taskType TaskType, priority TaskPriority, duration time.Duration) *PhaseTask { now := time.Now() return &PhaseTask{ ID: generateTaskID(), Name: name, Description: description, Type: taskType, Priority: priority, Duration: duration, Progress: 0.0, Status: TaskStatusPending, DependsOn: make([]string, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // Start 开始任务 func (pt *PhaseTask) Start() { now := time.Now() pt.StartedAt = &now pt.Status = TaskStatusInProgress pt.UpdatedAt = now } // Complete 完成任务 func (pt *PhaseTask) Complete() { now := time.Now() pt.CompletedAt = &now pt.Progress = 100.0 pt.Status = TaskStatusCompleted pt.UpdatedAt = now } // Assign 分配任务 func (pt *PhaseTask) Assign(workerID uint64) { pt.AssignedTo = &workerID pt.UpdatedAt = time.Now() } // Unassign 取消分配 func (pt *PhaseTask) Unassign() { pt.AssignedTo = nil pt.UpdatedAt = time.Now() } // AddDependency 添加依赖 func (pt *PhaseTask) AddDependency(taskID string) { // 检查是否已存在 for _, existing := range pt.DependsOn { if existing == taskID { return } } pt.DependsOn = append(pt.DependsOn, taskID) pt.UpdatedAt = time.Now() } // RemoveDependency 移除依赖 func (pt *PhaseTask) RemoveDependency(taskID string) { for i, dep := range pt.DependsOn { if dep == taskID { pt.DependsOn = append(pt.DependsOn[:i], pt.DependsOn[i+1:]...) pt.UpdatedAt = time.Now() return } } } // UpdateProgress 更新进度 func (pt *PhaseTask) UpdateProgress(progress float64) error { if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } pt.Progress = progress pt.UpdatedAt = time.Now() if progress >= 100 { pt.Complete() } return nil } // Blueprint 蓝图实体 type Blueprint struct { ID string `json:"id" bson:"_id"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` Version string `json:"version" bson:"version"` Author string `json:"author" bson:"author"` Category BuildingCategory `json:"category" bson:"category"` Size *Size `json:"size" bson:"size"` Layers []*BlueprintLayer `json:"layers" bson:"layers"` Materials []*MaterialRequirement `json:"materials" bson:"materials"` Costs []*ResourceCost `json:"costs" bson:"costs"` Duration time.Duration `json:"duration" bson:"duration"` Difficulty int32 `json:"difficulty" bson:"difficulty"` Tags []string `json:"tags" bson:"tags"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewBlueprint 创建新蓝图 func NewBlueprint(name, description string, category BuildingCategory) *Blueprint { now := time.Now() return &Blueprint{ ID: generateBlueprintID(), Name: name, Description: description, Version: "1.0.0", Author: "", Category: category, Size: NewSize(1, 1, 1), Layers: make([]*BlueprintLayer, 0), Materials: make([]*MaterialRequirement, 0), Costs: make([]*ResourceCost, 0), Duration: 1 * time.Hour, Difficulty: 1, Tags: make([]string, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // AddLayer 添加层 func (bp *Blueprint) AddLayer(layer *BlueprintLayer) error { if layer == nil { return fmt.Errorf("blueprint layer cannot be nil") } bp.Layers = append(bp.Layers, layer) bp.UpdatedAt = time.Now() return nil } // AddMaterial 添加材料需求 func (bp *Blueprint) AddMaterial(material *MaterialRequirement) error { if material == nil { return fmt.Errorf("material requirement cannot be nil") } bp.Materials = append(bp.Materials, material) bp.UpdatedAt = time.Now() return nil } // AddCost 添加成本 func (bp *Blueprint) AddCost(cost *ResourceCost) error { if cost == nil { return fmt.Errorf("resource cost cannot be nil") } bp.Costs = append(bp.Costs, cost) bp.UpdatedAt = time.Now() return nil } // AddTag 添加标签 func (bp *Blueprint) AddTag(tag string) { // 检查是否已存在 for _, existing := range bp.Tags { if existing == tag { return } } bp.Tags = append(bp.Tags, tag) bp.UpdatedAt = time.Now() } // BlueprintLayer 蓝图层实体 type BlueprintLayer struct { ID string `json:"id" bson:"_id"` Name string `json:"name" bson:"name"` Level int32 `json:"level" bson:"level"` Blocks []*BlueprintBlock `json:"blocks" bson:"blocks"` Connections []*BlueprintConnection `json:"connections" bson:"connections"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewBlueprintLayer 创建新蓝图层 func NewBlueprintLayer(name string, level int32) *BlueprintLayer { now := time.Now() return &BlueprintLayer{ ID: generateLayerID(), Name: name, Level: level, Blocks: make([]*BlueprintBlock, 0), Connections: make([]*BlueprintConnection, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // BlueprintBlock 蓝图块实体 type BlueprintBlock struct { ID string `json:"id" bson:"_id"` Type string `json:"type" bson:"type"` Position *Position `json:"position" bson:"position"` Size *Size `json:"size" bson:"size"` Orientation Orientation `json:"orientation" bson:"orientation"` Material string `json:"material" bson:"material"` Properties map[string]interface{} `json:"properties" bson:"properties"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewBlueprintBlock 创建新蓝图块 func NewBlueprintBlock(blockType string, position *Position, size *Size) *BlueprintBlock { now := time.Now() return &BlueprintBlock{ ID: generateBlockID(), Type: blockType, Position: position, Size: size, Orientation: OrientationNorth, Material: "stone", Properties: make(map[string]interface{}), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // BlueprintConnection 蓝图连接实体 type BlueprintConnection struct { ID string `json:"id" bson:"_id"` FromBlock string `json:"from_block" bson:"from_block"` ToBlock string `json:"to_block" bson:"to_block"` Type string `json:"type" bson:"type"` Properties map[string]interface{} `json:"properties" bson:"properties"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewBlueprintConnection 创建新蓝图连接 func NewBlueprintConnection(fromBlock, toBlock, connectionType string) *BlueprintConnection { now := time.Now() return &BlueprintConnection{ ID: generateConnectionID(), FromBlock: fromBlock, ToBlock: toBlock, Type: connectionType, Properties: make(map[string]interface{}), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // MaterialRequirement 材料需求实体 type MaterialRequirement struct { MaterialType string `json:"material_type" bson:"material_type"` Quantity int64 `json:"quantity" bson:"quantity"` Quality float64 `json:"quality" bson:"quality"` Optional bool `json:"optional" bson:"optional"` Alternatives []string `json:"alternatives" bson:"alternatives"` } // NewMaterialRequirement 创建新材料需求 func NewMaterialRequirement(materialType string, quantity int64) *MaterialRequirement { return &MaterialRequirement{ MaterialType: materialType, Quantity: quantity, Quality: 1.0, Optional: false, Alternatives: make([]string, 0), } } // UpgradeBenefit 升级收益实体 type UpgradeBenefit struct { Type string `json:"type" bson:"type"` Target string `json:"target" bson:"target"` Value float64 `json:"value" bson:"value"` Description string `json:"description" bson:"description"` } // NewUpgradeBenefit 创建新升级收益 func NewUpgradeBenefit(benefitType, target string, value float64, description string) *UpgradeBenefit { return &UpgradeBenefit{ Type: benefitType, Target: target, Value: value, Description: description, } } // 辅助函数 // generateConstructionID 生成建造ID func generateConstructionID() string { return fmt.Sprintf("construction_%d", time.Now().UnixNano()) } // generateUpgradeID 生成升级ID func generateUpgradeID() string { return fmt.Sprintf("upgrade_%d", time.Now().UnixNano()) } // generateWorkerAssignmentID 生成工人分配ID func generateWorkerAssignmentID() string { return fmt.Sprintf("assignment_%d", time.Now().UnixNano()) } // generateMaterialUsageID 生成材料使用ID func generateMaterialUsageID() string { return fmt.Sprintf("material_%d", time.Now().UnixNano()) } // generatePhaseID 生成阶段ID func generatePhaseID() string { return fmt.Sprintf("phase_%d", time.Now().UnixNano()) } // generateTaskID 生成任务ID func generateTaskID() string { return fmt.Sprintf("task_%d", time.Now().UnixNano()) } // generateBlueprintID 生成蓝图ID func generateBlueprintID() string { return fmt.Sprintf("blueprint_%d", time.Now().UnixNano()) } // generateLayerID 生成层ID func generateLayerID() string { return fmt.Sprintf("layer_%d", time.Now().UnixNano()) } // generateBlockID 生成块ID func generateBlockID() string { return fmt.Sprintf("block_%d", time.Now().UnixNano()) } // generateConnectionID 生成连接ID func generateConnectionID() string { return fmt.Sprintf("connection_%d", time.Now().UnixNano()) } ================================================ FILE: internal/domain/building/errors.go ================================================ package building import ( "fmt" "time" ) // BuildingError 建筑错误接口 type BuildingError interface { error GetCode() string GetMessage() string GetSeverity() ErrorSeverity GetTimestamp() time.Time GetContext() map[string]interface{} SetContext(key string, value interface{}) IsRetryable() bool GetRetryAfter() time.Duration } // ErrorSeverity 错误严重程度 type ErrorSeverity int32 const ( ErrorSeverityLow ErrorSeverity = iota + 1 // 低严重程度 ErrorSeverityMedium // 中等严重程度 ErrorSeverityHigh // 高严重程度 ErrorSeverityCritical // 关键严重程度 ) // String 返回错误严重程度的字符串表示 func (es ErrorSeverity) String() string { switch es { case ErrorSeverityLow: return "low" case ErrorSeverityMedium: return "medium" case ErrorSeverityHigh: return "high" case ErrorSeverityCritical: return "critical" default: return "unknown" } } // IsValid 检查错误严重程度是否有效 func (es ErrorSeverity) IsValid() bool { return es >= ErrorSeverityLow && es <= ErrorSeverityCritical } // BaseBuildingError 建筑错误基础结构 type BaseBuildingError struct { code string message string severity ErrorSeverity timestamp time.Time context map[string]interface{} retryable bool retryAfter time.Duration } // NewBuildingError 创建新建筑错误 func NewBuildingError(code, message string, severity ErrorSeverity) *BaseBuildingError { return &BaseBuildingError{ code: code, message: message, severity: severity, timestamp: time.Now(), context: make(map[string]interface{}), retryable: false, } } // Error 实现error接口 func (e *BaseBuildingError) Error() string { return fmt.Sprintf("[%s] %s (severity: %s)", e.code, e.message, e.severity.String()) } // GetCode 获取错误代码 func (e *BaseBuildingError) GetCode() string { return e.code } // GetMessage 获取错误消息 func (e *BaseBuildingError) GetMessage() string { return e.message } // GetSeverity 获取错误严重程度 func (e *BaseBuildingError) GetSeverity() ErrorSeverity { return e.severity } // GetTimestamp 获取错误时间戳 func (e *BaseBuildingError) GetTimestamp() time.Time { return e.timestamp } // GetContext 获取错误上下文 func (e *BaseBuildingError) GetContext() map[string]interface{} { return e.context } // SetContext 设置错误上下文 func (e *BaseBuildingError) SetContext(key string, value interface{}) { if e.context == nil { e.context = make(map[string]interface{}) } e.context[key] = value } // IsRetryable 检查错误是否可重试 func (e *BaseBuildingError) IsRetryable() bool { return e.retryable } // GetRetryAfter 获取重试间隔 func (e *BaseBuildingError) GetRetryAfter() time.Duration { return e.retryAfter } // SetRetryable 设置错误可重试 func (e *BaseBuildingError) SetRetryable(retryable bool, retryAfter time.Duration) { e.retryable = retryable e.retryAfter = retryAfter } // 具体错误类型 // BuildingNotFoundError 建筑未找到错误 type BuildingNotFoundError struct { *BaseBuildingError BuildingID string `json:"building_id"` } // NewBuildingNotFoundError 创建建筑未找到错误 func NewBuildingNotFoundError(buildingID string) *BuildingNotFoundError { err := &BuildingNotFoundError{ BaseBuildingError: NewBuildingError( ErrCodeBuildingNotFound, fmt.Sprintf("building with ID %s not found", buildingID), ErrorSeverityHigh, ), BuildingID: buildingID, } err.SetContext("building_id", buildingID) return err } // ConstructionNotFoundError 建造未找到错误 type ConstructionNotFoundError struct { *BaseBuildingError ConstructionID string `json:"construction_id"` } // NewConstructionNotFoundError 创建建造未找到错误 func NewConstructionNotFoundError(constructionID string) *ConstructionNotFoundError { err := &ConstructionNotFoundError{ BaseBuildingError: NewBuildingError( ErrCodeConstructionNotFound, fmt.Sprintf("construction with ID %s not found", constructionID), ErrorSeverityHigh, ), ConstructionID: constructionID, } err.SetContext("construction_id", constructionID) return err } // UpgradeNotFoundError 升级未找到错误 type UpgradeNotFoundError struct { *BaseBuildingError UpgradeID string `json:"upgrade_id"` } // NewUpgradeNotFoundError 创建升级未找到错误 func NewUpgradeNotFoundError(upgradeID string) *UpgradeNotFoundError { err := &UpgradeNotFoundError{ BaseBuildingError: NewBuildingError( ErrCodeUpgradeNotFound, fmt.Sprintf("upgrade with ID %s not found", upgradeID), ErrorSeverityHigh, ), UpgradeID: upgradeID, } err.SetContext("upgrade_id", upgradeID) return err } // BlueprintNotFoundError 蓝图未找到错误 type BlueprintNotFoundError struct { *BaseBuildingError BlueprintID string `json:"blueprint_id"` } // NewBlueprintNotFoundError 创建蓝图未找到错误 func NewBlueprintNotFoundError(blueprintID string) *BlueprintNotFoundError { err := &BlueprintNotFoundError{ BaseBuildingError: NewBuildingError( ErrCodeBlueprintNotFound, fmt.Sprintf("blueprint with ID %s not found", blueprintID), ErrorSeverityHigh, ), BlueprintID: blueprintID, } err.SetContext("blueprint_id", blueprintID) return err } // InvalidBuildingStateError 无效建筑状态错误 type InvalidBuildingStateError struct { *BaseBuildingError BuildingID string `json:"building_id"` CurrentState BuildingStatus `json:"current_state"` ExpectedState BuildingStatus `json:"expected_state"` Operation string `json:"operation"` } // NewInvalidBuildingStateError 创建无效建筑状态错误 func NewInvalidBuildingStateError(buildingID string, currentState, expectedState BuildingStatus, operation string) *InvalidBuildingStateError { err := &InvalidBuildingStateError{ BaseBuildingError: NewBuildingError( ErrCodeInvalidBuildingState, fmt.Sprintf("building %s is in state %s, expected %s for operation %s", buildingID, currentState.String(), expectedState.String(), operation), ErrorSeverityHigh, ), BuildingID: buildingID, CurrentState: currentState, ExpectedState: expectedState, Operation: operation, } err.SetContext("building_id", buildingID) err.SetContext("current_state", currentState.String()) err.SetContext("expected_state", expectedState.String()) err.SetContext("operation", operation) return err } // InsufficientResourcesError 资源不足错误 type InsufficientResourcesError struct { *BaseBuildingError ResourceType string `json:"resource_type"` Required int64 `json:"required"` Available int64 `json:"available"` OwnerID uint64 `json:"owner_id"` } // NewInsufficientResourcesError 创建资源不足错误 func NewInsufficientResourcesError(resourceType string, required, available int64, ownerID uint64) *InsufficientResourcesError { err := &InsufficientResourcesError{ BaseBuildingError: NewBuildingError( ErrCodeInsufficientResources, fmt.Sprintf("insufficient %s: required %d, available %d", resourceType, required, available), ErrorSeverityHigh, ), ResourceType: resourceType, Required: required, Available: available, OwnerID: ownerID, } err.SetContext("resource_type", resourceType) err.SetContext("required", required) err.SetContext("available", available) err.SetContext("owner_id", ownerID) return err } // PositionOccupiedError 位置被占用错误 type PositionOccupiedError struct { *BaseBuildingError Position *Position `json:"position"` OccupyingBuilding string `json:"occupying_building"` } // NewPositionOccupiedError 创建位置被占用错误 func NewPositionOccupiedError(position *Position, occupyingBuilding string) *PositionOccupiedError { err := &PositionOccupiedError{ BaseBuildingError: NewBuildingError( ErrCodePositionOccupied, fmt.Sprintf("position (%d,%d,%d) is occupied by building %s", position.X, position.Y, position.Z, occupyingBuilding), ErrorSeverityHigh, ), Position: position, OccupyingBuilding: occupyingBuilding, } err.SetContext("position", fmt.Sprintf("%d,%d,%d", position.X, position.Y, position.Z)) err.SetContext("occupying_building", occupyingBuilding) return err } // ConstructionFailedError 建造失败错误 type ConstructionFailedError struct { *BaseBuildingError BuildingID string `json:"building_id"` ConstructionID string `json:"construction_id"` Reason string `json:"reason"` } // NewConstructionFailedError 创建建造失败错误 func NewConstructionFailedError(buildingID, constructionID, reason string) *ConstructionFailedError { err := &ConstructionFailedError{ BaseBuildingError: NewBuildingError( ErrCodeConstructionFailed, fmt.Sprintf("construction %s for building %s failed: %s", constructionID, buildingID, reason), ErrorSeverityHigh, ), BuildingID: buildingID, ConstructionID: constructionID, Reason: reason, } err.SetContext("building_id", buildingID) err.SetContext("construction_id", constructionID) err.SetContext("reason", reason) err.SetRetryable(true, 5*time.Minute) return err } // UpgradeFailedError 升级失败错误 type UpgradeFailedError struct { *BaseBuildingError BuildingID string `json:"building_id"` UpgradeID string `json:"upgrade_id"` Reason string `json:"reason"` } // NewUpgradeFailedError 创建升级失败错误 func NewUpgradeFailedError(buildingID, upgradeID, reason string) *UpgradeFailedError { err := &UpgradeFailedError{ BaseBuildingError: NewBuildingError( ErrCodeUpgradeFailed, fmt.Sprintf("upgrade %s for building %s failed: %s", upgradeID, buildingID, reason), ErrorSeverityHigh, ), BuildingID: buildingID, UpgradeID: upgradeID, Reason: reason, } err.SetContext("building_id", buildingID) err.SetContext("upgrade_id", upgradeID) err.SetContext("reason", reason) err.SetRetryable(true, 5*time.Minute) return err } // RepairFailedError 修复失败错误 type RepairFailedError struct { *BaseBuildingError BuildingID string `json:"building_id"` Reason string `json:"reason"` } // NewRepairFailedError 创建修复失败错误 func NewRepairFailedError(buildingID, reason string) *RepairFailedError { err := &RepairFailedError{ BaseBuildingError: NewBuildingError( ErrCodeRepairFailed, fmt.Sprintf("repair for building %s failed: %s", buildingID, reason), ErrorSeverityMedium, ), BuildingID: buildingID, Reason: reason, } err.SetContext("building_id", buildingID) err.SetContext("reason", reason) err.SetRetryable(true, 1*time.Minute) return err } // DestroyFailedError 摧毁失败错误 type DestroyFailedError struct { *BaseBuildingError BuildingID string `json:"building_id"` Reason string `json:"reason"` } // NewDestroyFailedError 创建摧毁失败错误 func NewDestroyFailedError(buildingID, reason string) *DestroyFailedError { err := &DestroyFailedError{ BaseBuildingError: NewBuildingError( ErrCodeDestroyFailed, fmt.Sprintf("destroy for building %s failed: %s", buildingID, reason), ErrorSeverityMedium, ), BuildingID: buildingID, Reason: reason, } err.SetContext("building_id", buildingID) err.SetContext("reason", reason) return err } // WorkerNotAvailableError 工人不可用错误 type WorkerNotAvailableError struct { *BaseBuildingError WorkerID uint64 `json:"worker_id"` Reason string `json:"reason"` } // NewWorkerNotAvailableError 创建工人不可用错误 func NewWorkerNotAvailableError(workerID uint64, reason string) *WorkerNotAvailableError { err := &WorkerNotAvailableError{ BaseBuildingError: NewBuildingError( ErrCodeWorkerNotAvailable, fmt.Sprintf("worker %d is not available: %s", workerID, reason), ErrorSeverityMedium, ), WorkerID: workerID, Reason: reason, } err.SetContext("worker_id", workerID) err.SetContext("reason", reason) err.SetRetryable(true, 10*time.Minute) return err } // InvalidInputError 无效输入错误 type InvalidInputError struct { *BaseBuildingError Field string `json:"field"` Value string `json:"value"` } // NewInvalidInputError 创建无效输入错误 func NewInvalidInputError(field, value, reason string) *InvalidInputError { err := &InvalidInputError{ BaseBuildingError: NewBuildingError( ErrCodeInvalidInput, fmt.Sprintf("invalid input for field %s with value %s: %s", field, value, reason), ErrorSeverityHigh, ), Field: field, Value: value, } err.SetContext("field", field) err.SetContext("value", value) return err } // RepositoryError 仓储错误 type RepositoryError struct { *BaseBuildingError Operation string `json:"operation"` Entity string `json:"entity"` } // NewRepositoryError 创建仓储错误 func NewRepositoryError(operation, entity, reason string) *RepositoryError { err := &RepositoryError{ BaseBuildingError: NewBuildingError( ErrCodeRepositoryError, fmt.Sprintf("repository error during %s operation on %s: %s", operation, entity, reason), ErrorSeverityMedium, ), Operation: operation, Entity: entity, } err.SetContext("operation", operation) err.SetContext("entity", entity) err.SetRetryable(true, 30*time.Second) return err } // ConcurrencyError 并发错误 type ConcurrencyError struct { *BaseBuildingError ResourceID string `json:"resource_id"` Operation string `json:"operation"` } // NewConcurrencyError 创建并发错误 func NewConcurrencyError(resourceID, operation string) *ConcurrencyError { err := &ConcurrencyError{ BaseBuildingError: NewBuildingError( ErrCodeConcurrencyError, fmt.Sprintf("concurrency conflict for resource %s during operation %s", resourceID, operation), ErrorSeverityMedium, ), ResourceID: resourceID, Operation: operation, } err.SetContext("resource_id", resourceID) err.SetContext("operation", operation) err.SetRetryable(true, 1*time.Second) return err } // 错误代码常量 const ( // 通用错误 ErrCodeInvalidInput = "BUILDING_INVALID_INPUT" ErrCodeRepositoryError = "BUILDING_REPOSITORY_ERROR" ErrCodeConcurrencyError = "BUILDING_CONCURRENCY_ERROR" ErrCodeInvalidOperation = "BUILDING_INVALID_OPERATION" // 建筑相关错误 ErrCodeBuildingNotFound = "BUILDING_NOT_FOUND" ErrCodeInvalidBuildingState = "BUILDING_INVALID_STATE" ErrCodePositionOccupied = "BUILDING_POSITION_OCCUPIED" ErrCodeInsufficientResources = "BUILDING_INSUFFICIENT_RESOURCES" // 建造相关错误 ErrCodeConstructionNotFound = "CONSTRUCTION_NOT_FOUND" ErrCodeConstructionFailed = "CONSTRUCTION_FAILED" // 升级相关错误 ErrCodeUpgradeNotFound = "UPGRADE_NOT_FOUND" ErrCodeUpgradeFailed = "UPGRADE_FAILED" ErrCodeInvalidUpgrade = "UPGRADE_INVALID" // 维护相关错误 ErrCodeRepairFailed = "REPAIR_FAILED" ErrCodeDestroyFailed = "DESTROY_FAILED" // 工人相关错误 ErrCodeWorkerNotFound = "WORKER_NOT_FOUND" ErrCodeWorkerNotAvailable = "WORKER_NOT_AVAILABLE" // 蓝图相关错误 ErrCodeBlueprintNotFound = "BLUEPRINT_NOT_FOUND" ErrCodeBlueprintInvalid = "BLUEPRINT_INVALID" // 系统错误 ErrCodeSystemError = "BUILDING_SYSTEM_ERROR" ErrCodeConfigError = "BUILDING_CONFIG_ERROR" ErrCodeNetworkError = "BUILDING_NETWORK_ERROR" ErrCodeTimeoutError = "BUILDING_TIMEOUT_ERROR" ) // 工具函数 // IsBuildingError 检查是否为建筑错误 func IsBuildingError(err error) bool { _, ok := err.(BuildingError) return ok } // GetBuildingError 获取建筑错误 func GetBuildingError(err error) (BuildingError, bool) { buildingErr, ok := err.(BuildingError) return buildingErr, ok } // IsRetryableError 检查错误是否可重试 func IsRetryableError(err error) bool { if buildingErr, ok := GetBuildingError(err); ok { return buildingErr.IsRetryable() } return false } // GetErrorSeverity 获取错误严重程度 func GetErrorSeverity(err error) ErrorSeverity { if buildingErr, ok := GetBuildingError(err); ok { return buildingErr.GetSeverity() } return ErrorSeverityLow } // GetErrorCode 获取错误代码 func GetErrorCode(err error) string { if buildingErr, ok := GetBuildingError(err); ok { return buildingErr.GetCode() } return "UNKNOWN_ERROR" } // 错误分类函数 // IsNotFoundError 检查是否为未找到错误 func IsNotFoundError(err error) bool { code := GetErrorCode(err) return code == ErrCodeBuildingNotFound || code == ErrCodeConstructionNotFound || code == ErrCodeUpgradeNotFound || code == ErrCodeBlueprintNotFound || code == ErrCodeWorkerNotFound } // IsValidationError 检查是否为验证错误 func IsValidationError(err error) bool { code := GetErrorCode(err) return code == ErrCodeInvalidInput || code == ErrCodeInvalidBuildingState || code == ErrCodeInvalidUpgrade || code == ErrCodeBlueprintInvalid } // IsResourceError 检查是否为资源错误 func IsResourceError(err error) bool { code := GetErrorCode(err) return code == ErrCodeInsufficientResources || code == ErrCodePositionOccupied || code == ErrCodeWorkerNotAvailable } // IsSystemError 检查是否为系统错误 func IsSystemError(err error) bool { code := GetErrorCode(err) return code == ErrCodeSystemError || code == ErrCodeConfigError || code == ErrCodeNetworkError || code == ErrCodeTimeoutError || code == ErrCodeRepositoryError || code == ErrCodeConcurrencyError } // IsOperationError 检查是否为操作错误 func IsOperationError(err error) bool { code := GetErrorCode(err) return code == ErrCodeConstructionFailed || code == ErrCodeUpgradeFailed || code == ErrCodeRepairFailed || code == ErrCodeDestroyFailed } // 错误恢复策略 // ErrorRecoveryStrategy 错误恢复策略 type ErrorRecoveryStrategy int32 const ( RecoveryStrategyNone ErrorRecoveryStrategy = iota + 1 // 无恢复策略 RecoveryStrategyRetry // 重试 RecoveryStrategyFallback // 回退 RecoveryStrategyCircuitBreaker // 熔断器 RecoveryStrategyGracefulDegradation // 优雅降级 ) // String 返回恢复策略的字符串表示 func (ers ErrorRecoveryStrategy) String() string { switch ers { case RecoveryStrategyNone: return "none" case RecoveryStrategyRetry: return "retry" case RecoveryStrategyFallback: return "fallback" case RecoveryStrategyCircuitBreaker: return "circuit_breaker" case RecoveryStrategyGracefulDegradation: return "graceful_degradation" default: return "unknown" } } // GetRecoveryStrategy 获取错误的恢复策略 func GetRecoveryStrategy(err error) ErrorRecoveryStrategy { code := GetErrorCode(err) severity := GetErrorSeverity(err) // 根据错误代码和严重程度确定恢复策略 switch { case IsSystemError(err) && severity == ErrorSeverityCritical: return RecoveryStrategyCircuitBreaker case IsSystemError(err) && severity == ErrorSeverityHigh: return RecoveryStrategyGracefulDegradation case IsResourceError(err): return RecoveryStrategyRetry case IsOperationError(err): return RecoveryStrategyFallback case code == ErrCodeConcurrencyError: return RecoveryStrategyRetry case code == ErrCodeRepositoryError: return RecoveryStrategyRetry default: return RecoveryStrategyNone } } // 错误统计 // ErrorStatistics 错误统计 type ErrorStatistics struct { TotalErrors int64 `json:"total_errors"` ErrorsByCode map[string]int64 `json:"errors_by_code"` ErrorsBySeverity map[ErrorSeverity]int64 `json:"errors_by_severity"` ErrorsByHour map[string]int64 `json:"errors_by_hour"` ErrorsByDay map[string]int64 `json:"errors_by_day"` RetryableErrors int64 `json:"retryable_errors"` LastErrorTime time.Time `json:"last_error_time"` UpdatedAt time.Time `json:"updated_at"` } // NewErrorStatistics 创建新错误统计 func NewErrorStatistics() *ErrorStatistics { return &ErrorStatistics{ TotalErrors: 0, ErrorsByCode: make(map[string]int64), ErrorsBySeverity: make(map[ErrorSeverity]int64), ErrorsByHour: make(map[string]int64), ErrorsByDay: make(map[string]int64), RetryableErrors: 0, UpdatedAt: time.Now(), } } // AddError 添加错误到统计 func (es *ErrorStatistics) AddError(err error) { es.TotalErrors++ if buildingErr, ok := GetBuildingError(err); ok { code := buildingErr.GetCode() severity := buildingErr.GetSeverity() timestamp := buildingErr.GetTimestamp() es.ErrorsByCode[code]++ es.ErrorsBySeverity[severity]++ hourKey := timestamp.Format("2006-01-02-15") dayKey := timestamp.Format("2006-01-02") es.ErrorsByHour[hourKey]++ es.ErrorsByDay[dayKey]++ if buildingErr.IsRetryable() { es.RetryableErrors++ } if timestamp.After(es.LastErrorTime) { es.LastErrorTime = timestamp } } else { // 非建筑错误 es.ErrorsByCode["UNKNOWN_ERROR"]++ es.ErrorsBySeverity[ErrorSeverityLow]++ now := time.Now() hourKey := now.Format("2006-01-02-15") dayKey := now.Format("2006-01-02") es.ErrorsByHour[hourKey]++ es.ErrorsByDay[dayKey]++ if now.After(es.LastErrorTime) { es.LastErrorTime = now } } es.UpdatedAt = time.Now() } // GetMostFrequentError 获取最频繁的错误 func (es *ErrorStatistics) GetMostFrequentError() string { maxCount := int64(0) mostFrequent := "" for code, count := range es.ErrorsByCode { if count > maxCount { maxCount = count mostFrequent = code } } return mostFrequent } // GetErrorRate 获取错误率(每小时) func (es *ErrorStatistics) GetErrorRate() float64 { if es.TotalErrors == 0 { return 0.0 } // 计算最近24小时的错误数 recentErrors := int64(0) now := time.Now() for i := 0; i < 24; i++ { hourKey := now.Add(-time.Duration(i) * time.Hour).Format("2006-01-02-15") recentErrors += es.ErrorsByHour[hourKey] } return float64(recentErrors) / 24.0 } // GetRetryableErrorRate 获取可重试错误率 func (es *ErrorStatistics) GetRetryableErrorRate() float64 { if es.TotalErrors == 0 { return 0.0 } return float64(es.RetryableErrors) / float64(es.TotalErrors) * 100.0 } // 辅助函数 // WrapError 包装错误 func WrapError(err error, code, message string, severity ErrorSeverity) BuildingError { buildingErr := NewBuildingError(code, message, severity) buildingErr.SetContext("wrapped_error", err.Error()) return buildingErr } // ChainErrors 链接错误 func ChainErrors(errors []error) BuildingError { if len(errors) == 0 { return nil } if len(errors) == 1 { if buildingErr, ok := GetBuildingError(errors[0]); ok { return buildingErr } return WrapError(errors[0], "CHAINED_ERROR", errors[0].Error(), ErrorSeverityMedium) } // 创建链式错误 messages := make([]string, len(errors)) for i, err := range errors { messages[i] = err.Error() } chainedErr := NewBuildingError( "CHAINED_ERROR", fmt.Sprintf("multiple errors occurred: %v", messages), ErrorSeverityHigh, ) for i, err := range errors { chainedErr.SetContext(fmt.Sprintf("error_%d", i), err.Error()) } return chainedErr } // ValidateErrorCode 验证错误代码 func ValidateErrorCode(code string) bool { validCodes := []string{ ErrCodeInvalidInput, ErrCodeRepositoryError, ErrCodeConcurrencyError, ErrCodeInvalidOperation, ErrCodeBuildingNotFound, ErrCodeInvalidBuildingState, ErrCodePositionOccupied, ErrCodeInsufficientResources, ErrCodeConstructionNotFound, ErrCodeConstructionFailed, ErrCodeUpgradeNotFound, ErrCodeUpgradeFailed, ErrCodeInvalidUpgrade, ErrCodeRepairFailed, ErrCodeDestroyFailed, ErrCodeWorkerNotFound, ErrCodeWorkerNotAvailable, ErrCodeBlueprintNotFound, ErrCodeBlueprintInvalid, ErrCodeSystemError, ErrCodeConfigError, ErrCodeNetworkError, ErrCodeTimeoutError, } for _, validCode := range validCodes { if code == validCode { return true } } return false } // FormatError 格式化错误信息 func FormatError(err error) string { if buildingErr, ok := GetBuildingError(err); ok { return fmt.Sprintf("[%s] %s (severity: %s, timestamp: %s)", buildingErr.GetCode(), buildingErr.GetMessage(), buildingErr.GetSeverity().String(), buildingErr.GetTimestamp().Format(time.RFC3339), ) } return err.Error() } // GetErrorDetails 获取错误详情 func GetErrorDetails(err error) map[string]interface{} { details := make(map[string]interface{}) if buildingErr, ok := GetBuildingError(err); ok { details["code"] = buildingErr.GetCode() details["message"] = buildingErr.GetMessage() details["severity"] = buildingErr.GetSeverity().String() details["timestamp"] = buildingErr.GetTimestamp() details["retryable"] = buildingErr.IsRetryable() details["retry_after"] = buildingErr.GetRetryAfter() details["context"] = buildingErr.GetContext() details["recovery_strategy"] = GetRecoveryStrategy(err).String() } else { details["code"] = "UNKNOWN_ERROR" details["message"] = err.Error() details["severity"] = ErrorSeverityLow.String() details["timestamp"] = time.Now() details["retryable"] = false details["retry_after"] = time.Duration(0) details["context"] = make(map[string]interface{}) details["recovery_strategy"] = RecoveryStrategyNone.String() } return details } ================================================ FILE: internal/domain/building/events.go ================================================ package building import ( "context" "fmt" "time" ) // BuildingEvent 建筑事件接口 type BuildingEvent interface { GetEventID() string GetEventType() string GetBuildingID() string GetTimestamp() time.Time GetPayload() interface{} Validate() error } // BaseBuildingEvent 建筑事件基础结构 type BaseBuildingEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` BuildingID string `json:"building_id"` Timestamp time.Time `json:"timestamp"` Payload map[string]interface{} `json:"payload"` Metadata map[string]interface{} `json:"metadata"` } // GetEventID 获取事件ID func (e *BaseBuildingEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseBuildingEvent) GetEventType() string { return e.EventType } // GetBuildingID 获取建筑ID func (e *BaseBuildingEvent) GetBuildingID() string { return e.BuildingID } // GetTimestamp 获取时间戳 func (e *BaseBuildingEvent) GetTimestamp() time.Time { return e.Timestamp } // GetPayload 获取载荷 func (e *BaseBuildingEvent) GetPayload() interface{} { return e.Payload } // Validate 验证事件 func (e *BaseBuildingEvent) Validate() error { if e.EventID == "" { return fmt.Errorf("event ID cannot be empty") } if e.EventType == "" { return fmt.Errorf("event type cannot be empty") } if e.BuildingID == "" { return fmt.Errorf("building ID cannot be empty") } if e.Timestamp.IsZero() { return fmt.Errorf("timestamp cannot be zero") } return nil } // SetMetadata 设置元数据 func (e *BaseBuildingEvent) SetMetadata(key string, value interface{}) { if e.Metadata == nil { e.Metadata = make(map[string]interface{}) } e.Metadata[key] = value } // GetMetadata 获取元数据 func (e *BaseBuildingEvent) GetMetadata(key string) (interface{}, bool) { if e.Metadata == nil { return nil, false } value, exists := e.Metadata[key] return value, exists } // 建筑生命周期事件 // BuildingCreatedEvent 建筑创建事件 type BuildingCreatedEvent struct { *BaseBuildingEvent Name string `json:"name"` Type BuildingType `json:"type"` Category BuildingCategory `json:"category"` OwnerID uint64 `json:"owner_id"` Position *Position `json:"position,omitempty"` Size *Size `json:"size,omitempty"` } // NewBuildingCreatedEvent 创建建筑创建事件 func NewBuildingCreatedEvent(buildingID, name string, buildingType BuildingType, ownerID uint64) *BuildingCreatedEvent { return &BuildingCreatedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingCreated, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, Name: name, Type: buildingType, OwnerID: ownerID, } } // BuildingUpdatedEvent 建筑更新事件 type BuildingUpdatedEvent struct { *BaseBuildingEvent Changes map[string]interface{} `json:"changes"` } // NewBuildingUpdatedEvent 创建建筑更新事件 func NewBuildingUpdatedEvent(buildingID string, changes map[string]interface{}) *BuildingUpdatedEvent { return &BuildingUpdatedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingUpdated, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, Changes: changes, } } // BuildingDeletedEvent 建筑删除事件 type BuildingDeletedEvent struct { *BaseBuildingEvent Reason string `json:"reason"` } // NewBuildingDeletedEvent 创建建筑删除事件 func NewBuildingDeletedEvent(buildingID, reason string) *BuildingDeletedEvent { return &BuildingDeletedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingDeleted, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, Reason: reason, } } // 建筑状态事件 // BuildingStatusChangedEvent 建筑状态变更事件 type BuildingStatusChangedEvent struct { *BaseBuildingEvent OldStatus BuildingStatus `json:"old_status"` NewStatus BuildingStatus `json:"new_status"` Reason string `json:"reason"` } // NewBuildingStatusChangedEvent 创建建筑状态变更事件 func NewBuildingStatusChangedEvent(buildingID string, oldStatus, newStatus BuildingStatus, reason string) *BuildingStatusChangedEvent { return &BuildingStatusChangedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingStatusChanged, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, OldStatus: oldStatus, NewStatus: newStatus, Reason: reason, } } // BuildingHealthChangedEvent 建筑健康度变更事件 type BuildingHealthChangedEvent struct { *BaseBuildingEvent OldHealth float64 `json:"old_health"` NewHealth float64 `json:"new_health"` Reason string `json:"reason"` } // NewBuildingHealthChangedEvent 创建建筑健康度变更事件 func NewBuildingHealthChangedEvent(buildingID string, oldHealth, newHealth float64, reason string) *BuildingHealthChangedEvent { return &BuildingHealthChangedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingHealthChanged, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, OldHealth: oldHealth, NewHealth: newHealth, Reason: reason, } } // BuildingLevelChangedEvent 建筑等级变更事件 type BuildingLevelChangedEvent struct { *BaseBuildingEvent OldLevel int32 `json:"old_level"` NewLevel int32 `json:"new_level"` Reason string `json:"reason"` } // NewBuildingLevelChangedEvent 创建建筑等级变更事件 func NewBuildingLevelChangedEvent(buildingID string, oldLevel, newLevel int32, reason string) *BuildingLevelChangedEvent { return &BuildingLevelChangedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingLevelChanged, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, OldLevel: oldLevel, NewLevel: newLevel, Reason: reason, } } // 建造相关事件 // ConstructionStartedEvent 建造开始事件 type ConstructionStartedEvent struct { *BaseBuildingEvent ConstructionID string `json:"construction_id"` Duration time.Duration `json:"duration"` Costs []*ResourceCost `json:"costs,omitempty"` } // NewConstructionStartedEvent 创建建造开始事件 func NewConstructionStartedEvent(buildingID, constructionID string, duration time.Duration) *ConstructionStartedEvent { return &ConstructionStartedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeConstructionStarted, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, ConstructionID: constructionID, Duration: duration, } } // ConstructionProgressUpdatedEvent 建造进度更新事件 type ConstructionProgressUpdatedEvent struct { *BaseBuildingEvent ConstructionID string `json:"construction_id"` Progress float64 `json:"progress"` OldProgress float64 `json:"old_progress"` } // NewConstructionProgressUpdatedEvent 创建建造进度更新事件 func NewConstructionProgressUpdatedEvent(buildingID, constructionID string, progress float64) *ConstructionProgressUpdatedEvent { return &ConstructionProgressUpdatedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeConstructionProgressUpdated, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, ConstructionID: constructionID, Progress: progress, } } // ConstructionCompletedEvent 建造完成事件 type ConstructionCompletedEvent struct { *BaseBuildingEvent ConstructionID string `json:"construction_id"` Duration time.Duration `json:"duration"` ActualCosts []*ResourceCost `json:"actual_costs,omitempty"` } // NewConstructionCompletedEvent 创建建造完成事件 func NewConstructionCompletedEvent(buildingID, constructionID string) *ConstructionCompletedEvent { return &ConstructionCompletedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeConstructionCompleted, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, ConstructionID: constructionID, } } // ConstructionCancelledEvent 建造取消事件 type ConstructionCancelledEvent struct { *BaseBuildingEvent ConstructionID string `json:"construction_id"` Reason string `json:"reason"` } // NewConstructionCancelledEvent 创建建造取消事件 func NewConstructionCancelledEvent(buildingID, constructionID, reason string) *ConstructionCancelledEvent { return &ConstructionCancelledEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeConstructionCancelled, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, ConstructionID: constructionID, Reason: reason, } } // 升级相关事件 // UpgradeStartedEvent 升级开始事件 type UpgradeStartedEvent struct { *BaseBuildingEvent UpgradeID string `json:"upgrade_id"` FromLevel int32 `json:"from_level"` ToLevel int32 `json:"to_level"` Duration time.Duration `json:"duration"` Costs []*ResourceCost `json:"costs,omitempty"` } // NewUpgradeStartedEvent 创建升级开始事件 func NewUpgradeStartedEvent(buildingID, upgradeID string, fromLevel, toLevel int32) *UpgradeStartedEvent { return &UpgradeStartedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeUpgradeStarted, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, UpgradeID: upgradeID, FromLevel: fromLevel, ToLevel: toLevel, } } // UpgradeProgressUpdatedEvent 升级进度更新事件 type UpgradeProgressUpdatedEvent struct { *BaseBuildingEvent UpgradeID string `json:"upgrade_id"` Progress float64 `json:"progress"` OldProgress float64 `json:"old_progress"` } // NewUpgradeProgressUpdatedEvent 创建升级进度更新事件 func NewUpgradeProgressUpdatedEvent(buildingID, upgradeID string, progress float64) *UpgradeProgressUpdatedEvent { return &UpgradeProgressUpdatedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeUpgradeProgressUpdated, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, UpgradeID: upgradeID, Progress: progress, } } // UpgradeCompletedEvent 升级完成事件 type UpgradeCompletedEvent struct { *BaseBuildingEvent UpgradeID string `json:"upgrade_id"` FromLevel int32 `json:"from_level"` ToLevel int32 `json:"to_level"` Duration time.Duration `json:"duration"` ActualCosts []*ResourceCost `json:"actual_costs,omitempty"` Benefits []*UpgradeBenefit `json:"benefits,omitempty"` } // NewUpgradeCompletedEvent 创建升级完成事件 func NewUpgradeCompletedEvent(buildingID, upgradeID string, fromLevel, toLevel int32) *UpgradeCompletedEvent { return &UpgradeCompletedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeUpgradeCompleted, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, UpgradeID: upgradeID, FromLevel: fromLevel, ToLevel: toLevel, } } // UpgradeCancelledEvent 升级取消事件 type UpgradeCancelledEvent struct { *BaseBuildingEvent UpgradeID string `json:"upgrade_id"` Reason string `json:"reason"` } // NewUpgradeCancelledEvent 创建升级取消事件 func NewUpgradeCancelledEvent(buildingID, upgradeID, reason string) *UpgradeCancelledEvent { return &UpgradeCancelledEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeUpgradeCancelled, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, UpgradeID: upgradeID, Reason: reason, } } // 维护相关事件 // BuildingRepairedEvent 建筑修复事件 type BuildingRepairedEvent struct { *BaseBuildingEvent OldHealth float64 `json:"old_health"` NewHealth float64 `json:"new_health"` RepairAmount float64 `json:"repair_amount"` Costs []*ResourceCost `json:"costs,omitempty"` } // NewBuildingRepairedEvent 创建建筑修复事件 func NewBuildingRepairedEvent(buildingID string, oldHealth, newHealth float64) *BuildingRepairedEvent { return &BuildingRepairedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingRepaired, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, OldHealth: oldHealth, NewHealth: newHealth, RepairAmount: newHealth - oldHealth, } } // BuildingDamagedEvent 建筑损坏事件 type BuildingDamagedEvent struct { *BaseBuildingEvent OldHealth float64 `json:"old_health"` NewHealth float64 `json:"new_health"` DamageAmount float64 `json:"damage_amount"` DamageType string `json:"damage_type"` Reason string `json:"reason"` } // NewBuildingDamagedEvent 创建建筑损坏事件 func NewBuildingDamagedEvent(buildingID string, oldHealth, newHealth float64, damageType, reason string) *BuildingDamagedEvent { return &BuildingDamagedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingDamaged, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, OldHealth: oldHealth, NewHealth: newHealth, DamageAmount: oldHealth - newHealth, DamageType: damageType, Reason: reason, } } // BuildingDestroyedEvent 建筑摧毁事件 type BuildingDestroyedEvent struct { *BaseBuildingEvent Reason string `json:"reason"` } // NewBuildingDestroyedEvent 创建建筑摧毁事件 func NewBuildingDestroyedEvent(buildingID, reason string) *BuildingDestroyedEvent { return &BuildingDestroyedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingDestroyed, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, Reason: reason, } } // 工人相关事件 // WorkerAssignedEvent 工人分配事件 type WorkerAssignedEvent struct { *BaseBuildingEvent WorkerID uint64 `json:"worker_id"` Role WorkerRole `json:"role"` Task string `json:"task"` ConstructionID string `json:"construction_id,omitempty"` UpgradeID string `json:"upgrade_id,omitempty"` } // NewWorkerAssignedEvent 创建工人分配事件 func NewWorkerAssignedEvent(buildingID string, workerID uint64, role WorkerRole, task string) *WorkerAssignedEvent { return &WorkerAssignedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeWorkerAssigned, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, WorkerID: workerID, Role: role, Task: task, } } // WorkerUnassignedEvent 工人取消分配事件 type WorkerUnassignedEvent struct { *BaseBuildingEvent WorkerID uint64 `json:"worker_id"` Reason string `json:"reason"` ConstructionID string `json:"construction_id,omitempty"` UpgradeID string `json:"upgrade_id,omitempty"` } // NewWorkerUnassignedEvent 创建工人取消分配事件 func NewWorkerUnassignedEvent(buildingID string, workerID uint64, reason string) *WorkerUnassignedEvent { return &WorkerUnassignedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeWorkerUnassigned, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, WorkerID: workerID, Reason: reason, } } // 蓝图相关事件 // BlueprintCreatedEvent 蓝图创建事件 type BlueprintCreatedEvent struct { *BaseBuildingEvent BlueprintID string `json:"blueprint_id"` Name string `json:"name"` Category BuildingCategory `json:"category"` Author string `json:"author"` Difficulty int32 `json:"difficulty"` } // NewBlueprintCreatedEvent 创建蓝图创建事件 func NewBlueprintCreatedEvent(blueprintID, name string, category BuildingCategory) *BlueprintCreatedEvent { return &BlueprintCreatedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBlueprintCreated, BuildingID: "", // 蓝图事件可能没有关联的建筑ID Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, BlueprintID: blueprintID, Name: name, Category: category, } } // BlueprintUsedEvent 蓝图使用事件 type BlueprintUsedEvent struct { *BaseBuildingEvent BlueprintID string `json:"blueprint_id"` UserID uint64 `json:"user_id"` } // NewBlueprintUsedEvent 创建蓝图使用事件 func NewBlueprintUsedEvent(buildingID, blueprintID string, userID uint64) *BlueprintUsedEvent { return &BlueprintUsedEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBlueprintUsed, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, BlueprintID: blueprintID, UserID: userID, } } // 系统事件 // BuildingSystemErrorEvent 建筑系统错误事件 type BuildingSystemErrorEvent struct { *BaseBuildingEvent ErrorCode string `json:"error_code"` ErrorMessage string `json:"error_message"` Severity string `json:"severity"` StackTrace string `json:"stack_trace,omitempty"` } // NewBuildingSystemErrorEvent 创建建筑系统错误事件 func NewBuildingSystemErrorEvent(buildingID, errorCode, errorMessage, severity string) *BuildingSystemErrorEvent { return &BuildingSystemErrorEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingSystemError, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, ErrorCode: errorCode, ErrorMessage: errorMessage, Severity: severity, } } // BuildingMaintenanceScheduledEvent 建筑维护计划事件 type BuildingMaintenanceScheduledEvent struct { *BaseBuildingEvent MaintenanceType string `json:"maintenance_type"` ScheduledAt time.Time `json:"scheduled_at"` Duration time.Duration `json:"duration"` Description string `json:"description"` } // NewBuildingMaintenanceScheduledEvent 创建建筑维护计划事件 func NewBuildingMaintenanceScheduledEvent(buildingID, maintenanceType string, scheduledAt time.Time, duration time.Duration) *BuildingMaintenanceScheduledEvent { return &BuildingMaintenanceScheduledEvent{ BaseBuildingEvent: &BaseBuildingEvent{ EventID: generateEventID(), EventType: EventTypeBuildingMaintenanceScheduled, BuildingID: buildingID, Timestamp: time.Now(), Payload: make(map[string]interface{}), Metadata: make(map[string]interface{}), }, MaintenanceType: maintenanceType, ScheduledAt: scheduledAt, Duration: duration, } } // 事件常量 const ( // 建筑生命周期事件 EventTypeBuildingCreated = "building.created" EventTypeBuildingUpdated = "building.updated" EventTypeBuildingDeleted = "building.deleted" // 建筑状态事件 EventTypeBuildingStatusChanged = "building.status_changed" EventTypeBuildingHealthChanged = "building.health_changed" EventTypeBuildingLevelChanged = "building.level_changed" // 建造相关事件 EventTypeConstructionStarted = "construction.started" EventTypeConstructionProgressUpdated = "construction.progress_updated" EventTypeConstructionCompleted = "construction.completed" EventTypeConstructionCancelled = "construction.cancelled" // 升级相关事件 EventTypeUpgradeStarted = "upgrade.started" EventTypeUpgradeProgressUpdated = "upgrade.progress_updated" EventTypeUpgradeCompleted = "upgrade.completed" EventTypeUpgradeCancelled = "upgrade.cancelled" // 维护相关事件 EventTypeBuildingRepaired = "building.repaired" EventTypeBuildingDamaged = "building.damaged" EventTypeBuildingDestroyed = "building.destroyed" // 工人相关事件 EventTypeWorkerAssigned = "worker.assigned" EventTypeWorkerUnassigned = "worker.unassigned" // 蓝图相关事件 EventTypeBlueprintCreated = "blueprint.created" EventTypeBlueprintUsed = "blueprint.used" // 系统事件 EventTypeBuildingSystemError = "building.system_error" EventTypeBuildingMaintenanceScheduled = "building.maintenance_scheduled" ) // 事件处理器接口 // BuildingEventHandler 建筑事件处理器接口 type BuildingEventHandler interface { Handle(ctx context.Context, event BuildingEvent) error CanHandle(eventType string) bool GetHandlerName() string } // BuildingEventBus 建筑事件总线接口 type BuildingEventBus interface { Publish(ctx context.Context, event BuildingEvent) error Subscribe(eventType string, handler BuildingEventHandler) error Unsubscribe(eventType string, handlerName string) error Start(ctx context.Context) error Stop(ctx context.Context) error } // 事件聚合器 // BuildingEventAggregator 建筑事件聚合器 type BuildingEventAggregator struct { buildingID string events []BuildingEvent version int64 createdAt time.Time updatedAt time.Time } // NewBuildingEventAggregator 创建新建筑事件聚合器 func NewBuildingEventAggregator(buildingID string) *BuildingEventAggregator { now := time.Now() return &BuildingEventAggregator{ buildingID: buildingID, events: make([]BuildingEvent, 0), version: 0, createdAt: now, updatedAt: now, } } // AddEvent 添加事件 func (ea *BuildingEventAggregator) AddEvent(event BuildingEvent) { ea.events = append(ea.events, event) ea.version++ ea.updatedAt = time.Now() } // GetEvents 获取所有事件 func (ea *BuildingEventAggregator) GetEvents() []BuildingEvent { return ea.events } // GetEventsByType 根据类型获取事件 func (ea *BuildingEventAggregator) GetEventsByType(eventType string) []BuildingEvent { var result []BuildingEvent for _, event := range ea.events { if event.GetEventType() == eventType { result = append(result, event) } } return result } // GetEventsAfter 获取指定时间后的事件 func (ea *BuildingEventAggregator) GetEventsAfter(timestamp time.Time) []BuildingEvent { var result []BuildingEvent for _, event := range ea.events { if event.GetTimestamp().After(timestamp) { result = append(result, event) } } return result } // GetEventCount 获取事件数量 func (ea *BuildingEventAggregator) GetEventCount() int { return len(ea.events) } // GetEventCountByType 根据类型获取事件数量 func (ea *BuildingEventAggregator) GetEventCountByType(eventType string) int { count := 0 for _, event := range ea.events { if event.GetEventType() == eventType { count++ } } return count } // Clear 清空事件 func (ea *BuildingEventAggregator) Clear() { ea.events = make([]BuildingEvent, 0) ea.version = 0 ea.updatedAt = time.Now() } // GetBuildingID 获取建筑ID func (ea *BuildingEventAggregator) GetBuildingID() string { return ea.buildingID } // GetVersion 获取版本 func (ea *BuildingEventAggregator) GetVersion() int64 { return ea.version } // GetCreatedAt 获取创建时间 func (ea *BuildingEventAggregator) GetCreatedAt() time.Time { return ea.createdAt } // GetUpdatedAt 获取更新时间 func (ea *BuildingEventAggregator) GetUpdatedAt() time.Time { return ea.updatedAt } // 事件统计 // BuildingEventStatistics 建筑事件统计 type BuildingEventStatistics struct { BuildingID string `json:"building_id"` TotalEvents int64 `json:"total_events"` EventsByType map[string]int64 `json:"events_by_type"` EventsByHour map[string]int64 `json:"events_by_hour"` EventsByDay map[string]int64 `json:"events_by_day"` LastEventTime time.Time `json:"last_event_time"` UpdatedAt time.Time `json:"updated_at"` } // NewBuildingEventStatistics 创建新建筑事件统计 func NewBuildingEventStatistics(buildingID string) *BuildingEventStatistics { return &BuildingEventStatistics{ BuildingID: buildingID, TotalEvents: 0, EventsByType: make(map[string]int64), EventsByHour: make(map[string]int64), EventsByDay: make(map[string]int64), UpdatedAt: time.Now(), } } // AddEvent 添加事件到统计 func (es *BuildingEventStatistics) AddEvent(event BuildingEvent) { es.TotalEvents++ es.EventsByType[event.GetEventType()]++ timestamp := event.GetTimestamp() hourKey := timestamp.Format("2006-01-02-15") dayKey := timestamp.Format("2006-01-02") es.EventsByHour[hourKey]++ es.EventsByDay[dayKey]++ if timestamp.After(es.LastEventTime) { es.LastEventTime = timestamp } es.UpdatedAt = time.Now() } // GetMostFrequentEventType 获取最频繁的事件类型 func (es *BuildingEventStatistics) GetMostFrequentEventType() string { maxCount := int64(0) mostFrequent := "" for eventType, count := range es.EventsByType { if count > maxCount { maxCount = count mostFrequent = eventType } } return mostFrequent } // GetEventsInLastHour 获取最近一小时的事件数量 func (es *BuildingEventStatistics) GetEventsInLastHour() int64 { hourKey := time.Now().Format("2006-01-02-15") return es.EventsByHour[hourKey] } // GetEventsInLastDay 获取最近一天的事件数量 func (es *BuildingEventStatistics) GetEventsInLastDay() int64 { dayKey := time.Now().Format("2006-01-02") return es.EventsByDay[dayKey] } // 辅助函数 // generateEventID 生成事件ID func generateEventID() string { return fmt.Sprintf("event_%d", time.Now().UnixNano()) } // ValidateEventType 验证事件类型 func ValidateEventType(eventType string) bool { validTypes := []string{ EventTypeBuildingCreated, EventTypeBuildingUpdated, EventTypeBuildingDeleted, EventTypeBuildingStatusChanged, EventTypeBuildingHealthChanged, EventTypeBuildingLevelChanged, EventTypeConstructionStarted, EventTypeConstructionProgressUpdated, EventTypeConstructionCompleted, EventTypeConstructionCancelled, EventTypeUpgradeStarted, EventTypeUpgradeProgressUpdated, EventTypeUpgradeCompleted, EventTypeUpgradeCancelled, EventTypeBuildingRepaired, EventTypeBuildingDamaged, EventTypeBuildingDestroyed, EventTypeWorkerAssigned, EventTypeWorkerUnassigned, EventTypeBlueprintCreated, EventTypeBlueprintUsed, EventTypeBuildingSystemError, EventTypeBuildingMaintenanceScheduled, } for _, validType := range validTypes { if eventType == validType { return true } } return false } // GetEventCategory 获取事件分类 func GetEventCategory(eventType string) string { switch eventType { case EventTypeBuildingCreated, EventTypeBuildingUpdated, EventTypeBuildingDeleted: return "lifecycle" case EventTypeBuildingStatusChanged, EventTypeBuildingHealthChanged, EventTypeBuildingLevelChanged: return "status" case EventTypeConstructionStarted, EventTypeConstructionProgressUpdated, EventTypeConstructionCompleted, EventTypeConstructionCancelled: return "construction" case EventTypeUpgradeStarted, EventTypeUpgradeProgressUpdated, EventTypeUpgradeCompleted, EventTypeUpgradeCancelled: return "upgrade" case EventTypeBuildingRepaired, EventTypeBuildingDamaged, EventTypeBuildingDestroyed: return "maintenance" case EventTypeWorkerAssigned, EventTypeWorkerUnassigned: return "worker" case EventTypeBlueprintCreated, EventTypeBlueprintUsed: return "blueprint" case EventTypeBuildingSystemError, EventTypeBuildingMaintenanceScheduled: return "system" default: return "unknown" } } // IsSystemEvent 检查是否为系统事件 func IsSystemEvent(eventType string) bool { return GetEventCategory(eventType) == "system" } // IsUserEvent 检查是否为用户事件 func IsUserEvent(eventType string) bool { category := GetEventCategory(eventType) return category != "system" && category != "unknown" } // GetEventPriority 获取事件优先级 func GetEventPriority(eventType string) int { switch eventType { case EventTypeBuildingSystemError: return 1 // 最高优先级 case EventTypeBuildingDestroyed, EventTypeBuildingDamaged: return 2 // 高优先级 case EventTypeConstructionCompleted, EventTypeUpgradeCompleted: return 3 // 中等优先级 case EventTypeBuildingCreated, EventTypeConstructionStarted, EventTypeUpgradeStarted: return 4 // 普通优先级 default: return 5 // 低优先级 } } ================================================ FILE: internal/domain/building/repository.go ================================================ package building import ( "context" "fmt" "time" ) // BuildingRepository 建筑仓储接口 type BuildingRepository interface { // 基础CRUD操作 Save(ctx context.Context, building *BuildingAggregate) error FindByID(ctx context.Context, id string) (*BuildingAggregate, error) FindByIDs(ctx context.Context, ids []string) ([]*BuildingAggregate, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByOwner(ctx context.Context, ownerID uint64) ([]*BuildingAggregate, error) FindByType(ctx context.Context, buildingType BuildingType) ([]*BuildingAggregate, error) FindByCategory(ctx context.Context, category BuildingCategory) ([]*BuildingAggregate, error) FindByStatus(ctx context.Context, status BuildingStatus) ([]*BuildingAggregate, error) FindByPosition(ctx context.Context, position *Position) (*BuildingAggregate, error) FindByPlayerAndPosition(ctx context.Context, playerID uint64, position *Position) (*BuildingAggregate, error) FindByArea(ctx context.Context, area *Area) ([]*BuildingAggregate, error) FindByQuery(ctx context.Context, query *BuildingQuery) ([]*BuildingAggregate, int64, error) // 统计操作 Count(ctx context.Context) (int64, error) CountByOwner(ctx context.Context, ownerID uint64) (int64, error) CountByType(ctx context.Context, buildingType BuildingType) (int64, error) CountByCategory(ctx context.Context, category BuildingCategory) (int64, error) CountByStatus(ctx context.Context, status BuildingStatus) (int64, error) GetStatistics(ctx context.Context, ownerID uint64) (*BuildingStatistics, error) // 批量操作 SaveAll(ctx context.Context, buildings []*BuildingAggregate) error DeleteAll(ctx context.Context, ids []string) error UpdateStatus(ctx context.Context, ids []string, status BuildingStatus) error UpdateHealth(ctx context.Context, id string, health float64) error UpdateLevel(ctx context.Context, id string, level int32) error } // ConstructionRepository 建造仓储接口 type ConstructionRepository interface { // 基础CRUD操作 Save(ctx context.Context, construction *ConstructionInfo) error FindByID(ctx context.Context, id string) (*ConstructionInfo, error) FindByIDs(ctx context.Context, ids []string) ([]*ConstructionInfo, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByBuildingID(ctx context.Context, buildingID string) ([]*ConstructionInfo, error) FindByStatus(ctx context.Context, status ConstructionStatus) ([]*ConstructionInfo, error) FindByWorker(ctx context.Context, workerID uint64) ([]*ConstructionInfo, error) FindByDateRange(ctx context.Context, startDate, endDate time.Time) ([]*ConstructionInfo, error) FindByQuery(ctx context.Context, query *ConstructionQuery) ([]*ConstructionInfo, int64, error) // 统计操作 Count(ctx context.Context) (int64, error) CountByStatus(ctx context.Context, status ConstructionStatus) (int64, error) CountByBuildingID(ctx context.Context, buildingID string) (int64, error) GetStatistics(ctx context.Context, buildingID string) (*ConstructionStatistics, error) // 批量操作 SaveAll(ctx context.Context, constructions []*ConstructionInfo) error DeleteAll(ctx context.Context, ids []string) error UpdateStatus(ctx context.Context, ids []string, status ConstructionStatus) error UpdateProgress(ctx context.Context, id string, progress float64) error } // UpgradeRepository 升级仓储接口 type UpgradeRepository interface { // 基础CRUD操作 Save(ctx context.Context, upgrade *UpgradeInfo) error FindByID(ctx context.Context, id string) (*UpgradeInfo, error) FindByIDs(ctx context.Context, ids []string) ([]*UpgradeInfo, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByBuildingID(ctx context.Context, buildingID string) ([]*UpgradeInfo, error) FindByStatus(ctx context.Context, status UpgradeStatus) ([]*UpgradeInfo, error) FindByLevel(ctx context.Context, fromLevel, toLevel int32) ([]*UpgradeInfo, error) FindByDateRange(ctx context.Context, startDate, endDate time.Time) ([]*UpgradeInfo, error) FindByQuery(ctx context.Context, query *UpgradeQuery) ([]*UpgradeInfo, int64, error) // 统计操作 Count(ctx context.Context) (int64, error) CountByStatus(ctx context.Context, status UpgradeStatus) (int64, error) CountByBuildingID(ctx context.Context, buildingID string) (int64, error) GetStatistics(ctx context.Context, buildingID string) (*UpgradeStatistics, error) // 批量操作 SaveAll(ctx context.Context, upgrades []*UpgradeInfo) error DeleteAll(ctx context.Context, ids []string) error UpdateStatus(ctx context.Context, ids []string, status UpgradeStatus) error UpdateProgress(ctx context.Context, id string, progress float64) error } // BlueprintRepository 蓝图仓储接口 type BlueprintRepository interface { // 基础CRUD操作 Save(ctx context.Context, blueprint *Blueprint) error FindByID(ctx context.Context, id string) (*Blueprint, error) FindByIDs(ctx context.Context, ids []string) ([]*Blueprint, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByName(ctx context.Context, name string) ([]*Blueprint, error) FindByAuthor(ctx context.Context, author string) ([]*Blueprint, error) FindByCategory(ctx context.Context, category BuildingCategory) ([]*Blueprint, error) FindByDifficulty(ctx context.Context, minDifficulty, maxDifficulty int32) ([]*Blueprint, error) FindByTags(ctx context.Context, tags []string) ([]*Blueprint, error) FindByQuery(ctx context.Context, query *BlueprintQuery) ([]*Blueprint, int64, error) // 统计操作 Count(ctx context.Context) (int64, error) CountByCategory(ctx context.Context, category BuildingCategory) (int64, error) CountByAuthor(ctx context.Context, author string) (int64, error) GetStatistics(ctx context.Context) (*BlueprintStatistics, error) // 批量操作 SaveAll(ctx context.Context, blueprints []*Blueprint) error DeleteAll(ctx context.Context, ids []string) error } // 查询条件结构体 // BuildingQuery 建筑查询条件 type BuildingQuery struct { // 基础字段 OwnerID *uint64 `json:"owner_id,omitempty"` Name *string `json:"name,omitempty"` Type *BuildingType `json:"type,omitempty"` Category *BuildingCategory `json:"category,omitempty"` Status *BuildingStatus `json:"status,omitempty"` // 等级范围 MinLevel *int32 `json:"min_level,omitempty"` MaxLevel *int32 `json:"max_level,omitempty"` // 健康度范围 MinHealth *float64 `json:"min_health,omitempty"` MaxHealth *float64 `json:"max_health,omitempty"` // 位置查询 Position *Position `json:"position,omitempty"` Area *Area `json:"area,omitempty"` // 时间范围 CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` UpdatedAfter *time.Time `json:"updated_after,omitempty"` UpdatedBefore *time.Time `json:"updated_before,omitempty"` // 标签 Tags []string `json:"tags,omitempty"` // 排序 SortBy string `json:"sort_by,omitempty"` // name, type, level, health, created_at, updated_at SortOrder string `json:"sort_order,omitempty"` // asc, desc // 分页 Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` } // ConstructionQuery 建造查询条件 type ConstructionQuery struct { // 基础字段 BuildingID *string `json:"building_id,omitempty"` Status *ConstructionStatus `json:"status,omitempty"` WorkerID *uint64 `json:"worker_id,omitempty"` // 进度范围 MinProgress *float64 `json:"min_progress,omitempty"` MaxProgress *float64 `json:"max_progress,omitempty"` // 时间范围 StartedAfter *time.Time `json:"started_after,omitempty"` StartedBefore *time.Time `json:"started_before,omitempty"` CompletedAfter *time.Time `json:"completed_after,omitempty"` CompletedBefore *time.Time `json:"completed_before,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` // 排序 SortBy string `json:"sort_by,omitempty"` // progress, started_at, duration, created_at SortOrder string `json:"sort_order,omitempty"` // asc, desc // 分页 Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` } // UpgradeQuery 升级查询条件 type UpgradeQuery struct { // 基础字段 BuildingID *string `json:"building_id,omitempty"` Status *UpgradeStatus `json:"status,omitempty"` // 等级范围 FromLevel *int32 `json:"from_level,omitempty"` ToLevel *int32 `json:"to_level,omitempty"` // 进度范围 MinProgress *float64 `json:"min_progress,omitempty"` MaxProgress *float64 `json:"max_progress,omitempty"` // 时间范围 StartedAfter *time.Time `json:"started_after,omitempty"` StartedBefore *time.Time `json:"started_before,omitempty"` CompletedAfter *time.Time `json:"completed_after,omitempty"` CompletedBefore *time.Time `json:"completed_before,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` // 排序 SortBy string `json:"sort_by,omitempty"` // from_level, to_level, progress, started_at, created_at SortOrder string `json:"sort_order,omitempty"` // asc, desc // 分页 Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` } // BlueprintQuery 蓝图查询条件 type BlueprintQuery struct { // 基础字段 Name *string `json:"name,omitempty"` Author *string `json:"author,omitempty"` Category *BuildingCategory `json:"category,omitempty"` Version *string `json:"version,omitempty"` // 难度范围 MinDifficulty *int32 `json:"min_difficulty,omitempty"` MaxDifficulty *int32 `json:"max_difficulty,omitempty"` // 尺寸范围 MinWidth *int32 `json:"min_width,omitempty"` MaxWidth *int32 `json:"max_width,omitempty"` MinHeight *int32 `json:"min_height,omitempty"` MaxHeight *int32 `json:"max_height,omitempty"` MinDepth *int32 `json:"min_depth,omitempty"` MaxDepth *int32 `json:"max_depth,omitempty"` // 时间范围 MinDuration *time.Duration `json:"min_duration,omitempty"` MaxDuration *time.Duration `json:"max_duration,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` UpdatedAfter *time.Time `json:"updated_after,omitempty"` UpdatedBefore *time.Time `json:"updated_before,omitempty"` // 标签 Tags []string `json:"tags,omitempty"` // 搜索关键词 Keyword *string `json:"keyword,omitempty"` // 排序 SortBy string `json:"sort_by,omitempty"` // name, author, difficulty, duration, created_at SortOrder string `json:"sort_order,omitempty"` // asc, desc // 分页 Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` } // 分页结果结构体 // PaginationResult 分页结果 type PaginationResult struct { Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int64 `json:"total_pages"` HasNext bool `json:"has_next"` HasPrevious bool `json:"has_previous"` } // 统计数据结构体 // BuildingStatistics 建筑统计数据 type BuildingStatistics struct { OwnerID uint64 `json:"owner_id"` // 总体统计 TotalBuildings int64 `json:"total_buildings"` ActiveBuildings int64 `json:"active_buildings"` InactiveBuildings int64 `json:"inactive_buildings"` UnderConstruction int64 `json:"under_construction"` UnderUpgrade int64 `json:"under_upgrade"` Damaged int64 `json:"damaged"` Destroyed int64 `json:"destroyed"` // 按类型统计 ByType map[BuildingType]int64 `json:"by_type"` // 按分类统计 ByCategory map[BuildingCategory]int64 `json:"by_category"` // 按等级统计 ByLevel map[int32]int64 `json:"by_level"` // 健康度统计 AverageHealth float64 `json:"average_health"` MinHealth float64 `json:"min_health"` MaxHealth float64 `json:"max_health"` // 等级统计 AverageLevel float64 `json:"average_level"` MinLevel int32 `json:"min_level"` MaxLevel int32 `json:"max_level"` // 时间统计 OldestBuilding time.Time `json:"oldest_building"` NewestBuilding time.Time `json:"newest_building"` // 更新时间 UpdatedAt time.Time `json:"updated_at"` } // ConstructionStatistics 建造统计数据 type ConstructionStatistics struct { BuildingID string `json:"building_id"` // 总体统计 TotalConstructions int64 `json:"total_constructions"` CompletedConstructions int64 `json:"completed_constructions"` InProgressConstructions int64 `json:"in_progress_constructions"` CancelledConstructions int64 `json:"cancelled_constructions"` FailedConstructions int64 `json:"failed_constructions"` // 进度统计 AverageProgress float64 `json:"average_progress"` MinProgress float64 `json:"min_progress"` MaxProgress float64 `json:"max_progress"` // 时间统计 AverageDuration time.Duration `json:"average_duration"` MinDuration time.Duration `json:"min_duration"` MaxDuration time.Duration `json:"max_duration"` // 效率统计 AverageEfficiency float64 `json:"average_efficiency"` MinEfficiency float64 `json:"min_efficiency"` MaxEfficiency float64 `json:"max_efficiency"` // 成本统计 TotalCost int64 `json:"total_cost"` AverageCost float64 `json:"average_cost"` MinCost int64 `json:"min_cost"` MaxCost int64 `json:"max_cost"` // 工人统计 TotalWorkers int64 `json:"total_workers"` AverageWorkers float64 `json:"average_workers"` MinWorkers int64 `json:"min_workers"` MaxWorkers int64 `json:"max_workers"` // 材料统计 TotalMaterials int64 `json:"total_materials"` AverageMaterials float64 `json:"average_materials"` MinMaterials int64 `json:"min_materials"` MaxMaterials int64 `json:"max_materials"` // 更新时间 UpdatedAt time.Time `json:"updated_at"` } // UpgradeStatistics 升级统计数据 type UpgradeStatistics struct { BuildingID string `json:"building_id"` // 总体统计 TotalUpgrades int64 `json:"total_upgrades"` CompletedUpgrades int64 `json:"completed_upgrades"` InProgressUpgrades int64 `json:"in_progress_upgrades"` CancelledUpgrades int64 `json:"cancelled_upgrades"` FailedUpgrades int64 `json:"failed_upgrades"` // 等级统计 AverageFromLevel float64 `json:"average_from_level"` AverageToLevel float64 `json:"average_to_level"` MaxLevelReached int32 `json:"max_level_reached"` // 进度统计 AverageProgress float64 `json:"average_progress"` MinProgress float64 `json:"min_progress"` MaxProgress float64 `json:"max_progress"` // 时间统计 AverageDuration time.Duration `json:"average_duration"` MinDuration time.Duration `json:"min_duration"` MaxDuration time.Duration `json:"max_duration"` // 成本统计 TotalCost int64 `json:"total_cost"` AverageCost float64 `json:"average_cost"` MinCost int64 `json:"min_cost"` MaxCost int64 `json:"max_cost"` // 收益统计 TotalBenefits int64 `json:"total_benefits"` AverageBenefits float64 `json:"average_benefits"` // 更新时间 UpdatedAt time.Time `json:"updated_at"` } // BlueprintStatistics 蓝图统计数据 type BlueprintStatistics struct { // 总体统计 TotalBlueprints int64 `json:"total_blueprints"` // 按分类统计 ByCategory map[BuildingCategory]int64 `json:"by_category"` // 按作者统计 ByAuthor map[string]int64 `json:"by_author"` // 难度统计 AverageDifficulty float64 `json:"average_difficulty"` MinDifficulty int32 `json:"min_difficulty"` MaxDifficulty int32 `json:"max_difficulty"` // 尺寸统计 AverageWidth float64 `json:"average_width"` AverageHeight float64 `json:"average_height"` AverageDepth float64 `json:"average_depth"` MinWidth int32 `json:"min_width"` MaxWidth int32 `json:"max_width"` MinHeight int32 `json:"min_height"` MaxHeight int32 `json:"max_height"` MinDepth int32 `json:"min_depth"` MaxDepth int32 `json:"max_depth"` // 时间统计 AverageDuration time.Duration `json:"average_duration"` MinDuration time.Duration `json:"min_duration"` MaxDuration time.Duration `json:"max_duration"` // 成本统计 AverageCost float64 `json:"average_cost"` MinCost int64 `json:"min_cost"` MaxCost int64 `json:"max_cost"` // 材料统计 AverageMaterials float64 `json:"average_materials"` MinMaterials int64 `json:"min_materials"` MaxMaterials int64 `json:"max_materials"` // 标签统计 PopularTags []TagStatistic `json:"popular_tags"` // 更新时间 UpdatedAt time.Time `json:"updated_at"` } // TagStatistic 标签统计 type TagStatistic struct { Tag string `json:"tag"` Count int64 `json:"count"` } // Area 区域 type Area struct { MinX int32 `json:"min_x"` MaxX int32 `json:"max_x"` MinY int32 `json:"min_y"` MaxY int32 `json:"max_y"` MinZ int32 `json:"min_z"` MaxZ int32 `json:"max_z"` } // NewArea 创建新区域 func NewArea(minX, maxX, minY, maxY, minZ, maxZ int32) *Area { return &Area{ MinX: minX, MaxX: maxX, MinY: minY, MaxY: maxY, MinZ: minZ, MaxZ: maxZ, } } // IsValid 检查区域是否有效 func (a *Area) IsValid() bool { return a.MinX <= a.MaxX && a.MinY <= a.MaxY && a.MinZ <= a.MaxZ } // Contains 检查是否包含位置 func (a *Area) Contains(pos *Position) bool { if pos == nil { return false } return pos.X >= a.MinX && pos.X <= a.MaxX && pos.Y >= a.MinY && pos.Y <= a.MaxY && pos.Z >= a.MinZ && pos.Z <= a.MaxZ } // Overlaps 检查是否与另一个区域重叠 func (a *Area) Overlaps(other *Area) bool { if other == nil { return false } return !(a.MaxX < other.MinX || a.MinX > other.MaxX || a.MaxY < other.MinY || a.MinY > other.MaxY || a.MaxZ < other.MinZ || a.MinZ > other.MaxZ) } // GetVolume 获取区域体积 func (a *Area) GetVolume() int64 { width := int64(a.MaxX - a.MinX + 1) height := int64(a.MaxY - a.MinY + 1) depth := int64(a.MaxZ - a.MinZ + 1) return width * height * depth } // GetCenter 获取区域中心点 func (a *Area) GetCenter() *Position { return &Position{ X: (a.MinX + a.MaxX) / 2, Y: (a.MinY + a.MaxY) / 2, Z: (a.MinZ + a.MaxZ) / 2, } } // 查询辅助函数 // NewBuildingQuery 创建新建筑查询 func NewBuildingQuery() *BuildingQuery { return &BuildingQuery{ Page: 1, PageSize: 20, SortBy: "created_at", SortOrder: "desc", } } // WithOwner 设置所有者 func (q *BuildingQuery) WithOwner(ownerID uint64) *BuildingQuery { q.OwnerID = &ownerID return q } // WithType 设置建筑类型 func (q *BuildingQuery) WithType(buildingType BuildingType) *BuildingQuery { q.Type = &buildingType return q } // WithCategory 设置建筑分类 func (q *BuildingQuery) WithCategory(category BuildingCategory) *BuildingQuery { q.Category = &category return q } // WithStatus 设置建筑状态 func (q *BuildingQuery) WithStatus(status BuildingStatus) *BuildingQuery { q.Status = &status return q } // WithLevelRange 设置等级范围 func (q *BuildingQuery) WithLevelRange(minLevel, maxLevel int32) *BuildingQuery { q.MinLevel = &minLevel q.MaxLevel = &maxLevel return q } // WithHealthRange 设置健康度范围 func (q *BuildingQuery) WithHealthRange(minHealth, maxHealth float64) *BuildingQuery { q.MinHealth = &minHealth q.MaxHealth = &maxHealth return q } // WithPosition 设置位置 func (q *BuildingQuery) WithPosition(position *Position) *BuildingQuery { q.Position = position return q } // WithArea 设置区域 func (q *BuildingQuery) WithArea(area *Area) *BuildingQuery { q.Area = area return q } // WithTags 设置标签 func (q *BuildingQuery) WithTags(tags []string) *BuildingQuery { q.Tags = tags return q } // WithSort 设置排序 func (q *BuildingQuery) WithSort(sortBy, sortOrder string) *BuildingQuery { q.SortBy = sortBy q.SortOrder = sortOrder return q } // WithPagination 设置分页 func (q *BuildingQuery) WithPagination(page, pageSize int) *BuildingQuery { q.Page = page q.PageSize = pageSize return q } // NewConstructionQuery 创建新建造查询 func NewConstructionQuery() *ConstructionQuery { return &ConstructionQuery{ Page: 1, PageSize: 20, SortBy: "created_at", SortOrder: "desc", } } // WithBuildingID 设置建筑ID func (q *ConstructionQuery) WithBuildingID(buildingID string) *ConstructionQuery { q.BuildingID = &buildingID return q } // WithStatus 设置状态 func (q *ConstructionQuery) WithStatus(status ConstructionStatus) *ConstructionQuery { q.Status = &status return q } // WithWorker 设置工人 func (q *ConstructionQuery) WithWorker(workerID uint64) *ConstructionQuery { q.WorkerID = &workerID return q } // WithProgressRange 设置进度范围 func (q *ConstructionQuery) WithProgressRange(minProgress, maxProgress float64) *ConstructionQuery { q.MinProgress = &minProgress q.MaxProgress = &maxProgress return q } // WithSort 设置排序 func (q *ConstructionQuery) WithSort(sortBy, sortOrder string) *ConstructionQuery { q.SortBy = sortBy q.SortOrder = sortOrder return q } // WithPagination 设置分页 func (q *ConstructionQuery) WithPagination(page, pageSize int) *ConstructionQuery { q.Page = page q.PageSize = pageSize return q } // NewUpgradeQuery 创建新升级查询 func NewUpgradeQuery() *UpgradeQuery { return &UpgradeQuery{ Page: 1, PageSize: 20, SortBy: "created_at", SortOrder: "desc", } } // WithBuildingID 设置建筑ID func (q *UpgradeQuery) WithBuildingID(buildingID string) *UpgradeQuery { q.BuildingID = &buildingID return q } // WithStatus 设置状态 func (q *UpgradeQuery) WithStatus(status UpgradeStatus) *UpgradeQuery { q.Status = &status return q } // WithLevelRange 设置等级范围 func (q *UpgradeQuery) WithLevelRange(fromLevel, toLevel int32) *UpgradeQuery { q.FromLevel = &fromLevel q.ToLevel = &toLevel return q } // WithProgressRange 设置进度范围 func (q *UpgradeQuery) WithProgressRange(minProgress, maxProgress float64) *UpgradeQuery { q.MinProgress = &minProgress q.MaxProgress = &maxProgress return q } // WithSort 设置排序 func (q *UpgradeQuery) WithSort(sortBy, sortOrder string) *UpgradeQuery { q.SortBy = sortBy q.SortOrder = sortOrder return q } // WithPagination 设置分页 func (q *UpgradeQuery) WithPagination(page, pageSize int) *UpgradeQuery { q.Page = page q.PageSize = pageSize return q } // NewBlueprintQuery 创建新蓝图查询 func NewBlueprintQuery() *BlueprintQuery { return &BlueprintQuery{ Page: 1, PageSize: 20, SortBy: "created_at", SortOrder: "desc", } } // WithName 设置名称 func (q *BlueprintQuery) WithName(name string) *BlueprintQuery { q.Name = &name return q } // WithAuthor 设置作者 func (q *BlueprintQuery) WithAuthor(author string) *BlueprintQuery { q.Author = &author return q } // WithCategory 设置分类 func (q *BlueprintQuery) WithCategory(category BuildingCategory) *BlueprintQuery { q.Category = &category return q } // WithDifficultyRange 设置难度范围 func (q *BlueprintQuery) WithDifficultyRange(minDifficulty, maxDifficulty int32) *BlueprintQuery { q.MinDifficulty = &minDifficulty q.MaxDifficulty = &maxDifficulty return q } // WithTags 设置标签 func (q *BlueprintQuery) WithTags(tags []string) *BlueprintQuery { q.Tags = tags return q } // WithKeyword 设置关键词 func (q *BlueprintQuery) WithKeyword(keyword string) *BlueprintQuery { q.Keyword = &keyword return q } // WithSort 设置排序 func (q *BlueprintQuery) WithSort(sortBy, sortOrder string) *BlueprintQuery { q.SortBy = sortBy q.SortOrder = sortOrder return q } // WithPagination 设置分页 func (q *BlueprintQuery) WithPagination(page, pageSize int) *BlueprintQuery { q.Page = page q.PageSize = pageSize return q } // 常量定义 const ( // 默认分页大小 DefaultPageSize = 20 MaxPageSize = 100 // 默认排序 DefaultSortBy = "created_at" DefaultSortOrder = "desc" // 查询限制 MaxTagsCount = 10 MaxKeywordLen = 100 MaxNameLen = 100 MaxAuthorLen = 50 MaxVersionLen = 20 ) // 验证函数 // ValidateQuery 验证查询参数 func ValidateQuery(page, pageSize int, sortBy, sortOrder string, validSortFields []string) error { if page < 1 { return fmt.Errorf("page must be at least 1") } if pageSize < 1 || pageSize > MaxPageSize { return fmt.Errorf("page size must be between 1 and %d", MaxPageSize) } if sortBy != "" { valid := false for _, field := range validSortFields { if sortBy == field { valid = true break } } if !valid { return fmt.Errorf("invalid sort field: %s", sortBy) } } if sortOrder != "" && sortOrder != "asc" && sortOrder != "desc" { return fmt.Errorf("sort order must be 'asc' or 'desc'") } return nil } // ValidateBuildingQuery 验证建筑查询 func ValidateBuildingQuery(query *BuildingQuery) error { if query == nil { return fmt.Errorf("query cannot be nil") } validSortFields := []string{"name", "type", "category", "level", "health", "created_at", "updated_at"} return ValidateQuery(query.Page, query.PageSize, query.SortBy, query.SortOrder, validSortFields) } // ValidateConstructionQuery 验证建造查询 func ValidateConstructionQuery(query *ConstructionQuery) error { if query == nil { return fmt.Errorf("query cannot be nil") } validSortFields := []string{"progress", "started_at", "duration", "created_at"} return ValidateQuery(query.Page, query.PageSize, query.SortBy, query.SortOrder, validSortFields) } // ValidateUpgradeQuery 验证升级查询 func ValidateUpgradeQuery(query *UpgradeQuery) error { if query == nil { return fmt.Errorf("query cannot be nil") } validSortFields := []string{"from_level", "to_level", "progress", "started_at", "created_at"} return ValidateQuery(query.Page, query.PageSize, query.SortBy, query.SortOrder, validSortFields) } // ValidateBlueprintQuery 验证蓝图查询 func ValidateBlueprintQuery(query *BlueprintQuery) error { if query == nil { return fmt.Errorf("query cannot be nil") } validSortFields := []string{"name", "author", "difficulty", "duration", "created_at"} return ValidateQuery(query.Page, query.PageSize, query.SortBy, query.SortOrder, validSortFields) } ================================================ FILE: internal/domain/building/service.go ================================================ package building import ( "context" "fmt" "time" ) // BuildingService 建筑领域服务 type BuildingService struct { buildingRepo BuildingRepository constructionRepo ConstructionRepository upgradeRepo UpgradeRepository blueprintRepo BlueprintRepository eventBus BuildingEventBus } // NewBuildingService 创建新建筑服务 func NewBuildingService( buildingRepo BuildingRepository, constructionRepo ConstructionRepository, upgradeRepo UpgradeRepository, blueprintRepo BlueprintRepository, eventBus BuildingEventBus, ) *BuildingService { return &BuildingService{ buildingRepo: buildingRepo, constructionRepo: constructionRepo, upgradeRepo: upgradeRepo, blueprintRepo: blueprintRepo, eventBus: eventBus, } } // CreateBuilding 创建建筑 func (bs *BuildingService) CreateBuilding(ctx context.Context, req *CreateBuildingRequest) (*BuildingAggregate, error) { if req == nil { return nil, NewBuildingError(ErrCodeInvalidInput, "create building request cannot be nil", ErrorSeverityHigh) } if err := req.Validate(); err != nil { return nil, NewBuildingError(ErrCodeInvalidInput, fmt.Sprintf("invalid request: %v", err), ErrorSeverityHigh) } // 检查位置是否可用 if req.Position != nil { existing, err := bs.buildingRepo.FindByPosition(ctx, req.Position) if err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to check position: %v", err), ErrorSeverityMedium) } if existing != nil { return nil, NewBuildingError(ErrCodePositionOccupied, "position is already occupied", ErrorSeverityHigh) } } // 创建建筑聚合根 building := NewBuildingAggregate(req.OwnerID, string(req.Type), req.Name, req.Category) // Note: SetOwner, SetPosition, SetSize, SetConfig methods need to be implemented // building.SetOwner(req.OwnerID) // building.SetPosition(req.Position) // building.SetSize(req.Size) // 设置配置 // if req.Config != nil { // building.SetConfig(req.Config) // } // 保存建筑 if err := bs.buildingRepo.Save(ctx, building); err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewBuildingCreatedEvent(building.ID, building.Name, BuildingType(building.BuildingTypeID), building.PlayerID) if err := bs.eventBus.Publish(ctx, event); err != nil { // 记录错误但不影响主流程 fmt.Printf("failed to publish building created event: %v\n", err) } return building, nil } // StartConstruction 开始建造 func (bs *BuildingService) StartConstruction(ctx context.Context, req *StartConstructionRequest) (*ConstructionInfo, error) { if req == nil { return nil, NewBuildingError(ErrCodeInvalidInput, "start construction request cannot be nil", ErrorSeverityHigh) } if err := req.Validate(); err != nil { return nil, NewBuildingError(ErrCodeInvalidInput, fmt.Sprintf("invalid request: %v", err), ErrorSeverityHigh) } // 获取建筑 building, err := bs.buildingRepo.FindByID(ctx, req.BuildingID) if err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find building: %v", err), ErrorSeverityMedium) } if building == nil { return nil, NewBuildingError(ErrCodeBuildingNotFound, "building not found", ErrorSeverityHigh) } // 检查建筑状态 if building.Status != BuildingStatusPlanning { return nil, NewBuildingError(ErrCodeInvalidBuildingState, "building is not in planned state", ErrorSeverityHigh) } // 检查资源 if req.Costs != nil { for _, cost := range req.Costs { if !bs.checkResourceAvailability(ctx, req.OwnerID, cost) { return nil, NewBuildingError(ErrCodeInsufficientResources, fmt.Sprintf("insufficient %s", cost.ResourceType), ErrorSeverityHigh) } } } // 开始建造 if err := building.StartConstruction(req.Duration, req.Costs); err != nil { return nil, NewBuildingError(ErrCodeConstructionFailed, fmt.Sprintf("failed to start construction: %v", err), ErrorSeverityHigh) } // 创建建造信息 construction := NewConstructionInfo(building.ID, req.Duration) if req.Costs != nil { construction.Costs = req.Costs } if req.Workers != nil { for _, worker := range req.Workers { construction.AddWorker(worker) } } if req.Materials != nil { for _, material := range req.Materials { construction.AddMaterial(material) } } // 保存建造信息 if err := bs.constructionRepo.Save(ctx, construction); err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save construction: %v", err), ErrorSeverityHigh) } // 保存建筑 if err := bs.buildingRepo.Save(ctx, building); err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewConstructionStartedEvent(building.ID, construction.ID, req.Duration) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish construction started event: %v\n", err) } return construction, nil } // UpdateConstructionProgress 更新建造进度 func (bs *BuildingService) UpdateConstructionProgress(ctx context.Context, req *UpdateConstructionProgressRequest) error { if req == nil { return NewBuildingError(ErrCodeInvalidInput, "update construction progress request cannot be nil", ErrorSeverityHigh) } if err := req.Validate(); err != nil { return NewBuildingError(ErrCodeInvalidInput, fmt.Sprintf("invalid request: %v", err), ErrorSeverityHigh) } // 获取建造信息 construction, err := bs.constructionRepo.FindByID(ctx, req.ConstructionID) if err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find construction: %v", err), ErrorSeverityMedium) } if construction == nil { return NewBuildingError(ErrCodeConstructionNotFound, "construction not found", ErrorSeverityHigh) } // 更新进度 if err := construction.UpdateProgress(req.Progress); err != nil { return NewBuildingError(ErrCodeConstructionFailed, fmt.Sprintf("failed to update progress: %v", err), ErrorSeverityMedium) } // 如果完成,更新建筑状态 if construction.Status == ConstructionStatusCompleted { building, err := bs.buildingRepo.FindByID(ctx, construction.BuildingID) if err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find building: %v", err), ErrorSeverityMedium) } if building != nil { building.CompleteConstruction() if err := bs.buildingRepo.Save(ctx, building); err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布完成事件 event := NewConstructionCompletedEvent(building.ID, construction.ID) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish construction completed event: %v\n", err) } } } // 保存建造信息 if err := bs.constructionRepo.Save(ctx, construction); err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save construction: %v", err), ErrorSeverityHigh) } // 发布进度更新事件 event := NewConstructionProgressUpdatedEvent(construction.BuildingID, construction.ID, req.Progress) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish construction progress updated event: %v\n", err) } return nil } // StartUpgrade 开始升级 func (bs *BuildingService) StartUpgrade(ctx context.Context, req *StartUpgradeRequest) (*UpgradeInfo, error) { if req == nil { return nil, NewBuildingError(ErrCodeInvalidInput, "start upgrade request cannot be nil", ErrorSeverityHigh) } if err := req.Validate(); err != nil { return nil, NewBuildingError(ErrCodeInvalidInput, fmt.Sprintf("invalid request: %v", err), ErrorSeverityHigh) } // 获取建筑 building, err := bs.buildingRepo.FindByID(ctx, req.BuildingID) if err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find building: %v", err), ErrorSeverityMedium) } if building == nil { return nil, NewBuildingError(ErrCodeBuildingNotFound, "building not found", ErrorSeverityHigh) } // 检查建筑状态 if building.Status != BuildingStatusActive { return nil, NewBuildingError(ErrCodeInvalidBuildingState, "building is not active", ErrorSeverityHigh) } // 检查升级条件 if building.Level >= req.ToLevel { return nil, NewBuildingError(ErrCodeInvalidUpgrade, "target level must be higher than current level", ErrorSeverityHigh) } // 检查资源 if req.Costs != nil { for _, cost := range req.Costs { if !bs.checkResourceAvailability(ctx, building.PlayerID, cost) { return nil, NewBuildingError(ErrCodeInsufficientResources, fmt.Sprintf("insufficient %s", cost.ResourceType), ErrorSeverityHigh) } } } // 开始升级 if err := building.StartUpgrade(req.ToLevel, req.Duration, req.Costs); err != nil { return nil, NewBuildingError(ErrCodeUpgradeFailed, fmt.Sprintf("failed to start upgrade: %v", err), ErrorSeverityHigh) } // 创建升级信息 upgrade := NewUpgradeInfo(building.ID, building.Level-1, req.ToLevel, req.Duration) if req.Costs != nil { upgrade.Costs = req.Costs } if req.Requirements != nil { for _, requirement := range req.Requirements { upgrade.AddRequirement(requirement) } } if req.Benefits != nil { for _, benefit := range req.Benefits { upgrade.AddBenefit(benefit) } } // 保存升级信息 if err := bs.upgradeRepo.Save(ctx, upgrade); err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save upgrade: %v", err), ErrorSeverityHigh) } // 保存建筑 if err := bs.buildingRepo.Save(ctx, building); err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewUpgradeStartedEvent(building.ID, upgrade.ID, building.Level-1, req.ToLevel) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish upgrade started event: %v\n", err) } return upgrade, nil } // CompleteUpgrade 完成升级 func (bs *BuildingService) CompleteUpgrade(ctx context.Context, upgradeID string) error { if upgradeID == "" { return NewBuildingError(ErrCodeInvalidInput, "upgrade ID cannot be empty", ErrorSeverityHigh) } // 获取升级信息 upgrade, err := bs.upgradeRepo.FindByID(ctx, upgradeID) if err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find upgrade: %v", err), ErrorSeverityMedium) } if upgrade == nil { return NewBuildingError(ErrCodeUpgradeNotFound, "upgrade not found", ErrorSeverityHigh) } // 获取建筑 building, err := bs.buildingRepo.FindByID(ctx, upgrade.BuildingID) if err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find building: %v", err), ErrorSeverityMedium) } if building == nil { return NewBuildingError(ErrCodeBuildingNotFound, "building not found", ErrorSeverityHigh) } // 完成升级 if err := building.CompleteUpgrade(); err != nil { return NewBuildingError(ErrCodeUpgradeFailed, fmt.Sprintf("failed to complete upgrade: %v", err), ErrorSeverityHigh) } // 更新升级状态 upgrade.UpdateProgress(100.0) // 保存 if err := bs.upgradeRepo.Save(ctx, upgrade); err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save upgrade: %v", err), ErrorSeverityHigh) } if err := bs.buildingRepo.Save(ctx, building); err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewUpgradeCompletedEvent(building.ID, upgrade.ID, upgrade.FromLevel, upgrade.ToLevel) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish upgrade completed event: %v\n", err) } return nil } // RepairBuilding 修复建筑 func (bs *BuildingService) RepairBuilding(ctx context.Context, req *RepairBuildingRequest) error { if req == nil { return NewBuildingError(ErrCodeInvalidInput, "repair building request cannot be nil", ErrorSeverityHigh) } if err := req.Validate(); err != nil { return NewBuildingError(ErrCodeInvalidInput, fmt.Sprintf("invalid request: %v", err), ErrorSeverityHigh) } // 获取建筑 building, err := bs.buildingRepo.FindByID(ctx, req.BuildingID) if err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find building: %v", err), ErrorSeverityMedium) } if building == nil { return NewBuildingError(ErrCodeBuildingNotFound, "building not found", ErrorSeverityHigh) } // 检查是否需要修复 if building.Health >= 100.0 { return NewBuildingError(ErrCodeInvalidOperation, "building does not need repair", ErrorSeverityLow) } // 检查资源 if req.Costs != nil { for _, cost := range req.Costs { if !bs.checkResourceAvailability(ctx, building.PlayerID, cost) { return NewBuildingError(ErrCodeInsufficientResources, fmt.Sprintf("insufficient %s", cost.ResourceType), ErrorSeverityHigh) } } } // 修复建筑 oldHealth := building.Health if err := building.Repair(int32(req.RepairAmount), req.Costs); err != nil { return NewBuildingError(ErrCodeRepairFailed, fmt.Sprintf("failed to repair building: %v", err), ErrorSeverityHigh) } // 保存建筑 if err := bs.buildingRepo.Save(ctx, building); err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewBuildingRepairedEvent(building.ID, float64(oldHealth), float64(building.Health)) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish building repaired event: %v\n", err) } return nil } // DestroyBuilding 摧毁建筑 func (bs *BuildingService) DestroyBuilding(ctx context.Context, buildingID string, reason string) error { if buildingID == "" { return NewBuildingError(ErrCodeInvalidInput, "building ID cannot be empty", ErrorSeverityHigh) } // 获取建筑 building, err := bs.buildingRepo.FindByID(ctx, buildingID) if err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find building: %v", err), ErrorSeverityMedium) } if building == nil { return NewBuildingError(ErrCodeBuildingNotFound, "building not found", ErrorSeverityHigh) } // 摧毁建筑 - Destroy方法需要实现 // TODO: 实现Destroy方法 //if err := building.Destroy(reason); err != nil { // return NewBuildingError(ErrCodeDestroyFailed, fmt.Sprintf("failed to destroy building: %v", err), ErrorSeverityHigh) //} // 临时实现:直接设置状态为已摧毁 _ = reason // 避免未使用变量警告 // 保存建筑 if err := bs.buildingRepo.Save(ctx, building); err != nil { return NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save building: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewBuildingDestroyedEvent(building.ID, reason) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish building destroyed event: %v\n", err) } return nil } // GetBuildingsByOwner 获取玩家的建筑列表 func (bs *BuildingService) GetBuildingsByOwner(ctx context.Context, ownerID uint64, query *BuildingQuery) ([]*BuildingAggregate, *PaginationResult, error) { if ownerID == 0 { return nil, nil, NewBuildingError(ErrCodeInvalidInput, "owner ID cannot be zero", ErrorSeverityHigh) } if query == nil { query = &BuildingQuery{} } query.OwnerID = &ownerID buildings, total, err := bs.buildingRepo.FindByQuery(ctx, query) if err != nil { return nil, nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find buildings: %v", err), ErrorSeverityMedium) } pagination := &PaginationResult{ Total: total, Page: query.Page, PageSize: query.PageSize, TotalPages: (total + int64(query.PageSize) - 1) / int64(query.PageSize), HasNext: int64(query.Page*query.PageSize) < total, HasPrevious: query.Page > 1, } return buildings, pagination, nil } // GetBuildingStatistics 获取建筑统计信息 func (bs *BuildingService) GetBuildingStatistics(ctx context.Context, ownerID uint64) (*BuildingStatistics, error) { if ownerID == 0 { return nil, NewBuildingError(ErrCodeInvalidInput, "owner ID cannot be zero", ErrorSeverityHigh) } stats, err := bs.buildingRepo.GetStatistics(ctx, ownerID) if err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to get statistics: %v", err), ErrorSeverityMedium) } return stats, nil } // CreateBlueprint 创建蓝图 func (bs *BuildingService) CreateBlueprint(ctx context.Context, req *CreateBlueprintRequest) (*Blueprint, error) { if req == nil { return nil, NewBuildingError(ErrCodeInvalidInput, "create blueprint request cannot be nil", ErrorSeverityHigh) } if err := req.Validate(); err != nil { return nil, NewBuildingError(ErrCodeInvalidInput, fmt.Sprintf("invalid request: %v", err), ErrorSeverityHigh) } // 创建蓝图 blueprint := NewBlueprint(req.Name, req.Description, req.Category) blueprint.Author = req.Author blueprint.Size = req.Size blueprint.Duration = req.Duration blueprint.Difficulty = req.Difficulty // 添加材料需求 if req.Materials != nil { for _, material := range req.Materials { blueprint.AddMaterial(material) } } // 添加成本 if req.Costs != nil { for _, cost := range req.Costs { blueprint.AddCost(cost) } } // 添加标签 if req.Tags != nil { for _, tag := range req.Tags { blueprint.AddTag(tag) } } // 保存蓝图 if err := bs.blueprintRepo.Save(ctx, blueprint); err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to save blueprint: %v", err), ErrorSeverityHigh) } // 发布事件 event := NewBlueprintCreatedEvent(blueprint.ID, blueprint.Name, blueprint.Category) if err := bs.eventBus.Publish(ctx, event); err != nil { fmt.Printf("failed to publish blueprint created event: %v\n", err) } return blueprint, nil } // GetBlueprints 获取蓝图列表 func (bs *BuildingService) GetBlueprints(ctx context.Context, query *BlueprintQuery) ([]*Blueprint, *PaginationResult, error) { if query == nil { query = &BlueprintQuery{} } blueprints, total, err := bs.blueprintRepo.FindByQuery(ctx, query) if err != nil { return nil, nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find blueprints: %v", err), ErrorSeverityMedium) } pagination := &PaginationResult{ Total: total, Page: query.Page, PageSize: query.PageSize, TotalPages: (total + int64(query.PageSize) - 1) / int64(query.PageSize), HasNext: int64(query.Page*query.PageSize) < total, HasPrevious: query.Page > 1, } return blueprints, pagination, nil } // ValidateBlueprint 验证蓝图 func (bs *BuildingService) ValidateBlueprint(ctx context.Context, blueprintID string) (*BlueprintValidationResult, error) { if blueprintID == "" { return nil, NewBuildingError(ErrCodeInvalidInput, "blueprint ID cannot be empty", ErrorSeverityHigh) } // 获取蓝图 blueprint, err := bs.blueprintRepo.FindByID(ctx, blueprintID) if err != nil { return nil, NewBuildingError(ErrCodeRepositoryError, fmt.Sprintf("failed to find blueprint: %v", err), ErrorSeverityMedium) } if blueprint == nil { return nil, NewBuildingError(ErrCodeBlueprintNotFound, "blueprint not found", ErrorSeverityHigh) } // 验证蓝图 result := &BlueprintValidationResult{ BlueprintID: blueprintID, IsValid: true, Errors: make([]string, 0), Warnings: make([]string, 0), } // 基础验证 if blueprint.Name == "" { result.IsValid = false result.Errors = append(result.Errors, "blueprint name is required") } if blueprint.Size == nil || !blueprint.Size.IsValid() { result.IsValid = false result.Errors = append(result.Errors, "invalid blueprint size") } if len(blueprint.Materials) == 0 { result.Warnings = append(result.Warnings, "no materials specified") } if len(blueprint.Costs) == 0 { result.Warnings = append(result.Warnings, "no costs specified") } // 层级验证 for i, layer := range blueprint.Layers { if layer.Name == "" { result.Warnings = append(result.Warnings, fmt.Sprintf("layer %d has no name", i)) } if len(layer.Blocks) == 0 { result.Warnings = append(result.Warnings, fmt.Sprintf("layer %d has no blocks", i)) } } return result, nil } // 私有方法 // checkResourceAvailability 检查资源可用性 func (bs *BuildingService) checkResourceAvailability(ctx context.Context, ownerID uint64, cost *ResourceCost) bool { // 这里应该调用资源服务来检查资源是否足够 // 暂时返回true作为示例 return true } // 请求结构体 // CreateBuildingRequest 创建建筑请求 type CreateBuildingRequest struct { Name string `json:"name"` Type BuildingType `json:"type"` Category BuildingCategory `json:"category"` OwnerID uint64 `json:"owner_id"` Position *Position `json:"position,omitempty"` Size *Size `json:"size,omitempty"` Config *BuildingConfig `json:"config,omitempty"` } // Validate 验证请求 func (req *CreateBuildingRequest) Validate() error { if req.Name == "" { return fmt.Errorf("name is required") } if !req.Type.IsValid() { return fmt.Errorf("invalid building type") } if !req.Category.IsValid() { return fmt.Errorf("invalid building category") } if req.OwnerID == 0 { return fmt.Errorf("owner ID is required") } // Position validation removed - IsValid method not needed if req.Size != nil && !req.Size.IsValid() { return fmt.Errorf("invalid size") } return nil } // StartConstructionRequest 开始建造请求 type StartConstructionRequest struct { BuildingID string `json:"building_id"` OwnerID uint64 `json:"owner_id"` Duration time.Duration `json:"duration"` Costs []*ResourceCost `json:"costs,omitempty"` Workers []*WorkerAssignment `json:"workers,omitempty"` Materials []*MaterialUsage `json:"materials,omitempty"` } // Validate 验证请求 func (req *StartConstructionRequest) Validate() error { if req.BuildingID == "" { return fmt.Errorf("building ID is required") } if req.OwnerID == 0 { return fmt.Errorf("owner ID is required") } if req.Duration <= 0 { return fmt.Errorf("duration must be positive") } return nil } // UpdateConstructionProgressRequest 更新建造进度请求 type UpdateConstructionProgressRequest struct { ConstructionID string `json:"construction_id"` Progress float64 `json:"progress"` } // Validate 验证请求 func (req *UpdateConstructionProgressRequest) Validate() error { if req.ConstructionID == "" { return fmt.Errorf("construction ID is required") } if req.Progress < 0 || req.Progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } return nil } // StartUpgradeRequest 开始升级请求 type StartUpgradeRequest struct { BuildingID string `json:"building_id"` ToLevel int32 `json:"to_level"` Duration time.Duration `json:"duration"` Costs []*ResourceCost `json:"costs,omitempty"` Requirements []*Requirement `json:"requirements,omitempty"` Benefits []*UpgradeBenefit `json:"benefits,omitempty"` } // Validate 验证请求 func (req *StartUpgradeRequest) Validate() error { if req.BuildingID == "" { return fmt.Errorf("building ID is required") } if req.ToLevel <= 0 { return fmt.Errorf("to level must be positive") } if req.Duration <= 0 { return fmt.Errorf("duration must be positive") } return nil } // RepairBuildingRequest 修复建筑请求 type RepairBuildingRequest struct { BuildingID string `json:"building_id"` RepairAmount float64 `json:"repair_amount"` Costs []*ResourceCost `json:"costs,omitempty"` } // Validate 验证请求 func (req *RepairBuildingRequest) Validate() error { if req.BuildingID == "" { return fmt.Errorf("building ID is required") } if req.RepairAmount <= 0 { return fmt.Errorf("repair amount must be positive") } return nil } // CreateBlueprintRequest 创建蓝图请求 type CreateBlueprintRequest struct { Name string `json:"name"` Description string `json:"description"` Author string `json:"author"` Category BuildingCategory `json:"category"` Size *Size `json:"size"` Materials []*MaterialRequirement `json:"materials,omitempty"` Costs []*ResourceCost `json:"costs,omitempty"` Duration time.Duration `json:"duration"` Difficulty int32 `json:"difficulty"` Tags []string `json:"tags,omitempty"` } // Validate 验证请求 func (req *CreateBlueprintRequest) Validate() error { if req.Name == "" { return fmt.Errorf("name is required") } if !req.Category.IsValid() { return fmt.Errorf("invalid category") } if req.Size == nil || !req.Size.IsValid() { return fmt.Errorf("invalid size") } if req.Duration <= 0 { return fmt.Errorf("duration must be positive") } if req.Difficulty < 1 || req.Difficulty > 10 { return fmt.Errorf("difficulty must be between 1 and 10") } return nil } // BlueprintValidationResult 蓝图验证结果 type BlueprintValidationResult struct { BlueprintID string `json:"blueprint_id"` IsValid bool `json:"is_valid"` Errors []string `json:"errors"` Warnings []string `json:"warnings"` } // 常量定义 const ( // 默认值 DefaultBuildingHealth = 100.0 DefaultBuildingLevel = 1 // 限制 MaxBuildingLevel = 100 MaxBuildingNameLen = 100 MaxDescriptionLen = 500 // MaxTagsCount = 10 // Moved to repository.go MaxLayersCount = 50 MaxBlocksPerLayer = 1000 MaxMaterialsCount = 100 MaxCostsCount = 20 MaxWorkersCount = 50 MaxPhasesCount = 20 MaxTasksPerPhase = 100 MaxDependenciesCount = 10 // 时间限制 MinConstructionDuration = 1 * time.Minute MaxConstructionDuration = 30 * 24 * time.Hour // 30天 MinUpgradeDuration = 1 * time.Minute MaxUpgradeDuration = 7 * 24 * time.Hour // 7天 ) // 辅助函数 // ValidateBuildingName 验证建筑名称 func ValidateBuildingName(name string) error { if name == "" { return fmt.Errorf("building name cannot be empty") } if len(name) > MaxBuildingNameLen { return fmt.Errorf("building name too long (max %d characters)", MaxBuildingNameLen) } return nil } // ValidateDescription 验证描述 func ValidateDescription(description string) error { if len(description) > MaxDescriptionLen { return fmt.Errorf("description too long (max %d characters)", MaxDescriptionLen) } return nil } // ValidateDuration 验证持续时间 func ValidateDuration(duration time.Duration, minDuration, maxDuration time.Duration) error { if duration < minDuration { return fmt.Errorf("duration too short (min %v)", minDuration) } if duration > maxDuration { return fmt.Errorf("duration too long (max %v)", maxDuration) } return nil } // ValidateLevel 验证等级 func ValidateLevel(level int32) error { if level < 1 { return fmt.Errorf("level must be at least 1") } if level > MaxBuildingLevel { return fmt.Errorf("level too high (max %d)", MaxBuildingLevel) } return nil } // ValidateHealth 验证健康度 func ValidateHealth(health float64) error { if health < 0 { return fmt.Errorf("health cannot be negative") } if health > 100 { return fmt.Errorf("health cannot exceed 100") } return nil } // ValidateProgress 验证进度 func ValidateProgress(progress float64) error { if progress < 0 { return fmt.Errorf("progress cannot be negative") } if progress > 100 { return fmt.Errorf("progress cannot exceed 100") } return nil } // CalculateConstructionTime 计算建造时间 func CalculateConstructionTime(baseTime time.Duration, difficulty int32, workerCount int) time.Duration { // 基础时间 * 难度系数 / 工人效率 difficultyFactor := float64(difficulty) / 5.0 // 难度1-10,转换为0.2-2.0 workerFactor := 1.0 / (1.0 + float64(workerCount)*0.1) // 工人越多,时间越短 adjustedTime := float64(baseTime) * difficultyFactor * workerFactor return time.Duration(adjustedTime) } // CalculateUpgradeCost 计算升级成本 func CalculateUpgradeCost(baseCost int64, fromLevel, toLevel int32) int64 { // 成本随等级指数增长 levelDiff := toLevel - fromLevel costMultiplier := float64(levelDiff) * 1.5 // 每级增加50% return int64(float64(baseCost) * costMultiplier) } // CalculateRepairCost 计算修复成本 func CalculateRepairCost(baseCost int64, currentHealth, targetHealth float64) int64 { // 修复成本与损坏程度成正比 damagePercent := (100.0 - currentHealth) / 100.0 repairPercent := (targetHealth - currentHealth) / 100.0 return int64(float64(baseCost) * damagePercent * repairPercent) } ================================================ FILE: internal/domain/building/types.go ================================================ package building // BuildingType 建筑类型 type BuildingType string const ( BuildingTypeResidential BuildingType = "residential" BuildingTypeCommercial BuildingType = "commercial" BuildingTypeIndustrial BuildingType = "industrial" BuildingTypePublic BuildingType = "public" BuildingTypeRecreational BuildingType = "recreational" ) // IsValid 检查建筑类型是否有效 func (bt BuildingType) IsValid() bool { switch bt { case BuildingTypeResidential, BuildingTypeCommercial, BuildingTypeIndustrial, BuildingTypePublic, BuildingTypeRecreational: return true default: return false } } // BuildingConfig 建筑配置 type BuildingConfig struct { Type BuildingType `json:"type"` Name string `json:"name"` Description string `json:"description"` MaxLevel int `json:"max_level"` BaseCost int64 `json:"base_cost"` UpgradeCost int64 `json:"upgrade_cost"` Capacity int `json:"capacity"` Efficiency float64 `json:"efficiency"` Requirements map[string]interface{} `json:"requirements"` } ================================================ FILE: internal/domain/building/value_object.go ================================================ package building import ( "fmt" "math" "time" ) // 建筑状态相关值对象 // BuildingStatus 建筑状态 type BuildingStatus int32 const ( BuildingStatusPlanning BuildingStatus = iota + 1 // 规划中 BuildingStatusUnderConstruction // 建造中 BuildingStatusActive // 活跃 BuildingStatusUpgrading // 升级中 BuildingStatusMaintenance // 维护中 BuildingStatusDamaged // 受损 BuildingStatusDestroyed // 被摧毁 BuildingStatusDemolished // 已拆除 BuildingStatusCancelled // 已取消 BuildingStatusInactive // 非活跃 ) // String 返回建筑状态的字符串表示 func (bs BuildingStatus) String() string { switch bs { case BuildingStatusPlanning: return "planning" case BuildingStatusUnderConstruction: return "under_construction" case BuildingStatusActive: return "active" case BuildingStatusUpgrading: return "upgrading" case BuildingStatusMaintenance: return "maintenance" case BuildingStatusDamaged: return "damaged" case BuildingStatusDestroyed: return "destroyed" case BuildingStatusDemolished: return "demolished" case BuildingStatusCancelled: return "cancelled" case BuildingStatusInactive: return "inactive" default: return "unknown" } } // IsValid 检查建筑状态是否有效 func (bs BuildingStatus) IsValid() bool { return bs >= BuildingStatusPlanning && bs <= BuildingStatusInactive } // CanTransitionTo 检查是否可以转换到目标状态 func (bs BuildingStatus) CanTransitionTo(target BuildingStatus) bool { switch bs { case BuildingStatusPlanning: return target == BuildingStatusUnderConstruction || target == BuildingStatusCancelled case BuildingStatusUnderConstruction: return target == BuildingStatusActive || target == BuildingStatusCancelled case BuildingStatusActive: return target == BuildingStatusUpgrading || target == BuildingStatusMaintenance || target == BuildingStatusDamaged || target == BuildingStatusDestroyed || target == BuildingStatusDemolished || target == BuildingStatusInactive case BuildingStatusUpgrading: return target == BuildingStatusActive || target == BuildingStatusCancelled case BuildingStatusMaintenance: return target == BuildingStatusActive case BuildingStatusDamaged: return target == BuildingStatusActive || target == BuildingStatusDestroyed || target == BuildingStatusDemolished case BuildingStatusDestroyed, BuildingStatusDemolished, BuildingStatusCancelled: return false // 终态,不能转换 case BuildingStatusInactive: return target == BuildingStatusActive || target == BuildingStatusDemolished default: return false } } // BuildingCategory 建筑分类 type BuildingCategory int32 const ( BuildingCategoryResidential BuildingCategory = iota + 1 // 住宅 BuildingCategoryCommercial // 商业 BuildingCategoryIndustrial // 工业 BuildingCategoryMilitary // 军事 BuildingCategoryReligious // 宗教 BuildingCategoryEducational // 教育 BuildingCategoryMedical // 医疗 BuildingCategoryEntertainment // 娱乐 BuildingCategoryUtility // 公用设施 BuildingCategoryDecoration // 装饰 BuildingCategorySpecial // 特殊 ) // String 返回建筑分类的字符串表示 func (bc BuildingCategory) String() string { switch bc { case BuildingCategoryResidential: return "residential" case BuildingCategoryCommercial: return "commercial" case BuildingCategoryIndustrial: return "industrial" case BuildingCategoryMilitary: return "military" case BuildingCategoryReligious: return "religious" case BuildingCategoryEducational: return "educational" case BuildingCategoryMedical: return "medical" case BuildingCategoryEntertainment: return "entertainment" case BuildingCategoryUtility: return "utility" case BuildingCategoryDecoration: return "decoration" case BuildingCategorySpecial: return "special" default: return "unknown" } } // IsValid 检查建筑分类是否有效 func (bc BuildingCategory) IsValid() bool { return bc >= BuildingCategoryResidential && bc <= BuildingCategorySpecial } // 位置和尺寸相关值对象 // Position 位置 type Position struct { X int32 `json:"x" bson:"x"` Y int32 `json:"y" bson:"y"` Z int32 `json:"z" bson:"z"` } // NewPosition 创建新位置 func NewPosition(x, y, z int32) *Position { return &Position{X: x, Y: y, Z: z} } // Distance 计算到另一个位置的距离 func (p *Position) Distance(other *Position) float64 { dx := float64(p.X - other.X) dy := float64(p.Y - other.Y) dz := float64(p.Z - other.Z) return math.Sqrt(dx*dx + dy*dy + dz*dz) } // IsAdjacent 检查是否相邻 func (p *Position) IsAdjacent(other *Position) bool { dx := abs(p.X - other.X) dy := abs(p.Y - other.Y) dz := abs(p.Z - other.Z) return (dx <= 1 && dy <= 1 && dz <= 1) && !(dx == 0 && dy == 0 && dz == 0) } // Validate 验证位置 func (p *Position) Validate() error { if p.X < 0 || p.Y < 0 || p.Z < 0 { return fmt.Errorf("position coordinates cannot be negative") } return nil } // Clone 克隆位置 func (p *Position) Clone() *Position { return &Position{X: p.X, Y: p.Y, Z: p.Z} } // Size 尺寸 type Size struct { Width int32 `json:"width" bson:"width"` Height int32 `json:"height" bson:"height"` Depth int32 `json:"depth" bson:"depth"` } // NewSize 创建新尺寸 func NewSize(width, height, depth int32) *Size { return &Size{Width: width, Height: height, Depth: depth} } // Volume 计算体积 func (s *Size) Volume() int32 { return s.Width * s.Height * s.Depth } // Area 计算面积 func (s *Size) Area() int32 { return s.Width * s.Height } // Validate 验证尺寸 func (s *Size) Validate() error { if s.Width <= 0 || s.Height <= 0 || s.Depth <= 0 { return fmt.Errorf("size dimensions must be positive") } return nil } // IsValid 检查尺寸是否有效 func (s *Size) IsValid() bool { return s != nil && s.Width > 0 && s.Height > 0 && s.Depth > 0 } // Clone 克隆尺寸 func (s *Size) Clone() *Size { return &Size{Width: s.Width, Height: s.Height, Depth: s.Depth} } // 注意:Position和NewPosition已经在文件前面定义,这里删除重复定义 // BoundingBox 边界框 type BoundingBox struct { MinX int32 `json:"min_x" bson:"min_x"` MinY int32 `json:"min_y" bson:"min_y"` MinZ int32 `json:"min_z" bson:"min_z"` MaxX int32 `json:"max_x" bson:"max_x"` MaxY int32 `json:"max_y" bson:"max_y"` MaxZ int32 `json:"max_z" bson:"max_z"` } // NewBoundingBox 创建新边界框 func NewBoundingBox(minX, minY, minZ, maxX, maxY, maxZ int32) *BoundingBox { return &BoundingBox{ MinX: minX, MinY: minY, MinZ: minZ, MaxX: maxX, MaxY: maxY, MaxZ: maxZ, } } // Contains 检查是否包含位置 func (bb *BoundingBox) Contains(pos *Position) bool { return pos.X >= bb.MinX && pos.X <= bb.MaxX && pos.Y >= bb.MinY && pos.Y <= bb.MaxY && pos.Z >= bb.MinZ && pos.Z <= bb.MaxZ } // Intersects 检查是否与另一个边界框相交 func (bb *BoundingBox) Intersects(other *BoundingBox) bool { return bb.MinX <= other.MaxX && bb.MaxX >= other.MinX && bb.MinY <= other.MaxY && bb.MaxY >= other.MinY && bb.MinZ <= other.MaxZ && bb.MaxZ >= other.MinZ } // Volume 计算体积 func (bb *BoundingBox) Volume() int32 { return (bb.MaxX - bb.MinX + 1) * (bb.MaxY - bb.MinY + 1) * (bb.MaxZ - bb.MinZ + 1) } // Orientation 朝向 type Orientation int32 const ( OrientationNorth Orientation = iota + 1 // 北 OrientationEast // 东 OrientationSouth // 南 OrientationWest // 西 OrientationUp // 上 OrientationDown // 下 ) // String 返回朝向的字符串表示 func (o Orientation) String() string { switch o { case OrientationNorth: return "north" case OrientationEast: return "east" case OrientationSouth: return "south" case OrientationWest: return "west" case OrientationUp: return "up" case OrientationDown: return "down" default: return "unknown" } } // IsValid 检查朝向是否有效 func (o Orientation) IsValid() bool { return o >= OrientationNorth && o <= OrientationDown } // Opposite 获取相反朝向 func (o Orientation) Opposite() Orientation { switch o { case OrientationNorth: return OrientationSouth case OrientationEast: return OrientationWest case OrientationSouth: return OrientationNorth case OrientationWest: return OrientationEast case OrientationUp: return OrientationDown case OrientationDown: return OrientationUp default: return OrientationNorth } } // 资源和成本相关值对象 // ResourceCost 资源成本 type ResourceCost struct { ResourceType string `json:"resource_type" bson:"resource_type"` Amount int64 `json:"amount" bson:"amount"` Optional bool `json:"optional" bson:"optional"` } // NewResourceCost 创建新资源成本 func NewResourceCost(resourceType string, amount int64) *ResourceCost { return &ResourceCost{ ResourceType: resourceType, Amount: amount, Optional: false, } } // Validate 验证资源成本 func (rc *ResourceCost) Validate() error { if rc.ResourceType == "" { return fmt.Errorf("resource type cannot be empty") } if rc.Amount < 0 { return fmt.Errorf("resource amount cannot be negative") } return nil } // Clone 克隆资源成本 func (rc *ResourceCost) Clone() *ResourceCost { return &ResourceCost{ ResourceType: rc.ResourceType, Amount: rc.Amount, Optional: rc.Optional, } } // 要求相关值对象 // RequirementType 要求类型 type RequirementType int32 const ( RequirementTypeLevel RequirementType = iota + 1 // 等级要求 RequirementTypeResource // 资源要求 RequirementTypeBuilding // 建筑要求 RequirementTypeTechnology // 科技要求 RequirementTypePopulation // 人口要求 RequirementTypeTime // 时间要求 RequirementTypeCustom // 自定义要求 ) // String 返回要求类型的字符串表示 func (rt RequirementType) String() string { switch rt { case RequirementTypeLevel: return "level" case RequirementTypeResource: return "resource" case RequirementTypeBuilding: return "building" case RequirementTypeTechnology: return "technology" case RequirementTypePopulation: return "population" case RequirementTypeTime: return "time" case RequirementTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查要求类型是否有效 func (rt RequirementType) IsValid() bool { return rt >= RequirementTypeLevel && rt <= RequirementTypeCustom } // Requirement 要求 type Requirement struct { Type RequirementType `json:"type" bson:"type"` Target string `json:"target" bson:"target"` Value int64 `json:"value" bson:"value"` Operator string `json:"operator" bson:"operator"` Description string `json:"description" bson:"description"` Optional bool `json:"optional" bson:"optional"` Met bool `json:"met" bson:"met"` } // NewRequirement 创建新要求 func NewRequirement(reqType RequirementType, target string, value int64, operator, description string) *Requirement { return &Requirement{ Type: reqType, Target: target, Value: value, Operator: operator, Description: description, Optional: false, Met: false, } } // IsMet 检查要求是否满足 func (r *Requirement) IsMet() bool { return r.Met || r.Optional } // SetMet 设置要求满足状态 func (r *Requirement) SetMet(met bool) { r.Met = met } // Validate 验证要求 func (r *Requirement) Validate() error { if !r.Type.IsValid() { return fmt.Errorf("invalid requirement type: %v", r.Type) } if r.Target == "" { return fmt.Errorf("requirement target cannot be empty") } if r.Operator == "" { return fmt.Errorf("requirement operator cannot be empty") } return nil } // Clone 克隆要求 func (r *Requirement) Clone() *Requirement { return &Requirement{ Type: r.Type, Target: r.Target, Value: r.Value, Operator: r.Operator, Description: r.Description, Optional: r.Optional, Met: r.Met, } } // 效果相关值对象 // EffectType 效果类型 type EffectType int32 const ( EffectTypeProduction EffectType = iota + 1 // 生产效果 EffectTypeDefense // 防御效果 EffectTypeEfficiency // 效率效果 EffectTypeCapacity // 容量效果 EffectTypeSpeed // 速度效果 EffectTypeCost // 成本效果 EffectTypeHealth // 生命值效果 EffectTypeDurability // 耐久度效果 EffectTypeRange // 范围效果 EffectTypeCustom // 自定义效果 ) // String 返回效果类型的字符串表示 func (et EffectType) String() string { switch et { case EffectTypeProduction: return "production" case EffectTypeDefense: return "defense" case EffectTypeEfficiency: return "efficiency" case EffectTypeCapacity: return "capacity" case EffectTypeSpeed: return "speed" case EffectTypeCost: return "cost" case EffectTypeHealth: return "health" case EffectTypeDurability: return "durability" case EffectTypeRange: return "range" case EffectTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查效果类型是否有效 func (et EffectType) IsValid() bool { return et >= EffectTypeProduction && et <= EffectTypeCustom } // BuildingEffect 建筑效果 type BuildingEffect struct { Type EffectType `json:"type" bson:"type"` Target string `json:"target" bson:"target"` Value float64 `json:"value" bson:"value"` Duration *time.Duration `json:"duration,omitempty" bson:"duration,omitempty"` StartTime *time.Time `json:"start_time,omitempty" bson:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty" bson:"end_time,omitempty"` Stackable bool `json:"stackable" bson:"stackable"` Permanent bool `json:"permanent" bson:"permanent"` Conditions map[string]interface{} `json:"conditions" bson:"conditions"` Description string `json:"description" bson:"description"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewBuildingEffect 创建新建筑效果 func NewBuildingEffect(effectType EffectType, target string, value float64) *BuildingEffect { now := time.Now() return &BuildingEffect{ Type: effectType, Target: target, Value: value, Stackable: false, Permanent: true, Conditions: make(map[string]interface{}), Description: fmt.Sprintf("%s effect on %s", effectType.String(), target), CreatedAt: now, UpdatedAt: now, } } // IsActive 检查效果是否活跃 func (be *BuildingEffect) IsActive() bool { if be.Permanent { return true } if be.EndTime != nil { return time.Now().Before(*be.EndTime) } if be.StartTime != nil && be.Duration != nil { endTime := be.StartTime.Add(*be.Duration) return time.Now().Before(endTime) } return true } // SetDuration 设置持续时间 func (be *BuildingEffect) SetDuration(duration time.Duration) { be.Duration = &duration if be.StartTime != nil { endTime := be.StartTime.Add(duration) be.EndTime = &endTime } be.UpdatedAt = time.Now() } // Start 开始效果 func (be *BuildingEffect) Start() { now := time.Now() be.StartTime = &now if be.Duration != nil { endTime := now.Add(*be.Duration) be.EndTime = &endTime } be.UpdatedAt = now } // Validate 验证建筑效果 func (be *BuildingEffect) Validate() error { if !be.Type.IsValid() { return fmt.Errorf("invalid effect type: %v", be.Type) } if be.Target == "" { return fmt.Errorf("effect target cannot be empty") } return nil } // Clone 克隆建筑效果 func (be *BuildingEffect) Clone() *BuildingEffect { clone := &BuildingEffect{ Type: be.Type, Target: be.Target, Value: be.Value, Stackable: be.Stackable, Permanent: be.Permanent, Conditions: make(map[string]interface{}), Description: be.Description, CreatedAt: be.CreatedAt, UpdatedAt: be.UpdatedAt, } // 深拷贝map for k, v := range be.Conditions { clone.Conditions[k] = v } // 深拷贝指针 if be.Duration != nil { duration := *be.Duration clone.Duration = &duration } if be.StartTime != nil { startTime := *be.StartTime clone.StartTime = &startTime } if be.EndTime != nil { endTime := *be.EndTime clone.EndTime = &endTime } return clone } // 生产相关值对象 // ProductionType 生产类型 type ProductionType int32 const ( ProductionTypeResource ProductionType = iota + 1 // 资源生产 ProductionTypeItem // 物品生产 ProductionTypeUnit // 单位生产 ProductionTypeService // 服务生产 ProductionTypeCustom // 自定义生产 ) // String 返回生产类型的字符串表示 func (pt ProductionType) String() string { switch pt { case ProductionTypeResource: return "resource" case ProductionTypeItem: return "item" case ProductionTypeUnit: return "unit" case ProductionTypeService: return "service" case ProductionTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查生产类型是否有效 func (pt ProductionType) IsValid() bool { return pt >= ProductionTypeResource && pt <= ProductionTypeCustom } // ProductionInfo 生产信息 type ProductionInfo struct { Type ProductionType `json:"type" bson:"type"` Outputs []*ProductionOutput `json:"outputs" bson:"outputs"` Inputs []*ProductionInput `json:"inputs" bson:"inputs"` Rate float64 `json:"rate" bson:"rate"` Efficiency float64 `json:"efficiency" bson:"efficiency"` Capacity int32 `json:"capacity" bson:"capacity"` Queue []*ProductionTask `json:"queue" bson:"queue"` CurrentTask *ProductionTask `json:"current_task,omitempty" bson:"current_task,omitempty"` AutoProduction bool `json:"auto_production" bson:"auto_production"` Conditions map[string]interface{} `json:"conditions" bson:"conditions"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewProductionInfo 创建新生产信息 func NewProductionInfo(productionType ProductionType) *ProductionInfo { now := time.Now() return &ProductionInfo{ Type: productionType, Outputs: make([]*ProductionOutput, 0), Inputs: make([]*ProductionInput, 0), Rate: 1.0, Efficiency: 1.0, Capacity: 10, Queue: make([]*ProductionTask, 0), AutoProduction: false, Conditions: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // AddOutput 添加产出 func (pi *ProductionInfo) AddOutput(output *ProductionOutput) { pi.Outputs = append(pi.Outputs, output) pi.UpdatedAt = time.Now() } // AddInput 添加输入 func (pi *ProductionInfo) AddInput(input *ProductionInput) { pi.Inputs = append(pi.Inputs, input) pi.UpdatedAt = time.Now() } // AddTask 添加生产任务 func (pi *ProductionInfo) AddTask(task *ProductionTask) { pi.Queue = append(pi.Queue, task) pi.UpdatedAt = time.Now() } // StartNextTask 开始下一个任务 func (pi *ProductionInfo) StartNextTask() *ProductionTask { if len(pi.Queue) == 0 { return nil } task := pi.Queue[0] pi.Queue = pi.Queue[1:] pi.CurrentTask = task task.Start() pi.UpdatedAt = time.Now() return task } // CompleteCurrentTask 完成当前任务 func (pi *ProductionInfo) CompleteCurrentTask() *ProductionTask { if pi.CurrentTask == nil { return nil } task := pi.CurrentTask task.Complete() pi.CurrentTask = nil pi.UpdatedAt = time.Now() return task } // Validate 验证生产信息 func (pi *ProductionInfo) Validate() error { if !pi.Type.IsValid() { return fmt.Errorf("invalid production type: %v", pi.Type) } if pi.Rate <= 0 { return fmt.Errorf("production rate must be positive") } if pi.Efficiency < 0 || pi.Efficiency > 2 { return fmt.Errorf("production efficiency must be between 0 and 2") } if pi.Capacity <= 0 { return fmt.Errorf("production capacity must be positive") } return nil } // ProductionOutput 生产产出 type ProductionOutput struct { ResourceType string `json:"resource_type" bson:"resource_type"` Amount int64 `json:"amount" bson:"amount"` Rate float64 `json:"rate" bson:"rate"` Quality float64 `json:"quality" bson:"quality"` } // NewProductionOutput 创建新生产产出 func NewProductionOutput(resourceType string, amount int64, rate float64) *ProductionOutput { return &ProductionOutput{ ResourceType: resourceType, Amount: amount, Rate: rate, Quality: 1.0, } } // ProductionInput 生产输入 type ProductionInput struct { ResourceType string `json:"resource_type" bson:"resource_type"` Amount int64 `json:"amount" bson:"amount"` Rate float64 `json:"rate" bson:"rate"` Optional bool `json:"optional" bson:"optional"` } // NewProductionInput 创建新生产输入 func NewProductionInput(resourceType string, amount int64, rate float64) *ProductionInput { return &ProductionInput{ ResourceType: resourceType, Amount: amount, Rate: rate, Optional: false, } } // ProductionTask 生产任务 type ProductionTask struct { ID string `json:"id" bson:"id"` Type ProductionType `json:"type" bson:"type"` Target string `json:"target" bson:"target"` Quantity int32 `json:"quantity" bson:"quantity"` Progress float64 `json:"progress" bson:"progress"` Duration time.Duration `json:"duration" bson:"duration"` StartedAt *time.Time `json:"started_at,omitempty" bson:"started_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty" bson:"completed_at,omitempty"` Status ProductionTaskStatus `json:"status" bson:"status"` Inputs []*ProductionInput `json:"inputs" bson:"inputs"` Outputs []*ProductionOutput `json:"outputs" bson:"outputs"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // ProductionTaskStatus 生产任务状态 type ProductionTaskStatus int32 const ( ProductionTaskStatusPending ProductionTaskStatus = iota + 1 // 等待中 ProductionTaskStatusInProgress // 进行中 ProductionTaskStatusCompleted // 已完成 ProductionTaskStatusCancelled // 已取消 ProductionTaskStatusFailed // 失败 ) // String 返回生产任务状态的字符串表示 func (pts ProductionTaskStatus) String() string { switch pts { case ProductionTaskStatusPending: return "pending" case ProductionTaskStatusInProgress: return "in_progress" case ProductionTaskStatusCompleted: return "completed" case ProductionTaskStatusCancelled: return "cancelled" case ProductionTaskStatusFailed: return "failed" default: return "unknown" } } // NewProductionTask 创建新生产任务 func NewProductionTask(taskType ProductionType, target string, quantity int32, duration time.Duration) *ProductionTask { now := time.Now() return &ProductionTask{ ID: fmt.Sprintf("task_%d", now.UnixNano()), Type: taskType, Target: target, Quantity: quantity, Progress: 0.0, Duration: duration, Status: ProductionTaskStatusPending, Inputs: make([]*ProductionInput, 0), Outputs: make([]*ProductionOutput, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // Start 开始任务 func (pt *ProductionTask) Start() { now := time.Now() pt.StartedAt = &now pt.Status = ProductionTaskStatusInProgress pt.UpdatedAt = now } // Complete 完成任务 func (pt *ProductionTask) Complete() { now := time.Now() pt.CompletedAt = &now pt.Progress = 100.0 pt.Status = ProductionTaskStatusCompleted pt.UpdatedAt = now } // Cancel 取消任务 func (pt *ProductionTask) Cancel() { pt.Status = ProductionTaskStatusCancelled pt.UpdatedAt = time.Now() } // UpdateProgress 更新进度 func (pt *ProductionTask) UpdateProgress(progress float64) { if progress < 0 { progress = 0 } if progress > 100 { progress = 100 } pt.Progress = progress pt.UpdatedAt = time.Now() if progress >= 100 { pt.Complete() } } // 存储相关值对象 // StorageType 存储类型 type StorageType int32 const ( StorageTypeGeneral StorageType = iota + 1 // 通用存储 StorageTypeResource // 资源存储 StorageTypeItem // 物品存储 StorageTypeFood // 食物存储 StorageTypeWeapon // 武器存储 StorageTypeArmor // 装备存储 StorageTypeLiquid // 液体存储 StorageTypeGas // 气体存储 StorageTypeSpecial // 特殊存储 ) // String 返回存储类型的字符串表示 func (st StorageType) String() string { switch st { case StorageTypeGeneral: return "general" case StorageTypeResource: return "resource" case StorageTypeItem: return "item" case StorageTypeFood: return "food" case StorageTypeWeapon: return "weapon" case StorageTypeArmor: return "armor" case StorageTypeLiquid: return "liquid" case StorageTypeGas: return "gas" case StorageTypeSpecial: return "special" default: return "unknown" } } // IsValid 检查存储类型是否有效 func (st StorageType) IsValid() bool { return st >= StorageTypeGeneral && st <= StorageTypeSpecial } // StorageInfo 存储信息 type StorageInfo struct { Type StorageType `json:"type" bson:"type"` Capacity int64 `json:"capacity" bson:"capacity"` Used int64 `json:"used" bson:"used"` Reserved int64 `json:"reserved" bson:"reserved"` Items []*StorageItem `json:"items" bson:"items"` Filters []string `json:"filters" bson:"filters"` AutoSort bool `json:"auto_sort" bson:"auto_sort"` AutoCompact bool `json:"auto_compact" bson:"auto_compact"` AccessRules []*AccessRule `json:"access_rules" bson:"access_rules"` Conditions map[string]interface{} `json:"conditions" bson:"conditions"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewStorageInfo 创建新存储信息 func NewStorageInfo(storageType StorageType, capacity int64) *StorageInfo { now := time.Now() return &StorageInfo{ Type: storageType, Capacity: capacity, Used: 0, Reserved: 0, Items: make([]*StorageItem, 0), Filters: make([]string, 0), AutoSort: true, AutoCompact: true, AccessRules: make([]*AccessRule, 0), Conditions: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // GetAvailable 获取可用容量 func (si *StorageInfo) GetAvailable() int64 { return si.Capacity - si.Used - si.Reserved } // GetUsagePercentage 获取使用率 func (si *StorageInfo) GetUsagePercentage() float64 { if si.Capacity == 0 { return 0.0 } return float64(si.Used) / float64(si.Capacity) * 100.0 } // IsFull 检查是否已满 func (si *StorageInfo) IsFull() bool { return si.GetAvailable() <= 0 } // CanStore 检查是否可以存储 func (si *StorageInfo) CanStore(itemType string, quantity int64) bool { if si.GetAvailable() < quantity { return false } // 检查过滤器 if len(si.Filters) > 0 { allowed := false for _, filter := range si.Filters { if filter == itemType || filter == "*" { allowed = true break } } if !allowed { return false } } return true } // AddItem 添加物品 func (si *StorageInfo) AddItem(item *StorageItem) error { if !si.CanStore(item.ItemType, item.Quantity) { return fmt.Errorf("cannot store item: insufficient space or not allowed") } // 查找是否已存在相同物品 for _, existing := range si.Items { if existing.ItemType == item.ItemType && existing.CanStack(item) { existing.Quantity += item.Quantity existing.UpdatedAt = time.Now() si.Used += item.Quantity si.UpdatedAt = time.Now() return nil } } // 添加新物品 si.Items = append(si.Items, item) si.Used += item.Quantity si.UpdatedAt = time.Now() return nil } // RemoveItem 移除物品 func (si *StorageInfo) RemoveItem(itemType string, quantity int64) error { for i, item := range si.Items { if item.ItemType == itemType { if item.Quantity < quantity { return fmt.Errorf("insufficient quantity: have %d, need %d", item.Quantity, quantity) } item.Quantity -= quantity item.UpdatedAt = time.Now() si.Used -= quantity // 如果数量为0,移除物品 if item.Quantity <= 0 { si.Items = append(si.Items[:i], si.Items[i+1:]...) } si.UpdatedAt = time.Now() return nil } } return fmt.Errorf("item not found: %s", itemType) } // Validate 验证存储信息 func (si *StorageInfo) Validate() error { if !si.Type.IsValid() { return fmt.Errorf("invalid storage type: %v", si.Type) } if si.Capacity <= 0 { return fmt.Errorf("storage capacity must be positive") } if si.Used < 0 { return fmt.Errorf("storage used cannot be negative") } if si.Reserved < 0 { return fmt.Errorf("storage reserved cannot be negative") } if si.Used+si.Reserved > si.Capacity { return fmt.Errorf("storage used+reserved cannot exceed capacity") } return nil } // StorageItem 存储物品 type StorageItem struct { ItemType string `json:"item_type" bson:"item_type"` Quantity int64 `json:"quantity" bson:"quantity"` Quality float64 `json:"quality" bson:"quality"` Durability float64 `json:"durability" bson:"durability"` Stackable bool `json:"stackable" bson:"stackable"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewStorageItem 创建新存储物品 func NewStorageItem(itemType string, quantity int64) *StorageItem { now := time.Now() return &StorageItem{ ItemType: itemType, Quantity: quantity, Quality: 1.0, Durability: 1.0, Stackable: true, Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // CanStack 检查是否可以堆叠 func (si *StorageItem) CanStack(other *StorageItem) bool { return si.Stackable && other.Stackable && si.ItemType == other.ItemType && si.Quality == other.Quality && si.Durability == other.Durability } // AccessRule 访问规则 type AccessRule struct { UserID *uint64 `json:"user_id,omitempty" bson:"user_id,omitempty"` Role *string `json:"role,omitempty" bson:"role,omitempty"` Permission string `json:"permission" bson:"permission"` ItemTypes []string `json:"item_types" bson:"item_types"` CreatedAt time.Time `json:"created_at" bson:"created_at"` } // NewAccessRule 创建新访问规则 func NewAccessRule(permission string) *AccessRule { return &AccessRule{ Permission: permission, ItemTypes: make([]string, 0), CreatedAt: time.Now(), } } // 防御相关值对象 // DamageType 伤害类型 type DamageType int32 const ( DamageTypePhysical DamageType = iota + 1 // 物理伤害 DamageTypeFire // 火焰伤害 DamageTypeIce // 冰霜伤害 DamageTypeLightning // 闪电伤害 DamageTypePoison // 毒素伤害 DamageTypeAcid // 酸性伤害 DamageTypeMagic // 魔法伤害 DamageTypeHoly // 神圣伤害 DamageTypeDark // 黑暗伤害 DamageTypeCustom // 自定义伤害 ) // String 返回伤害类型的字符串表示 func (dt DamageType) String() string { switch dt { case DamageTypePhysical: return "physical" case DamageTypeFire: return "fire" case DamageTypeIce: return "ice" case DamageTypeLightning: return "lightning" case DamageTypePoison: return "poison" case DamageTypeAcid: return "acid" case DamageTypeMagic: return "magic" case DamageTypeHoly: return "holy" case DamageTypeDark: return "dark" case DamageTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查伤害类型是否有效 func (dt DamageType) IsValid() bool { return dt >= DamageTypePhysical && dt <= DamageTypeCustom } // DefenseInfo 防御信息 type DefenseInfo struct { Armor int32 `json:"armor" bson:"armor"` Resistances map[DamageType]int32 `json:"resistances" bson:"resistances"` Immunities []DamageType `json:"immunities" bson:"immunities"` Weaknesses []DamageType `json:"weaknesses" bson:"weaknesses"` Shield int32 `json:"shield" bson:"shield"` MaxShield int32 `json:"max_shield" bson:"max_shield"` RegenRate float64 `json:"regen_rate" bson:"regen_rate"` Absorption float64 `json:"absorption" bson:"absorption"` Reflection float64 `json:"reflection" bson:"reflection"` Conditions map[string]interface{} `json:"conditions" bson:"conditions"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewDefenseInfo 创建新防御信息 func NewDefenseInfo() *DefenseInfo { now := time.Now() return &DefenseInfo{ Armor: 0, Resistances: make(map[DamageType]int32), Immunities: make([]DamageType, 0), Weaknesses: make([]DamageType, 0), Shield: 0, MaxShield: 0, RegenRate: 0.0, Absorption: 0.0, Reflection: 0.0, Conditions: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // GetDefenseValue 获取对特定伤害类型的防御值 func (di *DefenseInfo) GetDefenseValue(damageType DamageType) int32 { // 检查免疫 for _, immunity := range di.Immunities { if immunity == damageType { return 999999 // 免疫,返回极高防御值 } } // 检查弱点 for _, weakness := range di.Weaknesses { if weakness == damageType { return -di.Armor // 弱点,负防御 } } // 基础护甲 + 特定抗性 defenseValue := di.Armor if resistance, exists := di.Resistances[damageType]; exists { defenseValue += resistance } return defenseValue } // AddResistance 添加抗性 func (di *DefenseInfo) AddResistance(damageType DamageType, value int32) { di.Resistances[damageType] = value di.UpdatedAt = time.Now() } // AddImmunity 添加免疫 func (di *DefenseInfo) AddImmunity(damageType DamageType) { // 检查是否已存在 for _, existing := range di.Immunities { if existing == damageType { return } } di.Immunities = append(di.Immunities, damageType) di.UpdatedAt = time.Now() } // AddWeakness 添加弱点 func (di *DefenseInfo) AddWeakness(damageType DamageType) { // 检查是否已存在 for _, existing := range di.Weaknesses { if existing == damageType { return } } di.Weaknesses = append(di.Weaknesses, damageType) di.UpdatedAt = time.Now() } // RegenerateShield 恢复护盾 func (di *DefenseInfo) RegenerateShield() { if di.Shield < di.MaxShield && di.RegenRate > 0 { di.Shield += int32(di.RegenRate) if di.Shield > di.MaxShield { di.Shield = di.MaxShield } di.UpdatedAt = time.Now() } } // Validate 验证防御信息 func (di *DefenseInfo) Validate() error { if di.Armor < 0 { return fmt.Errorf("armor cannot be negative") } if di.Shield < 0 || di.Shield > di.MaxShield { return fmt.Errorf("shield must be between 0 and max shield") } if di.MaxShield < 0 { return fmt.Errorf("max shield cannot be negative") } if di.RegenRate < 0 { return fmt.Errorf("regen rate cannot be negative") } if di.Absorption < 0 || di.Absorption > 1 { return fmt.Errorf("absorption must be between 0 and 1") } if di.Reflection < 0 || di.Reflection > 1 { return fmt.Errorf("reflection must be between 0 and 1") } return nil } // 工人相关值对象 // WorkerRole 工人角色 type WorkerRole int32 const ( WorkerRoleGeneral WorkerRole = iota + 1 // 通用工人 WorkerRoleBuilder // 建造工人 WorkerRoleMaintenance // 维护工人 WorkerRoleOperator // 操作工人 WorkerRoleGuard // 守卫 WorkerRoleManager // 管理员 WorkerRoleSpecialist // 专家 ) // String 返回工人角色的字符串表示 func (wr WorkerRole) String() string { switch wr { case WorkerRoleGeneral: return "general" case WorkerRoleBuilder: return "builder" case WorkerRoleMaintenance: return "maintenance" case WorkerRoleOperator: return "operator" case WorkerRoleGuard: return "guard" case WorkerRoleManager: return "manager" case WorkerRoleSpecialist: return "specialist" default: return "unknown" } } // IsValid 检查工人角色是否有效 func (wr WorkerRole) IsValid() bool { return wr >= WorkerRoleGeneral && wr <= WorkerRoleSpecialist } // WorkerStatus 工人状态 type WorkerStatus int32 const ( WorkerStatusActive WorkerStatus = iota + 1 // 活跃 WorkerStatusIdle // 空闲 WorkerStatusBusy // 忙碌 WorkerStatusResting // 休息 WorkerStatusSick // 生病 WorkerStatusOnLeave // 请假 WorkerStatusDismissed // 解雇 ) // String 返回工人状态的字符串表示 func (ws WorkerStatus) String() string { switch ws { case WorkerStatusActive: return "active" case WorkerStatusIdle: return "idle" case WorkerStatusBusy: return "busy" case WorkerStatusResting: return "resting" case WorkerStatusSick: return "sick" case WorkerStatusOnLeave: return "on_leave" case WorkerStatusDismissed: return "dismissed" default: return "unknown" } } // IsValid 检查工人状态是否有效 func (ws WorkerStatus) IsValid() bool { return ws >= WorkerStatusActive && ws <= WorkerStatusDismissed } // WorkerInfo 工人信息 type WorkerInfo struct { WorkerID uint64 `json:"worker_id" bson:"worker_id"` Role WorkerRole `json:"role" bson:"role"` Status WorkerStatus `json:"status" bson:"status"` Efficiency float64 `json:"efficiency" bson:"efficiency"` Experience int32 `json:"experience" bson:"experience"` Level int32 `json:"level" bson:"level"` Salary int64 `json:"salary" bson:"salary"` AssignedAt time.Time `json:"assigned_at" bson:"assigned_at"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewWorkerInfo 创建新工人信息 func NewWorkerInfo(workerID uint64, role WorkerRole) *WorkerInfo { now := time.Now() return &WorkerInfo{ WorkerID: workerID, Role: role, Status: WorkerStatusActive, Efficiency: 1.0, Experience: 0, Level: 1, Salary: 100, AssignedAt: now, CreatedAt: now, UpdatedAt: now, } } // VisitorInfo 访客信息 type VisitorInfo struct { VisitorID uint64 `json:"visitor_id" bson:"visitor_id"` Purpose string `json:"purpose" bson:"purpose"` ArrivedAt time.Time `json:"arrived_at" bson:"arrived_at"` LeftAt *time.Time `json:"left_at,omitempty" bson:"left_at,omitempty"` Duration time.Duration `json:"duration" bson:"duration"` CreatedAt time.Time `json:"created_at" bson:"created_at"` } // NewVisitorInfo 创建新访客信息 func NewVisitorInfo(visitorID uint64, purpose string) *VisitorInfo { now := time.Now() return &VisitorInfo{ VisitorID: visitorID, Purpose: purpose, ArrivedAt: now, Duration: 0, CreatedAt: now, } } // Leave 离开 func (vi *VisitorInfo) Leave() { now := time.Now() vi.LeftAt = &now vi.Duration = now.Sub(vi.ArrivedAt) } // 维护相关值对象 // MaintenanceType 维护类型 type MaintenanceType int32 const ( MaintenanceTypeRoutine MaintenanceType = iota + 1 // 常规维护 MaintenanceTypePreventive // 预防性维护 MaintenanceTypeEmergency // 紧急维护 MaintenanceTypeRepair // 修理维护 MaintenanceTypeUpgrade // 升级维护 MaintenanceTypeCleaning // 清洁维护 MaintenanceTypeInspection // 检查维护 ) // String 返回维护类型的字符串表示 func (mt MaintenanceType) String() string { switch mt { case MaintenanceTypeRoutine: return "routine" case MaintenanceTypePreventive: return "preventive" case MaintenanceTypeEmergency: return "emergency" case MaintenanceTypeRepair: return "repair" case MaintenanceTypeUpgrade: return "upgrade" case MaintenanceTypeCleaning: return "cleaning" case MaintenanceTypeInspection: return "inspection" default: return "unknown" } } // IsValid 检查维护类型是否有效 func (mt MaintenanceType) IsValid() bool { return mt >= MaintenanceTypeRoutine && mt <= MaintenanceTypeInspection } // MaintenanceInfo 维护信息 type MaintenanceInfo struct { LastMaintenanceAt *time.Time `json:"last_maintenance_at,omitempty" bson:"last_maintenance_at,omitempty"` NextMaintenanceAt time.Time `json:"next_maintenance_at" bson:"next_maintenance_at"` MaintenanceLevel int32 `json:"maintenance_level" bson:"maintenance_level"` Costs []*ResourceCost `json:"costs" bson:"costs"` History []*MaintenanceRecord `json:"history" bson:"history"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewMaintenanceInfo 创建新维护信息 func NewMaintenanceInfo() *MaintenanceInfo { now := time.Now() return &MaintenanceInfo{ NextMaintenanceAt: now.Add(24 * time.Hour), MaintenanceLevel: 100, Costs: make([]*ResourceCost, 0), History: make([]*MaintenanceRecord, 0), CreatedAt: now, UpdatedAt: now, } } // MaintenanceRecord 维护记录 type MaintenanceRecord struct { Type MaintenanceType `json:"type" bson:"type"` PerformedAt time.Time `json:"performed_at" bson:"performed_at"` Costs []*ResourceCost `json:"costs" bson:"costs"` Result string `json:"result" bson:"result"` Notes string `json:"notes" bson:"notes"` } // NewMaintenanceRecord 创建新维护记录 func NewMaintenanceRecord(maintenanceType MaintenanceType) *MaintenanceRecord { return &MaintenanceRecord{ Type: maintenanceType, PerformedAt: time.Now(), Costs: make([]*ResourceCost, 0), Result: "success", Notes: "", } } // 辅助函数 // abs 计算绝对值 func abs(x int32) int32 { if x < 0 { return -x } return x } ================================================ FILE: internal/domain/character/actor.go ================================================ package character import ( "context" "fmt" "sync" ) // Actor 角色实体 - 具有战斗属性的实体(玩家、怪物等) // 继承自Entity,添加战斗相关的属性和行为 type Actor struct { *Entity // 组合Entity mu sync.RWMutex // 基础信息 name string level int32 // 战斗属性 hp float32 // 当前生命值 mp float32 // 当前魔法值 speed float32 // 移动速度 // 状态 flagState FlagState // 状态标志位 // 伤害来源信息 damageSourceInfo *DamageInfo // 子系统(聚合其他值对象或服务) attributeManager *AttributeManager // 属性管理器 skillManager *SkillManager // 技能管理器 buffManager *BuffManager // Buff管理器 spell *Spell // 施法器 // 领域事件发布器(可选注入) publisher EventPublisher } // DamageInfo 伤害信息 type DamageInfo struct { TargetID EntityID // 目标ID AttackerInfo AttackerInfo // 攻击者信息 Amount int32 // 伤害数值 DamageType DamageType // 伤害类型 IsCrit bool // 是否暴击 IsMiss bool // 是否未命中 } // AttackerInfo 攻击者信息 type AttackerInfo struct { AttackerID EntityID // 攻击者ID AttackerType AttackerType // 攻击者类型 SkillID int32 // 技能ID BuffID int32 // BuffID } // DamageType 伤害类型 type DamageType int32 const ( DamageTypeUnknown DamageType = 0 // 未知 DamageTypePhysical DamageType = 1 // 物理伤害 DamageTypeMagical DamageType = 2 // 魔法伤害 DamageTypeReal DamageType = 3 // 真实伤害 DamageTypeHeal DamageType = 4 // 治疗 ) // AttackerType 攻击者类型 type AttackerType int32 const ( AttackerTypeSkill AttackerType = 0 // 技能攻击 AttackerTypeBuff AttackerType = 1 // Buff伤害 AttackerTypeNormal AttackerType = 2 // 普通攻击 AttackerTypeEnvironment AttackerType = 3 // 环境伤害 ) // NewActor 创建新Actor(工厂方法) func NewActor( entityID EntityID, entityType EntityType, unitID int32, position Vector3, direction Vector3, name string, level int32, ) *Actor { entity := NewEntity(entityID, entityType, unitID, position, direction) actor := &Actor{ Entity: entity, name: name, level: level, flagState: FlagStateZero, } // 初始化子系统 actor.attributeManager = NewAttributeManager(actor) actor.skillManager = NewSkillManager(actor) actor.buffManager = NewBuffManager(actor) actor.spell = NewSpell(actor) return actor } // ========== 基础信息 ========== // Name 获取名称 func (a *Actor) Name() string { a.mu.RLock() defer a.mu.RUnlock() return a.name } // Level 获取等级 func (a *Actor) Level() int32 { a.mu.RLock() defer a.mu.RUnlock() return a.level } // SetLevel 设置等级 func (a *Actor) SetLevel(level int32) { a.mu.Lock() defer a.mu.Unlock() a.level = level } // ========== 战斗属性 ========== // HP 获取当前生命值 func (a *Actor) HP() float32 { a.mu.RLock() defer a.mu.RUnlock() return a.hp } // MP 获取当前魔法值 func (a *Actor) MP() float32 { a.mu.RLock() defer a.mu.RUnlock() return a.mp } // Speed 获取移动速度 func (a *Actor) Speed() float32 { a.mu.RLock() defer a.mu.RUnlock() return a.speed } // ChangeHP 改变生命值 func (a *Actor) ChangeHP(amount float32) { a.mu.Lock() defer a.mu.Unlock() a.hp += amount if a.hp <= 0 { a.hp = 0 } maxHP := a.attributeManager.Final().MaxHP if a.hp > maxHP { a.hp = maxHP } // TODO: 同步属性变化到客户端 // a.syncAttributeEntry(AttributeTypeHP, int32(a.hp)) } // ChangeMP 改变魔法值 func (a *Actor) ChangeMP(amount float32) { a.mu.Lock() defer a.mu.Unlock() a.mp += amount if a.mp <= 0 { a.mp = 0 } maxMP := a.attributeManager.Final().MaxMP if a.mp > maxMP { a.mp = maxMP } // TODO: 同步属性变化到客户端 // a.syncAttributeEntry(AttributeTypeMP, int32(a.mp)) } // IsDeath 是否死亡 func (a *Actor) IsDeath() bool { a.mu.RLock() defer a.mu.RUnlock() return a.hp <= 0 } // Revive 复活(由子类重写) func (a *Actor) Revive(ctx context.Context) error { // 基类默认实现:恢复满血满蓝 maxHP := a.attributeManager.Final().MaxHP maxMP := a.attributeManager.Final().MaxMP a.ChangeHP(maxHP) a.ChangeMP(maxMP) return nil } // ========== 状态标志 ========== // FlagState 获取状态标志 func (a *Actor) GetFlagState() FlagState { a.mu.RLock() defer a.mu.RUnlock() return a.flagState } // AddFlagState 添加状态标志 func (a *Actor) AddFlagState(state FlagState) { a.mu.Lock() defer a.mu.Unlock() newState := a.flagState.AddFlag(state) if newState == a.flagState { return } a.flagState = newState // TODO: 同步状态变化到客户端 // a.syncAttributeEntry(AttributeTypeFlagState, int32(a.flagState)) } // RemoveFlagState 移除状态标志 func (a *Actor) RemoveFlagState(state FlagState) { a.mu.Lock() defer a.mu.Unlock() newState := a.flagState.RemoveFlag(state) if newState == a.flagState { return } a.flagState = newState // TODO: 同步状态变化到客户端 // a.syncAttributeEntry(AttributeTypeFlagState, int32(a.flagState)) } // ZeroFlagState 清空状态标志 func (a *Actor) ZeroFlagState() { a.mu.Lock() defer a.mu.Unlock() if a.flagState == FlagStateZero { return } a.flagState = FlagStateZero // TODO: 同步状态变化到客户端 // a.syncAttributeEntry(AttributeTypeFlagState, int32(a.flagState)) } // SetFlagStateExact 设置为精确的状态标志(用于根据 Buff 汇总结果覆盖) func (a *Actor) SetFlagStateExact(state FlagState) { a.mu.Lock() if a.flagState == state { a.mu.Unlock() return } a.flagState = state a.mu.Unlock() // TODO: 同步状态变化到客户端 } // ========== 伤害处理 ========== // OnHurt 受到伤害 func (a *Actor) OnHurt(ctx context.Context, info *DamageInfo) error { a.mu.Lock() a.damageSourceInfo = info a.mu.Unlock() // TODO: 广播受伤消息到地图内的玩家 // mapRef := a.GetMap() // if gameMap, ok := mapRef.(*Map); ok { // gameMap.BroadcastEntityHurt(info) // } // 扣除生命值 a.ChangeHP(-float32(info.Amount)) // 发布造成伤害事件(由被伤害方触发发布,聚合ID为攻击者) if a.publisher != nil { evt := NewDamageDealtEvent(info.AttackerInfo.AttackerID, a.ID(), info.Amount, info.DamageType, info.IsCrit) a.publisher.Publish(evt) } // TODO: 记录日志 // logger.Debug("%s受到%d的%s攻击, 扣除%d血量, 剩余血量:%f", // a.String(), info.AttackerInfo.AttackerID, info.AttackerInfo.AttackerType, info.Amount, a.hp) return nil } // DamageSource 获取伤害来源信息 func (a *Actor) DamageSource() *DamageInfo { a.mu.RLock() defer a.mu.RUnlock() return a.damageSourceInfo } // ========== 子系统访问 ========== // AttributeManager 获取属性管理器 func (a *Actor) GetAttributeManager() *AttributeManager { return a.attributeManager } // SkillManager 获取技能管理器 func (a *Actor) GetSkillManager() *SkillManager { return a.skillManager } // BuffManager 获取Buff管理器 func (a *Actor) GetBuffManager() *BuffManager { return a.buffManager } // Spell 获取施法器 func (a *Actor) GetSpell() *Spell { return a.spell } // ========== 事件发布 ========== // EventPublisher 领域事件发布器接口 type EventPublisher interface { Publish(event DomainEvent) } // SetEventPublisher 注入事件发布器 func (a *Actor) SetEventPublisher(p EventPublisher) { a.publisher = p } // GetEventPublisher 获取事件发布器 func (a *Actor) GetEventPublisher() EventPublisher { return a.publisher } // ========== 生命周期 ========== // Start 初始化Actor func (a *Actor) Start(ctx context.Context) error { // 调用Entity的Start if err := a.Entity.Start(ctx); err != nil { return err } // 初始化子系统 if err := a.attributeManager.Start(ctx); err != nil { return fmt.Errorf("attributeManager start failed: %w", err) } if err := a.skillManager.Start(ctx); err != nil { return fmt.Errorf("skillManager start failed: %w", err) } if err := a.buffManager.Start(ctx); err != nil { return fmt.Errorf("buffManager start failed: %w", err) } // 根据最终属性初始化当前生命、魔法与移动速度 fin := a.attributeManager.Final() a.mu.Lock() a.hp = fin.MaxHP a.mp = fin.MaxMP a.speed = fin.Speed a.mu.Unlock() return nil } // Update 每帧更新 func (a *Actor) Update(ctx context.Context, deltaTime float32) error { // 调用Entity的Update if err := a.Entity.Update(ctx, deltaTime); err != nil { return err } // 更新子系统 if err := a.skillManager.Update(ctx, deltaTime); err != nil { return err } if err := a.buffManager.Update(ctx, deltaTime); err != nil { return err } // 按回复速度恢复生命与魔法,并刷新移动速度 fin := a.attributeManager.Final() if fin.HPRegen != 0 || fin.MPRegen != 0 { a.ChangeHP(fin.HPRegen * deltaTime) a.ChangeMP(fin.MPRegen * deltaTime) } a.mu.Lock() a.speed = fin.Speed a.mu.Unlock() return nil } // String 字符串表示 func (a *Actor) String() string { a.mu.RLock() defer a.mu.RUnlock() return fmt.Sprintf("%s:\"%s(%d)\"", a.Type().String(), a.name, a.ID()) } ================================================ FILE: internal/domain/character/buff_attributes_test.go ================================================ package character import ( "context" "testing" ) func TestBuffAttributeModifiersAffectFinalsAndSpeed(t *testing.T) { actor := NewActor(3, EntityTypePlayer, 1, NewVector3(0, 0, 0), NewVector3(1, 0, 0), "buffed", 1) if err := actor.Start(context.Background()); err != nil { t.Fatalf("actor start failed: %v", err) } am := actor.GetAttributeManager() baseMaxHP := am.Final().MaxHP baseSpeed := am.Final().Speed // Create a buff with +50 MaxHP and +10% MaxHP, and +20% Speed b := NewBuff(2001, actor, actor, 5.0) b.SetModifier(AttributeModifier{MaxHPAdd: 50, MaxHPMul: 0.1, SpeedMul: 0.2}) actor.GetBuffManager().AddBuff(b) fin := am.Final() if fin.MaxHP <= baseMaxHP { t.Fatalf("buff should increase MaxHP, got base=%v final=%v", baseMaxHP, fin.MaxHP) } // Speed is applied to actor each update; tick once to refresh speed from finals _ = actor.Update(context.Background(), 0.016) if actor.Speed() <= baseSpeed { t.Fatalf("buff should increase movement speed, got base=%v current=%v", baseSpeed, actor.Speed()) } } ================================================ FILE: internal/domain/character/buff_flags_test.go ================================================ package character import ( "context" "testing" ) func TestBuffFlagsAffectActorState(t *testing.T) { actor := NewActor(4, EntityTypePlayer, 1, NewVector3(0, 0, 0), NewVector3(1, 0, 0), "flagged", 1) if err := actor.Start(context.Background()); err != nil { t.Fatalf("actor start failed: %v", err) } if actor.GetFlagState().HasFlag(FlagStateStun) { t.Fatalf("should not be stunned initially") } // Add a stun buff b := NewBuff(3001, actor, actor, 1.0) b.SetFlagAdd(FlagStateStun) actor.GetBuffManager().AddBuff(b) if !actor.GetFlagState().HasFlag(FlagStateStun) { t.Fatalf("actor should be stunned after adding stun buff") } // Remove the buff actor.GetBuffManager().RemoveBuff(b) if actor.GetFlagState().HasFlag(FlagStateStun) { t.Fatalf("actor should not be stunned after removing stun buff") } } ================================================ FILE: internal/domain/character/entity.go ================================================ package character import ( "context" "sync" ) // Entity 实体基类 - 所有游戏实体的基础 // 遵循DDD原则:Entity是具有唯一标识的领域对象 type Entity struct { mu sync.RWMutex // 保护并发访问 // 唯一标识 entityID EntityID entityType EntityType // 定义数据(配置ID) unitID int32 // 空间属性 transform Transform position2D Vector2 // 2D位置(用于AOI) // 状态 valid bool // 实体是否有效 // 所属地图(聚合根引用) mapRef interface{} // 避免循环依赖,实际类型为 *Map // AOI实体引用(基础设施层) aoiEntity interface{} // 实际类型为 AOI系统的实体对象 } // NewEntity 创建新实体(工厂方法) func NewEntity( entityID EntityID, entityType EntityType, unitID int32, position Vector3, direction Vector3, ) *Entity { return &Entity{ entityID: entityID, entityType: entityType, unitID: unitID, transform: Transform{ Position: position, Direction: direction, }, position2D: position.ToVector2(), valid: true, } } // ========== 身份标识 ========== // ID 获取实体ID func (e *Entity) ID() EntityID { e.mu.RLock() defer e.mu.RUnlock() return e.entityID } // Type 获取实体类型 func (e *Entity) Type() EntityType { e.mu.RLock() defer e.mu.RUnlock() return e.entityType } // UnitID 获取单位定义ID func (e *Entity) UnitID() int32 { e.mu.RLock() defer e.mu.RUnlock() return e.unitID } // ========== 空间属性 ========== // Position 获取3D位置 func (e *Entity) Position() Vector3 { e.mu.RLock() defer e.mu.RUnlock() return e.transform.Position } // Position2D 获取2D位置 func (e *Entity) Position2D() Vector2 { e.mu.RLock() defer e.mu.RUnlock() return e.position2D } // Direction 获取方向 func (e *Entity) Direction() Vector3 { e.mu.RLock() defer e.mu.RUnlock() return e.transform.Direction } // Transform 获取Transform func (e *Entity) GetTransform() Transform { e.mu.RLock() defer e.mu.RUnlock() return e.transform } // SetPosition 设置位置 func (e *Entity) SetPosition(pos Vector3) { e.mu.Lock() defer e.mu.Unlock() e.transform.Position = pos e.position2D = pos.ToVector2() } // SetDirection 设置方向 func (e *Entity) SetDirection(dir Vector3) { e.mu.Lock() defer e.mu.Unlock() e.transform.Direction = dir } // SetTransform 设置Transform func (e *Entity) SetTransform(transform Transform) { e.mu.Lock() defer e.mu.Unlock() e.transform = transform e.position2D = transform.Position.ToVector2() } // ========== 状态管理 ========== // IsValid 检查实体是否有效 func (e *Entity) IsValid() bool { e.mu.RLock() defer e.mu.RUnlock() return e.valid } // Invalidate 使实体失效 func (e *Entity) Invalidate() { e.mu.Lock() defer e.mu.Unlock() e.valid = false } // ========== 地图关联 ========== // SetMap 设置所属地图(由基础设施层调用) func (e *Entity) SetMap(mapRef interface{}) { e.mu.Lock() defer e.mu.Unlock() e.mapRef = mapRef } // GetMap 获取所属地图 func (e *Entity) GetMap() interface{} { e.mu.RLock() defer e.mu.RUnlock() return e.mapRef } // ========== AOI关联 ========== // SetAOIEntity 设置AOI实体(由基础设施层调用) func (e *Entity) SetAOIEntity(aoiEntity interface{}) { e.mu.Lock() defer e.mu.Unlock() e.aoiEntity = aoiEntity } // GetAOIEntity 获取AOI实体 func (e *Entity) GetAOIEntity() interface{} { e.mu.RLock() defer e.mu.RUnlock() return e.aoiEntity } // ========== 生命周期钩子 ========== // Start 实体初始化(由子类重写) func (e *Entity) Start(ctx context.Context) error { // 基类默认实现为空 return nil } // Update 每帧更新(由子类重写) func (e *Entity) Update(ctx context.Context, deltaTime float32) error { // 基类默认实现为空 return nil } // Destroy 实体销毁(由子类重写) func (e *Entity) Destroy(ctx context.Context) error { e.Invalidate() return nil } // ========== 工具方法 ========== // DistanceTo 计算到另一个实体的距离 func (e *Entity) DistanceTo(other *Entity) float32 { e.mu.RLock() otherPos := other.Position2D() myPos := e.position2D e.mu.RUnlock() return myPos.Distance(otherPos) } // String 字符串表示 func (e *Entity) String() string { e.mu.RLock() defer e.mu.RUnlock() return e.entityType.String() + ":" + string(rune(e.entityID)) } ================================================ FILE: internal/domain/character/events.go ================================================ package character import ( "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { EventName() string OccurredOn() time.Time AggregateID() interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { eventName string occurredOn time.Time aggregateID interface{} } func NewBaseDomainEvent(eventName string, aggregateID interface{}) BaseDomainEvent { return BaseDomainEvent{ eventName: eventName, occurredOn: time.Now(), aggregateID: aggregateID, } } func (e BaseDomainEvent) EventName() string { return e.eventName } func (e BaseDomainEvent) OccurredOn() time.Time { return e.occurredOn } func (e BaseDomainEvent) AggregateID() interface{} { return e.aggregateID } // ========== 实体相关事件 ========== // EntityCreatedEvent 实体创建事件 type EntityCreatedEvent struct { BaseDomainEvent EntityID EntityID EntityType EntityType } func NewEntityCreatedEvent(entityID EntityID, entityType EntityType) *EntityCreatedEvent { return &EntityCreatedEvent{ BaseDomainEvent: NewBaseDomainEvent("EntityCreated", entityID), EntityID: entityID, EntityType: entityType, } } // EntityDestroyedEvent 实体销毁事件 type EntityDestroyedEvent struct { BaseDomainEvent EntityID EntityID EntityType EntityType } func NewEntityDestroyedEvent(entityID EntityID, entityType EntityType) *EntityDestroyedEvent { return &EntityDestroyedEvent{ BaseDomainEvent: NewBaseDomainEvent("EntityDestroyed", entityID), EntityID: entityID, EntityType: entityType, } } // ========== 玩家相关事件 ========== // PlayerCreatedEvent 玩家创建事件 type PlayerCreatedEvent struct { BaseDomainEvent CharacterID int64 UserID int64 Name string Level int32 } func NewPlayerCreatedEvent(characterID, userID int64, name string, level int32) *PlayerCreatedEvent { return &PlayerCreatedEvent{ BaseDomainEvent: NewBaseDomainEvent("PlayerCreated", characterID), CharacterID: characterID, UserID: userID, Name: name, Level: level, } } // PlayerLevelUpEvent 玩家升级事件 type PlayerLevelUpEvent struct { BaseDomainEvent CharacterID int64 OldLevel int32 NewLevel int32 } func NewPlayerLevelUpEvent(characterID int64, oldLevel, newLevel int32) *PlayerLevelUpEvent { return &PlayerLevelUpEvent{ BaseDomainEvent: NewBaseDomainEvent("PlayerLevelUp", characterID), CharacterID: characterID, OldLevel: oldLevel, NewLevel: newLevel, } } // PlayerDeathEvent 玩家死亡事件 type PlayerDeathEvent struct { BaseDomainEvent CharacterID int64 KillerID EntityID Position Vector3 } func NewPlayerDeathEvent(characterID int64, killerID EntityID, position Vector3) *PlayerDeathEvent { return &PlayerDeathEvent{ BaseDomainEvent: NewBaseDomainEvent("PlayerDeath", characterID), CharacterID: characterID, KillerID: killerID, Position: position, } } // ========== 战斗相关事件 ========== // DamageDealtEvent 造成伤害事件 type DamageDealtEvent struct { BaseDomainEvent AttackerID EntityID TargetID EntityID Amount int32 DamageType DamageType IsCrit bool } func NewDamageDealtEvent(attackerID, targetID EntityID, amount int32, damageType DamageType, isCrit bool) *DamageDealtEvent { return &DamageDealtEvent{ BaseDomainEvent: NewBaseDomainEvent("DamageDealt", attackerID), AttackerID: attackerID, TargetID: targetID, Amount: amount, DamageType: damageType, IsCrit: isCrit, } } // SkillCastEvent 技能释放事件 type SkillCastEvent struct { BaseDomainEvent CasterID EntityID SkillID int32 TargetID EntityID } func NewSkillCastEvent(casterID EntityID, skillID int32, targetID EntityID) *SkillCastEvent { return &SkillCastEvent{ BaseDomainEvent: NewBaseDomainEvent("SkillCast", casterID), CasterID: casterID, SkillID: skillID, TargetID: targetID, } } // BuffAddedEvent Buff添加事件 type BuffAddedEvent struct { BaseDomainEvent TargetID EntityID BuffID int32 CasterID EntityID Duration float32 } func NewBuffAddedEvent(targetID EntityID, buffID int32, casterID EntityID, duration float32) *BuffAddedEvent { return &BuffAddedEvent{ BaseDomainEvent: NewBaseDomainEvent("BuffAdded", targetID), TargetID: targetID, BuffID: buffID, CasterID: casterID, Duration: duration, } } // BuffRemovedEvent Buff移除事件 type BuffRemovedEvent struct { BaseDomainEvent TargetID EntityID BuffID int32 } func NewBuffRemovedEvent(targetID EntityID, buffID int32) *BuffRemovedEvent { return &BuffRemovedEvent{ BaseDomainEvent: NewBaseDomainEvent("BuffRemoved", targetID), TargetID: targetID, BuffID: buffID, } } // ========== 怪物相关事件 ========== // MonsterDeathEvent 怪物死亡事件 type MonsterDeathEvent struct { BaseDomainEvent MonsterID EntityID KillerID EntityID Position Vector3 DropItems []int32 // 掉落物品ID列表 DropExp int32 // 掉落经验 } func NewMonsterDeathEvent(monsterID, killerID EntityID, position Vector3, dropItems []int32, dropExp int32) *MonsterDeathEvent { return &MonsterDeathEvent{ BaseDomainEvent: NewBaseDomainEvent("MonsterDeath", monsterID), MonsterID: monsterID, KillerID: killerID, Position: position, DropItems: dropItems, DropExp: dropExp, } } ================================================ FILE: internal/domain/character/events_test.go ================================================ package character import ( "context" "sync" "testing" ) type fakePublisher struct { mu sync.Mutex events []DomainEvent } func (f *fakePublisher) Publish(e DomainEvent) { f.mu.Lock() defer f.mu.Unlock() f.events = append(f.events, e) } func (f *fakePublisher) CountByName(name string) int { f.mu.Lock() defer f.mu.Unlock() c := 0 for _, e := range f.events { if e.EventName() == name { c++ } } return c } func TestDamageDealtEventIsPublished(t *testing.T) { attacker := NewActor(21, EntityTypePlayer, 1, NewVector3(0, 0, 0), NewVector3(1, 0, 0), "att", 1) defender := NewActor(22, EntityTypePlayer, 1, NewVector3(1, 0, 0), NewVector3(-1, 0, 0), "def", 1) if err := attacker.Start(context.Background()); err != nil { t.Fatalf("att start: %v", err) } if err := defender.Start(context.Background()); err != nil { t.Fatalf("def start: %v", err) } pub := &fakePublisher{} // Attach publisher to defender (OnHurt publishes) defender.SetEventPublisher(pub) sk := NewSkill(6001, attacker) sk.SetTimings(0.0, 0.01, 0.01) sk.SetDamage(10, 1.0, 0.0, DamageTypePhysical) attacker.GetSkillManager().AddSkill(sk) if ok := attacker.GetSpell().Cast(sk.ID(), defender); !ok { t.Fatalf("cast failed") } _ = attacker.Update(context.Background(), 0.02) if pub.CountByName("DamageDealt") == 0 { t.Fatalf("expected DamageDealt event to be published") } } ================================================ FILE: internal/domain/character/monster.go ================================================ package character import ( "context" "fmt" ) // Monster 怪物实体 type Monster struct { *Actor // 继承Actor // 初始位置(用于AI返回出生点) initPosition Vector3 // 刷新点定义 spawnDefine *SpawnDefine // AI系统 ai AI } // SpawnDefine 刷新点定义(从配置加载) type SpawnDefine struct { SpawnID int32 // 刷新点ID WalkRange float32 // 巡逻范围 ChaseRange float32 // 追击范围 AttackRange float32 // 攻击范围 } // AI 怪物AI接口 type AI interface { Start(ctx context.Context) error Update(ctx context.Context, deltaTime float32) error OnDeath(ctx context.Context) error } // NewMonster 创建怪物(工厂方法) func NewMonster( entityID EntityID, unitID int32, position Vector3, direction Vector3, name string, level int32, spawnDefine *SpawnDefine, ) *Monster { actor := NewActor( entityID, EntityTypeMonster, unitID, position, direction, name, level, ) monster := &Monster{ Actor: actor, initPosition: position, spawnDefine: spawnDefine, } // TODO: 根据怪物类型创建对应的AI // monster.ai = NewMonsterAI(monster) return monster } // InitPosition 获取初始位置 func (m *Monster) InitPosition() Vector3 { return m.initPosition } // SpawnDefine 获取刷新点定义 func (m *Monster) GetSpawnDefine() *SpawnDefine { return m.spawnDefine } // GetAI 获取AI func (m *Monster) GetAI() AI { return m.ai } // Start 初始化怪物 func (m *Monster) Start(ctx context.Context) error { // 调用Actor的Start if err := m.Actor.Start(ctx); err != nil { return err } // 启动AI if m.ai != nil { if err := m.ai.Start(ctx); err != nil { return fmt.Errorf("ai start failed: %w", err) } } return nil } // Update 更新怪物 func (m *Monster) Update(ctx context.Context, deltaTime float32) error { // 调用Actor的Update if err := m.Actor.Update(ctx, deltaTime); err != nil { return err } // 更新AI if m.ai != nil { if err := m.ai.Update(ctx, deltaTime); err != nil { return err } } return nil } // Revive 怪物复活 func (m *Monster) Revive(ctx context.Context) error { // 调用Actor的复活逻辑 if err := m.Actor.Revive(ctx); err != nil { return err } // 重置到出生点 m.SetPosition(m.initPosition) // 清空伤害来源 m.damageSourceInfo = nil return nil } // String 字符串表示 func (m *Monster) String() string { return fmt.Sprintf("Monster:\"%s(%d)\"", m.Name(), m.ID()) } // ========== NPC 实体 ========== // NPC NPC实体 type NPC struct { *Entity // NPC不是Actor,因为不参与战斗 // NPC信息 npcID int32 // NPC定义ID name string // NPC名称 // 功能定义 functions []NPCFunction // NPC功能列表(对话、商店、任务等) } // NPCFunction NPC功能类型 type NPCFunction int32 const ( NPCFunctionDialogue NPCFunction = 0 // 对话 NPCFunctionShop NPCFunction = 1 // 商店 NPCFunctionQuest NPCFunction = 2 // 任务 NPCFunctionTeleport NPCFunction = 3 // 传送 NPCFunctionCraft NPCFunction = 4 // 制作 ) // NewNPC 创建NPC(工厂方法) func NewNPC( entityID EntityID, npcID int32, unitID int32, position Vector3, direction Vector3, name string, functions []NPCFunction, ) *NPC { entity := NewEntity(entityID, EntityTypeNPC, unitID, position, direction) return &NPC{ Entity: entity, npcID: npcID, name: name, functions: functions, } } // NPCID 获取NPC定义ID func (n *NPC) NPCID() int32 { return n.npcID } // Name 获取NPC名称 func (n *NPC) Name() string { return n.name } // HasFunction 检查是否具有某个功能 func (n *NPC) HasFunction(function NPCFunction) bool { for _, f := range n.functions { if f == function { return true } } return false } // Functions 获取所有功能 func (n *NPC) Functions() []NPCFunction { return n.functions } // String 字符串表示 func (n *NPC) String() string { return fmt.Sprintf("NPC:\"%s(%d)\"[NPCID:%d]", n.name, n.ID(), n.npcID) } // ========== Missile 投射物实体 ========== // Missile 投射物(技能子弹等) type Missile struct { *Entity // 投射物是简单实体 // 投射物信息 casterID EntityID // 施法者ID targetID EntityID // 目标ID(单体目标) target Vector3 // 目标位置(范围技能) // 运动参数 speed float32 // 飞行速度 lifetime float32 // 生命周期 elapsed float32 // 已存在时间 // 技能信息 skillID int32 // 关联的技能ID } // NewMissile 创建投射物 func NewMissile( entityID EntityID, unitID int32, position Vector3, direction Vector3, casterID EntityID, skillID int32, speed float32, lifetime float32, ) *Missile { entity := NewEntity(entityID, EntityTypeMissile, unitID, position, direction) return &Missile{ Entity: entity, casterID: casterID, skillID: skillID, speed: speed, lifetime: lifetime, elapsed: 0, } } // Update 更新投射物 func (m *Missile) Update(ctx context.Context, deltaTime float32) error { // 调用Entity的Update if err := m.Entity.Update(ctx, deltaTime); err != nil { return err } // 更新飞行时间 m.elapsed += deltaTime // 检查是否超时 if m.elapsed >= m.lifetime { m.Invalidate() return nil } // TODO: 更新位置 // 沿方向移动 // newPos := m.Position().Add(m.Direction().Mul(m.speed * deltaTime)) // m.SetPosition(newPos) // TODO: 检查碰撞 // if hit detected { // m.Invalidate() // trigger skill effect // } return nil } // String 字符串表示 func (m *Missile) String() string { return fmt.Sprintf("Missile(%d)[Skill:%d,Caster:%d]", m.ID(), m.skillID, m.casterID) } ================================================ FILE: internal/domain/character/player.go ================================================ package character import ( "context" "fmt" ) // Player 玩家实体 - 聚合根 type Player struct { *Actor // 继承Actor // 用户关联 userID int64 // 所属用户ID // 角色数据 characterID int64 // 角色ID(数据库主键) exp int32 // 经验值 gold int64 // 金币 // 背包系统(聚合) inventory *Inventory // 任务系统(聚合) taskManager *TaskManager // 对话系统 dialogueManager *DialogueManager // 当前交互的NPC interactingNPC *NPC } // NewPlayer 创建玩家(工厂方法) func NewPlayer( entityID EntityID, characterID int64, userID int64, unitID int32, position Vector3, direction Vector3, name string, level int32, ) *Player { actor := NewActor( entityID, EntityTypePlayer, unitID, position, direction, name, level, ) player := &Player{ Actor: actor, userID: userID, characterID: characterID, } // 初始化子系统 player.inventory = NewInventory(player) player.taskManager = NewTaskManager(player) player.dialogueManager = NewDialogueManager(player) return player } // ========== 身份信息 ========== // UserID 获取用户ID func (p *Player) UserID() int64 { return p.userID } // CharacterID 获取角色ID func (p *Player) CharacterID() int64 { return p.characterID } // ========== 角色属性 ========== // Exp 获取经验值 func (p *Player) Exp() int32 { return p.exp } // Gold 获取金币 func (p *Player) Gold() int64 { return p.gold } // GetName 获取角色名称 func (p *Player) GetName() string { return p.Name() } // GetRace 获取种族 func (p *Player) GetRace() int32 { // 从UnitID获取种族信息,这里简化返回0 // 实际应该从DataManager中的UnitDefine获取 return 0 } // GetClass 获取职业 func (p *Player) GetClass() int32 { return p.UnitID() } // GetExp 获取经验值 func (p *Player) GetExp() int64 { return int64(p.exp) } // GetGold 获取金币 func (p *Player) GetGold() int64 { return p.gold } // AddExp 添加经验值 func (p *Player) AddExp(amount int64) { p.exp += int32(amount) // 检查升级 for p.CanLevelUp() { p.LevelUp() } } // AddGold 添加金币 func (p *Player) AddGold(amount int64) { p.gold += amount if p.gold < 0 { p.gold = 0 } } // CanLevelUp 是否可以升级 func (p *Player) CanLevelUp() bool { currentLevel := p.Level() if currentLevel >= 100 { // 最大等级100 return false } // 简化的升级经验公式: exp >= level * 1000 requiredExp := int32(currentLevel * 1000) return p.exp >= requiredExp } // LevelUp 升级 func (p *Player) LevelUp() { currentLevel := p.Level() newLevel := currentLevel + 1 // 设置新等级 p.SetLevel(newLevel) // 扣除升级所需经验 requiredExp := int32(currentLevel * 1000) p.exp -= requiredExp // 恢复生命和魔法 - 使用ChangeHP/ChangeMP设置为最大值 attrs := p.Actor.GetAttributeManager().Final() maxHP := attrs.MaxHP maxMP := attrs.MaxMP currentHP := p.Actor.HP() currentMP := p.Actor.MP() p.Actor.ChangeHP(maxHP - currentHP) p.Actor.ChangeMP(maxMP - currentMP) // TODO: 触发升级事件 // p.PublishEvent(&PlayerLevelUpEvent{...}) } // ChangeExp 改变经验值 func (p *Player) ChangeExp(amount int32) { p.exp += amount // 处理升级逻辑 for p.CanLevelUp() { p.LevelUp() } // TODO: 同步属性变化 // p.syncAttributeEntry(AttributeTypeExp, p.exp) } // ChangeGold 改变金币 func (p *Player) ChangeGold(amount int64) { p.gold += amount if p.gold < 0 { p.gold = 0 } // TODO: 同步属性变化 // p.syncAttributeEntry(AttributeTypeGold, int32(p.gold)) } // ========== 子系统访问 ========== // GetInventory 获取背包 func (p *Player) GetInventory() *Inventory { return p.inventory } // GetTaskManager 获取任务管理器 func (p *Player) GetTaskManager() *TaskManager { return p.taskManager } // GetDialogueManager 获取对话管理器 func (p *Player) GetDialogueManager() *DialogueManager { return p.dialogueManager } // ========== NPC交互 ========== // SetInteractingNPC 设置当前交互的NPC func (p *Player) SetInteractingNPC(npc *NPC) { p.interactingNPC = npc } // GetInteractingNPC 获取当前交互的NPC func (p *Player) GetInteractingNPC() *NPC { return p.interactingNPC } // ========== 生命周期 ========== // Start 初始化玩家 func (p *Player) Start(ctx context.Context) error { // 调用Actor的Start if err := p.Actor.Start(ctx); err != nil { return err } // 初始化背包 if err := p.inventory.Start(ctx); err != nil { return fmt.Errorf("inventory start failed: %w", err) } // 初始化任务管理器 if err := p.taskManager.Start(ctx); err != nil { return fmt.Errorf("taskManager start failed: %w", err) } return nil } // Revive 玩家复活 func (p *Player) Revive(ctx context.Context) error { // 调用Actor的复活逻辑 if err := p.Actor.Revive(ctx); err != nil { return err } // 玩家特有的复活逻辑(比如扣除经验等) // TODO: 实现玩家复活逻辑 return nil } // String 字符串表示 func (p *Player) String() string { return fmt.Sprintf("Player:\"%s(%d)\"[User:%d,Char:%d]", p.Name(), p.ID(), p.userID, p.characterID) } // ========== 占位符子系统 ========== // Inventory 背包(占位符) type Inventory struct { owner *Player } func NewInventory(owner *Player) *Inventory { return &Inventory{owner: owner} } func (i *Inventory) Start(ctx context.Context) error { return nil } // TaskManager 任务管理器(占位符) type TaskManager struct { owner *Player } func NewTaskManager(owner *Player) *TaskManager { return &TaskManager{owner: owner} } func (tm *TaskManager) Start(ctx context.Context) error { return nil } // DialogueManager 对话管理器(占位符) type DialogueManager struct { owner *Player } func NewDialogueManager(owner *Player) *DialogueManager { return &DialogueManager{owner: owner} } ================================================ FILE: internal/domain/character/repository.go ================================================ package character import ( "context" ) // EntityRepository 实体仓储接口 - 由基础设施层实现 type EntityRepository interface { // 通用实体操作 Register(ctx context.Context, entity *Entity) error Unregister(ctx context.Context, entityID EntityID) error Get(ctx context.Context, entityID EntityID) (*Entity, error) GetAll(ctx context.Context) ([]*Entity, error) // 按类型查询 GetByType(ctx context.Context, entityType EntityType) ([]*Entity, error) } // PlayerRepository 玩家仓储接口 type PlayerRepository interface { // 保存玩家数据到数据库 Save(ctx context.Context, player *Player) error // 从数据库加载玩家 Load(ctx context.Context, characterID int64) (*Player, error) // 删除玩家 Delete(ctx context.Context, characterID int64) error // 查询用户的所有角色 FindByUserID(ctx context.Context, userID int64) ([]*Player, error) // 检查角色名是否存在 ExistsByName(ctx context.Context, name string) (bool, error) } // MonsterRepository 怪物仓储接口 type MonsterRepository interface { // 创建怪物实例 Create(ctx context.Context, monster *Monster) error // 销毁怪物实例 Destroy(ctx context.Context, entityID EntityID) error // 根据刷新点ID获取怪物列表 GetBySpawnID(ctx context.Context, spawnID int32) ([]*Monster, error) } // NPCRepository NPC仓储接口 type NPCRepository interface { // 创建NPC实例 Create(ctx context.Context, npc *NPC) error // 销毁NPC实例 Destroy(ctx context.Context, entityID EntityID) error // 根据地图ID获取NPC列表 GetByMapID(ctx context.Context, mapID int32) ([]*NPC, error) } // UnitDefineRepository 单位定义仓储接口(配置数据) type UnitDefineRepository interface { // 获取单位定义 Get(ctx context.Context, unitID int32) (*UnitDefine, error) // 加载所有单位定义 LoadAll(ctx context.Context) (map[int32]*UnitDefine, error) } // UnitDefine 单位定义(从配置文件加载) type UnitDefine struct { ID int32 // 单位ID Name string // 单位名称 Type string // 单位类型 // 基础属性 BaseHP float32 // 基础生命值 BaseMP float32 // 基础魔法值 BaseAD float32 // 基础物理攻击 BaseAP float32 // 基础法术攻击 // 其他属性 Speed float32 // 移动速度 ViewRange float32 // 视野范围 HurtTime float32 // 受击硬直时间 DropExpBase float32 // 基础经验掉落 DropExpLevelFactor float32 // 经验等级系数 // 技能列表 Skills []int32 // 技能ID列表 } ================================================ FILE: internal/domain/character/skill_damage_test.go ================================================ package character import ( "context" "testing" ) func TestSkillDamageReducesTargetHP(t *testing.T) { attacker := NewActor(10, EntityTypePlayer, 1, NewVector3(0, 0, 0), NewVector3(1, 0, 0), "att", 1) defender := NewActor(11, EntityTypePlayer, 1, NewVector3(1, 0, 0), NewVector3(-1, 0, 0), "def", 1) if err := attacker.Start(context.Background()); err != nil { t.Fatalf("attacker start: %v", err) } if err := defender.Start(context.Background()); err != nil { t.Fatalf("defender start: %v", err) } // Setup a simple physical skill sk := NewSkill(5001, attacker) sk.SetTimings(0.0, 0.01, 0.01) // instant, short active and cooldown sk.SetDamage(20, 1.0, 0.0, DamageTypePhysical) attacker.GetSkillManager().AddSkill(sk) // Record defender HP baseHP := defender.HP() if ok := attacker.GetSpell().Cast(sk.ID(), defender); !ok { t.Fatalf("failed to cast skill") } // Advance time to process Active window and apply damage _ = attacker.Update(context.Background(), 0.02) if defender.HP() >= baseHP { t.Fatalf("expected defender HP to reduce; before=%v after=%v", baseHP, defender.HP()) } } ================================================ FILE: internal/domain/character/subsystems.go ================================================ package character import ( "context" "sync" ) // AttributeManager 属性管理器 - 管理Actor的属性计算 type AttributeManager struct { owner *Actor mu sync.RWMutex base *Attributes // 基础属性 final *Attributes // 最终属性(经过装备、Buff等加成) } // Attributes 属性集合 type Attributes struct { MaxHP float32 // 最大生命值 MaxMP float32 // 最大魔法值 HPRegen float32 // 生命回复 MPRegen float32 // 魔法回复 AD float32 // 物理攻击力 AP float32 // 法术攻击力 Def float32 // 物理防御 MDef float32 // 法术防御 Cri float32 // 暴击率 Crd float32 // 暴击伤害 HitRate float32 // 命中率 DodgeRate float32 // 闪避率 Speed float32 // 移动速度 AttackSpeed float32 // 攻击速度 } // AttributeModifier 属性修饰器(用于 Buff/装备 等对属性的加成) // 约定:Add 为加法;Mul 为乘法(叠加时相加后一次性乘以 1+总和) type AttributeModifier struct { MaxHPAdd, MaxHPMul float32 MaxMPAdd, MaxMPMul float32 HPRegenAdd, MPRegenAdd float32 ADAdd, ADMul float32 APAdd, APMul float32 DefAdd, DefMul float32 MDefAdd, MDefMul float32 CriAdd, CrdAdd float32 HitRateAdd float32 DodgeRateAdd float32 SpeedAdd, SpeedMul float32 AttackSpeedAdd, AttackSpeedMul float32 } // NewAttributeManager 创建属性管理器 func NewAttributeManager(owner *Actor) *AttributeManager { return &AttributeManager{ owner: owner, base: &Attributes{}, final: &Attributes{}, } } // Start 初始化 func (am *AttributeManager) Start(ctx context.Context) error { // TODO: 从配置中加载基础属性(占位:按等级给出默认值,待 DataManager 接入后替换) am.mu.Lock() if am.base.MaxHP == 0 { lvl := float32(am.owner.Level()) am.base.MaxHP = 100 + 10*lvl am.base.MaxMP = 50 + 5*lvl am.base.HPRegen = 1 am.base.MPRegen = 0.5 am.base.AD = 10 + 2*lvl am.base.AP = 5 + 1*lvl am.base.Def = 2 + 1*lvl am.base.MDef = 1 + 0.5*lvl am.base.Cri = 0.05 am.base.Crd = 1.5 am.base.HitRate = 0.9 am.base.DodgeRate = 0.05 am.base.Speed = 5 am.base.AttackSpeed = 1 } am.mu.Unlock() am.Recalculate() return nil } // Base 获取基础属性 func (am *AttributeManager) Base() *Attributes { am.mu.RLock() defer am.mu.RUnlock() return am.base } // Final 获取最终属性 func (am *AttributeManager) Final() *Attributes { am.mu.RLock() defer am.mu.RUnlock() return am.final } // Recalculate 重新计算最终属性 func (am *AttributeManager) Recalculate() { am.mu.Lock() defer am.mu.Unlock() // 基础拷贝 *am.final = *am.base // TODO: 装备加成(占位) // 例如:am.final.AD += equip.ADAdd; am.final.AD *= (1 + equip.ADMul) // Buff 加成(叠加所有 Buff 的属性修饰器) if am.owner != nil && am.owner.buffManager != nil { mods := am.owner.buffManager.CollectModifiers() var m AttributeModifier // 汇总 for _, mod := range mods { m.MaxHPAdd += mod.MaxHPAdd m.MaxHPMul += mod.MaxHPMul m.MaxMPAdd += mod.MaxMPAdd m.MaxMPMul += mod.MaxMPMul m.HPRegenAdd += mod.HPRegenAdd m.MPRegenAdd += mod.MPRegenAdd m.ADAdd += mod.ADAdd m.ADMul += mod.ADMul m.APAdd += mod.APAdd m.APMul += mod.APMul m.DefAdd += mod.DefAdd m.DefMul += mod.DefMul m.MDefAdd += mod.MDefAdd m.MDefMul += mod.MDefMul m.CriAdd += mod.CriAdd m.CrdAdd += mod.CrdAdd m.HitRateAdd += mod.HitRateAdd m.DodgeRateAdd += mod.DodgeRateAdd m.SpeedAdd += mod.SpeedAdd m.SpeedMul += mod.SpeedMul m.AttackSpeedAdd += mod.AttackSpeedAdd m.AttackSpeedMul += mod.AttackSpeedMul } // 应用到最终属性 am.final.MaxHP = (am.final.MaxHP + m.MaxHPAdd) * (1 + m.MaxHPMul) am.final.MaxMP = (am.final.MaxMP + m.MaxMPAdd) * (1 + m.MaxMPMul) am.final.HPRegen = am.final.HPRegen + m.HPRegenAdd am.final.MPRegen = am.final.MPRegen + m.MPRegenAdd am.final.AD = (am.final.AD + m.ADAdd) * (1 + m.ADMul) am.final.AP = (am.final.AP + m.APAdd) * (1 + m.APMul) am.final.Def = (am.final.Def + m.DefAdd) * (1 + m.DefMul) am.final.MDef = (am.final.MDef + m.MDefAdd) * (1 + m.MDefMul) am.final.Cri += m.CriAdd am.final.Crd += m.CrdAdd am.final.HitRate += m.HitRateAdd am.final.DodgeRate += m.DodgeRateAdd am.final.Speed = (am.final.Speed + m.SpeedAdd) * (1 + m.SpeedMul) am.final.AttackSpeed = (am.final.AttackSpeed + m.AttackSpeedAdd) * (1 + m.AttackSpeedMul) } // 下游:可在此应用被动/天赋等 } // SetBase 设置基础属性(整体替换) func (am *AttributeManager) SetBase(attrs Attributes) { am.mu.Lock() am.base = &attrs am.mu.Unlock() am.Recalculate() } // ModifyBase 对基础属性进行增量修改(加法) func (am *AttributeManager) ModifyBase(mod func(a *Attributes)) { am.mu.Lock() mod(am.base) am.mu.Unlock() am.Recalculate() } // ========== SkillManager 技能管理器 ========== // SkillManager 技能管理器 type SkillManager struct { owner *Actor mu sync.RWMutex skills map[int32]*Skill // 技能ID -> 技能实例 } // NewSkillManager 创建技能管理器 func NewSkillManager(owner *Actor) *SkillManager { return &SkillManager{ owner: owner, skills: make(map[int32]*Skill), } } // Start 初始化 func (sm *SkillManager) Start(ctx context.Context) error { // TODO: 从配置中加载技能 return nil } // Update 每帧更新 func (sm *SkillManager) Update(ctx context.Context, deltaTime float32) error { sm.mu.RLock() defer sm.mu.RUnlock() // 更新所有技能 for _, skill := range sm.skills { if err := skill.Update(ctx, deltaTime); err != nil { return err } } return nil } // GetSkill 获取技能 func (sm *SkillManager) GetSkill(skillID int32) *Skill { sm.mu.RLock() defer sm.mu.RUnlock() return sm.skills[skillID] } // AddSkill 添加技能 func (sm *SkillManager) AddSkill(skill *Skill) { sm.mu.Lock() defer sm.mu.Unlock() sm.skills[skill.ID()] = skill } // ========== Skill 技能 ========== // Skill 技能实例 type Skill struct { id int32 owner *Actor mu sync.RWMutex // 技能状态 state SkillState cooldownTimer float32 // 冷却计时器 castTimer float32 // 施法计时器 // 配置(占位,后续由 DataManager 驱动) castTime float32 // 吟唱时间 activeTime float32 // 生效窗口时间(如持续伤害/命中帧窗口) cooldownTime float32 // 冷却时长 // 伤害配置(简化版) baseDamage float32 scaleAD float32 scaleAP float32 dmgType DamageType } // SkillState 技能状态 type SkillState int32 const ( SkillStateIdle SkillState = 0 // 空闲 SkillStateReady SkillState = 1 // 就绪 SkillStateIntonate SkillState = 2 // 吟唱中 SkillStateActive SkillState = 3 // 激活中 SkillStateCooling SkillState = 4 // 冷却中 ) // NewSkill 创建技能 func NewSkill(id int32, owner *Actor) *Skill { return &Skill{ id: id, owner: owner, state: SkillStateReady, // 默认占位:瞬发、短冷却 castTime: 0, activeTime: 0.1, cooldownTime: 1.0, } } // ID 获取技能ID func (s *Skill) ID() int32 { return s.id } // SetDamage 配置技能伤害参数 func (s *Skill) SetDamage(base, scaleAD, scaleAP float32, dmgType DamageType) { s.mu.Lock() s.baseDamage = base s.scaleAD = scaleAD s.scaleAP = scaleAP s.dmgType = dmgType s.mu.Unlock() } // Update 更新技能 func (s *Skill) Update(ctx context.Context, deltaTime float32) error { s.mu.Lock() defer s.mu.Unlock() switch s.state { case SkillStateIntonate: s.castTimer -= deltaTime if s.castTimer <= 0 { // 进入激活 s.state = SkillStateActive s.castTimer = s.activeTime // 命中/效果应用(命中目标、生成投射物等) if s.owner != nil && s.owner.spell != nil { s.owner.spell.ApplySkillEffect(s) } } case SkillStateActive: s.castTimer -= deltaTime if s.castTimer <= 0 { // 进入冷却 s.state = SkillStateCooling s.cooldownTimer = s.cooldownTime } case SkillStateCooling: s.cooldownTimer -= deltaTime if s.cooldownTimer <= 0 { s.state = SkillStateReady s.cooldownTimer = 0 } } return nil } // State 获取当前技能状态 func (s *Skill) State() SkillState { s.mu.RLock() defer s.mu.RUnlock() return s.state } // SetTimings 设置技能关键时序(吟唱/激活/冷却) func (s *Skill) SetTimings(cast, active, cooldown float32) { s.mu.Lock() s.castTime = cast s.activeTime = active s.cooldownTime = cooldown s.mu.Unlock() } // StartCast 尝试开始施法(由施法器或应用层触发) func (s *Skill) StartCast() bool { s.mu.Lock() defer s.mu.Unlock() if s.owner == nil || s.owner.IsDeath() { return false } // 眩晕/沉默等状态约束 flags := s.owner.GetFlagState() if flags.HasFlag(FlagStateStun) || flags.HasFlag(FlagStateSilence) { return false } // 冷却中不可释放 if s.state == SkillStateCooling || s.state == SkillStateActive || s.state == SkillStateIntonate { return false } if s.castTime > 0 { s.state = SkillStateIntonate s.castTimer = s.castTime } else { s.state = SkillStateActive s.castTimer = s.activeTime } return true } // ========== BuffManager Buff管理器 ========== // BuffManager Buff管理器 type BuffManager struct { owner *Actor mu sync.RWMutex buffs []*Buff // Buff列表 } // NewBuffManager 创建Buff管理器 func NewBuffManager(owner *Actor) *BuffManager { return &BuffManager{ owner: owner, buffs: make([]*Buff, 0), } } // Start 初始化 func (bm *BuffManager) Start(ctx context.Context) error { return nil } // Update 每帧更新 func (bm *BuffManager) Update(ctx context.Context, deltaTime float32) error { bm.mu.Lock() defer bm.mu.Unlock() // 更新所有Buff toRemove := make([]int, 0) for i, buff := range bm.buffs { if err := buff.Update(ctx, deltaTime); err != nil { return err } if buff.IsExpired() { toRemove = append(toRemove, i) } } // 移除过期的Buff for i := len(toRemove) - 1; i >= 0; i-- { idx := toRemove[i] bm.buffs = append(bm.buffs[:idx], bm.buffs[idx+1:]...) } if len(toRemove) > 0 { // Buff 变化后触发属性重算 if bm.owner != nil && bm.owner.attributeManager != nil { bm.owner.attributeManager.Recalculate() } // 刷新基于 Buff 的状态标志 bm.refreshActorFlags() } return nil } // AddBuff 添加Buff func (bm *BuffManager) AddBuff(buff *Buff) { bm.mu.Lock() defer bm.mu.Unlock() bm.buffs = append(bm.buffs, buff) // Buff 变化后触发属性重算 if bm.owner != nil && bm.owner.attributeManager != nil { bm.owner.attributeManager.Recalculate() } // 刷新状态标志 bm.refreshActorFlags() } // RemoveBuff 移除Buff func (bm *BuffManager) RemoveBuff(buff *Buff) { bm.mu.Lock() defer bm.mu.Unlock() for i, b := range bm.buffs { if b == buff { bm.buffs = append(bm.buffs[:i], bm.buffs[i+1:]...) break } } // Buff 变化后触发属性重算 if bm.owner != nil && bm.owner.attributeManager != nil { bm.owner.attributeManager.Recalculate() } // 刷新状态标志 bm.refreshActorFlags() } // GetBuffByID 按ID获取Buff func (bm *BuffManager) GetBuffByID(id int32) *Buff { bm.mu.RLock() defer bm.mu.RUnlock() for _, b := range bm.buffs { if b != nil && b.id == id { return b } } return nil } // RemoveBuffByID 按ID移除Buff func (bm *BuffManager) RemoveBuffByID(id int32) { bm.mu.Lock() defer bm.mu.Unlock() for i := 0; i < len(bm.buffs); i++ { if bm.buffs[i] != nil && bm.buffs[i].id == id { bm.buffs = append(bm.buffs[:i], bm.buffs[i+1:]...) i-- } } if bm.owner != nil && bm.owner.attributeManager != nil { bm.owner.attributeManager.Recalculate() } bm.refreshActorFlags() } // CollectModifiers 汇总当前 Buff 的属性修饰器快照 func (bm *BuffManager) CollectModifiers() []AttributeModifier { bm.mu.RLock() defer bm.mu.RUnlock() mods := make([]AttributeModifier, 0, len(bm.buffs)) for _, b := range bm.buffs { mods = append(mods, b.Modifier()) } return mods } // 汇总 Buff 的状态标志位(位或) func (bm *BuffManager) collectFlags() FlagState { var flags FlagState = FlagStateZero for _, b := range bm.buffs { flags = flags.AddFlag(b.FlagAdd()) } return flags } // 刷新 Actor 的状态标志,基于当前 Buff 汇总 func (bm *BuffManager) refreshActorFlags() { if bm.owner == nil { return } flags := bm.collectFlags() bm.owner.SetFlagStateExact(flags) } // ========== Buff ========== // Buff Buff实例 type Buff struct { id int32 owner *Actor caster *Actor duration float32 elapsed float32 modifier AttributeModifier // 状态效果:为简化,使用位或累加的 FlagState addFlags FlagState } // NewBuff 创建Buff func NewBuff(id int32, owner, caster *Actor, duration float32) *Buff { return &Buff{ id: id, owner: owner, caster: caster, duration: duration, elapsed: 0, } } // Update 更新Buff func (b *Buff) Update(ctx context.Context, deltaTime float32) error { b.elapsed += deltaTime return nil } // IsExpired 是否过期 func (b *Buff) IsExpired() bool { return b.elapsed >= b.duration } // SetModifier 设置属性修饰器 func (b *Buff) SetModifier(mod AttributeModifier) { b.modifier = mod } // Modifier 获取属性修饰器 func (b *Buff) Modifier() AttributeModifier { return b.modifier } // SetFlagAdd 设置该 Buff 施加的状态标志 func (b *Buff) SetFlagAdd(flags FlagState) { b.addFlags = flags } // FlagAdd 获取该 Buff 施加的状态标志 func (b *Buff) FlagAdd() FlagState { return b.addFlags } // ========== Spell 施法器 ========== // Spell 施法器 - 管理当前正在施放的技能 type Spell struct { owner *Actor mu sync.RWMutex currentSkill *Skill // 当前正在施放的技能 target *Actor // 当前施法目标(简化:单体) } // NewSpell 创建施法器 func NewSpell(owner *Actor) *Spell { return &Spell{ owner: owner, } } // CurrentSkill 获取当前技能 func (s *Spell) CurrentSkill() *Skill { s.mu.RLock() defer s.mu.RUnlock() return s.currentSkill } // SetCurrentSkill 设置当前技能 func (s *Spell) SetCurrentSkill(skill *Skill) { s.mu.Lock() defer s.mu.Unlock() s.currentSkill = skill } // Cast 根据技能ID对目标施放技能 func (s *Spell) Cast(skillID int32, target *Actor) bool { if s.owner == nil { return false } sm := s.owner.GetSkillManager() if sm == nil { return false } sk := sm.GetSkill(skillID) if sk == nil { return false } if !sk.StartCast() { return false } s.mu.Lock() s.currentSkill = sk s.target = target s.mu.Unlock() return true } // ApplySkillEffect 在技能进入 Active 时调用,应用技能效果到目标 func (s *Spell) ApplySkillEffect(skill *Skill) { s.mu.RLock() target := s.target s.mu.RUnlock() if target == nil || target.IsDeath() || s.owner == nil { return } // 计算伤害并应用 dmg := computeDamage(s.owner, target, skill) if dmg <= 0 { return } info := &DamageInfo{ TargetID: target.ID(), AttackerInfo: AttackerInfo{AttackerID: s.owner.ID(), AttackerType: AttackerTypeSkill, SkillID: skill.ID()}, Amount: int32(dmg), DamageType: skill.dmgType, IsCrit: false, IsMiss: false, } _ = target.OnHurt(context.Background(), info) } // computeDamage 伤害计算(简化且确定性) func computeDamage(attacker *Actor, defender *Actor, skill *Skill) float32 { if attacker == nil || defender == nil || skill == nil { return 0 } af := attacker.GetAttributeManager().Final() df := defender.GetAttributeManager().Final() // 攻击力构成 atk := skill.baseDamage + af.AD*skill.scaleAD + af.AP*skill.scaleAP if atk <= 0 { return 0 } // 防御减伤: dmg * (1 - def/(def+100)) var def float32 if skill.dmgType == DamageTypeMagical { def = df.MDef } else { def = df.Def } reduction := float32(1.0) if def > 0 { reduction = 1 - def/(def+100) } dmg := atk * reduction if dmg < 0 { dmg = 0 } return dmg } // SetTarget 设置当前施法目标 func (s *Spell) SetTarget(target *Actor) { s.mu.Lock() s.target = target s.mu.Unlock() } // Target 获取当前施法目标 func (s *Spell) Target() *Actor { s.mu.RLock() defer s.mu.RUnlock() return s.target } ================================================ FILE: internal/domain/character/subsystems_test.go ================================================ package character import ( "context" "testing" ) func TestSkillFSMTransitions(t *testing.T) { actor := NewActor(1, EntityTypePlayer, 1, NewVector3(0, 0, 0), NewVector3(1, 0, 0), "tester", 1) if err := actor.Start(context.Background()); err != nil { t.Fatalf("actor start failed: %v", err) } skill := NewSkill(1001, actor) skill.SetTimings(0.05, 0.05, 0.05) if st := skill.State(); st != SkillStateReady { t.Fatalf("expected Ready at start, got %v", st) } if ok := skill.StartCast(); !ok { t.Fatalf("StartCast should succeed in Ready state") } // Intonate window _ = skill.Update(context.Background(), 0.03) if st := skill.State(); st != SkillStateIntonate && st != SkillStateActive { t.Fatalf("expected Intonate or Active, got %v", st) } // Enter Active _ = skill.Update(context.Background(), 0.03) if st := skill.State(); st != SkillStateActive { t.Fatalf("expected Active, got %v", st) } // Enter Cooling _ = skill.Update(context.Background(), 0.06) if st := skill.State(); st != SkillStateCooling { t.Fatalf("expected Cooling, got %v", st) } // Back to Ready _ = skill.Update(context.Background(), 0.06) if st := skill.State(); st != SkillStateReady { t.Fatalf("expected Ready, got %v", st) } } func TestAttributesInitAndRegen(t *testing.T) { actor := NewActor(2, EntityTypePlayer, 1, NewVector3(0, 0, 0), NewVector3(1, 0, 0), "tester2", 5) if err := actor.Start(context.Background()); err != nil { t.Fatalf("actor start failed: %v", err) } fin := actor.GetAttributeManager().Final() if fin.MaxHP <= 0 || fin.Speed <= 0 { t.Fatalf("final attributes should be initialized with defaults, got MaxHP=%v Speed=%v", fin.MaxHP, fin.Speed) } if actor.HP() != fin.MaxHP { t.Fatalf("actor HP should init to MaxHP, got %v vs %v", actor.HP(), fin.MaxHP) } // Reduce HP and test regen actor.ChangeHP(-10) hpAfterHit := actor.HP() if hpAfterHit >= fin.MaxHP { t.Fatalf("HP should be reduced after damage") } _ = actor.Update(context.Background(), 2.0) // 2 seconds regen if actor.HP() <= hpAfterHit { t.Fatalf("HP should regenerate over time; before=%v after=%v", hpAfterHit, actor.HP()) } if actor.HP() > fin.MaxHP { t.Fatalf("HP should not exceed MaxHP; got %v > %v", actor.HP(), fin.MaxHP) } } ================================================ FILE: internal/domain/character/value_objects.go ================================================ package character import ( "math" ) // Vector3 三维向量(位置、方向) type Vector3 struct { X float32 Y float32 Z float32 } // NewVector3 创建新的三维向量 func NewVector3(x, y, z float32) Vector3 { return Vector3{X: x, Y: y, Z: z} } // Zero 返回零向量 func (v Vector3) Zero() Vector3 { return Vector3{X: 0, Y: 0, Z: 0} } // ToVector2 转换为二维向量(忽略Y轴) func (v Vector3) ToVector2() Vector2 { return Vector2{X: v.X, Y: v.Z} } // Distance 计算两个三维向量之间的距离 func (v Vector3) Distance(other Vector3) float32 { dx := v.X - other.X dy := v.Y - other.Y dz := v.Z - other.Z return float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz))) } // Vector2 二维向量(平面位置) type Vector2 struct { X float32 Y float32 } // NewVector2 创建新的二维向量 func NewVector2(x, y float32) Vector2 { return Vector2{X: x, Y: y} } // ToVector3 转换为三维向量(Y=0) func (v Vector2) ToVector3() Vector3 { return Vector3{X: v.X, Y: 0, Z: v.Y} } // Distance 计算两个二维向量之间的距离 func (v Vector2) Distance(other Vector2) float32 { dx := v.X - other.X dy := v.Y - other.Y return float32(math.Sqrt(float64(dx*dx + dy*dy))) } // Normalized 返回归一化向量 func (v Vector2) Normalized() Vector2 { length := float32(math.Sqrt(float64(v.X*v.X + v.Y*v.Y))) if length == 0 { return Vector2{X: 0, Y: 0} } return Vector2{X: v.X / length, Y: v.Y / length} } // Add 向量加法 func (v Vector2) Add(other Vector2) Vector2 { return Vector2{X: v.X + other.X, Y: v.Y + other.Y} } // Sub 向量减法 func (v Vector2) Sub(other Vector2) Vector2 { return Vector2{X: v.X - other.X, Y: v.Y - other.Y} } // Mul 向量数乘 func (v Vector2) Mul(scalar float32) Vector2 { return Vector2{X: v.X * scalar, Y: v.Y * scalar} } // EntityID 实体ID值对象 type EntityID int32 // IsValid 检查实体ID是否有效 func (id EntityID) IsValid() bool { return id > 0 } // Int32 转换为int32 func (id EntityID) Int32() int32 { return int32(id) } // EntityType 实体类型 type EntityType int32 const ( EntityTypePlayer EntityType = 0 // 玩家 EntityTypeMonster EntityType = 1 // 怪物 EntityTypeNPC EntityType = 2 // NPC EntityTypeMissile EntityType = 3 // 投射物 EntityTypeDroppedItem EntityType = 4 // 掉落物 EntityTypePet EntityType = 5 // 宠物 EntityTypeSummon EntityType = 6 // 召唤物 ) // String 返回实体类型的字符串表示 func (t EntityType) String() string { switch t { case EntityTypePlayer: return "Player" case EntityTypeMonster: return "Monster" case EntityTypeNPC: return "NPC" case EntityTypeMissile: return "Missile" case EntityTypeDroppedItem: return "DroppedItem" case EntityTypePet: return "Pet" case EntityTypeSummon: return "Summon" default: return "Unknown" } } // AnimationState 动画状态 type AnimationState int32 const ( AnimationStateIdle AnimationState = 0 // 空闲 AnimationStateMove AnimationState = 1 // 移动 AnimationStateSkill AnimationState = 2 // 释放技能 AnimationStateHurt AnimationState = 3 // 受伤 AnimationStateDeath AnimationState = 4 // 死亡 AnimationStateJump AnimationState = 5 // 跳跃 AnimationStateFall AnimationState = 6 // 下落 ) // FlagState 状态标志位(可组合) type FlagState int32 const ( FlagStateZero FlagState = 0 // 无状态 FlagStateStun FlagState = 1 // 眩晕 FlagStateRoot FlagState = 2 // 定身 FlagStateSilence FlagState = 4 // 沉默 FlagStateInvincible FlagState = 8 // 无敌 FlagStateInvisible FlagState = 16 // 隐身 FlagStateDisarm FlagState = 32 // 缴械 FlagStateSlow FlagState = 64 // 减速 ) // HasFlag 检查是否包含某个状态标志 func (f FlagState) HasFlag(flag FlagState) bool { return (f & flag) != 0 } // AddFlag 添加状态标志 func (f FlagState) AddFlag(flag FlagState) FlagState { return f | flag } // RemoveFlag 移除状态标志 func (f FlagState) RemoveFlag(flag FlagState) FlagState { return f & ^flag } // Transform 位置和方向 type Transform struct { Position Vector3 // 位置 Direction Vector3 // 方向 } // NewTransform 创建新的Transform func NewTransform(pos, dir Vector3) Transform { return Transform{ Position: pos, Direction: dir, } } ================================================ FILE: internal/domain/dialogue/dialogue_manager.go ================================================ package dialogue import ( "fmt" "sync" ) // DialogueType 对话类型 type DialogueType int32 const ( DialogueTypeNormal DialogueType = 0 // 普通对话 DialogueTypeQuest DialogueType = 1 // 任务对话 DialogueTypeShop DialogueType = 2 // 商店对话 ) // DialogueAction 对话动作 type DialogueAction int32 const ( DialogueActionNone DialogueAction = 0 // 无动作 DialogueActionAcceptQuest DialogueAction = 1 // 接受任务 DialogueActionSubmitQuest DialogueAction = 2 // 提交任务 DialogueActionOpenShop DialogueAction = 3 // 打开商店 ) // DialogueNode 对话节点 type DialogueNode struct { ID int32 // 节点ID Text string // 对话文本 Options []*DialogueOption // 选项列表 } // DialogueOption 对话选项 type DialogueOption struct { Text string // 选项文本 NextNode int32 // 下一个节点ID Action DialogueAction // 触发的动作 ActionID int32 // 动作ID(如任务ID) } // DialogueManager 对话管理器 type DialogueManager struct { mu sync.RWMutex ownerID int64 // 所属玩家ID currentNPCID int32 // 当前交互的NPC ID currentNode *DialogueNode // 当前对话节点 } // NewDialogueManager 创建对话管理器 func NewDialogueManager(ownerID int64) *DialogueManager { return &DialogueManager{ ownerID: ownerID, } } // StartDialogue 开始对话 func (dm *DialogueManager) StartDialogue(npcID int32, dialogueID int32) error { dm.mu.Lock() defer dm.mu.Unlock() // TODO: 从DataManager加载对话数据 dm.currentNPCID = npcID dm.currentNode = &DialogueNode{ ID: dialogueID, Text: "你好,旅行者。", Options: []*DialogueOption{ {Text: "有什么可以帮助你的吗?", NextNode: 2, Action: DialogueActionNone}, {Text: "再见。", NextNode: -1, Action: DialogueActionNone}, }, } return nil } // SelectOption 选择对话选项 func (dm *DialogueManager) SelectOption(optionIndex int32) (*DialogueNode, DialogueAction, int32, error) { dm.mu.Lock() defer dm.mu.Unlock() if dm.currentNode == nil { return nil, DialogueActionNone, 0, fmt.Errorf("no active dialogue") } if optionIndex < 0 || optionIndex >= int32(len(dm.currentNode.Options)) { return nil, DialogueActionNone, 0, fmt.Errorf("invalid option index") } option := dm.currentNode.Options[optionIndex] // 处理对话结束 if option.NextNode == -1 { dm.currentNode = nil dm.currentNPCID = 0 return nil, option.Action, option.ActionID, nil } // TODO: 加载下一个节点 nextNode := &DialogueNode{ ID: option.NextNode, Text: "下一步对话...", Options: []*DialogueOption{}, } dm.currentNode = nextNode return nextNode, option.Action, option.ActionID, nil } // EndDialogue 结束对话 func (dm *DialogueManager) EndDialogue() { dm.mu.Lock() defer dm.mu.Unlock() dm.currentNode = nil dm.currentNPCID = 0 } // GetCurrentNode 获取当前对话节点 func (dm *DialogueManager) GetCurrentNode() *DialogueNode { dm.mu.RLock() defer dm.mu.RUnlock() return dm.currentNode } // GetCurrentNPCID 获取当前NPC ID func (dm *DialogueManager) GetCurrentNPCID() int32 { dm.mu.RLock() defer dm.mu.RUnlock() return dm.currentNPCID } // ShopItem 商店物品 type ShopItem struct { ItemDefineID int32 // 物品定义ID Price int32 // 价格 Stock int32 // 库存(-1表示无限) } // Shop 商店 type Shop struct { ID int32 // 商店ID Name string // 商店名称 Items []*ShopItem // 商品列表 } // ShopManager 商店管理器 type ShopManager struct { mu sync.RWMutex shops map[int32]*Shop // 商店ID -> 商店 } // NewShopManager 创建商店管理器 func NewShopManager() *ShopManager { return &ShopManager{ shops: make(map[int32]*Shop), } } // GetShop 获取商店 func (sm *ShopManager) GetShop(shopID int32) *Shop { sm.mu.RLock() defer sm.mu.RUnlock() return sm.shops[shopID] } // Buy 购买物品 func (sm *ShopManager) Buy(shopID int32, itemDefineID int32, count int32) error { sm.mu.Lock() defer sm.mu.Unlock() shop, exists := sm.shops[shopID] if !exists { return fmt.Errorf("shop not found: %d", shopID) } for _, item := range shop.Items { if item.ItemDefineID == itemDefineID { if item.Stock != -1 && item.Stock < count { return fmt.Errorf("insufficient stock") } // TODO: 扣除玩家金币 // TODO: 添加物品到背包 if item.Stock != -1 { item.Stock -= count } return nil } } return fmt.Errorf("item not found in shop") } // Sell 出售物品 func (sm *ShopManager) Sell(itemDefineID int32, count int32) error { // TODO: 从背包移除物品 // TODO: 增加玩家金币 return nil } ================================================ FILE: internal/domain/events/domain_event.go ================================================ // Package events 领域事件定义 package events import ( "encoding/json" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventType() string GetAggregateID() string GetVersion() int64 GetOccurredAt() time.Time GetData() interface{} ToJSON() ([]byte, error) } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` Version int64 `json:"version"` OccurredAt time.Time `json:"occurred_at"` Data interface{} `json:"data"` } // NewBaseDomainEvent 创建基础领域事件 func NewBaseDomainEvent(eventType, aggregateID string, version int64, data interface{}) *BaseDomainEvent { return &BaseDomainEvent{ EventType: eventType, AggregateID: aggregateID, Version: version, OccurredAt: time.Now(), Data: data, } } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetVersion 获取版本号 func (e *BaseDomainEvent) GetVersion() int64 { return e.Version } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetData 获取事件数据 func (e *BaseDomainEvent) GetData() interface{} { return e.Data } // ToJSON 转换为JSON func (e *BaseDomainEvent) ToJSON() ([]byte, error) { return json.Marshal(e) } // 玩家相关事件 const ( EventTypePlayerCreated = "player.created" EventTypePlayerLoggedIn = "player.logged_in" EventTypePlayerLoggedOut = "player.logged_out" EventTypePlayerMoved = "player.moved" EventTypePlayerLeveledUp = "player.leveled_up" EventTypePlayerDied = "player.died" EventTypePlayerHealed = "player.healed" EventTypePlayerGainedExp = "player.gained_exp" ) // 战斗相关事件 const ( EventTypeBattleCreated = "battle.created" EventTypeBattleStarted = "battle.started" EventTypeBattleFinished = "battle.finished" EventTypeBattleCancelled = "battle.cancelled" EventTypePlayerJoinedBattle = "battle.player_joined" EventTypePlayerLeftBattle = "battle.player_left" EventTypeBattleActionExecuted = "battle.action_executed" ) // PlayerCreatedEvent 玩家创建事件 type PlayerCreatedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` Level int `json:"level"` } // NewPlayerCreatedEvent 创建玩家创建事件 func NewPlayerCreatedEvent(playerID, playerName string, level int) *PlayerCreatedEvent { baseEvent := NewBaseDomainEvent(EventTypePlayerCreated, playerID, 1, nil) return &PlayerCreatedEvent{ BaseDomainEvent: baseEvent, PlayerID: playerID, PlayerName: playerName, Level: level, } } // PlayerLoggedInEvent 玩家登录事件 type PlayerLoggedInEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` } // NewPlayerLoggedInEvent 创建玩家登录事件 func NewPlayerLoggedInEvent(playerID string) *PlayerLoggedInEvent { baseEvent := NewBaseDomainEvent(EventTypePlayerLoggedIn, playerID, 1, nil) return &PlayerLoggedInEvent{ BaseDomainEvent: baseEvent, PlayerID: playerID, } } // PlayerLoggedOutEvent 玩家登出事件 type PlayerLoggedOutEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` } // NewPlayerLoggedOutEvent 创建玩家登出事件 func NewPlayerLoggedOutEvent(playerID string) *PlayerLoggedOutEvent { baseEvent := NewBaseDomainEvent(EventTypePlayerLoggedOut, playerID, 1, nil) return &PlayerLoggedOutEvent{ BaseDomainEvent: baseEvent, PlayerID: playerID, } } // PlayerMovedEvent 玩家移动事件 type PlayerMovedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // NewPlayerMovedEvent 创建玩家移动事件 func NewPlayerMovedEvent(playerID string, x, y, z float64) *PlayerMovedEvent { baseEvent := NewBaseDomainEvent(EventTypePlayerMoved, playerID, 1, nil) return &PlayerMovedEvent{ BaseDomainEvent: baseEvent, PlayerID: playerID, X: x, Y: y, Z: z, } } // PlayerLeveledUpEvent 玩家升级事件 type PlayerLeveledUpEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` OldLevel int `json:"old_level"` NewLevel int `json:"new_level"` } // NewPlayerLeveledUpEvent 创建玩家升级事件 func NewPlayerLeveledUpEvent(playerID string, oldLevel, newLevel int) *PlayerLeveledUpEvent { baseEvent := NewBaseDomainEvent(EventTypePlayerLeveledUp, playerID, 1, nil) return &PlayerLeveledUpEvent{ BaseDomainEvent: baseEvent, PlayerID: playerID, OldLevel: oldLevel, NewLevel: newLevel, } } // BattleCreatedEvent 战斗创建事件 type BattleCreatedEvent struct { *BaseDomainEvent BattleID string `json:"battle_id"` BattleType string `json:"battle_type"` CreatorID string `json:"creator_id"` } // NewBattleCreatedEvent 创建战斗创建事件 func NewBattleCreatedEvent(battleID, battleType, creatorID string) *BattleCreatedEvent { baseEvent := NewBaseDomainEvent(EventTypeBattleCreated, battleID, 1, nil) return &BattleCreatedEvent{ BaseDomainEvent: baseEvent, BattleID: battleID, BattleType: battleType, CreatorID: creatorID, } } // BattleStartedEvent 战斗开始事件 type BattleStartedEvent struct { *BaseDomainEvent BattleID string `json:"battle_id"` } // NewBattleStartedEvent 创建战斗开始事件 func NewBattleStartedEvent(battleID string) *BattleStartedEvent { baseEvent := NewBaseDomainEvent(EventTypeBattleStarted, battleID, 1, nil) return &BattleStartedEvent{ BaseDomainEvent: baseEvent, BattleID: battleID, } } // BattleFinishedEvent 战斗结束事件 type BattleFinishedEvent struct { *BaseDomainEvent BattleID string `json:"battle_id"` WinnerID *string `json:"winner_id,omitempty"` Duration int64 `json:"duration"` // 战斗持续时间(秒) } // NewBattleFinishedEvent 创建战斗结束事件 func NewBattleFinishedEvent(battleID string, winnerID *string, duration int64) *BattleFinishedEvent { baseEvent := NewBaseDomainEvent(EventTypeBattleFinished, battleID, 1, nil) return &BattleFinishedEvent{ BaseDomainEvent: baseEvent, BattleID: battleID, WinnerID: winnerID, Duration: duration, } } ================================================ FILE: internal/domain/inventory/dressup/aggregate.go ================================================ package dressup import ( "time" // "github.com/google/uuid" ) // DressupAggregate 换装聚合根 type DressupAggregate struct { playerID string outfits map[string]*Outfit currentSet *OutfitSet savedSets map[string]*OutfitSet // 保存的套装方案 fashionSets map[string]*FashionSet // 时装套装配置 dyeColors map[string]*DyeColor // 已解锁的染色 styles map[string]*DressupStyle // 风格配置 currentStyle string // 当前风格ID preferences *DressupPreferences // 换装偏好 statistics *DressupStatistics // 换装统计 updatedAt time.Time version int } // DressupPreferences 换装偏好 type DressupPreferences struct { autoEquipBetter bool // 自动装备更好的装备 preferredRarities []Rarity // 偏好的稀有度 preferredTypes []OutfitType // 偏好的类型 hideHelmet bool // 隐藏头盔 showCape bool // 显示斗篷 defaultStyle string // 默认风格 } // DressupStatistics 换装统计 type DressupStatistics struct { totalOutfits int // 总服装数 equippedOutfits int // 已装备数 totalPower int // 总战力 changeCount int // 换装次数 lastChangeTime time.Time // 最后换装时间 rarityDistribution map[Rarity]int // 稀有度分布 typeDistribution map[OutfitType]int // 类型分布 qualityDistribution map[OutfitQuality]int // 品质分布 favoriteSet string // 最常用套装 mostUsedOutfit string // 最常用单品 } // NewDressupAggregate 创建换装聚合根 func NewDressupAggregate(playerID string) *DressupAggregate { return &DressupAggregate{ playerID: playerID, outfits: make(map[string]*Outfit), currentSet: NewOutfitSet(), savedSets: make(map[string]*OutfitSet), fashionSets: make(map[string]*FashionSet), dyeColors: make(map[string]*DyeColor), styles: make(map[string]*DressupStyle), currentStyle: "", preferences: NewDressupPreferences(), statistics: NewDressupStatistics(), updatedAt: time.Now(), version: 1, } } // NewDressupPreferences 创建换装偏好 func NewDressupPreferences() *DressupPreferences { return &DressupPreferences{ autoEquipBetter: false, preferredRarities: make([]Rarity, 0), preferredTypes: make([]OutfitType, 0), hideHelmet: false, showCape: true, defaultStyle: "", } } // NewDressupStatistics 创建换装统计 func NewDressupStatistics() *DressupStatistics { return &DressupStatistics{ totalOutfits: 0, equippedOutfits: 0, totalPower: 0, changeCount: 0, lastChangeTime: time.Time{}, rarityDistribution: make(map[Rarity]int), typeDistribution: make(map[OutfitType]int), qualityDistribution: make(map[OutfitQuality]int), favoriteSet: "", mostUsedOutfit: "", } } // GetPlayerID 获取玩家ID func (d *DressupAggregate) GetPlayerID() string { return d.playerID } // AddOutfit 添加服装 func (d *DressupAggregate) AddOutfit(outfit *Outfit) error { if outfit == nil { return ErrInvalidOutfit } d.outfits[outfit.GetID()] = outfit d.updateVersion() return nil } // EquipOutfit 装备服装 func (d *DressupAggregate) EquipOutfit(outfitID string, slot OutfitSlot) error { outfit, exists := d.outfits[outfitID] if !exists { return ErrOutfitNotFound } if !outfit.CanEquipToSlot(slot) { return ErrInvalidSlot } d.currentSet.EquipToSlot(slot, outfit) d.updateVersion() return nil } // UnequipOutfit 卸下服装 func (d *DressupAggregate) UnequipOutfit(slot OutfitSlot) error { d.currentSet.UnequipFromSlot(slot) d.updateVersion() return nil } // GetCurrentSet 获取当前装备套装 func (d *DressupAggregate) GetCurrentSet() *OutfitSet { return d.currentSet } // GetOutfits 获取所有服装 func (d *DressupAggregate) GetOutfits() map[string]*Outfit { return d.outfits } // updateVersion 更新版本 func (d *DressupAggregate) updateVersion() { d.version++ d.updatedAt = time.Now() } // GetVersion 获取版本 func (d *DressupAggregate) GetVersion() int { return d.version } // GetUpdatedAt 获取更新时间 func (d *DressupAggregate) GetUpdatedAt() time.Time { return d.updatedAt } // SaveOutfitSet 保存套装方案 func (d *DressupAggregate) SaveOutfitSet(name string) error { if name == "" { return ErrInvalidSetName } // 克隆当前套装 savedSet := d.currentSet.Clone() d.savedSets[name] = savedSet d.updateVersion() return nil } // LoadOutfitSet 加载套装方案 func (d *DressupAggregate) LoadOutfitSet(name string) error { savedSet, exists := d.savedSets[name] if !exists { return ErrSetNotFound } // 先卸下当前装备 for slot := range d.currentSet.GetAllEquipped() { d.UnequipOutfit(slot) } // 装备保存的套装 for slot, outfit := range savedSet.GetAllEquipped() { if outfit != nil { d.EquipOutfit(outfit.GetID(), slot) } } d.statistics.changeCount++ d.statistics.lastChangeTime = time.Now() d.updateVersion() return nil } // DeleteOutfitSet 删除套装方案 func (d *DressupAggregate) DeleteOutfitSet(name string) error { if _, exists := d.savedSets[name]; !exists { return ErrSetNotFound } delete(d.savedSets, name) d.updateVersion() return nil } // GetSavedSets 获取保存的套装方案 func (d *DressupAggregate) GetSavedSets() map[string]*OutfitSet { return d.savedSets } // AddFashionSet 添加时装套装配置 func (d *DressupAggregate) AddFashionSet(fashionSet *FashionSet) error { if fashionSet == nil { return ErrInvalidFashionSet } d.fashionSets[fashionSet.GetSetID()] = fashionSet d.updateVersion() return nil } // GetFashionSets 获取时装套装配置 func (d *DressupAggregate) GetFashionSets() map[string]*FashionSet { return d.fashionSets } // GetFashionSet 获取指定时装套装 func (d *DressupAggregate) GetFashionSet(setID string) *FashionSet { return d.fashionSets[setID] } // UnlockDyeColor 解锁染色 func (d *DressupAggregate) UnlockDyeColor(color *DyeColor) error { if color == nil { return ErrInvalidDyeColor } color.Unlock() d.dyeColors[color.GetColorID()] = color d.updateVersion() return nil } // GetDyeColors 获取已解锁的染色 func (d *DressupAggregate) GetDyeColors() map[string]*DyeColor { return d.dyeColors } // GetUnlockedDyeColors 获取已解锁的染色列表 func (d *DressupAggregate) GetUnlockedDyeColors() []*DyeColor { colors := make([]*DyeColor, 0) for _, color := range d.dyeColors { if color.IsUnlocked() { colors = append(colors, color) } } return colors } // DyeOutfit 给服装染色 func (d *DressupAggregate) DyeOutfit(outfitID, part, colorID string) error { outfit, exists := d.outfits[outfitID] if !exists { return ErrOutfitNotFound } color, exists := d.dyeColors[colorID] if !exists || !color.IsUnlocked() { return ErrDyeColorNotUnlocked } outfit.SetDyeColor(part, color) d.updateVersion() return nil } // AddStyle 添加风格 func (d *DressupAggregate) AddStyle(style *DressupStyle) error { if style == nil { return ErrInvalidStyle } d.styles[style.GetStyleID()] = style d.updateVersion() return nil } // SetCurrentStyle 设置当前风格 func (d *DressupAggregate) SetCurrentStyle(styleID string) error { style, exists := d.styles[styleID] if !exists { return ErrStyleNotFound } d.currentStyle = styleID d.currentSet.SetStyleBonus(style) d.updateVersion() return nil } // GetCurrentStyle 获取当前风格 func (d *DressupAggregate) GetCurrentStyle() string { return d.currentStyle } // GetStyles 获取所有风格 func (d *DressupAggregate) GetStyles() map[string]*DressupStyle { return d.styles } // GetPreferences 获取换装偏好 func (d *DressupAggregate) GetPreferences() *DressupPreferences { return d.preferences } // UpdatePreferences 更新换装偏好 func (d *DressupAggregate) UpdatePreferences(preferences *DressupPreferences) { d.preferences = preferences d.updateVersion() } // GetStatistics 获取换装统计 func (d *DressupAggregate) GetStatistics() *DressupStatistics { return d.statistics } // UpdateStatistics 更新统计信息 func (d *DressupAggregate) UpdateStatistics() { d.statistics.totalOutfits = len(d.outfits) d.statistics.equippedOutfits = d.currentSet.GetEquippedCount() d.statistics.totalPower = d.currentSet.GetTotalPower() // 重置分布统计 d.statistics.rarityDistribution = make(map[Rarity]int) d.statistics.typeDistribution = make(map[OutfitType]int) d.statistics.qualityDistribution = make(map[OutfitQuality]int) // 统计分布 for _, outfit := range d.outfits { d.statistics.rarityDistribution[outfit.GetRarity()]++ d.statistics.typeDistribution[outfit.GetType()]++ d.statistics.qualityDistribution[outfit.GetQuality()]++ } // 找出最常用的装备 maxUseCount := 0 for _, outfit := range d.outfits { if outfit.GetUseCount() > maxUseCount { maxUseCount = outfit.GetUseCount() d.statistics.mostUsedOutfit = outfit.GetID() } } } // FilterOutfits 筛选服装 func (d *DressupAggregate) FilterOutfits(filter *OutfitFilter) []*Outfit { result := make([]*Outfit, 0) for _, outfit := range d.outfits { if d.matchesFilter(outfit, filter) { result = append(result, outfit) } } return result } // matchesFilter 检查服装是否匹配筛选条件 func (d *DressupAggregate) matchesFilter(outfit *Outfit, filter *OutfitFilter) bool { if filter == nil { return true } // 类型筛选 if filter.GetOutfitType() != nil && outfit.GetType() != *filter.GetOutfitType() { return false } // 稀有度筛选 if filter.GetRarity() != nil && outfit.GetRarity() != *filter.GetRarity() { return false } // 品质筛选 if filter.GetQuality() != nil && outfit.GetQuality() != *filter.GetQuality() { return false } // 来源筛选 if filter.GetSource() != nil && outfit.GetSource() != *filter.GetSource() { return false } // 槽位筛选 if filter.GetSlot() != nil { canEquip := outfit.CanEquipToSlot(*filter.GetSlot()) if !canEquip { return false } } // 锁定状态筛选 if filter.GetLocked() != nil && outfit.IsLocked() != *filter.GetLocked() { return false } // 等级范围筛选 if filter.GetMinLevel() != nil && outfit.GetLevel() < *filter.GetMinLevel() { return false } if filter.GetMaxLevel() != nil && outfit.GetLevel() > *filter.GetMaxLevel() { return false } // 搜索文本筛选 if filter.GetSearchText() != "" { searchText := filter.GetSearchText() if !d.containsIgnoreCase(outfit.GetName(), searchText) && !d.containsIgnoreCase(outfit.GetDescription(), searchText) { return false } } // 标签筛选 if len(filter.GetTags()) > 0 { outfitTags := outfit.GetTags() for _, filterTag := range filter.GetTags() { found := false for _, outfitTag := range outfitTags { if outfitTag == filterTag { found = true break } } if !found { return false } } } return true } // containsIgnoreCase 忽略大小写包含检查 func (d *DressupAggregate) containsIgnoreCase(str, substr string) bool { // 简单实现,实际应该使用更好的字符串匹配算法 return len(str) >= len(substr) && str != "" && substr != "" } // GetRecommendedOutfits 获取推荐服装 func (d *DressupAggregate) GetRecommendedOutfits(slot OutfitSlot, limit int) []*Outfit { recommended := make([]*Outfit, 0) for _, outfit := range d.outfits { if outfit.CanEquipToSlot(slot) && !outfit.IsEquipped() && !outfit.IsLocked() { recommended = append(recommended, outfit) } } // 按战力排序(简单实现) for i := 0; i < len(recommended)-1; i++ { for j := i + 1; j < len(recommended); j++ { if recommended[i].GetPower() < recommended[j].GetPower() { recommended[i], recommended[j] = recommended[j], recommended[i] } } } // 限制数量 if limit > 0 && len(recommended) > limit { recommended = recommended[:limit] } return recommended } // AutoEquipBest 自动装备最佳装备 func (d *DressupAggregate) AutoEquipBest() error { if !d.preferences.autoEquipBetter { return ErrAutoEquipDisabled } allSlots := []OutfitSlot{ SlotWeapon, SlotArmor, SlotHelmet, SlotShoes, SlotRing, SlotNecklace, SlotFashionWeapon, SlotFashionArmor, SlotFashionHelmet, SlotPet, SlotMount, } for _, slot := range allSlots { recommended := d.GetRecommendedOutfits(slot, 1) if len(recommended) > 0 { best := recommended[0] current := d.currentSet.GetEquippedOutfit(slot) // 如果推荐的比当前的好,则装备 if current == nil || best.GetPower() > current.GetPower() { d.EquipOutfit(best.GetID(), slot) } } } return nil } // GetTotalPower 获取总战力 func (d *DressupAggregate) GetTotalPower() int { return d.currentSet.GetTotalPower() } // GetTotalAttributes 获取总属性 func (d *DressupAggregate) GetTotalAttributes() map[string]int { return d.currentSet.GetTotalAttributes() } // CanUpgradeAnyOutfit 是否有可升级的服装 func (d *DressupAggregate) CanUpgradeAnyOutfit() bool { for _, outfit := range d.outfits { if outfit.CanUpgrade() { return true } } return false } // CanEnhanceAnyOutfit 是否有可强化的服装 func (d *DressupAggregate) CanEnhanceAnyOutfit() bool { for _, outfit := range d.outfits { if outfit.CanEnhance() { return true } } return false } // GetOutfitsBySet 根据套装ID获取服装 func (d *DressupAggregate) GetOutfitsBySet(setID string) []*Outfit { outfits := make([]*Outfit, 0) for _, outfit := range d.outfits { if outfit.GetSetID() == setID { outfits = append(outfits, outfit) } } return outfits } // GetSetCompletionRate 获取套装完成度 func (d *DressupAggregate) GetSetCompletionRate(setID string) float64 { fashionSet := d.fashionSets[setID] if fashionSet == nil { return 0.0 } owned := len(d.GetOutfitsBySet(setID)) total := len(fashionSet.GetPieces()) if total == 0 { return 0.0 } return float64(owned) / float64(total) * 100.0 } ================================================ FILE: internal/domain/inventory/dressup/entity.go ================================================ package dressup import ( "time" "github.com/google/uuid" ) // Outfit 服装实体 type Outfit struct { id string name string description string outfitType OutfitType rarity Rarity quality OutfitQuality source OutfitSource attributes map[string]int slots []OutfitSlot isLocked bool isEquipped bool level int exp int maxExp int tags []string setID string // 所属套装ID appearance *AppearanceConfig dyeColors map[string]*DyeColor // 部位 -> 染色 enhanceLevel int enhanceBonuses map[string]int obtainedAt time.Time lastUsedAt *time.Time useCount int metadata map[string]interface{} } // NewOutfit 创建服装实体 func NewOutfit(name string, outfitType OutfitType, rarity Rarity) *Outfit { return &Outfit{ id: uuid.New().String(), name: name, description: "", outfitType: outfitType, rarity: rarity, quality: QualityNormal, source: SourceShop, attributes: make(map[string]int), slots: []OutfitSlot{}, isLocked: false, isEquipped: false, level: 1, exp: 0, maxExp: 100, tags: make([]string, 0), setID: "", appearance: NewAppearanceConfig(), dyeColors: make(map[string]*DyeColor), enhanceLevel: 0, enhanceBonuses: make(map[string]int), obtainedAt: time.Now(), lastUsedAt: nil, useCount: 0, metadata: make(map[string]interface{}), } } // GetID 获取服装ID func (o *Outfit) GetID() string { return o.id } // GetName 获取服装名称 func (o *Outfit) GetName() string { return o.name } // GetType 获取服装类型 func (o *Outfit) GetType() OutfitType { return o.outfitType } // GetRarity 获取稀有度 func (o *Outfit) GetRarity() Rarity { return o.rarity } // AddAttribute 添加属性 func (o *Outfit) AddAttribute(attr string, value int) { o.attributes[attr] = value } // GetAttributes 获取所有属性 func (o *Outfit) GetAttributes() map[string]int { return o.attributes } // AddSlot 添加可装备槽位 func (o *Outfit) AddSlot(slot OutfitSlot) { o.slots = append(o.slots, slot) } // CanEquipToSlot 检查是否可以装备到指定槽位 func (o *Outfit) CanEquipToSlot(slot OutfitSlot) bool { for _, s := range o.slots { if s == slot { return true } } return false } // Lock 锁定服装 func (o *Outfit) Lock() { o.isLocked = true } // Unlock 解锁服装 func (o *Outfit) Unlock() { o.isLocked = false } // IsLocked 检查是否锁定 func (o *Outfit) IsLocked() bool { return o.isLocked } // GetObtainedAt 获取获得时间 func (o *Outfit) GetObtainedAt() time.Time { return o.obtainedAt } // GetDescription 获取描述 func (o *Outfit) GetDescription() string { return o.description } // SetDescription 设置描述 func (o *Outfit) SetDescription(description string) { o.description = description } // GetQuality 获取品质 func (o *Outfit) GetQuality() OutfitQuality { return o.quality } // SetQuality 设置品质 func (o *Outfit) SetQuality(quality OutfitQuality) { o.quality = quality } // GetSource 获取来源 func (o *Outfit) GetSource() OutfitSource { return o.source } // SetSource 设置来源 func (o *Outfit) SetSource(source OutfitSource) { o.source = source } // IsEquipped 是否已装备 func (o *Outfit) IsEquipped() bool { return o.isEquipped } // SetEquipped 设置装备状态 func (o *Outfit) SetEquipped(equipped bool) { o.isEquipped = equipped if equipped { now := time.Now() o.lastUsedAt = &now o.useCount++ } } // GetLevel 获取等级 func (o *Outfit) GetLevel() int { return o.level } // GetExp 获取经验值 func (o *Outfit) GetExp() int { return o.exp } // GetMaxExp 获取最大经验值 func (o *Outfit) GetMaxExp() int { return o.maxExp } // AddExp 增加经验值 func (o *Outfit) AddExp(exp int) bool { o.exp += exp leveledUp := false // 检查是否升级 for o.exp >= o.maxExp && o.level < 100 { o.exp -= o.maxExp o.level++ o.maxExp = o.calculateMaxExp(o.level) leveledUp = true } return leveledUp } // calculateMaxExp 计算最大经验值 func (o *Outfit) calculateMaxExp(level int) int { return 100 + (level-1)*50 // 基础100,每级增加50 } // GetTags 获取标签 func (o *Outfit) GetTags() []string { return o.tags } // AddTag 添加标签 func (o *Outfit) AddTag(tag string) { // 检查是否已存在 for _, existingTag := range o.tags { if existingTag == tag { return } } o.tags = append(o.tags, tag) } // RemoveTag 移除标签 func (o *Outfit) RemoveTag(tag string) { for i, existingTag := range o.tags { if existingTag == tag { o.tags = append(o.tags[:i], o.tags[i+1:]...) return } } } // GetSetID 获取套装ID func (o *Outfit) GetSetID() string { return o.setID } // SetSetID 设置套装ID func (o *Outfit) SetSetID(setID string) { o.setID = setID } // GetAppearance 获取外观配置 func (o *Outfit) GetAppearance() *AppearanceConfig { return o.appearance } // SetAppearance 设置外观配置 func (o *Outfit) SetAppearance(appearance *AppearanceConfig) { o.appearance = appearance } // GetDyeColors 获取染色配置 func (o *Outfit) GetDyeColors() map[string]*DyeColor { return o.dyeColors } // SetDyeColor 设置部位染色 func (o *Outfit) SetDyeColor(part string, color *DyeColor) { o.dyeColors[part] = color } // RemoveDyeColor 移除部位染色 func (o *Outfit) RemoveDyeColor(part string) { delete(o.dyeColors, part) } // GetEnhanceLevel 获取强化等级 func (o *Outfit) GetEnhanceLevel() int { return o.enhanceLevel } // Enhance 强化服装 func (o *Outfit) Enhance() bool { if o.enhanceLevel >= 20 { // 最大强化等级 return false } o.enhanceLevel++ // 计算强化加成 for attr, baseValue := range o.attributes { enhanceBonus := int(float64(baseValue) * 0.1 * float64(o.enhanceLevel)) o.enhanceBonuses[attr] = enhanceBonus } return true } // GetEnhanceBonuses 获取强化加成 func (o *Outfit) GetEnhanceBonuses() map[string]int { return o.enhanceBonuses } // GetTotalAttributes 获取总属性(基础+强化) func (o *Outfit) GetTotalAttributes() map[string]int { total := make(map[string]int) // 基础属性 for attr, value := range o.attributes { total[attr] = value } // 强化加成 for attr, bonus := range o.enhanceBonuses { total[attr] += bonus } // 品质加成 qualityMultiplier := o.quality.GetQualityMultiplier() for attr, value := range total { total[attr] = int(float64(value) * qualityMultiplier) } // 稀有度加成 rarityMultiplier := o.rarity.GetRarityMultiplier() for attr, value := range total { total[attr] = int(float64(value) * rarityMultiplier) } return total } // GetLastUsedAt 获取最后使用时间 func (o *Outfit) GetLastUsedAt() *time.Time { return o.lastUsedAt } // GetUseCount 获取使用次数 func (o *Outfit) GetUseCount() int { return o.useCount } // GetMetadata 获取元数据 func (o *Outfit) GetMetadata() map[string]interface{} { return o.metadata } // SetMetadata 设置元数据 func (o *Outfit) SetMetadata(key string, value interface{}) { o.metadata[key] = value } // GetMetadataValue 获取元数据值 func (o *Outfit) GetMetadataValue(key string) (interface{}, bool) { value, exists := o.metadata[key] return value, exists } // CanUpgrade 是否可以升级 func (o *Outfit) CanUpgrade() bool { return o.level < 100 && o.exp >= o.maxExp } // CanEnhance 是否可以强化 func (o *Outfit) CanEnhance() bool { return o.enhanceLevel < 20 } // GetPower 获取战力值 func (o *Outfit) GetPower() int { power := 0 totalAttrs := o.GetTotalAttributes() for _, value := range totalAttrs { power += value } // 等级加成 power += o.level * 10 // 强化加成 power += o.enhanceLevel * 20 return power } // Clone 克隆服装 func (o *Outfit) Clone() *Outfit { cloned := &Outfit{ id: uuid.New().String(), // 新ID name: o.name, description: o.description, outfitType: o.outfitType, rarity: o.rarity, quality: o.quality, source: o.source, attributes: make(map[string]int), slots: make([]OutfitSlot, len(o.slots)), isLocked: o.isLocked, isEquipped: false, // 克隆的不装备 level: o.level, exp: o.exp, maxExp: o.maxExp, tags: make([]string, len(o.tags)), setID: o.setID, appearance: NewAppearanceConfig(), dyeColors: make(map[string]*DyeColor), enhanceLevel: o.enhanceLevel, enhanceBonuses: make(map[string]int), obtainedAt: time.Now(), lastUsedAt: nil, useCount: 0, metadata: make(map[string]interface{}), } // 复制属性 for attr, value := range o.attributes { cloned.attributes[attr] = value } // 复制槽位 copy(cloned.slots, o.slots) // 复制标签 copy(cloned.tags, o.tags) // 复制外观配置 if o.appearance != nil { cloned.appearance = NewAppearanceConfig() for part, color := range o.appearance.GetColorScheme() { cloned.appearance.SetColor(part, color) } for _, effect := range o.appearance.GetEffects() { cloned.appearance.AddEffect(effect) } for _, animation := range o.appearance.GetAnimations() { cloned.appearance.AddAnimation(animation) } for part, texture := range o.appearance.GetTextures() { cloned.appearance.SetTexture(part, texture) } cloned.appearance.SetScale(o.appearance.GetScale()) cloned.appearance.SetTransparency(o.appearance.GetTransparency()) cloned.appearance.SetGlowIntensity(o.appearance.GetGlowIntensity()) } // 复制染色 for part, color := range o.dyeColors { cloned.dyeColors[part] = &DyeColor{ colorID: color.colorID, colorName: color.colorName, hexValue: color.hexValue, rarity: color.rarity, isUnlocked: color.isUnlocked, } } // 复制强化加成 for attr, bonus := range o.enhanceBonuses { cloned.enhanceBonuses[attr] = bonus } // 复制元数据 for key, value := range o.metadata { cloned.metadata[key] = value } return cloned } // OutfitSet 套装实体 type OutfitSet struct { equippedOutfits map[OutfitSlot]*Outfit setBonuses map[string]int fashionSets map[string]*FashionSetBonus // 套装ID -> 套装加成信息 styleBonus *DressupStyle totalPower int lastUpdated time.Time } // FashionSetBonus 时装套装加成信息 type FashionSetBonus struct { setID string setName string equippedCount int totalCount int activeBonus map[string]int } // NewOutfitSet 创建套装 func NewOutfitSet() *OutfitSet { return &OutfitSet{ equippedOutfits: make(map[OutfitSlot]*Outfit), setBonuses: make(map[string]int), fashionSets: make(map[string]*FashionSetBonus), styleBonus: nil, totalPower: 0, lastUpdated: time.Now(), } } // EquipToSlot 装备到槽位 func (os *OutfitSet) EquipToSlot(slot OutfitSlot, outfit *Outfit) { os.equippedOutfits[slot] = outfit os.calculateSetBonuses() } // UnequipFromSlot 从槽位卸下 func (os *OutfitSet) UnequipFromSlot(slot OutfitSlot) { delete(os.equippedOutfits, slot) os.calculateSetBonuses() } // GetEquippedOutfit 获取指定槽位的装备 func (os *OutfitSet) GetEquippedOutfit(slot OutfitSlot) *Outfit { return os.equippedOutfits[slot] } // GetAllEquipped 获取所有装备 func (os *OutfitSet) GetAllEquipped() map[OutfitSlot]*Outfit { return os.equippedOutfits } // GetSetBonuses 获取套装加成 func (os *OutfitSet) GetSetBonuses() map[string]int { return os.setBonuses } // calculateSetBonuses 计算套装加成 func (os *OutfitSet) calculateSetBonuses() { // 清空现有加成 os.setBonuses = make(map[string]int) os.fashionSets = make(map[string]*FashionSetBonus) os.totalPower = 0 // 统计各类型服装数量 typeCount := make(map[OutfitType]int) setCount := make(map[string]int) // 套装ID -> 装备数量 setNames := make(map[string]string) // 套装ID -> 套装名称 for _, outfit := range os.equippedOutfits { if outfit != nil { typeCount[outfit.GetType()]++ // 统计套装 if outfit.GetSetID() != "" { setCount[outfit.GetSetID()]++ // 这里应该从套装配置中获取名称,暂时使用ID setNames[outfit.GetSetID()] = outfit.GetSetID() } // 累计战力 os.totalPower += outfit.GetPower() } } // 根据类型数量计算基础加成 for _, count := range typeCount { if count >= 2 { os.setBonuses["attack"] += count * 10 } if count >= 4 { os.setBonuses["defense"] += count * 15 } if count >= 6 { os.setBonuses["hp"] += count * 20 } } // 计算时装套装加成 for setID, equippedCount := range setCount { setBonus := &FashionSetBonus{ setID: setID, setName: setNames[setID], equippedCount: equippedCount, totalCount: 6, // 假设每套装有6件,实际应该从配置获取 activeBonus: make(map[string]int), } // 根据装备数量计算套装加成 if equippedCount >= 2 { setBonus.activeBonus["attack"] = equippedCount * 15 os.setBonuses["attack"] += setBonus.activeBonus["attack"] } if equippedCount >= 4 { setBonus.activeBonus["defense"] = equippedCount * 20 os.setBonuses["defense"] += setBonus.activeBonus["defense"] } if equippedCount >= 6 { setBonus.activeBonus["hp"] = equippedCount * 30 setBonus.activeBonus["crit_rate"] = 10 // 暴击率+10% os.setBonuses["hp"] += setBonus.activeBonus["hp"] os.setBonuses["crit_rate"] += setBonus.activeBonus["crit_rate"] } os.fashionSets[setID] = setBonus } // 应用风格加成 if os.styleBonus != nil { for attr, bonus := range os.styleBonus.GetBonuses() { os.setBonuses[attr] += bonus } } os.lastUpdated = time.Now() } // GetFashionSets 获取时装套装信息 func (os *OutfitSet) GetFashionSets() map[string]*FashionSetBonus { return os.fashionSets } // GetFashionSetBonus 获取指定套装的加成信息 func (os *OutfitSet) GetFashionSetBonus(setID string) *FashionSetBonus { return os.fashionSets[setID] } // SetStyleBonus 设置风格加成 func (os *OutfitSet) SetStyleBonus(style *DressupStyle) { os.styleBonus = style os.calculateSetBonuses() // 重新计算加成 } // GetStyleBonus 获取风格加成 func (os *OutfitSet) GetStyleBonus() *DressupStyle { return os.styleBonus } // GetTotalPower 获取总战力 func (os *OutfitSet) GetTotalPower() int { return os.totalPower } // GetLastUpdated 获取最后更新时间 func (os *OutfitSet) GetLastUpdated() time.Time { return os.lastUpdated } // GetEquippedCount 获取已装备数量 func (os *OutfitSet) GetEquippedCount() int { count := 0 for _, outfit := range os.equippedOutfits { if outfit != nil { count++ } } return count } // GetEmptySlots 获取空槽位 func (os *OutfitSet) GetEmptySlots() []OutfitSlot { allSlots := []OutfitSlot{ SlotWeapon, SlotArmor, SlotHelmet, SlotShoes, SlotRing, SlotNecklace, SlotFashionWeapon, SlotFashionArmor, SlotFashionHelmet, SlotPet, SlotMount, } emptySlots := make([]OutfitSlot, 0) for _, slot := range allSlots { if os.equippedOutfits[slot] == nil { emptySlots = append(emptySlots, slot) } } return emptySlots } // GetOutfitsByType 根据类型获取装备 func (os *OutfitSet) GetOutfitsByType(outfitType OutfitType) []*Outfit { outfits := make([]*Outfit, 0) for _, outfit := range os.equippedOutfits { if outfit != nil && outfit.GetType() == outfitType { outfits = append(outfits, outfit) } } return outfits } // GetOutfitsByRarity 根据稀有度获取装备 func (os *OutfitSet) GetOutfitsByRarity(rarity Rarity) []*Outfit { outfits := make([]*Outfit, 0) for _, outfit := range os.equippedOutfits { if outfit != nil && outfit.GetRarity() == rarity { outfits = append(outfits, outfit) } } return outfits } // GetTotalAttributes 获取套装总属性 func (os *OutfitSet) GetTotalAttributes() map[string]int { total := make(map[string]int) // 累计装备属性 for _, outfit := range os.equippedOutfits { if outfit != nil { for attr, value := range outfit.GetTotalAttributes() { total[attr] += value } } } // 加上套装加成 for attr, bonus := range os.setBonuses { total[attr] += bonus } return total } // CanEquipOutfit 检查是否可以装备服装 func (os *OutfitSet) CanEquipOutfit(outfit *Outfit, slot OutfitSlot) bool { if outfit == nil { return false } // 检查服装是否支持该槽位 if !outfit.CanEquipToSlot(slot) { return false } // 检查是否已装备 if outfit.IsEquipped() { return false } // 检查是否锁定 if outfit.IsLocked() { return false } return true } // GetSetCompletionRate 获取套装完成度 func (os *OutfitSet) GetSetCompletionRate(setID string) float64 { setBonus := os.fashionSets[setID] if setBonus == nil { return 0.0 } return float64(setBonus.equippedCount) / float64(setBonus.totalCount) * 100.0 } // GetHighestQualityOutfit 获取品质最高的装备 func (os *OutfitSet) GetHighestQualityOutfit() *Outfit { var highest *Outfit highestQuality := QualityNormal for _, outfit := range os.equippedOutfits { if outfit != nil && outfit.GetQuality() > highestQuality { highest = outfit highestQuality = outfit.GetQuality() } } return highest } // GetAverageLevel 获取平均等级 func (os *OutfitSet) GetAverageLevel() float64 { totalLevel := 0 count := 0 for _, outfit := range os.equippedOutfits { if outfit != nil { totalLevel += outfit.GetLevel() count++ } } if count == 0 { return 0.0 } return float64(totalLevel) / float64(count) } // GetAverageEnhanceLevel 获取平均强化等级 func (os *OutfitSet) GetAverageEnhanceLevel() float64 { totalEnhance := 0 count := 0 for _, outfit := range os.equippedOutfits { if outfit != nil { totalEnhance += outfit.GetEnhanceLevel() count++ } } if count == 0 { return 0.0 } return float64(totalEnhance) / float64(count) } // Clone 克隆套装 func (os *OutfitSet) Clone() *OutfitSet { cloned := NewOutfitSet() // 复制装备(注意:这里复制引用,不是深拷贝装备本身) for slot, outfit := range os.equippedOutfits { cloned.equippedOutfits[slot] = outfit } // 复制套装加成 for attr, bonus := range os.setBonuses { cloned.setBonuses[attr] = bonus } // 复制时装套装信息 for setID, setBonus := range os.fashionSets { cloned.fashionSets[setID] = &FashionSetBonus{ setID: setBonus.setID, setName: setBonus.setName, equippedCount: setBonus.equippedCount, totalCount: setBonus.totalCount, activeBonus: make(map[string]int), } // 复制激活加成 for attr, bonus := range setBonus.activeBonus { cloned.fashionSets[setID].activeBonus[attr] = bonus } } // 复制风格加成 cloned.styleBonus = os.styleBonus cloned.totalPower = os.totalPower cloned.lastUpdated = os.lastUpdated return cloned } ================================================ FILE: internal/domain/inventory/dressup/errors.go ================================================ package dressup import "errors" // 换装系统相关错误 var ( ErrInvalidOutfit = errors.New("invalid outfit") ErrOutfitNotFound = errors.New("outfit not found") ErrInvalidSlot = errors.New("invalid slot") ErrInvalidSetName = errors.New("invalid set name") ErrSetNotFound = errors.New("set not found") ErrInvalidFashionSet = errors.New("invalid fashion set") ErrInvalidDyeColor = errors.New("invalid dye color") ErrDyeColorNotUnlocked = errors.New("dye color not unlocked") ErrInvalidStyle = errors.New("invalid style") ErrStyleNotFound = errors.New("style not found") ErrAutoEquipDisabled = errors.New("auto equip disabled") ErrOutfitAlreadyEquipped = errors.New("outfit already equipped") ErrOutfitLocked = errors.New("outfit is locked") ErrInsufficientLevel = errors.New("insufficient level") ErrInsufficientExp = errors.New("insufficient experience") ErrMaxEnhanceLevel = errors.New("max enhance level reached") ErrInvalidFilter = errors.New("invalid filter") ErrNoOutfitsFound = errors.New("no outfits found") ) ================================================ FILE: internal/domain/inventory/dressup/events.go ================================================ package dressup import ( "github.com/google/uuid" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetEventData() interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` OccurredAt time.Time `json:"occurred_at"` } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // OutfitEquippedEvent 服装装备事件 type OutfitEquippedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` OutfitID string `json:"outfit_id"` Slot OutfitSlot `json:"slot"` Outfit *Outfit `json:"outfit"` } // NewOutfitEquippedEvent 创建服装装备事件 func NewOutfitEquippedEvent(playerID, outfitID string, slot OutfitSlot, outfit *Outfit) *OutfitEquippedEvent { return &OutfitEquippedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "OutfitEquipped", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, OutfitID: outfitID, Slot: slot, Outfit: outfit, } } // GetEventData 获取事件数据 func (e *OutfitEquippedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "outfit_id": e.OutfitID, "slot": e.Slot, "outfit": e.Outfit, } } // OutfitUnequippedEvent 服装卸下事件 type OutfitUnequippedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` OutfitID string `json:"outfit_id"` Slot OutfitSlot `json:"slot"` } // NewOutfitUnequippedEvent 创建服装卸下事件 func NewOutfitUnequippedEvent(playerID, outfitID string, slot OutfitSlot) *OutfitUnequippedEvent { return &OutfitUnequippedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "OutfitUnequipped", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, OutfitID: outfitID, Slot: slot, } } // GetEventData 获取事件数据 func (e *OutfitUnequippedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "outfit_id": e.OutfitID, "slot": e.Slot, } } // OutfitObtainedEvent 服装获得事件 type OutfitObtainedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` Outfit *Outfit `json:"outfit"` Source string `json:"source"` // 获得来源:shop, quest, drop, etc. } // NewOutfitObtainedEvent 创建服装获得事件 func NewOutfitObtainedEvent(playerID string, outfit *Outfit, source string) *OutfitObtainedEvent { return &OutfitObtainedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "OutfitObtained", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, Outfit: outfit, Source: source, } } // GetEventData 获取事件数据 func (e *OutfitObtainedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "outfit": e.Outfit, "source": e.Source, } } // OutfitUpgradedEvent 服装升级事件 type OutfitUpgradedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` OutfitID string `json:"outfit_id"` OldLevel int `json:"old_level"` NewLevel int `json:"new_level"` OldAttrs map[string]int `json:"old_attrs"` NewAttrs map[string]int `json:"new_attrs"` } // NewOutfitUpgradedEvent 创建服装升级事件 func NewOutfitUpgradedEvent(playerID, outfitID string, oldLevel, newLevel int, oldAttrs, newAttrs map[string]int) *OutfitUpgradedEvent { return &OutfitUpgradedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "OutfitUpgraded", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, OutfitID: outfitID, OldLevel: oldLevel, NewLevel: newLevel, OldAttrs: oldAttrs, NewAttrs: newAttrs, } } // GetEventData 获取事件数据 func (e *OutfitUpgradedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "outfit_id": e.OutfitID, "old_level": e.OldLevel, "new_level": e.NewLevel, "old_attrs": e.OldAttrs, "new_attrs": e.NewAttrs, } } // StyleAppliedEvent 风格应用事件 type StyleAppliedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` StyleID string `json:"style_id"` Style *DressupStyle `json:"style"` } // NewStyleAppliedEvent 创建风格应用事件 func NewStyleAppliedEvent(playerID, styleID string, style *DressupStyle) *StyleAppliedEvent { return &StyleAppliedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "StyleApplied", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, StyleID: styleID, Style: style, } } // GetEventData 获取事件数据 func (e *StyleAppliedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "style_id": e.StyleID, "style": e.Style, } } // SetBonusActivatedEvent 套装加成激活事件 type SetBonusActivatedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` SetType string `json:"set_type"` PieceCount int `json:"piece_count"` Bonuses map[string]int `json:"bonuses"` } // NewSetBonusActivatedEvent 创建套装加成激活事件 func NewSetBonusActivatedEvent(playerID, setType string, pieceCount int, bonuses map[string]int) *SetBonusActivatedEvent { return &SetBonusActivatedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "SetBonusActivated", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, SetType: setType, PieceCount: pieceCount, Bonuses: bonuses, } } // GetEventData 获取事件数据 func (e *SetBonusActivatedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "set_type": e.SetType, "piece_count": e.PieceCount, "bonuses": e.Bonuses, } } ================================================ FILE: internal/domain/inventory/dressup/repository.go ================================================ package dressup import "context" // DressupRepository 换装仓储接口 type DressupRepository interface { // SaveDressupAggregate 保存换装聚合根 SaveDressupAggregate(ctx context.Context, aggregate *DressupAggregate) error // GetDressupAggregate 获取换装聚合根 GetDressupAggregate(ctx context.Context, playerID string) (*DressupAggregate, error) // DeleteDressupAggregate 删除换装聚合根 DeleteDressupAggregate(ctx context.Context, playerID string) error // SaveOutfit 保存服装 SaveOutfit(ctx context.Context, playerID string, outfit *Outfit) error // GetOutfit 获取服装 GetOutfit(ctx context.Context, playerID, outfitID string) (*Outfit, error) // GetPlayerOutfits 获取玩家所有服装 GetPlayerOutfits(ctx context.Context, playerID string) ([]*Outfit, error) // DeleteOutfit 删除服装 DeleteOutfit(ctx context.Context, playerID, outfitID string) error // SaveOutfitSet 保存套装配置 SaveOutfitSet(ctx context.Context, playerID string, outfitSet *OutfitSet) error // GetOutfitSet 获取套装配置 GetOutfitSet(ctx context.Context, playerID string) (*OutfitSet, error) // GetOutfitsByType 根据类型获取服装 GetOutfitsByType(ctx context.Context, playerID string, outfitType OutfitType) ([]*Outfit, error) // GetOutfitsByRarity 根据稀有度获取服装 GetOutfitsByRarity(ctx context.Context, playerID string, rarity Rarity) ([]*Outfit, error) // UpdateOutfitLockStatus 更新服装锁定状态 UpdateOutfitLockStatus(ctx context.Context, playerID, outfitID string, locked bool) error // GetOutfitCount 获取服装数量 GetOutfitCount(ctx context.Context, playerID string) (int, error) // GetOutfitsBySlot 根据槽位获取可装备的服装 GetOutfitsBySlot(ctx context.Context, playerID string, slot OutfitSlot) ([]*Outfit, error) } // OutfitTemplateRepository 服装模板仓储接口 type OutfitTemplateRepository interface { // GetOutfitTemplate 获取服装模板 GetOutfitTemplate(ctx context.Context, templateID string) (*OutfitTemplate, error) // GetOutfitTemplatesByType 根据类型获取服装模板 GetOutfitTemplatesByType(ctx context.Context, outfitType OutfitType) ([]*OutfitTemplate, error) // GetOutfitTemplatesByRarity 根据稀有度获取服装模板 GetOutfitTemplatesByRarity(ctx context.Context, rarity Rarity) ([]*OutfitTemplate, error) // SaveOutfitTemplate 保存服装模板 SaveOutfitTemplate(ctx context.Context, template *OutfitTemplate) error // DeleteOutfitTemplate 删除服装模板 DeleteOutfitTemplate(ctx context.Context, templateID string) error } // OutfitTemplate 服装模板 type OutfitTemplate struct { ID string `json:"id"` Name string `json:"name"` Type OutfitType `json:"type"` Rarity Rarity `json:"rarity"` BaseAttrs map[string]int `json:"base_attrs"` Slots []OutfitSlot `json:"slots"` RequireLevel int `json:"require_level"` Description string `json:"description"` IconURL string `json:"icon_url"` ModelURL string `json:"model_url"` } // CreateOutfitFromTemplate 从模板创建服装 func (ot *OutfitTemplate) CreateOutfitFromTemplate() *Outfit { outfit := NewOutfit(ot.Name, ot.Type, ot.Rarity) // 复制基础属性 for attr, value := range ot.BaseAttrs { outfit.AddAttribute(attr, value) } // 添加槽位 for _, slot := range ot.Slots { outfit.AddSlot(slot) } return outfit } ================================================ FILE: internal/domain/inventory/dressup/service.go ================================================ package dressup import ( "math/rand" "time" ) // DressupService 换装领域服务 type DressupService struct { outfitFactory *OutfitFactory styleManager *StyleManager } // NewDressupService 创建换装服务 func NewDressupService() *DressupService { return &DressupService{ outfitFactory: NewOutfitFactory(), styleManager: NewStyleManager(), } } // CreateRandomOutfit 创建随机服装 func (ds *DressupService) CreateRandomOutfit(outfitType OutfitType, playerLevel int) *Outfit { return ds.outfitFactory.CreateRandomOutfit(outfitType, playerLevel) } // CalculateTotalAttributes 计算总属性 func (ds *DressupService) CalculateTotalAttributes(aggregate *DressupAggregate) map[string]int { totalAttrs := make(map[string]int) // 计算装备属性 for _, outfit := range aggregate.GetCurrentSet().GetAllEquipped() { if outfit != nil { for attr, value := range outfit.GetAttributes() { totalAttrs[attr] += value } } } // 添加套装加成 for attr, bonus := range aggregate.GetCurrentSet().GetSetBonuses() { totalAttrs[attr] += bonus } return totalAttrs } // ValidateOutfitCombination 验证服装搭配 func (ds *DressupService) ValidateOutfitCombination(outfits map[OutfitSlot]*Outfit) error { // 检查是否有冲突的装备 for slot, outfit := range outfits { if outfit != nil && !outfit.CanEquipToSlot(slot) { return ErrInvalidSlot } } return nil } // ApplyStyle 应用换装风格 func (ds *DressupService) ApplyStyle(aggregate *DressupAggregate, styleID string) error { style := ds.styleManager.GetStyle(styleID) if style == nil { return ErrStyleNotFound } // 应用风格逻辑 // 这里可以根据风格调整装备外观等 return nil } // OutfitFactory 服装工厂 type OutfitFactory struct { random *rand.Rand } // NewOutfitFactory 创建服装工厂 func NewOutfitFactory() *OutfitFactory { return &OutfitFactory{ random: rand.New(rand.NewSource(time.Now().UnixNano())), } } // CreateRandomOutfit 创建随机服装 func (of *OutfitFactory) CreateRandomOutfit(outfitType OutfitType, playerLevel int) *Outfit { // 根据玩家等级确定稀有度 rarity := of.determineRarity(playerLevel) // 创建基础服装 outfit := NewOutfit(of.generateOutfitName(outfitType, rarity), outfitType, rarity) // 添加对应槽位 of.addSlotsForType(outfit, outfitType) // 生成随机属性 of.generateRandomAttributes(outfit, playerLevel) return outfit } // determineRarity 确定稀有度 func (of *OutfitFactory) determineRarity(playerLevel int) Rarity { roll := of.random.Float64() // 根据玩家等级调整稀有度概率 levelBonus := float64(playerLevel) / 100.0 switch { case roll < 0.5-levelBonus*0.1: return RarityCommon case roll < 0.75-levelBonus*0.05: return RarityUncommon case roll < 0.9: return RarityRare case roll < 0.97: return RarityEpic case roll < 0.995: return RarityLegendary default: return RarityMythic } } // generateOutfitName 生成服装名称 func (of *OutfitFactory) generateOutfitName(outfitType OutfitType, rarity Rarity) string { prefixes := map[Rarity][]string{ RarityCommon: {"普通的", "基础的", "简单的"}, RarityUncommon: {"精良的", "优质的", "改良的"}, RarityRare: {"稀有的", "精制的", "卓越的"}, RarityEpic: {"史诗的", "传说的", "神话的"}, RarityLegendary: {"传奇的", "不朽的", "永恒的"}, RarityMythic: {"神器", "至尊", "无上"}, } names := map[OutfitType][]string{ OutfitTypeWeapon: {"剑", "刀", "枪", "弓", "法杖"}, OutfitTypeArmor: {"护甲", "战袍", "铠甲", "法袍"}, OutfitTypeHelmet: {"头盔", "帽子", "头饰", "王冠"}, OutfitTypeShoes: {"靴子", "鞋子", "战靴", "法靴"}, OutfitTypeAccessory: {"戒指", "项链", "手镯", "护符"}, } prefixList := prefixes[rarity] nameList := names[outfitType] if len(prefixList) == 0 || len(nameList) == 0 { return "未知装备" } prefix := prefixList[of.random.Intn(len(prefixList))] name := nameList[of.random.Intn(len(nameList))] return prefix + name } // addSlotsForType 为服装类型添加槽位 func (of *OutfitFactory) addSlotsForType(outfit *Outfit, outfitType OutfitType) { switch outfitType { case OutfitTypeWeapon: outfit.AddSlot(SlotWeapon) outfit.AddSlot(SlotFashionWeapon) case OutfitTypeArmor: outfit.AddSlot(SlotArmor) outfit.AddSlot(SlotFashionArmor) case OutfitTypeHelmet: outfit.AddSlot(SlotHelmet) outfit.AddSlot(SlotFashionHelmet) case OutfitTypeShoes: outfit.AddSlot(SlotShoes) case OutfitTypeAccessory: outfit.AddSlot(SlotRing) outfit.AddSlot(SlotNecklace) case OutfitTypePet: outfit.AddSlot(SlotPet) case OutfitTypeMount: outfit.AddSlot(SlotMount) } } // generateRandomAttributes 生成随机属性 func (of *OutfitFactory) generateRandomAttributes(outfit *Outfit, playerLevel int) { baseValue := playerLevel * 2 multiplier := outfit.GetRarity().GetRarityMultiplier() // 基础属性 attack := int(float64(baseValue) * multiplier * (0.8 + of.random.Float64()*0.4)) defense := int(float64(baseValue) * multiplier * (0.8 + of.random.Float64()*0.4)) hp := int(float64(baseValue*5) * multiplier * (0.8 + of.random.Float64()*0.4)) outfit.AddAttribute("attack", attack) outfit.AddAttribute("defense", defense) outfit.AddAttribute("hp", hp) // 根据稀有度添加额外属性 if outfit.GetRarity() >= RarityRare { critRate := of.random.Intn(10) + 1 outfit.AddAttribute("crit_rate", critRate) } if outfit.GetRarity() >= RarityEpic { critDamage := of.random.Intn(20) + 10 outfit.AddAttribute("crit_damage", critDamage) } } // StyleManager 风格管理器 type StyleManager struct { styles map[string]*DressupStyle } // NewStyleManager 创建风格管理器 func NewStyleManager() *StyleManager { sm := &StyleManager{ styles: make(map[string]*DressupStyle), } // 初始化默认风格 sm.initDefaultStyles() return sm } // GetStyle 获取风格 func (sm *StyleManager) GetStyle(styleID string) *DressupStyle { return sm.styles[styleID] } // AddStyle 添加风格 func (sm *StyleManager) AddStyle(style *DressupStyle) { sm.styles[style.GetStyleID()] = style } // initDefaultStyles 初始化默认风格 func (sm *StyleManager) initDefaultStyles() { // 战士风格 warriorStyle := NewDressupStyle("warrior", "战士风格", "战斗") warriorStyle.AddBonus("attack", 50) warriorStyle.AddBonus("defense", 30) sm.AddStyle(warriorStyle) // 法师风格 mageStyle := NewDressupStyle("mage", "法师风格", "魔法") mageStyle.AddBonus("magic_attack", 60) mageStyle.AddBonus("mana", 100) sm.AddStyle(mageStyle) // 刺客风格 assassinStyle := NewDressupStyle("assassin", "刺客风格", "敏捷") assassinStyle.AddBonus("crit_rate", 15) assassinStyle.AddBonus("speed", 25) sm.AddStyle(assassinStyle) } ================================================ FILE: internal/domain/inventory/dressup/value_object.go ================================================ package dressup // OutfitType 服装类型 type OutfitType int const ( OutfitTypeWeapon OutfitType = iota + 1 OutfitTypeArmor OutfitTypeHelmet OutfitTypeShoes OutfitTypeAccessory OutfitTypeFashion OutfitTypePet OutfitTypeMount ) // String 返回服装类型字符串 func (ot OutfitType) String() string { switch ot { case OutfitTypeWeapon: return "weapon" case OutfitTypeArmor: return "armor" case OutfitTypeHelmet: return "helmet" case OutfitTypeShoes: return "shoes" case OutfitTypeAccessory: return "accessory" case OutfitTypeFashion: return "fashion" case OutfitTypePet: return "pet" case OutfitTypeMount: return "mount" default: return "unknown" } } // OutfitSlot 装备槽位 type OutfitSlot int const ( SlotWeapon OutfitSlot = iota + 1 SlotArmor SlotHelmet SlotShoes SlotRing SlotNecklace SlotFashionWeapon SlotFashionArmor SlotFashionHelmet SlotPet SlotMount ) // String 返回槽位字符串 func (os OutfitSlot) String() string { switch os { case SlotWeapon: return "weapon" case SlotArmor: return "armor" case SlotHelmet: return "helmet" case SlotShoes: return "shoes" case SlotRing: return "ring" case SlotNecklace: return "necklace" case SlotFashionWeapon: return "fashion_weapon" case SlotFashionArmor: return "fashion_armor" case SlotFashionHelmet: return "fashion_helmet" case SlotPet: return "pet" case SlotMount: return "mount" default: return "unknown" } } // Rarity 稀有度 type Rarity int const ( RarityCommon Rarity = iota + 1 RarityUncommon RarityRare RarityEpic RarityLegendary RarityMythic ) // String 返回稀有度字符串 func (r Rarity) String() string { switch r { case RarityCommon: return "common" case RarityUncommon: return "uncommon" case RarityRare: return "rare" case RarityEpic: return "epic" case RarityLegendary: return "legendary" case RarityMythic: return "mythic" default: return "unknown" } } // GetRarityMultiplier 获取稀有度属性倍数 func (r Rarity) GetRarityMultiplier() float64 { switch r { case RarityCommon: return 1.0 case RarityUncommon: return 1.2 case RarityRare: return 1.5 case RarityEpic: return 2.0 case RarityLegendary: return 3.0 case RarityMythic: return 5.0 default: return 1.0 } } // DressupStyle 换装风格 type DressupStyle struct { styleID string styleName string theme string bonuses map[string]int } // NewDressupStyle 创建换装风格 func NewDressupStyle(styleID, styleName, theme string) *DressupStyle { return &DressupStyle{ styleID: styleID, styleName: styleName, theme: theme, bonuses: make(map[string]int), } } // GetStyleID 获取风格ID func (ds *DressupStyle) GetStyleID() string { return ds.styleID } // GetStyleName 获取风格名称 func (ds *DressupStyle) GetStyleName() string { return ds.styleName } // GetTheme 获取主题 func (ds *DressupStyle) GetTheme() string { return ds.theme } // AddBonus 添加风格加成 func (ds *DressupStyle) AddBonus(attr string, value int) { ds.bonuses[attr] = value } // GetBonuses 获取所有加成 func (ds *DressupStyle) GetBonuses() map[string]int { return ds.bonuses } // OutfitQuality 服装品质 type OutfitQuality int const ( QualityNormal OutfitQuality = iota + 1 // 普通 QualityGood // 良好 QualityExcellent // 优秀 QualityPerfect // 完美 QualityMasterwork // 大师级 ) // String 返回品质字符串 func (oq OutfitQuality) String() string { switch oq { case QualityNormal: return "normal" case QualityGood: return "good" case QualityExcellent: return "excellent" case QualityPerfect: return "perfect" case QualityMasterwork: return "masterwork" default: return "unknown" } } // GetQualityMultiplier 获取品质属性倍数 func (oq OutfitQuality) GetQualityMultiplier() float64 { switch oq { case QualityNormal: return 1.0 case QualityGood: return 1.1 case QualityExcellent: return 1.25 case QualityPerfect: return 1.5 case QualityMasterwork: return 2.0 default: return 1.0 } } // OutfitSource 服装来源 type OutfitSource int const ( SourceShop OutfitSource = iota + 1 // 商店购买 SourceCraft // 制作 SourceDrop // 掉落 SourceEvent // 活动 SourceGift // 礼品 SourceAchievement // 成就 SourceVIP // VIP SourceLimitedTime // 限时 ) // String 返回来源字符串 func (os OutfitSource) String() string { switch os { case SourceShop: return "shop" case SourceCraft: return "craft" case SourceDrop: return "drop" case SourceEvent: return "event" case SourceGift: return "gift" case SourceAchievement: return "achievement" case SourceVIP: return "vip" case SourceLimitedTime: return "limited_time" default: return "unknown" } } // FashionSet 时装套装 type FashionSet struct { setID string setName string description string pieces []string // 套装部件ID列表 setBonuses map[int]map[string]int // 件数 -> 属性加成 theme string season string isLimited bool } // NewFashionSet 创建时装套装 func NewFashionSet(setID, setName, description string) *FashionSet { return &FashionSet{ setID: setID, setName: setName, description: description, pieces: make([]string, 0), setBonuses: make(map[int]map[string]int), isLimited: false, } } // GetSetID 获取套装ID func (fs *FashionSet) GetSetID() string { return fs.setID } // GetSetName 获取套装名称 func (fs *FashionSet) GetSetName() string { return fs.setName } // GetDescription 获取描述 func (fs *FashionSet) GetDescription() string { return fs.description } // AddPiece 添加套装部件 func (fs *FashionSet) AddPiece(pieceID string) { fs.pieces = append(fs.pieces, pieceID) } // GetPieces 获取套装部件 func (fs *FashionSet) GetPieces() []string { return fs.pieces } // AddSetBonus 添加套装加成 func (fs *FashionSet) AddSetBonus(pieceCount int, attribute string, value int) { if fs.setBonuses[pieceCount] == nil { fs.setBonuses[pieceCount] = make(map[string]int) } fs.setBonuses[pieceCount][attribute] = value } // GetSetBonuses 获取套装加成 func (fs *FashionSet) GetSetBonuses() map[int]map[string]int { return fs.setBonuses } // GetBonusForPieceCount 获取指定件数的加成 func (fs *FashionSet) GetBonusForPieceCount(pieceCount int) map[string]int { return fs.setBonuses[pieceCount] } // SetTheme 设置主题 func (fs *FashionSet) SetTheme(theme string) { fs.theme = theme } // GetTheme 获取主题 func (fs *FashionSet) GetTheme() string { return fs.theme } // SetSeason 设置季节 func (fs *FashionSet) SetSeason(season string) { fs.season = season } // GetSeason 获取季节 func (fs *FashionSet) GetSeason() string { return fs.season } // SetLimited 设置限定状态 func (fs *FashionSet) SetLimited(limited bool) { fs.isLimited = limited } // IsLimited 是否限定 func (fs *FashionSet) IsLimited() bool { return fs.isLimited } // AppearanceConfig 外观配置 type AppearanceConfig struct { colorScheme map[string]string // 颜色方案 effects []string // 特效列表 animations []string // 动画列表 textures map[string]string // 材质贴图 scale float64 // 缩放比例 transparency float64 // 透明度 glowIntensity float64 // 发光强度 } // NewAppearanceConfig 创建外观配置 func NewAppearanceConfig() *AppearanceConfig { return &AppearanceConfig{ colorScheme: make(map[string]string), effects: make([]string, 0), animations: make([]string, 0), textures: make(map[string]string), scale: 1.0, transparency: 1.0, glowIntensity: 0.0, } } // SetColor 设置颜色 func (ac *AppearanceConfig) SetColor(part, color string) { ac.colorScheme[part] = color } // GetColor 获取颜色 func (ac *AppearanceConfig) GetColor(part string) string { return ac.colorScheme[part] } // GetColorScheme 获取颜色方案 func (ac *AppearanceConfig) GetColorScheme() map[string]string { return ac.colorScheme } // AddEffect 添加特效 func (ac *AppearanceConfig) AddEffect(effect string) { ac.effects = append(ac.effects, effect) } // GetEffects 获取特效列表 func (ac *AppearanceConfig) GetEffects() []string { return ac.effects } // AddAnimation 添加动画 func (ac *AppearanceConfig) AddAnimation(animation string) { ac.animations = append(ac.animations, animation) } // GetAnimations 获取动画列表 func (ac *AppearanceConfig) GetAnimations() []string { return ac.animations } // SetTexture 设置材质 func (ac *AppearanceConfig) SetTexture(part, texture string) { ac.textures[part] = texture } // GetTexture 获取材质 func (ac *AppearanceConfig) GetTexture(part string) string { return ac.textures[part] } // GetTextures 获取所有材质 func (ac *AppearanceConfig) GetTextures() map[string]string { return ac.textures } // SetScale 设置缩放 func (ac *AppearanceConfig) SetScale(scale float64) { ac.scale = scale } // GetScale 获取缩放 func (ac *AppearanceConfig) GetScale() float64 { return ac.scale } // SetTransparency 设置透明度 func (ac *AppearanceConfig) SetTransparency(transparency float64) { ac.transparency = transparency } // GetTransparency 获取透明度 func (ac *AppearanceConfig) GetTransparency() float64 { return ac.transparency } // SetGlowIntensity 设置发光强度 func (ac *AppearanceConfig) SetGlowIntensity(intensity float64) { ac.glowIntensity = intensity } // GetGlowIntensity 获取发光强度 func (ac *AppearanceConfig) GetGlowIntensity() float64 { return ac.glowIntensity } // AttributeBonus 属性加成 type AttributeBonus struct { attribute string // 属性名称 baseValue int // 基础值 bonus float64 // 加成倍数 bonusType string // 加成类型:percentage, fixed } // NewAttributeBonus 创建属性加成 func NewAttributeBonus(attribute string, baseValue int, bonus float64, bonusType string) *AttributeBonus { return &AttributeBonus{ attribute: attribute, baseValue: baseValue, bonus: bonus, bonusType: bonusType, } } // GetAttribute 获取属性名称 func (ab *AttributeBonus) GetAttribute() string { return ab.attribute } // GetBaseValue 获取基础值 func (ab *AttributeBonus) GetBaseValue() int { return ab.baseValue } // GetBonus 获取加成倍数 func (ab *AttributeBonus) GetBonus() float64 { return ab.bonus } // GetBonusType 获取加成类型 func (ab *AttributeBonus) GetBonusType() string { return ab.bonusType } // CalculateFinalValue 计算最终值 func (ab *AttributeBonus) CalculateFinalValue() int { switch ab.bonusType { case "percentage": return int(float64(ab.baseValue) * (1.0 + ab.bonus)) case "fixed": return ab.baseValue + int(ab.bonus) default: return ab.baseValue } } // DyeColor 染色颜色 type DyeColor struct { colorID string colorName string hexValue string rarity Rarity isUnlocked bool } // NewDyeColor 创建染色颜色 func NewDyeColor(colorID, colorName, hexValue string, rarity Rarity) *DyeColor { return &DyeColor{ colorID: colorID, colorName: colorName, hexValue: hexValue, rarity: rarity, isUnlocked: false, } } // GetColorID 获取颜色ID func (dc *DyeColor) GetColorID() string { return dc.colorID } // GetColorName 获取颜色名称 func (dc *DyeColor) GetColorName() string { return dc.colorName } // GetHexValue 获取十六进制值 func (dc *DyeColor) GetHexValue() string { return dc.hexValue } // GetRarity 获取稀有度 func (dc *DyeColor) GetRarity() Rarity { return dc.rarity } // Unlock 解锁颜色 func (dc *DyeColor) Unlock() { dc.isUnlocked = true } // IsUnlocked 是否已解锁 func (dc *DyeColor) IsUnlocked() bool { return dc.isUnlocked } // OutfitFilter 服装筛选器 type OutfitFilter struct { outfitType *OutfitType rarity *Rarity quality *OutfitQuality source *OutfitSource slot *OutfitSlot isLocked *bool hasSetBonus *bool minLevel *int maxLevel *int searchText string tags []string } // NewOutfitFilter 创建服装筛选器 func NewOutfitFilter() *OutfitFilter { return &OutfitFilter{ tags: make([]string, 0), } } // SetOutfitType 设置服装类型筛选 func (of *OutfitFilter) SetOutfitType(outfitType OutfitType) { of.outfitType = &outfitType } // SetRarity 设置稀有度筛选 func (of *OutfitFilter) SetRarity(rarity Rarity) { of.rarity = &rarity } // SetQuality 设置品质筛选 func (of *OutfitFilter) SetQuality(quality OutfitQuality) { of.quality = &quality } // SetSource 设置来源筛选 func (of *OutfitFilter) SetSource(source OutfitSource) { of.source = &source } // SetSlot 设置槽位筛选 func (of *OutfitFilter) SetSlot(slot OutfitSlot) { of.slot = &slot } // SetLocked 设置锁定状态筛选 func (of *OutfitFilter) SetLocked(locked bool) { of.isLocked = &locked } // SetHasSetBonus 设置套装加成筛选 func (of *OutfitFilter) SetHasSetBonus(hasSetBonus bool) { of.hasSetBonus = &hasSetBonus } // SetLevelRange 设置等级范围筛选 func (of *OutfitFilter) SetLevelRange(minLevel, maxLevel int) { of.minLevel = &minLevel of.maxLevel = &maxLevel } // SetSearchText 设置搜索文本 func (of *OutfitFilter) SetSearchText(text string) { of.searchText = text } // AddTag 添加标签筛选 func (of *OutfitFilter) AddTag(tag string) { of.tags = append(of.tags, tag) } // GetOutfitType 获取服装类型筛选 func (of *OutfitFilter) GetOutfitType() *OutfitType { return of.outfitType } // GetRarity 获取稀有度筛选 func (of *OutfitFilter) GetRarity() *Rarity { return of.rarity } // GetQuality 获取品质筛选 func (of *OutfitFilter) GetQuality() *OutfitQuality { return of.quality } // GetSource 获取来源筛选 func (of *OutfitFilter) GetSource() *OutfitSource { return of.source } // GetSlot 获取槽位筛选 func (of *OutfitFilter) GetSlot() *OutfitSlot { return of.slot } // GetLocked 获取锁定状态筛选 func (of *OutfitFilter) GetLocked() *bool { return of.isLocked } // GetHasSetBonus 获取套装加成筛选 func (of *OutfitFilter) GetHasSetBonus() *bool { return of.hasSetBonus } // GetMinLevel 获取最小等级 func (of *OutfitFilter) GetMinLevel() *int { return of.minLevel } // GetMaxLevel 获取最大等级 func (of *OutfitFilter) GetMaxLevel() *int { return of.maxLevel } // GetSearchText 获取搜索文本 func (of *OutfitFilter) GetSearchText() string { return of.searchText } // GetTags 获取标签列表 func (of *OutfitFilter) GetTags() []string { return of.tags } ================================================ FILE: internal/domain/inventory/errors.go ================================================ package inventory import "errors" var ( // 背包相关错误 ErrInventoryFull = errors.New("inventory is full") ErrInvalidCapacity = errors.New("invalid inventory capacity") ErrSlotNotFound = errors.New("inventory slot not found") // 物品相关错误 ErrItemNotFound = errors.New("item not found") ErrInvalidQuantity = errors.New("invalid item quantity") ErrInsufficientQuantity = errors.New("insufficient item quantity") ErrExceedsMaxStack = errors.New("exceeds maximum stack size") ErrItemNotUsable = errors.New("item is not usable") ErrItemNotEquippable = errors.New("item is not equippable") ErrItemOnCooldown = errors.New("item is on cooldown") ErrItemExpired = errors.New("item has expired") ErrInvalidItemType = errors.New("invalid item type") // 装备相关错误 ErrInvalidEquipment = errors.New("invalid equipment") ErrEquipmentDamaged = errors.New("equipment is damaged") ErrInsufficientLevel = errors.New("insufficient level to equip") ErrClassRestriction = errors.New("class restriction for equipment") // 宝石相关错误 ErrGemSlotFull = errors.New("gem slot is full") ErrInvalidGemType = errors.New("invalid gem type") ErrGemNotFound = errors.New("gem not found") // 交易相关错误 ErrItemNotTradeable = errors.New("item is not tradeable") ErrTradeRestricted = errors.New("trade is restricted") ) ================================================ FILE: internal/domain/inventory/inventory.go ================================================ // Package inventory 背包领域 package inventory import ( "errors" "greatestworks/internal/domain/player" "time" "github.com/google/uuid" ) // 错误定义 var ( ErrInsufficientItems = errors.New("insufficient items") ErrInvalidSlot = errors.New("invalid slot") ErrSlotEmpty = errors.New("slot is empty") ErrSlotLocked = errors.New("slot is locked") ) // InventoryID 背包ID值对象 type InventoryID struct { value string } // ItemID 物品ID值对象 type ItemID struct { value string } // NewItemID 创建新的物品ID func NewItemID() ItemID { return ItemID{value: uuid.New().String()} } // String 返回字符串表示 func (id ItemID) String() string { return id.value } // NewInventoryID 创建新的背包ID func NewInventoryID() InventoryID { return InventoryID{value: uuid.New().String()} } // String 返回字符串表示 func (id InventoryID) String() string { return id.value } // ItemType 物品类型枚举 type ItemType int const ( ItemTypeWeapon ItemType = iota ItemTypeArmor ItemTypeConsumable ItemTypeMaterial ItemTypeQuest ItemTypeCurrency ) // ItemRarity 物品稀有度枚举 type ItemRarity int const ( ItemRarityCommon ItemRarity = iota ItemRarityUncommon ItemRarityRare ItemRarityEpic ItemRarityLegendary ) // Item 物品实体 type Item struct { id ItemID name string description string itemType ItemType rarity ItemRarity maxStack int value int attributes map[string]int createdAt time.Time } // NewItem 创建新物品 func NewItem(name, description string, itemType ItemType, rarity ItemRarity, maxStack, value int) *Item { return &Item{ id: NewItemID(), name: name, description: description, itemType: itemType, rarity: rarity, maxStack: maxStack, value: value, attributes: make(map[string]int), createdAt: time.Now(), } } // ID 获取物品ID func (i *Item) ID() ItemID { return i.id } // Name 获取物品名称 func (i *Item) Name() string { return i.name } // Type 获取物品类型 func (i *Item) Type() ItemType { return i.itemType } // Rarity 获取物品稀有度 func (i *Item) Rarity() ItemRarity { return i.rarity } // MaxStack 获取最大堆叠数量 func (i *Item) MaxStack() int { return i.maxStack } // Value 获取物品价值 func (i *Item) Value() int { return i.value } // InventorySlot 背包槽位 type InventorySlot struct { SlotIndex int `json:"slot_index"` ItemID *ItemID `json:"item_id,omitempty"` Quantity int `json:"quantity"` Locked bool `json:"locked"` } // IsEmpty 是否为空槽位 func (s *InventorySlot) IsEmpty() bool { return s.ItemID == nil || s.Quantity <= 0 } // CanStack 是否可以堆叠指定物品 func (s *InventorySlot) CanStack(itemID ItemID, item *Item) bool { if s.IsEmpty() { return true } if s.ItemID == nil || *s.ItemID != itemID { return false } return s.Quantity < item.MaxStack() } // Inventory 背包聚合根 type Inventory struct { id InventoryID playerID player.PlayerID slots []*InventorySlot capacity int createdAt time.Time updatedAt time.Time version int64 } // NewInventory 创建新背包 func NewInventory(playerID player.PlayerID, capacity int) *Inventory { now := time.Now() slots := make([]*InventorySlot, capacity) for i := 0; i < capacity; i++ { slots[i] = &InventorySlot{ SlotIndex: i, Quantity: 0, Locked: false, } } return &Inventory{ id: NewInventoryID(), playerID: playerID, slots: slots, capacity: capacity, createdAt: now, updatedAt: now, version: 1, } } // ID 获取背包ID func (inv *Inventory) ID() InventoryID { return inv.id } // PlayerID 获取玩家ID func (inv *Inventory) PlayerID() string { return inv.playerID.String() } // Capacity 获取背包容量 func (inv *Inventory) Capacity() int { return inv.capacity } // UsedSlots 获取已使用槽位 func (inv *Inventory) UsedSlots() int { return len(inv.slots) } // Items 获取所有物品 func (inv *Inventory) Items() map[string]*Item { items := make(map[string]*Item) for _, slot := range inv.slots { if slot.ItemID != nil { item, exists := inv.GetItemByID(*slot.ItemID) if exists { items[item.ID().String()] = item } } } return items } // GetItem 获取指定物品 func (inv *Inventory) GetItem(itemID string) (*Item, bool) { return inv.GetItemByID(ItemID{value: itemID}) } // GetItemByID 通过ItemID获取物品 func (inv *Inventory) GetItemByID(itemID ItemID) (*Item, bool) { for _, slot := range inv.slots { if slot.ItemID != nil && *slot.ItemID == itemID { // 这里需要从物品仓库获取物品详情 // TODO: 实现从物品仓库获取物品详情的逻辑 // 临时实现:创建一个基本的Item return &Item{ id: itemID, // 其他字段需要从物品仓库获取 }, true } } return nil, false } // Slots 获取所有槽位 func (inv *Inventory) Slots() []*InventorySlot { return inv.slots } // AddItem 添加物品 func (inv *Inventory) AddItem(item *Item, quantity int) error { if quantity <= 0 { return ErrInvalidQuantity } remainingQuantity := quantity // 首先尝试堆叠到现有槽位 for _, slot := range inv.slots { if slot.CanStack(item.ID(), item) && !slot.Locked { if slot.IsEmpty() { // 空槽位 addQuantity := remainingQuantity if addQuantity > item.MaxStack() { addQuantity = item.MaxStack() } slot.ItemID = &item.id slot.Quantity = addQuantity remainingQuantity -= addQuantity } else { // 已有相同物品的槽位 canAdd := item.MaxStack() - slot.Quantity if canAdd > 0 { addQuantity := remainingQuantity if addQuantity > canAdd { addQuantity = canAdd } slot.Quantity += addQuantity remainingQuantity -= addQuantity } } if remainingQuantity <= 0 { break } } } if remainingQuantity > 0 { return ErrInventoryFull } inv.updatedAt = time.Now() inv.version++ return nil } // RemoveItem 移除物品 func (inv *Inventory) RemoveItem(itemID ItemID, quantity int) error { if quantity <= 0 { return ErrInvalidQuantity } // 检查是否有足够的物品 totalQuantity := inv.GetItemQuantity(itemID) if totalQuantity < quantity { return ErrInsufficientItems } remainingToRemove := quantity // 从槽位中移除物品 for _, slot := range inv.slots { if !slot.IsEmpty() && slot.ItemID != nil && *slot.ItemID == itemID && !slot.Locked { if slot.Quantity <= remainingToRemove { // 移除整个槽位的物品 remainingToRemove -= slot.Quantity slot.ItemID = nil slot.Quantity = 0 } else { // 部分移除 slot.Quantity -= remainingToRemove remainingToRemove = 0 } if remainingToRemove <= 0 { break } } } inv.updatedAt = time.Now() inv.version++ return nil } // GetItemQuantity 获取物品总数量 func (inv *Inventory) GetItemQuantity(itemID ItemID) int { total := 0 for _, slot := range inv.slots { if !slot.IsEmpty() && slot.ItemID != nil && *slot.ItemID == itemID { total += slot.Quantity } } return total } // HasItem 检查是否拥有指定数量的物品 func (inv *Inventory) HasItem(itemID ItemID, quantity int) bool { return inv.GetItemQuantity(itemID) >= quantity } // GetEmptySlotCount 获取空槽位数量 func (inv *Inventory) GetEmptySlotCount() int { count := 0 for _, slot := range inv.slots { if slot.IsEmpty() && !slot.Locked { count++ } } return count } // IsFull 检查背包是否已满 func (inv *Inventory) IsFull() bool { return inv.GetEmptySlotCount() == 0 } // MoveItem 移动物品到指定槽位 func (inv *Inventory) MoveItem(fromSlot, toSlot int) error { if fromSlot < 0 || fromSlot >= inv.capacity || toSlot < 0 || toSlot >= inv.capacity { return ErrInvalidSlot } if fromSlot == toSlot { return nil } from := inv.slots[fromSlot] to := inv.slots[toSlot] if from.IsEmpty() { return ErrSlotEmpty } if from.Locked || to.Locked { return ErrSlotLocked } // 交换槽位内容 from.ItemID, to.ItemID = to.ItemID, from.ItemID from.Quantity, to.Quantity = to.Quantity, from.Quantity inv.updatedAt = time.Now() inv.version++ return nil } // LockSlot 锁定槽位 func (inv *Inventory) LockSlot(slotIndex int) error { if slotIndex < 0 || slotIndex >= inv.capacity { return ErrInvalidSlot } inv.slots[slotIndex].Locked = true inv.updatedAt = time.Now() inv.version++ return nil } // UnlockSlot 解锁槽位 func (inv *Inventory) UnlockSlot(slotIndex int) error { if slotIndex < 0 || slotIndex >= inv.capacity { return ErrInvalidSlot } inv.slots[slotIndex].Locked = false inv.updatedAt = time.Now() inv.version++ return nil } // Version 获取版本号 func (inv *Inventory) Version() int64 { return inv.version } ================================================ FILE: internal/domain/inventory/repository.go ================================================ package inventory import ( "context" "time" ) // Quality 物品品质 type Quality int const ( QualityCommon Quality = iota + 1 // 普通 QualityUncommon // 不常见 QualityRare // 稀有 QualityEpic // 史诗 QualityLegendary // 传说 QualityMythic // 神话 ) // Repository 背包仓储接口 type Repository interface { // 基础CRUD操作 Save(ctx context.Context, inventory *Inventory) error FindByPlayerID(ctx context.Context, playerID string) (*Inventory, error) Delete(ctx context.Context, playerID string) error Exists(ctx context.Context, playerID string) (bool, error) // 批量操作 SaveBatch(ctx context.Context, inventories []*Inventory) error FindByPlayerIDs(ctx context.Context, playerIDs []string) ([]*Inventory, error) // 查询操作 FindItemsByType(ctx context.Context, playerID string, itemType ItemType) ([]*Item, error) FindExpiredItems(ctx context.Context, playerID string, before time.Time) ([]*Item, error) CountItemsByType(ctx context.Context, playerID string, itemType ItemType) (int64, error) // 统计操作 GetInventoryStats(ctx context.Context, playerID string) (*InventoryStats, error) GetPlayerItemHistory(ctx context.Context, playerID string, limit int) ([]*ItemHistory, error) } // InventoryStats 背包统计信息 type InventoryStats struct { PlayerID string `json:"player_id"` TotalItems int64 `json:"total_items"` UsedSlots int `json:"used_slots"` Capacity int `json:"capacity"` ItemsByType map[ItemType]int64 `json:"items_by_type"` ItemsByQuality map[Quality]int64 `json:"items_by_quality"` LastUpdate time.Time `json:"last_update"` } // ItemHistory 物品历史记录 type ItemHistory struct { ID string `json:"id"` PlayerID string `json:"player_id"` ItemID string `json:"item_id"` Action string `json:"action"` // add, remove, use, trade Quantity int64 `json:"quantity"` Reason string `json:"reason"` OccurredAt time.Time `json:"occurred_at"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // ItemQueryFilter 物品查询过滤器 type ItemQueryFilter struct { PlayerID string `json:"player_id"` ItemTypes []ItemType `json:"item_types,omitempty"` Qualities []Quality `json:"qualities,omitempty"` MinQuantity *int64 `json:"min_quantity,omitempty"` MaxQuantity *int64 `json:"max_quantity,omitempty"` ExpiredOnly bool `json:"expired_only"` UsableOnly bool `json:"usable_only"` Limit int `json:"limit"` Offset int `json:"offset"` } // ItemRepository 物品仓储接口 type ItemRepository interface { // Save 保存物品 Save(ctx context.Context, item *Item) error // FindByID 根据ID查找物品 FindByID(ctx context.Context, id ItemID) (*Item, error) // FindByType 根据类型查找物品 FindByType(ctx context.Context, itemType ItemType, limit int) ([]*Item, error) // FindByRarity 根据稀有度查找物品 FindByRarity(ctx context.Context, rarity ItemRarity, limit int) ([]*Item, error) // Update 更新物品 Update(ctx context.Context, item *Item) error // Delete 删除物品 Delete(ctx context.Context, itemID string) error } ================================================ FILE: internal/domain/inventory/synthesis/aggregate.go ================================================ package synthesis import ( "math/rand" "time" // "github.com/google/uuid" ) // SynthesisAggregate 合成聚合根 type SynthesisAggregate struct { playerID string recipes map[string]*Recipe materials map[string]*Material history []*SynthesisRecord updatedAt time.Time version int } // NewSynthesisAggregate 创建合成聚合根 func NewSynthesisAggregate(playerID string) *SynthesisAggregate { return &SynthesisAggregate{ playerID: playerID, recipes: make(map[string]*Recipe), materials: make(map[string]*Material), history: make([]*SynthesisRecord, 0), updatedAt: time.Now(), version: 1, } } // GetPlayerID 获取玩家ID func (s *SynthesisAggregate) GetPlayerID() string { return s.playerID } // AddRecipe 添加配方 func (s *SynthesisAggregate) AddRecipe(recipe *Recipe) error { if recipe == nil { return ErrInvalidRecipe } s.recipes[recipe.GetID()] = recipe s.updateVersion() return nil } // GetRecipe 获取配方 func (s *SynthesisAggregate) GetRecipe(recipeID string) *Recipe { return s.recipes[recipeID] } // GetAllRecipes 获取所有配方 func (s *SynthesisAggregate) GetAllRecipes() map[string]*Recipe { return s.recipes } // AddMaterial 添加材料 func (s *SynthesisAggregate) AddMaterial(material *Material) error { if material == nil { return ErrInvalidMaterial } existing, exists := s.materials[material.GetID()] if exists { existing.AddQuantity(material.GetQuantity()) } else { s.materials[material.GetID()] = material } s.updateVersion() return nil } // ConsumeMaterial 消耗材料 func (s *SynthesisAggregate) ConsumeMaterial(materialID string, quantity int) error { material, exists := s.materials[materialID] if !exists { return ErrMaterialNotFound } if material.GetQuantity() < quantity { return ErrInsufficientMaterial } material.ConsumeQuantity(quantity) if material.GetQuantity() <= 0 { delete(s.materials, materialID) } s.updateVersion() return nil } // GetMaterial 获取材料 func (s *SynthesisAggregate) GetMaterial(materialID string) *Material { return s.materials[materialID] } // GetAllMaterials 获取所有材料 func (s *SynthesisAggregate) GetAllMaterials() map[string]*Material { return s.materials } // CanSynthesize 检查是否可以合成 func (s *SynthesisAggregate) CanSynthesize(recipeID string) error { recipe, exists := s.recipes[recipeID] if !exists { return ErrRecipeNotFound } // 检查材料是否足够 for _, requirement := range recipe.GetRequirements() { material, exists := s.materials[requirement.MaterialID] if !exists || material.GetQuantity() < requirement.Quantity { return ErrInsufficientMaterial } } return nil } // Synthesize 执行合成 func (s *SynthesisAggregate) Synthesize(recipeID string, quantity int) (*SynthesisResult, error) { recipe, exists := s.recipes[recipeID] if !exists { return nil, ErrRecipeNotFound } // 检查材料是否足够 for _, requirement := range recipe.GetRequirements() { requiredQuantity := requirement.Quantity * quantity material, exists := s.materials[requirement.MaterialID] if !exists || material.GetQuantity() < requiredQuantity { return nil, ErrInsufficientMaterial } } // 消耗材料 for _, requirement := range recipe.GetRequirements() { requiredQuantity := requirement.Quantity * quantity err := s.ConsumeMaterial(requirement.MaterialID, requiredQuantity) if err != nil { return nil, err } } // 计算合成结果 result := s.calculateSynthesisResult(recipe, quantity) // 记录合成历史 record := NewSynthesisRecord(s.playerID, recipeID, quantity, result) s.history = append(s.history, record) s.updateVersion() return result, nil } // calculateSynthesisResult 计算合成结果 func (s *SynthesisAggregate) calculateSynthesisResult(recipe *Recipe, quantity int) *SynthesisResult { result := NewSynthesisResult() for i := 0; i < quantity; i++ { // 计算成功率 if s.rollSuccess(recipe.GetSuccessRate()) { // 成功,添加产出物品 for _, output := range recipe.GetOutputs() { result.AddSuccessItem(output.ItemID, output.Quantity) } } else { // 失败,可能有失败产出 for _, failOutput := range recipe.GetFailOutputs() { result.AddFailItem(failOutput.ItemID, failOutput.Quantity) } } } return result } // rollSuccess 计算成功率 func (s *SynthesisAggregate) rollSuccess(successRate float64) bool { // 这里可以加入更复杂的成功率计算逻辑 // 比如玩家技能等级、装备加成等 return rand.Float64() < successRate } // GetSynthesisHistory 获取合成历史 func (s *SynthesisAggregate) GetSynthesisHistory() []*SynthesisRecord { return s.history } // GetRecentHistory 获取最近的合成历史 func (s *SynthesisAggregate) GetRecentHistory(limit int) []*SynthesisRecord { if len(s.history) <= limit { return s.history } return s.history[len(s.history)-limit:] } // updateVersion 更新版本 func (s *SynthesisAggregate) updateVersion() { s.version++ s.updatedAt = time.Now() } // GetVersion 获取版本 func (s *SynthesisAggregate) GetVersion() int { return s.version } // GetUpdatedAt 获取更新时间 func (s *SynthesisAggregate) GetUpdatedAt() time.Time { return s.updatedAt } // GetMaterialQuantity 获取材料数量 func (s *SynthesisAggregate) GetMaterialQuantity(materialID string) int { material, exists := s.materials[materialID] if !exists { return 0 } return material.GetQuantity() } // HasRecipe 检查是否拥有配方 func (s *SynthesisAggregate) HasRecipe(recipeID string) bool { _, exists := s.recipes[recipeID] return exists } // GetRecipesByCategory 根据分类获取配方 func (s *SynthesisAggregate) GetRecipesByCategory(category RecipeCategory) []*Recipe { var recipes []*Recipe for _, recipe := range s.recipes { if recipe.GetCategory() == category { recipes = append(recipes, recipe) } } return recipes } ================================================ FILE: internal/domain/inventory/synthesis/entity.go ================================================ package synthesis import ( "time" "github.com/google/uuid" // "math/rand" // "github.com/google/uuid" ) // Recipe 配方实体 type Recipe struct { id string name string category RecipeCategory requirements []*MaterialRequirement outputs []*ItemOutput failOutputs []*ItemOutput successRate float64 craftTime time.Duration requireLevel int description string unlockedAt time.Time } // NewRecipe 创建配方 func NewRecipe(name string, category RecipeCategory, successRate float64) *Recipe { return &Recipe{ id: uuid.New().String(), name: name, category: category, requirements: make([]*MaterialRequirement, 0), outputs: make([]*ItemOutput, 0), failOutputs: make([]*ItemOutput, 0), successRate: successRate, craftTime: time.Minute * 5, // 默认5分钟 requireLevel: 1, unlockedAt: time.Now(), } } // GetID 获取配方ID func (r *Recipe) GetID() string { return r.id } // GetName 获取配方名称 func (r *Recipe) GetName() string { return r.name } // GetCategory 获取配方分类 func (r *Recipe) GetCategory() RecipeCategory { return r.category } // AddRequirement 添加材料需求 func (r *Recipe) AddRequirement(materialID string, quantity int) { r.requirements = append(r.requirements, &MaterialRequirement{ MaterialID: materialID, Quantity: quantity, }) } // GetRequirements 获取材料需求 func (r *Recipe) GetRequirements() []*MaterialRequirement { return r.requirements } // AddOutput 添加产出物品 func (r *Recipe) AddOutput(itemID string, quantity int, probability float64) { r.outputs = append(r.outputs, &ItemOutput{ ItemID: itemID, Quantity: quantity, Probability: probability, }) } // GetOutputs 获取产出物品 func (r *Recipe) GetOutputs() []*ItemOutput { return r.outputs } // AddFailOutput 添加失败产出 func (r *Recipe) AddFailOutput(itemID string, quantity int, probability float64) { r.failOutputs = append(r.failOutputs, &ItemOutput{ ItemID: itemID, Quantity: quantity, Probability: probability, }) } // GetFailOutputs 获取失败产出 func (r *Recipe) GetFailOutputs() []*ItemOutput { return r.failOutputs } // GetSuccessRate 获取成功率 func (r *Recipe) GetSuccessRate() float64 { return r.successRate } // SetSuccessRate 设置成功率 func (r *Recipe) SetSuccessRate(rate float64) { if rate < 0 { rate = 0 } else if rate > 1 { rate = 1 } r.successRate = rate } // GetCraftTime 获取制作时间 func (r *Recipe) GetCraftTime() time.Duration { return r.craftTime } // SetCraftTime 设置制作时间 func (r *Recipe) SetCraftTime(duration time.Duration) { r.craftTime = duration } // GetRequireLevel 获取需求等级 func (r *Recipe) GetRequireLevel() int { return r.requireLevel } // SetRequireLevel 设置需求等级 func (r *Recipe) SetRequireLevel(level int) { r.requireLevel = level } // GetDescription 获取描述 func (r *Recipe) GetDescription() string { return r.description } // SetDescription 设置描述 func (r *Recipe) SetDescription(desc string) { r.description = desc } // GetUnlockedAt 获取解锁时间 func (r *Recipe) GetUnlockedAt() time.Time { return r.unlockedAt } // Material 材料实体 type Material struct { id string name string materialType MaterialType quality Quality quantity int maxStack int description string obtainedAt time.Time } // NewMaterial 创建材料 func NewMaterial(id, name string, materialType MaterialType, quality Quality, quantity int) *Material { return &Material{ id: id, name: name, materialType: materialType, quality: quality, quantity: quantity, maxStack: 999, // 默认最大堆叠999 obtainedAt: time.Now(), } } // GetID 获取材料ID func (m *Material) GetID() string { return m.id } // GetName 获取材料名称 func (m *Material) GetName() string { return m.name } // GetType 获取材料类型 func (m *Material) GetType() MaterialType { return m.materialType } // GetQuality 获取品质 func (m *Material) GetQuality() Quality { return m.quality } // GetQuantity 获取数量 func (m *Material) GetQuantity() int { return m.quantity } // AddQuantity 增加数量 func (m *Material) AddQuantity(amount int) { m.quantity += amount if m.quantity > m.maxStack { m.quantity = m.maxStack } } // ConsumeQuantity 消耗数量 func (m *Material) ConsumeQuantity(amount int) error { if m.quantity < amount { return ErrInsufficientMaterial } m.quantity -= amount return nil } // GetMaxStack 获取最大堆叠 func (m *Material) GetMaxStack() int { return m.maxStack } // SetMaxStack 设置最大堆叠 func (m *Material) SetMaxStack(maxStack int) { m.maxStack = maxStack } // GetDescription 获取描述 func (m *Material) GetDescription() string { return m.description } // SetDescription 设置描述 func (m *Material) SetDescription(desc string) { m.description = desc } // GetObtainedAt 获取获得时间 func (m *Material) GetObtainedAt() time.Time { return m.obtainedAt } // SynthesisRecord 合成记录实体 type SynthesisRecord struct { id string playerID string recipeID string quantity int result *SynthesisResult createdAt time.Time } // NewSynthesisRecord 创建合成记录 func NewSynthesisRecord(playerID, recipeID string, quantity int, result *SynthesisResult) *SynthesisRecord { return &SynthesisRecord{ id: uuid.New().String(), playerID: playerID, recipeID: recipeID, quantity: quantity, result: result, createdAt: time.Now(), } } // GetID 获取记录ID func (sr *SynthesisRecord) GetID() string { return sr.id } // GetPlayerID 获取玩家ID func (sr *SynthesisRecord) GetPlayerID() string { return sr.playerID } // GetRecipeID 获取配方ID func (sr *SynthesisRecord) GetRecipeID() string { return sr.recipeID } // GetQuantity 获取合成数量 func (sr *SynthesisRecord) GetQuantity() int { return sr.quantity } // GetResult 获取合成结果 func (sr *SynthesisRecord) GetResult() *SynthesisResult { return sr.result } // GetCreatedAt 获取创建时间 func (sr *SynthesisRecord) GetCreatedAt() time.Time { return sr.createdAt } // SynthesisResult 合成结果 type SynthesisResult struct { successItems map[string]int // 成功获得的物品 failItems map[string]int // 失败获得的物品 successCount int // 成功次数 failCount int // 失败次数 } // NewSynthesisResult 创建合成结果 func NewSynthesisResult() *SynthesisResult { return &SynthesisResult{ successItems: make(map[string]int), failItems: make(map[string]int), successCount: 0, failCount: 0, } } // AddSuccessItem 添加成功物品 func (sr *SynthesisResult) AddSuccessItem(itemID string, quantity int) { sr.successItems[itemID] += quantity sr.successCount++ } // AddFailItem 添加失败物品 func (sr *SynthesisResult) AddFailItem(itemID string, quantity int) { sr.failItems[itemID] += quantity sr.failCount++ } // GetSuccessItems 获取成功物品 func (sr *SynthesisResult) GetSuccessItems() map[string]int { return sr.successItems } // GetFailItems 获取失败物品 func (sr *SynthesisResult) GetFailItems() map[string]int { return sr.failItems } // GetSuccessCount 获取成功次数 func (sr *SynthesisResult) GetSuccessCount() int { return sr.successCount } // GetFailCount 获取失败次数 func (sr *SynthesisResult) GetFailCount() int { return sr.failCount } // GetTotalCount 获取总次数 func (sr *SynthesisResult) GetTotalCount() int { return sr.successCount + sr.failCount } // GetSuccessRate 获取成功率 func (sr *SynthesisResult) GetSuccessRate() float64 { total := sr.GetTotalCount() if total == 0 { return 0 } return float64(sr.successCount) / float64(total) } ================================================ FILE: internal/domain/inventory/synthesis/errors.go ================================================ package synthesis import "errors" // 合成系统相关错误 var ( ErrInvalidRecipe = errors.New("invalid recipe") ErrRecipeNotFound = errors.New("recipe not found") ErrInvalidMaterial = errors.New("invalid material") ErrMaterialNotFound = errors.New("material not found") ErrInsufficientMaterial = errors.New("insufficient material") ErrInvalidQuantity = errors.New("invalid quantity") ErrRecipeAlreadyExists = errors.New("recipe already exists") ErrInsufficientLevel = errors.New("insufficient level") ErrConditionNotMet = errors.New("crafting condition not met") ErrCraftingInProgress = errors.New("crafting already in progress") ErrInvalidCategory = errors.New("invalid recipe category") ErrInvalidQuality = errors.New("invalid material quality") ErrMaxStackExceeded = errors.New("max stack size exceeded") ErrSynthesisFailed = errors.New("synthesis failed") ErrInvalidBonus = errors.New("invalid synthesis bonus") ) ================================================ FILE: internal/domain/inventory/synthesis/events.go ================================================ package synthesis import ( "github.com/google/uuid" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetEventData() interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` OccurredAt time.Time `json:"occurred_at"` } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // RecipeLearnedEvent 配方学习事件 type RecipeLearnedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` Recipe *Recipe `json:"recipe"` Source string `json:"source"` // 学习来源:quest, shop, drop, etc. } // NewRecipeLearnedEvent 创建配方学习事件 func NewRecipeLearnedEvent(playerID string, recipe *Recipe, source string) *RecipeLearnedEvent { return &RecipeLearnedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "RecipeLearned", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, Recipe: recipe, Source: source, } } // GetEventData 获取事件数据 func (e *RecipeLearnedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "recipe": e.Recipe, "source": e.Source, } } // MaterialObtainedEvent 材料获得事件 type MaterialObtainedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` Material *Material `json:"material"` Quantity int `json:"quantity"` Source string `json:"source"` } // NewMaterialObtainedEvent 创建材料获得事件 func NewMaterialObtainedEvent(playerID string, material *Material, quantity int, source string) *MaterialObtainedEvent { return &MaterialObtainedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "MaterialObtained", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, Material: material, Quantity: quantity, Source: source, } } // GetEventData 获取事件数据 func (e *MaterialObtainedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "material": e.Material, "quantity": e.Quantity, "source": e.Source, } } // SynthesisStartedEvent 合成开始事件 type SynthesisStartedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` RecipeID string `json:"recipe_id"` Quantity int `json:"quantity"` StartTime time.Time `json:"start_time"` FinishTime time.Time `json:"finish_time"` } // NewSynthesisStartedEvent 创建合成开始事件 func NewSynthesisStartedEvent(playerID, recipeID string, quantity int, craftTime time.Duration) *SynthesisStartedEvent { startTime := time.Now() return &SynthesisStartedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "SynthesisStarted", AggregateID: playerID, OccurredAt: startTime, }, PlayerID: playerID, RecipeID: recipeID, Quantity: quantity, StartTime: startTime, FinishTime: startTime.Add(craftTime), } } // GetEventData 获取事件数据 func (e *SynthesisStartedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "recipe_id": e.RecipeID, "quantity": e.Quantity, "start_time": e.StartTime, "finish_time": e.FinishTime, } } // SynthesisCompletedEvent 合成完成事件 type SynthesisCompletedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` RecipeID string `json:"recipe_id"` Quantity int `json:"quantity"` Result *SynthesisResult `json:"result"` Record *SynthesisRecord `json:"record"` } // NewSynthesisCompletedEvent 创建合成完成事件 func NewSynthesisCompletedEvent(playerID, recipeID string, quantity int, result *SynthesisResult, record *SynthesisRecord) *SynthesisCompletedEvent { return &SynthesisCompletedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "SynthesisCompleted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, RecipeID: recipeID, Quantity: quantity, Result: result, Record: record, } } // GetEventData 获取事件数据 func (e *SynthesisCompletedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "recipe_id": e.RecipeID, "quantity": e.Quantity, "result": e.Result, "record": e.Record, } } // MaterialConsumedEvent 材料消耗事件 type MaterialConsumedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` MaterialID string `json:"material_id"` Quantity int `json:"quantity"` Reason string `json:"reason"` // 消耗原因:synthesis, upgrade, etc. } // NewMaterialConsumedEvent 创建材料消耗事件 func NewMaterialConsumedEvent(playerID, materialID string, quantity int, reason string) *MaterialConsumedEvent { return &MaterialConsumedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "MaterialConsumed", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, MaterialID: materialID, Quantity: quantity, Reason: reason, } } // GetEventData 获取事件数据 func (e *MaterialConsumedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "material_id": e.MaterialID, "quantity": e.Quantity, "reason": e.Reason, } } // SynthesisBonusAppliedEvent 合成加成应用事件 type SynthesisBonusAppliedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` RecipeID string `json:"recipe_id"` Bonuses []*SynthesisBonus `json:"bonuses"` } // NewSynthesisBonusAppliedEvent 创建合成加成应用事件 func NewSynthesisBonusAppliedEvent(playerID, recipeID string, bonuses []*SynthesisBonus) *SynthesisBonusAppliedEvent { return &SynthesisBonusAppliedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "SynthesisBonusApplied", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, RecipeID: recipeID, Bonuses: bonuses, } } // GetEventData 获取事件数据 func (e *SynthesisBonusAppliedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "recipe_id": e.RecipeID, "bonuses": e.Bonuses, } } // RareItemSynthesizedEvent 稀有物品合成事件 type RareItemSynthesizedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` RecipeID string `json:"recipe_id"` ItemID string `json:"item_id"` Quality Quality `json:"quality"` Quantity int `json:"quantity"` } // NewRareItemSynthesizedEvent 创建稀有物品合成事件 func NewRareItemSynthesizedEvent(playerID, recipeID, itemID string, quality Quality, quantity int) *RareItemSynthesizedEvent { return &RareItemSynthesizedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "RareItemSynthesized", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, RecipeID: recipeID, ItemID: itemID, Quality: quality, Quantity: quantity, } } // GetEventData 获取事件数据 func (e *RareItemSynthesizedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "recipe_id": e.RecipeID, "item_id": e.ItemID, "quality": e.Quality, "quantity": e.Quantity, } } // SynthesisFailedEvent 合成失败事件 type SynthesisFailedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` RecipeID string `json:"recipe_id"` Quantity int `json:"quantity"` Reason string `json:"reason"` } // NewSynthesisFailedEvent 创建合成失败事件 func NewSynthesisFailedEvent(playerID, recipeID string, quantity int, reason string) *SynthesisFailedEvent { return &SynthesisFailedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "SynthesisFailed", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, RecipeID: recipeID, Quantity: quantity, Reason: reason, } } // GetEventData 获取事件数据 func (e *SynthesisFailedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "recipe_id": e.RecipeID, "quantity": e.Quantity, "reason": e.Reason, } } ================================================ FILE: internal/domain/inventory/synthesis/repository.go ================================================ package synthesis import "context" // SynthesisRepository 合成仓储接口 type SynthesisRepository interface { // SaveSynthesisAggregate 保存合成聚合根 SaveSynthesisAggregate(ctx context.Context, aggregate *SynthesisAggregate) error // GetSynthesisAggregate 获取合成聚合根 GetSynthesisAggregate(ctx context.Context, playerID string) (*SynthesisAggregate, error) // DeleteSynthesisAggregate 删除合成聚合根 DeleteSynthesisAggregate(ctx context.Context, playerID string) error // SaveRecipe 保存配方 SaveRecipe(ctx context.Context, playerID string, recipe *Recipe) error // GetRecipe 获取配方 GetRecipe(ctx context.Context, playerID, recipeID string) (*Recipe, error) // GetPlayerRecipes 获取玩家所有配方 GetPlayerRecipes(ctx context.Context, playerID string) ([]*Recipe, error) // DeleteRecipe 删除配方 DeleteRecipe(ctx context.Context, playerID, recipeID string) error // SaveMaterial 保存材料 SaveMaterial(ctx context.Context, playerID string, material *Material) error // GetMaterial 获取材料 GetMaterial(ctx context.Context, playerID, materialID string) (*Material, error) // GetPlayerMaterials 获取玩家所有材料 GetPlayerMaterials(ctx context.Context, playerID string) ([]*Material, error) // UpdateMaterialQuantity 更新材料数量 UpdateMaterialQuantity(ctx context.Context, playerID, materialID string, quantity int) error // DeleteMaterial 删除材料 DeleteMaterial(ctx context.Context, playerID, materialID string) error // SaveSynthesisRecord 保存合成记录 SaveSynthesisRecord(ctx context.Context, record *SynthesisRecord) error // GetSynthesisRecords 获取合成记录 GetSynthesisRecords(ctx context.Context, playerID string, limit, offset int) ([]*SynthesisRecord, error) // GetRecipesByCategory 根据分类获取配方 GetRecipesByCategory(ctx context.Context, playerID string, category RecipeCategory) ([]*Recipe, error) // GetMaterialsByType 根据类型获取材料 GetMaterialsByType(ctx context.Context, playerID string, materialType MaterialType) ([]*Material, error) // GetMaterialsByQuality 根据品质获取材料 GetMaterialsByQuality(ctx context.Context, playerID string, quality Quality) ([]*Material, error) // GetRecipeCount 获取配方数量 GetRecipeCount(ctx context.Context, playerID string) (int, error) // GetMaterialCount 获取材料数量 GetMaterialCount(ctx context.Context, playerID string) (int, error) } // RecipeTemplateRepository 配方模板仓储接口 type RecipeTemplateRepository interface { // GetRecipeTemplate 获取配方模板 GetRecipeTemplate(ctx context.Context, templateID string) (*RecipeTemplate, error) // GetRecipeTemplatesByCategory 根据分类获取配方模板 GetRecipeTemplatesByCategory(ctx context.Context, category RecipeCategory) ([]*RecipeTemplate, error) // GetRecipeTemplatesByLevel 根据等级获取配方模板 GetRecipeTemplatesByLevel(ctx context.Context, minLevel, maxLevel int) ([]*RecipeTemplate, error) // SaveRecipeTemplate 保存配方模板 SaveRecipeTemplate(ctx context.Context, template *RecipeTemplate) error // DeleteRecipeTemplate 删除配方模板 DeleteRecipeTemplate(ctx context.Context, templateID string) error // GetAllRecipeTemplates 获取所有配方模板 GetAllRecipeTemplates(ctx context.Context) ([]*RecipeTemplate, error) } // MaterialTemplateRepository 材料模板仓储接口 type MaterialTemplateRepository interface { // GetMaterialTemplate 获取材料模板 GetMaterialTemplate(ctx context.Context, templateID string) (*MaterialTemplate, error) // GetMaterialTemplatesByType 根据类型获取材料模板 GetMaterialTemplatesByType(ctx context.Context, materialType MaterialType) ([]*MaterialTemplate, error) // GetMaterialTemplatesByQuality 根据品质获取材料模板 GetMaterialTemplatesByQuality(ctx context.Context, quality Quality) ([]*MaterialTemplate, error) // SaveMaterialTemplate 保存材料模板 SaveMaterialTemplate(ctx context.Context, template *MaterialTemplate) error // DeleteMaterialTemplate 删除材料模板 DeleteMaterialTemplate(ctx context.Context, templateID string) error // GetAllMaterialTemplates 获取所有材料模板 GetAllMaterialTemplates(ctx context.Context) ([]*MaterialTemplate, error) } // RecipeTemplate 配方模板 type RecipeTemplate struct { ID string `json:"id"` Name string `json:"name"` Category RecipeCategory `json:"category"` Requirements []*MaterialRequirement `json:"requirements"` Outputs []*ItemOutput `json:"outputs"` FailOutputs []*ItemOutput `json:"fail_outputs"` SuccessRate float64 `json:"success_rate"` CraftTime int64 `json:"craft_time"` // 毫秒 RequireLevel int `json:"require_level"` Conditions []*CraftingCondition `json:"conditions"` Description string `json:"description"` IconURL string `json:"icon_url"` } // CreateRecipeFromTemplate 从模板创建配方 func (rt *RecipeTemplate) CreateRecipeFromTemplate() *Recipe { recipe := NewRecipe(rt.Name, rt.Category, rt.SuccessRate) // 复制材料需求 for _, req := range rt.Requirements { recipe.AddRequirement(req.MaterialID, req.Quantity) } // 复制产出 for _, output := range rt.Outputs { recipe.AddOutput(output.ItemID, output.Quantity, output.Probability) } // 复制失败产出 for _, failOutput := range rt.FailOutputs { recipe.AddFailOutput(failOutput.ItemID, failOutput.Quantity, failOutput.Probability) } recipe.SetRequireLevel(rt.RequireLevel) recipe.SetDescription(rt.Description) return recipe } // MaterialTemplate 材料模板 type MaterialTemplate struct { ID string `json:"id"` Name string `json:"name"` Type MaterialType `json:"type"` Quality Quality `json:"quality"` MaxStack int `json:"max_stack"` Description string `json:"description"` IconURL string `json:"icon_url"` ObtainMethods []string `json:"obtain_methods"` // 获取方式 } // CreateMaterialFromTemplate 从模板创建材料 func (mt *MaterialTemplate) CreateMaterialFromTemplate(quantity int) *Material { material := NewMaterial(mt.ID, mt.Name, mt.Type, mt.Quality, quantity) material.SetMaxStack(mt.MaxStack) material.SetDescription(mt.Description) return material } ================================================ FILE: internal/domain/inventory/synthesis/service.go ================================================ package synthesis import ( "math/rand" "time" ) // SynthesisService 合成领域服务 type SynthesisService struct { recipeFactory *RecipeFactory materialFactory *MaterialFactory bonusCalculator *BonusCalculator } // NewSynthesisService 创建合成服务 func NewSynthesisService() *SynthesisService { return &SynthesisService{ recipeFactory: NewRecipeFactory(), materialFactory: NewMaterialFactory(), bonusCalculator: NewBonusCalculator(), } } // ValidateRecipe 验证配方 func (ss *SynthesisService) ValidateRecipe(recipe *Recipe) error { if recipe == nil { return ErrInvalidRecipe } if len(recipe.GetRequirements()) == 0 { return ErrInvalidRecipe } if len(recipe.GetOutputs()) == 0 { return ErrInvalidRecipe } if recipe.GetSuccessRate() < 0 || recipe.GetSuccessRate() > 1 { return ErrInvalidRecipe } return nil } // CalculateEnhancedSuccessRate 计算增强成功率 func (ss *SynthesisService) CalculateEnhancedSuccessRate(baseRate float64, bonuses []*SynthesisBonus, playerLevel int) float64 { enhancedRate := baseRate // 应用加成 for _, bonus := range bonuses { if bonus.GetBonusType() == BonusTypeSuccessRate { enhancedRate += bonus.GetBonusValue() } } // 玩家等级加成 levelBonus := float64(playerLevel) * 0.001 // 每级0.1%加成 enhancedRate += levelBonus // 确保在合理范围内 if enhancedRate > 1.0 { enhancedRate = 1.0 } else if enhancedRate < 0.0 { enhancedRate = 0.0 } return enhancedRate } // CalculateCraftTime 计算制作时间 func (ss *SynthesisService) CalculateCraftTime(baseCraftTime time.Duration, bonuses []*SynthesisBonus) time.Duration { speedMultiplier := 1.0 // 应用速度加成 for _, bonus := range bonuses { if bonus.GetBonusType() == BonusTypeCraftSpeed { speedMultiplier += bonus.GetBonusValue() } } // 计算最终时间 finalTime := time.Duration(float64(baseCraftTime) / speedMultiplier) // 最小时间限制 minTime := time.Second * 1 if finalTime < minTime { finalTime = minTime } return finalTime } // CalculateMaterialConsumption 计算材料消耗 func (ss *SynthesisService) CalculateMaterialConsumption(requirements []*MaterialRequirement, bonuses []*SynthesisBonus) []*MaterialRequirement { materialSaveRate := 0.0 // 计算材料节省率 for _, bonus := range bonuses { if bonus.GetBonusType() == BonusTypeMaterialSave { materialSaveRate += bonus.GetBonusValue() } } // 应用材料节省 adjustedRequirements := make([]*MaterialRequirement, len(requirements)) for i, req := range requirements { adjustedQuantity := int(float64(req.GetQuantity()) * (1.0 - materialSaveRate)) if adjustedQuantity < 1 { adjustedQuantity = 1 // 至少需要1个 } adjustedRequirements[i] = NewMaterialRequirement(req.GetMaterialID(), adjustedQuantity) } return adjustedRequirements } // GenerateRandomMaterial 生成随机材料 func (ss *SynthesisService) GenerateRandomMaterial(materialType MaterialType, playerLevel int) *Material { return ss.materialFactory.CreateRandomMaterial(materialType, playerLevel) } // CreateRecipeFromTemplate 从模板创建配方 func (ss *SynthesisService) CreateRecipeFromTemplate(templateID string, playerLevel int) *Recipe { return ss.recipeFactory.CreateFromTemplate(templateID, playerLevel) } // RecipeFactory 配方工厂 type RecipeFactory struct { random *rand.Rand } // NewRecipeFactory 创建配方工厂 func NewRecipeFactory() *RecipeFactory { return &RecipeFactory{ random: rand.New(rand.NewSource(time.Now().UnixNano())), } } // CreateFromTemplate 从模板创建配方 func (rf *RecipeFactory) CreateFromTemplate(templateID string, playerLevel int) *Recipe { // 这里应该从配置或数据库加载模板 // 为了演示,创建一个示例配方 recipe := NewRecipe("示例配方", RecipeCategoryWeapon, 0.8) recipe.AddRequirement("iron_ore", 5) recipe.AddRequirement("coal", 2) recipe.AddOutput("iron_sword", 1, 1.0) recipe.SetRequireLevel(playerLevel) return recipe } // CreateRandomRecipe 创建随机配方 func (rf *RecipeFactory) CreateRandomRecipe(category RecipeCategory, playerLevel int) *Recipe { successRate := 0.5 + rf.random.Float64()*0.4 // 50%-90%成功率 recipe := NewRecipe(rf.generateRecipeName(category), category, successRate) // 添加随机材料需求 materialCount := rf.random.Intn(3) + 2 // 2-4种材料 for i := 0; i < materialCount; i++ { materialID := rf.generateMaterialID(category) quantity := rf.random.Intn(5) + 1 recipe.AddRequirement(materialID, quantity) } // 添加产出 outputID := rf.generateOutputID(category) recipe.AddOutput(outputID, 1, 1.0) recipe.SetRequireLevel(playerLevel) return recipe } // generateRecipeName 生成配方名称 func (rf *RecipeFactory) generateRecipeName(category RecipeCategory) string { names := map[RecipeCategory][]string{ RecipeCategoryWeapon: {"铁剑制作", "钢刀锻造", "魔法杖合成"}, RecipeCategoryArmor: {"皮甲制作", "铁甲锻造", "法袍缝制"}, RecipeCategoryAccessory: {"戒指打造", "项链制作", "护符合成"}, RecipeCategoryConsumable: {"生命药水", "魔法药水", "解毒剂"}, } nameList := names[category] if len(nameList) == 0 { return "未知配方" } return nameList[rf.random.Intn(len(nameList))] } // generateMaterialID 生成材料ID func (rf *RecipeFactory) generateMaterialID(category RecipeCategory) string { materials := map[RecipeCategory][]string{ RecipeCategoryWeapon: {"iron_ore", "coal", "leather"}, RecipeCategoryArmor: {"leather", "cloth", "metal_plate"}, RecipeCategoryAccessory: {"gem", "gold", "silver"}, RecipeCategoryConsumable: {"herb", "water", "magic_essence"}, } materialList := materials[category] if len(materialList) == 0 { return "unknown_material" } return materialList[rf.random.Intn(len(materialList))] } // generateOutputID 生成产出ID func (rf *RecipeFactory) generateOutputID(category RecipeCategory) string { outputs := map[RecipeCategory][]string{ RecipeCategoryWeapon: {"iron_sword", "steel_blade", "magic_wand"}, RecipeCategoryArmor: {"leather_armor", "iron_armor", "magic_robe"}, RecipeCategoryAccessory: {"power_ring", "magic_necklace", "protection_amulet"}, RecipeCategoryConsumable: {"health_potion", "mana_potion", "antidote"}, } outputList := outputs[category] if len(outputList) == 0 { return "unknown_item" } return outputList[rf.random.Intn(len(outputList))] } // MaterialFactory 材料工厂 type MaterialFactory struct { random *rand.Rand } // NewMaterialFactory 创建材料工厂 func NewMaterialFactory() *MaterialFactory { return &MaterialFactory{ random: rand.New(rand.NewSource(time.Now().UnixNano())), } } // CreateRandomMaterial 创建随机材料 func (mf *MaterialFactory) CreateRandomMaterial(materialType MaterialType, playerLevel int) *Material { quality := mf.determineQuality(playerLevel) quantity := mf.random.Intn(10) + 1 materialID := mf.generateMaterialID(materialType) materialName := mf.generateMaterialName(materialType, quality) return NewMaterial(materialID, materialName, materialType, quality, quantity) } // determineQuality 确定品质 func (mf *MaterialFactory) determineQuality(playerLevel int) Quality { roll := mf.random.Float64() levelBonus := float64(playerLevel) / 100.0 switch { case roll < 0.5-levelBonus*0.1: return QualityCommon case roll < 0.75-levelBonus*0.05: return QualityUncommon case roll < 0.9: return QualityRare case roll < 0.97: return QualityEpic case roll < 0.995: return QualityLegendary default: return QualityMythic } } // generateMaterialID 生成材料ID func (mf *MaterialFactory) generateMaterialID(materialType MaterialType) string { ids := map[MaterialType][]string{ MaterialTypeOre: {"iron_ore", "copper_ore", "gold_ore"}, MaterialTypeHerb: {"healing_herb", "mana_herb", "poison_herb"}, MaterialTypeLeather: {"wolf_leather", "bear_leather", "dragon_leather"}, MaterialTypeCloth: {"cotton_cloth", "silk_cloth", "magic_cloth"}, MaterialTypeWood: {"oak_wood", "pine_wood", "magic_wood"}, } idList := ids[materialType] if len(idList) == 0 { return "unknown_material" } return idList[mf.random.Intn(len(idList))] } // generateMaterialName 生成材料名称 func (mf *MaterialFactory) generateMaterialName(materialType MaterialType, quality Quality) string { qualityPrefix := map[Quality]string{ QualityCommon: "普通的", QualityUncommon: "优质的", QualityRare: "稀有的", QualityEpic: "史诗的", QualityLegendary: "传说的", QualityMythic: "神话的", } typeNames := map[MaterialType]string{ MaterialTypeOre: "矿石", MaterialTypeHerb: "草药", MaterialTypeLeather: "皮革", MaterialTypeCloth: "布料", MaterialTypeWood: "木材", } prefix := qualityPrefix[quality] typeName := typeNames[materialType] return prefix + typeName } // BonusCalculator 加成计算器 type BonusCalculator struct{} // NewBonusCalculator 创建加成计算器 func NewBonusCalculator() *BonusCalculator { return &BonusCalculator{} } // CalculateTotalBonus 计算总加成 func (bc *BonusCalculator) CalculateTotalBonus(bonuses []*SynthesisBonus, bonusType BonusType) float64 { total := 0.0 for _, bonus := range bonuses { if bonus.GetBonusType() == bonusType { total += bonus.GetBonusValue() } } return total } // ApplyQualityBonus 应用品质加成 func (bc *BonusCalculator) ApplyQualityBonus(baseValue int, quality Quality) int { multiplier := quality.GetQualityMultiplier() return int(float64(baseValue) * multiplier) } ================================================ FILE: internal/domain/inventory/synthesis/value_object.go ================================================ package synthesis // RecipeCategory 配方分类 type RecipeCategory int const ( RecipeCategoryWeapon RecipeCategory = iota + 1 RecipeCategoryArmor RecipeCategoryAccessory RecipeCategoryConsumable RecipeCategoryMaterial RecipeCategorySpecial RecipeCategoryEnchant RecipeCategoryGem ) // String 返回配方分类字符串 func (rc RecipeCategory) String() string { switch rc { case RecipeCategoryWeapon: return "weapon" case RecipeCategoryArmor: return "armor" case RecipeCategoryAccessory: return "accessory" case RecipeCategoryConsumable: return "consumable" case RecipeCategoryMaterial: return "material" case RecipeCategorySpecial: return "special" case RecipeCategoryEnchant: return "enchant" case RecipeCategoryGem: return "gem" default: return "unknown" } } // MaterialType 材料类型 type MaterialType int const ( MaterialTypeOre MaterialType = iota + 1 MaterialTypeHerb MaterialTypeLeather MaterialTypeCloth MaterialTypeWood MaterialTypeStone MaterialTypeMetal MaterialTypeGem MaterialTypeMagic MaterialTypeRare ) // String 返回材料类型字符串 func (mt MaterialType) String() string { switch mt { case MaterialTypeOre: return "ore" case MaterialTypeHerb: return "herb" case MaterialTypeLeather: return "leather" case MaterialTypeCloth: return "cloth" case MaterialTypeWood: return "wood" case MaterialTypeStone: return "stone" case MaterialTypeMetal: return "metal" case MaterialTypeGem: return "gem" case MaterialTypeMagic: return "magic" case MaterialTypeRare: return "rare" default: return "unknown" } } // Quality 品质 type Quality int const ( QualityCommon Quality = iota + 1 QualityUncommon QualityRare QualityEpic QualityLegendary QualityMythic ) // String 返回品质字符串 func (q Quality) String() string { switch q { case QualityCommon: return "common" case QualityUncommon: return "uncommon" case QualityRare: return "rare" case QualityEpic: return "epic" case QualityLegendary: return "legendary" case QualityMythic: return "mythic" default: return "unknown" } } // GetQualityMultiplier 获取品质倍数 func (q Quality) GetQualityMultiplier() float64 { switch q { case QualityCommon: return 1.0 case QualityUncommon: return 1.2 case QualityRare: return 1.5 case QualityEpic: return 2.0 case QualityLegendary: return 3.0 case QualityMythic: return 5.0 default: return 1.0 } } // MaterialRequirement 材料需求值对象 type MaterialRequirement struct { MaterialID string `json:"material_id"` Quantity int `json:"quantity"` } // NewMaterialRequirement 创建材料需求 func NewMaterialRequirement(materialID string, quantity int) *MaterialRequirement { return &MaterialRequirement{ MaterialID: materialID, Quantity: quantity, } } // GetMaterialID 获取材料ID func (mr *MaterialRequirement) GetMaterialID() string { return mr.MaterialID } // GetQuantity 获取数量 func (mr *MaterialRequirement) GetQuantity() int { return mr.Quantity } // ItemOutput 物品产出值对象 type ItemOutput struct { ItemID string `json:"item_id"` Quantity int `json:"quantity"` Probability float64 `json:"probability"` // 产出概率 0-1 } // NewItemOutput 创建物品产出 func NewItemOutput(itemID string, quantity int, probability float64) *ItemOutput { return &ItemOutput{ ItemID: itemID, Quantity: quantity, Probability: probability, } } // GetItemID 获取物品ID func (io *ItemOutput) GetItemID() string { return io.ItemID } // GetQuantity 获取数量 func (io *ItemOutput) GetQuantity() int { return io.Quantity } // GetProbability 获取概率 func (io *ItemOutput) GetProbability() float64 { return io.Probability } // SynthesisBonus 合成加成值对象 type SynthesisBonus struct { bonusType BonusType bonusValue float64 duration int // 持续时间(秒),0表示永久 description string } // BonusType 加成类型 type BonusType int const ( BonusTypeSuccessRate BonusType = iota + 1 BonusTypeCraftSpeed BonusTypeMaterialSave BonusTypeQualityUp BonusTypeExtraOutput ) // String 返回加成类型字符串 func (bt BonusType) String() string { switch bt { case BonusTypeSuccessRate: return "success_rate" case BonusTypeCraftSpeed: return "craft_speed" case BonusTypeMaterialSave: return "material_save" case BonusTypeQualityUp: return "quality_up" case BonusTypeExtraOutput: return "extra_output" default: return "unknown" } } // NewSynthesisBonus 创建合成加成 func NewSynthesisBonus(bonusType BonusType, bonusValue float64, duration int, description string) *SynthesisBonus { return &SynthesisBonus{ bonusType: bonusType, bonusValue: bonusValue, duration: duration, description: description, } } // GetBonusType 获取加成类型 func (sb *SynthesisBonus) GetBonusType() BonusType { return sb.bonusType } // GetBonusValue 获取加成值 func (sb *SynthesisBonus) GetBonusValue() float64 { return sb.bonusValue } // GetDuration 获取持续时间 func (sb *SynthesisBonus) GetDuration() int { return sb.duration } // GetDescription 获取描述 func (sb *SynthesisBonus) GetDescription() string { return sb.description } // IsPermanent 是否永久 func (sb *SynthesisBonus) IsPermanent() bool { return sb.duration == 0 } // CraftingCondition 制作条件值对象 type CraftingCondition struct { conditionType ConditionType value interface{} description string } // ConditionType 条件类型 type ConditionType int const ( ConditionTypeLevel ConditionType = iota + 1 ConditionTypeSkill ConditionTypeItem ConditionTypeQuest ConditionTypeAchievement ConditionTypeTime ConditionTypeLocation ) // String 返回条件类型字符串 func (ct ConditionType) String() string { switch ct { case ConditionTypeLevel: return "level" case ConditionTypeSkill: return "skill" case ConditionTypeItem: return "item" case ConditionTypeQuest: return "quest" case ConditionTypeAchievement: return "achievement" case ConditionTypeTime: return "time" case ConditionTypeLocation: return "location" default: return "unknown" } } // NewCraftingCondition 创建制作条件 func NewCraftingCondition(conditionType ConditionType, value interface{}, description string) *CraftingCondition { return &CraftingCondition{ conditionType: conditionType, value: value, description: description, } } // GetConditionType 获取条件类型 func (cc *CraftingCondition) GetConditionType() ConditionType { return cc.conditionType } // GetValue 获取条件值 func (cc *CraftingCondition) GetValue() interface{} { return cc.value } // GetDescription 获取描述 func (cc *CraftingCondition) GetDescription() string { return cc.description } ================================================ FILE: internal/domain/mapmanager/map.go ================================================ package mapmanager import ( "context" "fmt" "sync" character "greatestworks/internal/domain/character" ) // Map 地图聚合根 type Map struct { mu sync.RWMutex id int32 // 地图ID name string // 地图名称 width int32 // 地图宽度 height int32 // 地图高度 entities map[character.EntityID]*character.Entity // 地图内的所有实体 // AOI系统(简化实现) aoiGrid *AOIGrid // 视野与广播 viewRadius float32 visibleSets map[character.EntityID]map[character.EntityID]struct{} broadcaster BroadcastFn } // NewMap 创建地图 func NewMap(id int32, name string, width, height int32) *Map { return &Map{ id: id, name: name, width: width, height: height, entities: make(map[character.EntityID]*character.Entity), aoiGrid: NewAOIGrid(width, height, 100), // 100单位网格大小 viewRadius: 200, visibleSets: make(map[character.EntityID]map[character.EntityID]struct{}), } } // BroadcastFn 应用层注入的广播函数:向指定接收者发送某个主题的消息 type BroadcastFn func(recipients []character.EntityID, topic string, payload interface{}) // SetBroadcaster 设置广播函数 func (m *Map) SetBroadcaster(fn BroadcastFn) { m.mu.Lock() m.broadcaster = fn m.mu.Unlock() } // Enter 实体进入地图 func (m *Map) Enter(ctx context.Context, entity *character.Entity) error { m.mu.Lock() defer m.mu.Unlock() if entity == nil { return fmt.Errorf("entity is nil") } entityID := entity.ID() if _, exists := m.entities[entityID]; exists { return fmt.Errorf("entity already in map: %d", entityID) } m.entities[entityID] = entity entity.SetMap(m) // 添加到AOI网格 pos := entity.Position2D() m.aoiGrid.Add(entityID, pos.X, pos.Y) // 初始化可见集并广播出现 m.refreshVisibilityFor(entityID) return nil } // Leave 实体离开地图 func (m *Map) Leave(ctx context.Context, entityID character.EntityID) error { m.mu.Lock() defer m.mu.Unlock() entity, exists := m.entities[entityID] if !exists { return fmt.Errorf("entity not in map: %d", entityID) } // 从AOI网格移除 pos := entity.Position2D() m.aoiGrid.Remove(entityID, pos.X, pos.Y) // 广播消失并清理可见集 m.broadcastDisappear(entityID) delete(m.entities, entityID) entity.SetMap(nil) // TODO: 广播实体离开事件给周围玩家 return nil } // UpdatePosition 更新实体位置 func (m *Map) UpdatePosition(entityID character.EntityID, newPos character.Vector3) error { m.mu.Lock() defer m.mu.Unlock() entity, exists := m.entities[entityID] if !exists { return fmt.Errorf("entity not in map: %d", entityID) } oldPos := entity.Position2D() newPos2D := newPos.ToVector2() // 更新AOI m.aoiGrid.Move(entityID, oldPos.X, oldPos.Y, newPos2D.X, newPos2D.Y) // 更新实体位置 entity.SetPosition(newPos) // 刷新视野与广播移动 m.refreshVisibilityFor(entityID) m.broadcastMove(entityID, newPos) return nil } // GetEntitiesInRange 获取范围内的实体 func (m *Map) GetEntitiesInRange(centerX, centerY, radius float32) []*character.Entity { m.mu.RLock() defer m.mu.RUnlock() nearbyIDs := m.aoiGrid.GetNearby(centerX, centerY, radius) entities := make([]*character.Entity, 0, len(nearbyIDs)) for _, id := range nearbyIDs { if entity, exists := m.entities[id]; exists { entities = append(entities, entity) } } return entities } // GetEntity 获取实体 func (m *Map) GetEntity(entityID character.EntityID) *character.Entity { m.mu.RLock() defer m.mu.RUnlock() return m.entities[entityID] } // GetAllEntities 获取所有实体 func (m *Map) GetAllEntities() []*character.Entity { m.mu.RLock() defer m.mu.RUnlock() entities := make([]*character.Entity, 0, len(m.entities)) for _, entity := range m.entities { entities = append(entities, entity) } return entities } // ID 获取地图ID func (m *Map) ID() int32 { return m.id } // Name 获取地图名称 func (m *Map) Name() string { return m.name } // ===== 视野与广播辅助 ===== // refreshVisibilityFor 计算并更新某实体的可见集,并广播出现/消失 func (m *Map) refreshVisibilityFor(entityID character.EntityID) { if m.broadcaster == nil { // 未注入广播器,仍然维护可见集以备将来使用 } entity, ok := m.entities[entityID] if !ok { return } pos := entity.Position2D() nearby := m.aoiGrid.GetNearby(pos.X, pos.Y, m.viewRadius) // 构建新集合(不包含自身) newSet := make(map[character.EntityID]struct{}) filtered := make([]character.EntityID, 0, len(nearby)) for _, id := range nearby { if id == entityID { continue } newSet[id] = struct{}{} filtered = append(filtered, id) } oldSet := m.visibleSets[entityID] if oldSet == nil { oldSet = make(map[character.EntityID]struct{}) } // 计算出现与消失 appear := make([]character.EntityID, 0) disappear := make([]character.EntityID, 0) for id := range newSet { if _, ok := oldSet[id]; !ok { appear = append(appear, id) } } for id := range oldSet { if _, ok := newSet[id]; !ok { disappear = append(disappear, id) } } // 保存新集 m.visibleSets[entityID] = newSet // 广播给自身:别人出现/消失 if m.broadcaster != nil { if len(appear) > 0 { payload := m.buildAppearPayload(appear) m.broadcaster([]character.EntityID{entityID}, "entity_appear", payload) } if len(disappear) > 0 { payload := m.buildDisappearPayload(disappear) m.broadcaster([]character.EntityID{entityID}, "entity_disappear", payload) } } // 对于 appear 集合中的每个实体,也需要让对方看到“我出现” if m.broadcaster != nil && len(appear) > 0 { me := m.buildAppearPayload([]character.EntityID{entityID}) m.broadcaster(appear, "entity_appear", me) } } // broadcastDisappear 在实体离开地图时通知其可见范围内的对象 func (m *Map) broadcastDisappear(entityID character.EntityID) { // 通知所有将其视为可见的实体(这里简化:对所有实体检查其可见集) recipients := make([]character.EntityID, 0) for viewer, set := range m.visibleSets { if viewer == entityID { continue } if _, ok := set[entityID]; ok { recipients = append(recipients, viewer) delete(set, entityID) } } delete(m.visibleSets, entityID) if m.broadcaster != nil && len(recipients) > 0 { payload := m.buildDisappearPayload([]character.EntityID{entityID}) m.broadcaster(recipients, "entity_disappear", payload) } } // broadcastMove 将移动事件广播给当前可见该实体的对象 func (m *Map) broadcastMove(entityID character.EntityID, pos character.Vector3) { viewers := make([]character.EntityID, 0) for viewer, set := range m.visibleSets { if viewer == entityID { continue } if _, ok := set[entityID]; ok { viewers = append(viewers, viewer) } } if m.broadcaster != nil && len(viewers) > 0 { payload := EntityMove{ID: entityID, Position: pos} m.broadcaster(viewers, "entity_move", payload) } } // buildAppearPayload 构造出现列表payload func (m *Map) buildAppearPayload(ids []character.EntityID) []EntityAppear { res := make([]EntityAppear, 0, len(ids)) for _, id := range ids { if e, ok := m.entities[id]; ok { t := e.GetTransform() res = append(res, EntityAppear{ID: id, Position: t.Position, Direction: t.Direction}) } } return res } // buildDisappearPayload 构造消失列表payload func (m *Map) buildDisappearPayload(ids []character.EntityID) []EntityDisappear { res := make([]EntityDisappear, 0, len(ids)) for _, id := range ids { res = append(res, EntityDisappear{ID: id}) } return res } // BroadcastInRange 使用AOI在范围内广播 func (m *Map) BroadcastInRange(x, y, radius float32, topic string, payload interface{}) { if m.broadcaster == nil { return } ids := m.aoiGrid.GetNearby(x, y, radius) if len(ids) == 0 { return } m.broadcaster(ids, topic, payload) } // BroadcastTo 向给定实体列表广播 func (m *Map) BroadcastTo(recipients []character.EntityID, topic string, payload interface{}) { if m.broadcaster == nil || len(recipients) == 0 { return } m.broadcaster(recipients, topic, payload) } // ===== 广播数据结构 ===== type EntityAppear struct { ID character.EntityID Position character.Vector3 Direction character.Vector3 } type EntityDisappear struct { ID character.EntityID } type EntityMove struct { ID character.EntityID Position character.Vector3 } // AOIGrid AOI网格系统 type AOIGrid struct { mu sync.RWMutex width int32 height int32 gridSize int32 gridsX int32 gridsY int32 grids map[int32]map[character.EntityID]bool // 网格索引 -> 实体ID集合 entityGrid map[character.EntityID]int32 // 实体ID -> 网格索引 } // NewAOIGrid 创建AOI网格 func NewAOIGrid(width, height, gridSize int32) *AOIGrid { gridsX := (width + gridSize - 1) / gridSize gridsY := (height + gridSize - 1) / gridSize return &AOIGrid{ width: width, height: height, gridSize: gridSize, gridsX: gridsX, gridsY: gridsY, grids: make(map[int32]map[character.EntityID]bool), entityGrid: make(map[character.EntityID]int32), } } // Add 添加实体到网格 func (aoi *AOIGrid) Add(entityID character.EntityID, x, y float32) { aoi.mu.Lock() defer aoi.mu.Unlock() gridIndex := aoi.getGridIndex(x, y) if _, exists := aoi.grids[gridIndex]; !exists { aoi.grids[gridIndex] = make(map[character.EntityID]bool) } aoi.grids[gridIndex][entityID] = true aoi.entityGrid[entityID] = gridIndex } // Remove 从网格移除实体 func (aoi *AOIGrid) Remove(entityID character.EntityID, x, y float32) { aoi.mu.Lock() defer aoi.mu.Unlock() if gridIndex, exists := aoi.entityGrid[entityID]; exists { if grid, ok := aoi.grids[gridIndex]; ok { delete(grid, entityID) } delete(aoi.entityGrid, entityID) } } // Move 移动实体 func (aoi *AOIGrid) Move(entityID character.EntityID, oldX, oldY, newX, newY float32) { oldGridIndex := aoi.getGridIndex(oldX, oldY) newGridIndex := aoi.getGridIndex(newX, newY) if oldGridIndex == newGridIndex { return // 未跨网格 } aoi.mu.Lock() defer aoi.mu.Unlock() // 从旧网格移除 if grid, exists := aoi.grids[oldGridIndex]; exists { delete(grid, entityID) } // 添加到新网格 if _, exists := aoi.grids[newGridIndex]; !exists { aoi.grids[newGridIndex] = make(map[character.EntityID]bool) } aoi.grids[newGridIndex][entityID] = true aoi.entityGrid[entityID] = newGridIndex } // GetNearby 获取附近的实体 func (aoi *AOIGrid) GetNearby(x, y, radius float32) []character.EntityID { aoi.mu.RLock() defer aoi.mu.RUnlock() // 计算需要检查的网格范围 gridRadius := int32(radius/float32(aoi.gridSize)) + 1 centerGridX, centerGridY := aoi.posToGrid(x, y) nearbyEntities := make([]character.EntityID, 0) visited := make(map[character.EntityID]bool) for dx := -gridRadius; dx <= gridRadius; dx++ { for dy := -gridRadius; dy <= gridRadius; dy++ { gridX := centerGridX + dx gridY := centerGridY + dy if gridX < 0 || gridX >= aoi.gridsX || gridY < 0 || gridY >= aoi.gridsY { continue } gridIndex := gridY*aoi.gridsX + gridX if grid, exists := aoi.grids[gridIndex]; exists { for entityID := range grid { if !visited[entityID] { visited[entityID] = true nearbyEntities = append(nearbyEntities, entityID) } } } } } return nearbyEntities } // getGridIndex 获取网格索引 func (aoi *AOIGrid) getGridIndex(x, y float32) int32 { gridX, gridY := aoi.posToGrid(x, y) return gridY*aoi.gridsX + gridX } // posToGrid 位置转网格坐标 func (aoi *AOIGrid) posToGrid(x, y float32) (int32, int32) { gridX := int32(x) / aoi.gridSize gridY := int32(y) / aoi.gridSize if gridX < 0 { gridX = 0 } if gridX >= aoi.gridsX { gridX = aoi.gridsX - 1 } if gridY < 0 { gridY = 0 } if gridY >= aoi.gridsY { gridY = aoi.gridsY - 1 } return gridX, gridY } ================================================ FILE: internal/domain/minigame/aggregate.go ================================================ package minigame import ( "fmt" "strconv" "sync" "time" ) // MinigameAggregate 小游戏聚合根 type MinigameAggregate struct { // 基础信息 ID string `json:"id" bson:"_id"` GameID string `json:"game_id" bson:"game_id"` GameType GameType `json:"game_type" bson:"game_type"` Category GameCategory `json:"category" bson:"category"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` // 游戏配置 Config *GameConfig `json:"config" bson:"config"` Rules *GameRules `json:"rules" bson:"rules"` Settings *GameSettings `json:"settings" bson:"settings"` // 游戏状态 Status GameStatus `json:"status" bson:"status"` Phase GamePhase `json:"phase" bson:"phase"` IsActive bool `json:"is_active" bson:"is_active"` StartTime *time.Time `json:"start_time,omitempty" bson:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty" bson:"end_time,omitempty"` Duration time.Duration `json:"duration" bson:"duration"` // 玩家信息 CreatorID uint64 `json:"creator_id" bson:"creator_id"` Players []*GamePlayer `json:"players" bson:"players"` MaxPlayers int32 `json:"max_players" bson:"max_players"` MinPlayers int32 `json:"min_players" bson:"min_players"` // 游戏数据 GameData *GameData `json:"game_data" bson:"game_data"` Scores []*GameScore `json:"scores" bson:"scores"` Results []*GameResult `json:"results" bson:"results"` // 奖励信息 Rewards []*GameReward `json:"rewards" bson:"rewards"` RewardPool *RewardPool `json:"reward_pool" bson:"reward_pool"` // 统计信息 Statistics *GameStatistics `json:"statistics" bson:"statistics"` PlayCount int64 `json:"play_count" bson:"play_count"` WinCount int64 `json:"win_count" bson:"win_count"` LoseCount int64 `json:"lose_count" bson:"lose_count"` // 版本和时间戳 Version int64 `json:"version" bson:"version"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` // 内部状态 mutex sync.RWMutex `json:"-" bson:"-"` dirty bool `json:"-" bson:"-"` events []MinigameEvent `json:"-" bson:"-"` } // NewMinigameAggregate 创建新的小游戏聚合 func NewMinigameAggregate(gameID string, gameType GameType, category GameCategory, creatorID uint64) *MinigameAggregate { now := time.Now() return &MinigameAggregate{ ID: generateMinigameID(gameID, now), GameID: gameID, GameType: gameType, Category: category, CreatorID: creatorID, Status: GameStatusWaiting, Phase: GamePhaseWaiting, IsActive: true, Players: make([]*GamePlayer, 0), MaxPlayers: DefaultMaxPlayers, MinPlayers: DefaultMinPlayers, Scores: make([]*GameScore, 0), Results: make([]*GameResult, 0), Rewards: make([]*GameReward, 0), Statistics: NewGameStatistics(), PlayCount: 0, WinCount: 0, LoseCount: 0, Version: 1, CreatedAt: now, UpdatedAt: now, dirty: true, events: make([]MinigameEvent, 0), } } // StartGame 开始游戏 func (mg *MinigameAggregate) StartGame() error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusWaiting { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusWaiting, "start_game") } // 检查玩家数量 if int32(len(mg.Players)) < mg.MinPlayers { return NewMinigameInsufficientPlayersError(mg.GameID, int32(len(mg.Players)), mg.MinPlayers) } // 更新游戏状态 now := time.Now() mg.Status = GameStatusRunning mg.Phase = GamePhaseRunning mg.StartTime = &now mg.PlayCount++ // 初始化游戏数据 mg.initializeGameData() // 发布游戏开始事件 mg.addEvent(NewGameStartedEvent(mg.GameID, mg.CreatorID)) // 标记为脏数据 mg.markDirty() return nil } // EndGame 结束游戏 func (mg *MinigameAggregate) EndGame(reason GameEndReason) error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusRunning { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusRunning, "end_game") } // 更新游戏状态 now := time.Now() mg.Status = GameStatusFinished mg.Phase = GamePhaseFinished mg.EndTime = &now // 计算游戏时长 if mg.StartTime != nil { mg.Duration = now.Sub(*mg.StartTime) } // 计算最终结果 mg.calculateFinalResults(reason) // 分发奖励 mg.distributeRewards() // 更新统计信息 mg.updateStatistics() // 发布游戏结束事件 mg.addEvent(NewGameEndedEvent(mg.GameID, reason, mg.CreatorID)) // 标记为脏数据 mg.markDirty() return nil } // AddPlayer 添加玩家 func (mg *MinigameAggregate) AddPlayer(playerID uint64, playerName string) error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusWaiting { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusWaiting, "add_player") } // 检查玩家是否已存在 if mg.hasPlayer(playerID) { return NewPlayerAlreadyInGameError(playerID, mg.GameID) } // 检查玩家数量限制 if int32(len(mg.Players)) >= mg.MaxPlayers { return NewGameFullError(mg.GameID, mg.MaxPlayers, int32(len(mg.Players))) } // 添加玩家 player := &GamePlayer{ PlayerID: playerID, Username: playerName, JoinTime: time.Now(), IsActive: true, } mg.Players = append(mg.Players, player) // 发布玩家加入事件 mg.addEvent(NewPlayerJoinedGameEvent(mg.GameID, playerID, playerName)) // 标记为脏数据 mg.markDirty() return nil } // RemovePlayer 移除玩家 func (mg *MinigameAggregate) RemovePlayer(playerID uint64, reason PlayerLeaveReason) error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status == GameStatusFinished { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusRunning, "remove_player") } // 查找并移除玩家 for i, player := range mg.Players { if player.PlayerID == playerID { // 更新玩家状态 player.IsActive = false // 从活跃玩家列表中移除 mg.Players = append(mg.Players[:i], mg.Players[i+1:]...) // 发布玩家离开事件 mg.addEvent(NewPlayerLeftGameEvent(mg.GameID, playerID, player.Username, reason.String())) // 检查是否需要结束游戏 if mg.Status == GameStatusRunning && int32(len(mg.Players)) < mg.MinPlayers { mg.EndGame(GameEndReasonInsufficientPlayers) } // 标记为脏数据 mg.markDirty() return nil } } return NewPlayerNotInGameError(mg.GameID, playerID) } // UpdatePlayerScore 更新玩家分数 func (mg *MinigameAggregate) UpdatePlayerScore(playerID uint64, score int64, scoreType ScoreType) error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusRunning { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusRunning, "update_score") } // 检查玩家是否在游戏中 player := mg.findPlayer(playerID) if player == nil { return NewPlayerNotInGameError(mg.GameID, playerID) } // 更新玩家分数 oldScore := player.Score player.Score = score // 记录分数历史 scoreRecord := &GameScore{ ID: generateScoreID(), GameID: mg.GameID, PlayerID: playerID, ScoreType: scoreType, Value: score, FinalScore: score, } mg.Scores = append(mg.Scores, scoreRecord) // 发布分数更新事件 mg.addEvent(NewPlayerScoreUpdatedEvent(mg.GameID, playerID, oldScore, score, scoreType)) // 检查游戏结束条件 mg.checkGameEndConditions() // 标记为脏数据 mg.markDirty() return nil } // UpdateGameData 更新游戏数据 func (mg *MinigameAggregate) UpdateGameData(key string, value interface{}) error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusRunning { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusRunning, "update_data") } // 初始化游戏数据 if mg.GameData == nil { mg.GameData = NewGameData() } // 更新数据 mg.GameData.SetData(key, value) // 发布数据更新事件 mg.addEvent(NewGameDataUpdatedEvent(mg.GameID, key, value)) // 标记为脏数据 mg.markDirty() return nil } // PauseGame 暂停游戏 func (mg *MinigameAggregate) PauseGame() error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusRunning { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusRunning, "pause_game") } // 更新游戏状态 mg.Status = GameStatusPaused mg.Phase = GamePhasePaused // 发布游戏暂停事件 mg.addEvent(NewGamePausedEvent(mg.GameID, mg.CreatorID)) // 标记为脏数据 mg.markDirty() return nil } // ResumeGame 恢复游戏 func (mg *MinigameAggregate) ResumeGame() error { mg.mutex.Lock() defer mg.mutex.Unlock() // 检查游戏状态 if mg.Status != GameStatusPaused { return NewMinigameInvalidStateError(mg.GameID, mg.Status, GameStatusPaused, "resume_game") } // 更新游戏状态 mg.Status = GameStatusRunning mg.Phase = GamePhaseRunning // 发布游戏恢复事件 mg.addEvent(NewGameResumedEvent(mg.GameID, mg.CreatorID)) // 标记为脏数据 mg.markDirty() return nil } // GetPlayer 获取玩家信息 func (mg *MinigameAggregate) GetPlayer(playerID uint64) *GamePlayer { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.findPlayer(playerID) } // GetPlayers 获取所有玩家 func (mg *MinigameAggregate) GetPlayers() []*GamePlayer { mg.mutex.RLock() defer mg.mutex.RUnlock() players := make([]*GamePlayer, len(mg.Players)) copy(players, mg.Players) return players } // GetGameData 获取游戏数据 func (mg *MinigameAggregate) GetGameData() *GameData { mg.mutex.RLock() defer mg.mutex.RUnlock() if mg.GameData == nil { return nil } return mg.GameData.Clone() } // GetScores 获取分数记录 func (mg *MinigameAggregate) GetScores() []*GameScore { mg.mutex.RLock() defer mg.mutex.RUnlock() scores := make([]*GameScore, len(mg.Scores)) copy(scores, mg.Scores) return scores } // GetResults 获取游戏结果 func (mg *MinigameAggregate) GetResults() []*GameResult { mg.mutex.RLock() defer mg.mutex.RUnlock() results := make([]*GameResult, len(mg.Results)) copy(results, mg.Results) return results } // GetID 获取小游戏ID func (mg *MinigameAggregate) GetID() string { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.ID } // GetName 获取小游戏名称 func (mg *MinigameAggregate) GetName() string { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.Name } // GetRewards 获取奖励列表 func (mg *MinigameAggregate) GetRewards() []*GameReward { mg.mutex.RLock() defer mg.mutex.RUnlock() rewards := make([]*GameReward, len(mg.Rewards)) copy(rewards, mg.Rewards) return rewards } // GetStatistics 获取游戏统计 func (mg *MinigameAggregate) GetStatistics() *GameStatistics { mg.mutex.RLock() defer mg.mutex.RUnlock() if mg.Statistics == nil { return nil } return mg.Statistics.Clone() } // GetDescription 获取描述 func (mg *MinigameAggregate) GetDescription() string { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.Description } // GetGameType 获取游戏类型 func (mg *MinigameAggregate) GetGameType() GameType { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.GameType } // GetDifficulty 获取难度 func (mg *MinigameAggregate) GetDifficulty() GameDifficulty { mg.mutex.RLock() defer mg.mutex.RUnlock() // TODO: 从配置或规则中获取难度,目前返回默认值 return GameDifficultyEasy } // GetMaxPlayers 获取最大玩家数 func (mg *MinigameAggregate) GetMaxPlayers() int32 { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.MaxPlayers } // GetTimeLimit 获取时间限制 func (mg *MinigameAggregate) GetTimeLimit() int32 { mg.mutex.RLock() defer mg.mutex.RUnlock() // TODO: 从配置中获取时间限制,目前返回默认值 return 300 // 5分钟 } // GetIsActive 检查是否激活 func (mg *MinigameAggregate) GetIsActive() bool { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.IsActive } // GetRules 获取游戏规则 func (mg *MinigameAggregate) GetRules() map[string]interface{} { mg.mutex.RLock() defer mg.mutex.RUnlock() if mg.Rules != nil { return mg.Rules.ToMap() } return make(map[string]interface{}) } // GetSettings 获取游戏设置 func (mg *MinigameAggregate) GetSettings() map[string]interface{} { mg.mutex.RLock() defer mg.mutex.RUnlock() if mg.Settings != nil { return mg.Settings.ToMap() } return make(map[string]interface{}) } // GetCreatedAt 获取创建时间 func (mg *MinigameAggregate) GetCreatedAt() time.Time { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.CreatedAt } // GetUpdatedAt 获取更新时间 func (mg *MinigameAggregate) GetUpdatedAt() time.Time { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.UpdatedAt } // GetVersion 获取版本 func (mg *MinigameAggregate) GetVersion() int64 { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.Version } // GetEvents 获取领域事件 func (mg *MinigameAggregate) GetEvents() []MinigameEvent { mg.mutex.RLock() defer mg.mutex.RUnlock() events := make([]MinigameEvent, len(mg.events)) copy(events, mg.events) return events } // ClearEvents 清除领域事件 func (mg *MinigameAggregate) ClearEvents() { mg.mutex.Lock() defer mg.mutex.Unlock() mg.events = make([]MinigameEvent, 0) } // IsDirty 检查是否有未保存的更改 func (mg *MinigameAggregate) IsDirty() bool { mg.mutex.RLock() defer mg.mutex.RUnlock() return mg.dirty } // MarkClean 标记为已保存 func (mg *MinigameAggregate) MarkClean() { mg.mutex.Lock() defer mg.mutex.Unlock() mg.dirty = false } // 私有方法 // initializeGameData 初始化游戏数据 func (mg *MinigameAggregate) initializeGameData() { if mg.GameData == nil { mg.GameData = NewGameData() } // 根据游戏类型初始化特定数据 switch mg.GameType { case GameTypeSaveDog: mg.initializeSaveDogData() case GameTypePuzzle: mg.initializePuzzleData() case GameTypeRacing: mg.initializeRacingData() default: mg.initializeDefaultData() } } // initializeSaveDogData 初始化拯救小狗游戏数据 func (mg *MinigameAggregate) initializeSaveDogData() { mg.GameData.SetData("dog_position", map[string]int{"x": 0, "y": 0}) mg.GameData.SetData("obstacles", make([]map[string]interface{}, 0)) mg.GameData.SetData("rescue_progress", 0) mg.GameData.SetData("time_limit", 300) // 5分钟 } // initializePuzzleData 初始化拼图游戏数据 func (mg *MinigameAggregate) initializePuzzleData() { mg.GameData.SetData("puzzle_pieces", make([]map[string]interface{}, 0)) mg.GameData.SetData("completed_pieces", 0) mg.GameData.SetData("total_pieces", 100) } // initializeRacingData 初始化赛车游戏数据 func (mg *MinigameAggregate) initializeRacingData() { mg.GameData.SetData("track_length", 1000) mg.GameData.SetData("laps", 3) mg.GameData.SetData("player_positions", make(map[uint64]map[string]interface{})) } // initializeDefaultData 初始化默认游戏数据 func (mg *MinigameAggregate) initializeDefaultData() { mg.GameData.SetData("initialized", true) mg.GameData.SetData("start_time", time.Now().Unix()) } // hasPlayer 检查玩家是否存在 func (mg *MinigameAggregate) hasPlayer(playerID uint64) bool { return mg.findPlayer(playerID) != nil } // findPlayer 查找玩家 func (mg *MinigameAggregate) findPlayer(playerID uint64) *GamePlayer { for _, player := range mg.Players { if player.PlayerID == playerID { return player } } return nil } // getPlayerIDs 获取所有玩家ID func (mg *MinigameAggregate) getPlayerIDs() []uint64 { playerIDs := make([]uint64, len(mg.Players)) for i, player := range mg.Players { playerIDs[i] = player.PlayerID } return playerIDs } // checkGameEndConditions 检查游戏结束条件 func (mg *MinigameAggregate) checkGameEndConditions() { // 根据游戏类型检查结束条件 switch mg.GameType { case GameTypeSaveDog: mg.checkSaveDogEndConditions() case GameTypePuzzle: mg.checkPuzzleEndConditions() case GameTypeRacing: mg.checkRacingEndConditions() default: mg.checkDefaultEndConditions() } } // checkSaveDogEndConditions 检查拯救小狗游戏结束条件 func (mg *MinigameAggregate) checkSaveDogEndConditions() { if mg.GameData == nil { return } // 检查救援进度 if progressData, exists := mg.GameData.GetData("rescue_progress"); exists { if progress, ok := progressData.(int); ok && progress >= 100 { mg.EndGame(GameEndReasonCompleted) return } } // 检查时间限制 if mg.StartTime != nil { if timeLimitData, exists := mg.GameData.GetData("time_limit"); exists { if timeLimit, ok := timeLimitData.(int); ok { if time.Since(*mg.StartTime).Seconds() >= float64(timeLimit) { mg.EndGame(GameEndReasonTimeout) } } } } } // checkPuzzleEndConditions 检查拼图游戏结束条件 func (mg *MinigameAggregate) checkPuzzleEndConditions() { if mg.GameData == nil { return } // 检查完成的拼图块数 var completedPieces, totalPieces int if completedData, exists := mg.GameData.GetData("completed_pieces"); exists { if val, ok := completedData.(int); ok { completedPieces = val } } if totalData, exists := mg.GameData.GetData("total_pieces"); exists { if val, ok := totalData.(int); ok { totalPieces = val } } if completedPieces >= totalPieces { mg.EndGame(GameEndReasonCompleted) } } // checkRacingEndConditions 检查赛车游戏结束条件 func (mg *MinigameAggregate) checkRacingEndConditions() { // 检查是否有玩家完成所有圈数 for _, player := range mg.Players { if player.Score >= 3 { // 假设3圈为完成条件 mg.EndGame(GameEndReasonCompleted) return } } } // checkDefaultEndConditions 检查默认游戏结束条件 func (mg *MinigameAggregate) checkDefaultEndConditions() { // 检查是否达到目标分数 for _, player := range mg.Players { if player.Score >= 1000 { // 默认目标分数 mg.EndGame(GameEndReasonCompleted) return } } } // calculateFinalResults 计算最终结果 func (mg *MinigameAggregate) calculateFinalResults(reason GameEndReason) { mg.Results = make([]*GameResult, 0, len(mg.Players)) // 按分数排序玩家 players := make([]*GamePlayer, len(mg.Players)) copy(players, mg.Players) // 简单的分数排序 for i := 0; i < len(players)-1; i++ { for j := i + 1; j < len(players); j++ { if players[i].Score < players[j].Score { players[i], players[j] = players[j], players[i] } } } // 生成结果 for i, player := range players { result := &GameResult{ GameID: mg.GameID, WinnerID: player.PlayerID, WinnerName: player.Username, FinalScore: player.Score, CompletedAt: time.Now(), } mg.Results = append(mg.Results, result) // 更新胜负统计 if i == 0 && reason == GameEndReasonCompleted { mg.WinCount++ } else { mg.LoseCount++ } } } // distributeRewards 分发奖励 func (mg *MinigameAggregate) distributeRewards() { if mg.RewardPool == nil { return } for _, result := range mg.Results { rewards := mg.RewardPool.CalculateRewards(result.Rank, result.Score, result.IsWinner) for _, reward := range rewards { reward.PlayerID = fmt.Sprintf("%d", result.PlayerID) reward.GameID = mg.GameID reward.Timestamp = time.Now() // 将Reward转换为GameReward playerID, _ := strconv.ParseUint(reward.PlayerID, 10, 64) gameReward := &GameReward{ RewardID: reward.RewardID, PlayerID: playerID, RewardType: RewardType(reward.RewardType), Amount: reward.Amount, ItemID: reward.ItemID, ItemCount: reward.ItemCount, GameID: reward.GameID, Timestamp: reward.Timestamp, } mg.Rewards = append(mg.Rewards, gameReward) } } } // updateStatistics 更新统计信息 func (mg *MinigameAggregate) updateStatistics() { if mg.Statistics == nil { mg.Statistics = NewGameStatistics() } mg.Statistics.TotalGames++ mg.Statistics.TotalPlayers += len(mg.Players) if len(mg.Results) > 0 { mg.Statistics.AverageScore = mg.calculateAverageScore() mg.Statistics.HighestScore = mg.getHighestScore() mg.Statistics.LowestScore = mg.getLowestScore() } mg.Statistics.AverageGameDuration = float64(mg.Duration.Nanoseconds()) / 1e9 mg.Statistics.LastPlayedAt = time.Now() mg.Statistics.UpdatedAt = time.Now() } // calculateAverageScore 计算平均分数 func (mg *MinigameAggregate) calculateAverageScore() float64 { if len(mg.Results) == 0 { return 0 } totalScore := int64(0) for _, result := range mg.Results { totalScore += result.Score } return float64(totalScore) / float64(len(mg.Results)) } // getHighestScore 获取最高分数 func (mg *MinigameAggregate) getHighestScore() int64 { if len(mg.Results) == 0 { return 0 } highest := mg.Results[0].Score for _, result := range mg.Results { if result.Score > highest { highest = result.Score } } return highest } // getLowestScore 获取最低分数 func (mg *MinigameAggregate) getLowestScore() int64 { if len(mg.Results) == 0 { return 0 } lowest := mg.Results[0].Score for _, result := range mg.Results { if result.Score < lowest { lowest = result.Score } } return lowest } // addEvent 添加领域事件 func (mg *MinigameAggregate) addEvent(event MinigameEvent) { mg.events = append(mg.events, event) } // markDirty 标记为脏数据 func (mg *MinigameAggregate) markDirty() { mg.dirty = true mg.UpdatedAt = time.Now() mg.Version++ } // 辅助函数 // generateMinigameID 生成小游戏ID func generateMinigameID(gameID string, timestamp time.Time) string { return fmt.Sprintf("minigame_%s_%d", gameID, timestamp.Unix()) } // 常量定义 const ( // 默认配置 DefaultMaxPlayers = 10 DefaultMinPlayers = 1 DefaultGameDuration = 10 * time.Minute // 游戏限制 MaxGameDuration = 60 * time.Minute MinGameDuration = 1 * time.Minute MaxPlayersLimit = 100 MinPlayersLimit = 1 ) // 验证方法 // Validate 验证小游戏聚合 func (mg *MinigameAggregate) Validate() error { if mg.GameID == "" { return NewMinigameValidationError("game_id cannot be empty") } if !mg.GameType.IsValid() { return NewMinigameValidationError("invalid game type") } if !mg.Category.IsValid() { return NewMinigameValidationError("invalid category") } if mg.MaxPlayers < mg.MinPlayers { return NewMinigameValidationError("max_players must be greater than or equal to min_players") } if mg.MaxPlayers > MaxPlayersLimit { return NewMinigameValidationError(fmt.Sprintf("max_players cannot exceed %d", MaxPlayersLimit)) } if mg.MinPlayers < MinPlayersLimit { return NewMinigameValidationError(fmt.Sprintf("min_players cannot be less than %d", MinPlayersLimit)) } return nil } // Clone 克隆小游戏聚合 func (mg *MinigameAggregate) Clone() *MinigameAggregate { mg.mutex.RLock() defer mg.mutex.RUnlock() clone := &MinigameAggregate{ ID: mg.ID, GameID: mg.GameID, GameType: mg.GameType, Category: mg.Category, Name: mg.Name, Description: mg.Description, Status: mg.Status, Phase: mg.Phase, IsActive: mg.IsActive, Duration: mg.Duration, CreatorID: mg.CreatorID, MaxPlayers: mg.MaxPlayers, MinPlayers: mg.MinPlayers, PlayCount: mg.PlayCount, WinCount: mg.WinCount, LoseCount: mg.LoseCount, Version: mg.Version, CreatedAt: mg.CreatedAt, UpdatedAt: mg.UpdatedAt, dirty: mg.dirty, } // 深拷贝时间 if mg.StartTime != nil { startTime := *mg.StartTime clone.StartTime = &startTime } if mg.EndTime != nil { endTime := *mg.EndTime clone.EndTime = &endTime } // 深拷贝配置 if mg.Config != nil { clone.Config = mg.Config.Clone() } if mg.Rules != nil { clone.Rules = mg.Rules.Clone() } if mg.Settings != nil { clone.Settings = mg.Settings.Clone() } // 深拷贝玩家 clone.Players = make([]*GamePlayer, len(mg.Players)) for i, player := range mg.Players { clone.Players[i] = player.Clone() } // 深拷贝游戏数据 if mg.GameData != nil { clone.GameData = mg.GameData.Clone() } // 深拷贝分数记录 clone.Scores = make([]*GameScore, len(mg.Scores)) for i, score := range mg.Scores { scoreCopy := *score clone.Scores[i] = &scoreCopy } // 深拷贝结果 clone.Results = make([]*GameResult, len(mg.Results)) for i, result := range mg.Results { resultCopy := *result clone.Results[i] = &resultCopy } // 深拷贝奖励 clone.Rewards = make([]*GameReward, len(mg.Rewards)) for i, reward := range mg.Rewards { rewardCopy := *reward clone.Rewards[i] = &rewardCopy } // 深拷贝奖励池 if mg.RewardPool != nil { clone.RewardPool = mg.RewardPool.Clone() } // 深拷贝统计信息 if mg.Statistics != nil { clone.Statistics = mg.Statistics.Clone() } // 深拷贝事件 clone.events = make([]MinigameEvent, len(mg.events)) copy(clone.events, mg.events) return clone } ================================================ FILE: internal/domain/minigame/entity.go ================================================ package minigame import ( "fmt" "time" ) // RewardType 奖励类型 type RewardType string const ( RewardTypeCoin RewardType = "coin" RewardTypeExp RewardType = "exp" RewardTypeItem RewardType = "item" RewardTypeCurrency RewardType = "currency" ) // GameSession 游戏会话实体 type GameSession struct { ID string `json:"id" bson:"_id"` GameID string `json:"game_id" bson:"game_id"` PlayerID uint64 `json:"player_id" bson:"player_id"` SessionToken string `json:"session_token" bson:"session_token"` Status PlayerStatus `json:"status" bson:"status"` JoinedAt time.Time `json:"joined_at" bson:"joined_at"` LeftAt *time.Time `json:"left_at,omitempty" bson:"left_at,omitempty"` LeaveReason *PlayerLeaveReason `json:"leave_reason,omitempty" bson:"leave_reason,omitempty"` Score int64 `json:"score" bson:"score"` HighScore int64 `json:"high_score" bson:"high_score"` PlayTime time.Duration `json:"play_time" bson:"play_time"` Moves int32 `json:"moves" bson:"moves"` Level int32 `json:"level" bson:"level"` Progress float64 `json:"progress" bson:"progress"` Achievements []string `json:"achievements" bson:"achievements"` Statistics map[string]interface{} `json:"statistics" bson:"statistics"` GameData map[string]interface{} `json:"game_data" bson:"game_data"` Rewards []*GameReward `json:"rewards" bson:"rewards"` LastActivity time.Time `json:"last_activity" bson:"last_activity"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // GameReward 游戏奖励 type GameReward struct { RewardID string `json:"reward_id" bson:"reward_id"` PlayerID uint64 `json:"player_id" bson:"player_id"` RewardType RewardType `json:"reward_type" bson:"reward_type"` Amount int64 `json:"amount" bson:"amount"` ItemID string `json:"item_id,omitempty" bson:"item_id,omitempty"` ItemCount int `json:"item_count,omitempty" bson:"item_count,omitempty"` GameID string `json:"game_id" bson:"game_id"` Timestamp time.Time `json:"timestamp" bson:"timestamp"` } // GetType 获取奖励类型 func (gr *GameReward) GetType() RewardType { return gr.RewardType } // GetItemID 获取物品ID func (gr *GameReward) GetItemID() string { return gr.ItemID } // GetQuantity 获取数量 func (gr *GameReward) GetQuantity() int { return int(gr.Amount) } // GetReason 获取奖励原因 func (gr *GameReward) GetReason() string { return "game_completion" // 默认原因,可以根据需要扩展 } // NewGameSession 创建新的游戏会话 func NewGameSession(gameID string, playerID uint64, sessionToken string) *GameSession { now := time.Now() return &GameSession{ ID: fmt.Sprintf("%s_%d_%d", gameID, playerID, now.Unix()), GameID: gameID, PlayerID: playerID, SessionToken: sessionToken, Status: PlayerStatusWaiting, JoinedAt: now, Score: 0, HighScore: 0, PlayTime: 0, Moves: 0, Level: 1, Progress: 0.0, Achievements: make([]string, 0), Statistics: make(map[string]interface{}), GameData: make(map[string]interface{}), Rewards: make([]*GameReward, 0), LastActivity: now, CreatedAt: now, UpdatedAt: now, } } // UpdateStatus 更新玩家状态 func (gs *GameSession) UpdateStatus(status PlayerStatus) error { if !status.IsValid() { return fmt.Errorf("invalid player status: %v", status) } gs.Status = status gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() return nil } // UpdateScore 更新分数 func (gs *GameSession) UpdateScore(score int64) { gs.Score = score if score > gs.HighScore { gs.HighScore = score } gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() } // AddScore 增加分数 func (gs *GameSession) AddScore(points int64) { gs.Score += points if gs.Score > gs.HighScore { gs.HighScore = gs.Score } gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() } // UpdateProgress 更新进度 func (gs *GameSession) UpdateProgress(progress float64) error { if progress < 0 || progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } gs.Progress = progress gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() return nil } // UpdateLevel 更新等级 func (gs *GameSession) UpdateLevel(level int32) error { if level < 1 { return fmt.Errorf("level must be positive") } gs.Level = level gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() return nil } // AddMove 增加移动次数 func (gs *GameSession) AddMove() { gs.Moves++ gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() } // AddPlayTime 增加游戏时间 func (gs *GameSession) AddPlayTime(duration time.Duration) { gs.PlayTime += duration gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() } // AddAchievement 添加成就 func (gs *GameSession) AddAchievement(achievement string) { // 检查是否已存在 for _, existing := range gs.Achievements { if existing == achievement { return // 已存在,不重复添加 } } gs.Achievements = append(gs.Achievements, achievement) gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() } // SetGameData 设置游戏数据 func (gs *GameSession) SetGameData(key string, value interface{}) { if gs.GameData == nil { gs.GameData = make(map[string]interface{}) } gs.GameData[key] = value gs.LastActivity = time.Now() gs.UpdatedAt = time.Now() } // GetGameData 获取游戏数据 func (gs *GameSession) GetGameData(key string) (interface{}, bool) { if gs.GameData == nil { return nil, false } value, exists := gs.GameData[key] return value, exists } // SetStatistic 设置统计信息 func (gs *GameSession) SetStatistic(key string, value interface{}) { if gs.Statistics == nil { gs.Statistics = make(map[string]interface{}) } gs.Statistics[key] = value gs.UpdatedAt = time.Now() } // SetStatus 设置会话状态 func (gs *GameSession) SetStatus(status SessionStatus) { gs.Status = PlayerStatus(status) gs.UpdatedAt = time.Now() } // SetScore 设置分数 func (gs *GameSession) SetScore(score int64) { gs.Score = score gs.UpdatedAt = time.Now() } // SetTimeElapsed 设置已用时间 func (gs *GameSession) SetTimeElapsed(elapsed time.Duration) { gs.PlayTime = elapsed gs.UpdatedAt = time.Now() } // SetSettings 设置游戏设置 func (gs *GameSession) SetSettings(settings map[string]interface{}) { gs.GameData = settings gs.UpdatedAt = time.Now() } // SetTimestamps 设置时间戳 func (gs *GameSession) SetTimestamps(startedAt, expiresAt, completedAt time.Time) { gs.JoinedAt = startedAt gs.LastActivity = expiresAt if !completedAt.IsZero() { gs.UpdatedAt = completedAt } } // GetStatistic 获取统计数据 func (gs *GameSession) GetStatistic(key string) (interface{}, bool) { if gs.Statistics == nil { return nil, false } value, exists := gs.Statistics[key] return value, exists } // Leave 离开游戏 func (gs *GameSession) Leave(reason PlayerLeaveReason) error { if !reason.IsValid() { return fmt.Errorf("invalid leave reason: %v", reason) } now := time.Now() gs.LeftAt = &now gs.LeaveReason = &reason gs.Status = PlayerStatusLeft gs.LastActivity = now gs.UpdatedAt = now return nil } // IsActive 检查会话是否活跃 func (gs *GameSession) IsActive() bool { return gs.Status == PlayerStatusWaiting || gs.Status == PlayerStatusReady || gs.Status == PlayerStatusPlaying } // IsFinished 检查会话是否已结束 func (gs *GameSession) IsFinished() bool { return gs.Status == PlayerStatusFinished || gs.Status == PlayerStatusLeft || gs.Status == PlayerStatusKicked } // GetDuration 获取会话持续时间 func (gs *GameSession) GetDuration() time.Duration { if gs.LeftAt != nil { return gs.LeftAt.Sub(gs.JoinedAt) } return time.Since(gs.JoinedAt) } // Clone 克隆游戏会话 func (gs *GameSession) Clone() *GameSession { clone := &GameSession{ ID: gs.ID, GameID: gs.GameID, PlayerID: gs.PlayerID, SessionToken: gs.SessionToken, Status: gs.Status, JoinedAt: gs.JoinedAt, Score: gs.Score, HighScore: gs.HighScore, PlayTime: gs.PlayTime, Moves: gs.Moves, Level: gs.Level, Progress: gs.Progress, Achievements: make([]string, len(gs.Achievements)), Statistics: make(map[string]interface{}), GameData: make(map[string]interface{}), LastActivity: gs.LastActivity, CreatedAt: gs.CreatedAt, UpdatedAt: gs.UpdatedAt, } // 深拷贝切片 copy(clone.Achievements, gs.Achievements) // 深拷贝map for k, v := range gs.Statistics { clone.Statistics[k] = v } for k, v := range gs.GameData { clone.GameData[k] = v } // 深拷贝指针 if gs.LeftAt != nil { leftAt := *gs.LeftAt clone.LeftAt = &leftAt } if gs.LeaveReason != nil { leaveReason := *gs.LeaveReason clone.LeaveReason = &leaveReason } return clone } // GetID 获取会话ID func (gs *GameSession) GetID() string { return gs.ID } // GetMinigameID 获取小游戏ID func (gs *GameSession) GetMinigameID() string { return gs.GameID } // GetPlayerID 获取玩家ID func (gs *GameSession) GetPlayerID() uint64 { return gs.PlayerID } // GetRewards 获取奖励列表 func (gs *GameSession) GetRewards() []*GameReward { return gs.Rewards } // GetStatus 获取会话状态 func (gs *GameSession) GetStatus() SessionStatus { return SessionStatus(gs.Status) } // GetScore 获取分数 func (gs *GameSession) GetScore() int64 { return gs.Score } // GetTimeLimit 获取时间限制 func (gs *GameSession) GetTimeLimit() time.Duration { // 假设没有专门的时间限制字段,返回默认值 return 30 * time.Minute } // GetTimeElapsed 获取已用时间 func (gs *GameSession) GetTimeElapsed() time.Duration { return gs.PlayTime } // GetSettings 获取游戏设置 func (gs *GameSession) GetSettings() map[string]interface{} { return gs.GameData } // GetStartedAt 获取开始时间 func (gs *GameSession) GetStartedAt() time.Time { return gs.JoinedAt } // GetExpiresAt 获取过期时间 func (gs *GameSession) GetExpiresAt() time.Time { return gs.LastActivity } // GetCompletedAt 获取完成时间 func (gs *GameSession) GetCompletedAt() time.Time { // 如果状态是完成状态,返回更新时间,否则返回零值 if gs.Status == PlayerStatusFinished { return gs.UpdatedAt } return time.Time{} } // GetCreatedAt 获取创建时间 func (gs *GameSession) GetCreatedAt() time.Time { return gs.CreatedAt } // GetUpdatedAt 获取更新时间 func (gs *GameSession) GetUpdatedAt() time.Time { return gs.UpdatedAt } // AddReward 添加奖励 func (gs *GameSession) AddReward(reward *GameReward) { if gs.Rewards == nil { gs.Rewards = make([]*GameReward, 0) } gs.Rewards = append(gs.Rewards, reward) gs.UpdatedAt = time.Now() } // GameScore 游戏分数实体 type GameScore struct { ID string `json:"id" bson:"_id"` GameID string `json:"game_id" bson:"game_id"` PlayerID uint64 `json:"player_id" bson:"player_id"` SessionID string `json:"session_id" bson:"session_id"` ScoreType ScoreType `json:"score_type" bson:"score_type"` Value int64 `json:"value" bson:"value"` MaxValue int64 `json:"max_value" bson:"max_value"` Multiplier float64 `json:"multiplier" bson:"multiplier"` Bonus int64 `json:"bonus" bson:"bonus"` Penalty int64 `json:"penalty" bson:"penalty"` FinalScore int64 `json:"final_score" bson:"final_score"` Rank int32 `json:"rank" bson:"rank"` Percentile float64 `json:"percentile" bson:"percentile"` Breakdown map[string]int64 `json:"breakdown" bson:"breakdown"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` AchievedAt time.Time `json:"achieved_at" bson:"achieved_at"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewGameScore 创建新的游戏分数 func NewGameScore(gameID string, playerID uint64, sessionID string, scoreType ScoreType) *GameScore { now := time.Now() return &GameScore{ ID: fmt.Sprintf("%s_%d_%s_%d", gameID, playerID, scoreType.String(), now.Unix()), GameID: gameID, PlayerID: playerID, SessionID: sessionID, ScoreType: scoreType, Value: 0, MaxValue: 0, Multiplier: 1.0, Bonus: 0, Penalty: 0, FinalScore: 0, Rank: 0, Percentile: 0.0, Breakdown: make(map[string]int64), Metadata: make(map[string]interface{}), AchievedAt: now, CreatedAt: now, UpdatedAt: now, } } // UpdateValue 更新分数值 func (gs *GameScore) UpdateValue(value int64) { gs.Value = value if value > gs.MaxValue { gs.MaxValue = value } gs.calculateFinalScore() gs.UpdatedAt = time.Now() } // AddValue 增加分数值 func (gs *GameScore) AddValue(points int64) { gs.Value += points if gs.Value > gs.MaxValue { gs.MaxValue = gs.Value } gs.calculateFinalScore() gs.UpdatedAt = time.Now() } // SetMultiplier 设置倍数 func (gs *GameScore) SetMultiplier(multiplier float64) error { if multiplier < 0 { return fmt.Errorf("multiplier cannot be negative") } gs.Multiplier = multiplier gs.calculateFinalScore() gs.UpdatedAt = time.Now() return nil } // AddBonus 添加奖励分数 func (gs *GameScore) AddBonus(bonus int64) { gs.Bonus += bonus gs.calculateFinalScore() gs.UpdatedAt = time.Now() } // AddPenalty 添加惩罚分数 func (gs *GameScore) AddPenalty(penalty int64) { gs.Penalty += penalty gs.calculateFinalScore() gs.UpdatedAt = time.Now() } // SetRank 设置排名 func (gs *GameScore) SetRank(rank int32, percentile float64) error { if rank < 0 { return fmt.Errorf("rank cannot be negative") } if percentile < 0 || percentile > 100 { return fmt.Errorf("percentile must be between 0 and 100") } gs.Rank = rank gs.Percentile = percentile gs.UpdatedAt = time.Now() return nil } // SetBreakdown 设置分数明细 func (gs *GameScore) SetBreakdown(breakdown map[string]int64) { gs.Breakdown = make(map[string]int64) for k, v := range breakdown { gs.Breakdown[k] = v } gs.UpdatedAt = time.Now() } // AddBreakdownItem 添加分数明细项 func (gs *GameScore) AddBreakdownItem(key string, value int64) { if gs.Breakdown == nil { gs.Breakdown = make(map[string]int64) } gs.Breakdown[key] = value gs.UpdatedAt = time.Now() } // SetMetadata 设置元数据 func (gs *GameScore) SetMetadata(key string, value interface{}) { if gs.Metadata == nil { gs.Metadata = make(map[string]interface{}) } gs.Metadata[key] = value gs.UpdatedAt = time.Now() } // GetMetadata 获取元数据 func (gs *GameScore) GetMetadata(key string) (interface{}, bool) { if gs.Metadata == nil { return nil, false } value, exists := gs.Metadata[key] return value, exists } // calculateFinalScore 计算最终分数 func (gs *GameScore) calculateFinalScore() { baseScore := float64(gs.Value) * gs.Multiplier gs.FinalScore = int64(baseScore) + gs.Bonus - gs.Penalty if gs.FinalScore < 0 { gs.FinalScore = 0 } } // Clone 克隆游戏分数 func (gs *GameScore) Clone() *GameScore { clone := &GameScore{ ID: gs.ID, GameID: gs.GameID, PlayerID: gs.PlayerID, SessionID: gs.SessionID, ScoreType: gs.ScoreType, Value: gs.Value, MaxValue: gs.MaxValue, Multiplier: gs.Multiplier, Bonus: gs.Bonus, Penalty: gs.Penalty, FinalScore: gs.FinalScore, Rank: gs.Rank, Percentile: gs.Percentile, Breakdown: make(map[string]int64), Metadata: make(map[string]interface{}), AchievedAt: gs.AchievedAt, CreatedAt: gs.CreatedAt, UpdatedAt: gs.UpdatedAt, } // 深拷贝map for k, v := range gs.Breakdown { clone.Breakdown[k] = v } for k, v := range gs.Metadata { clone.Metadata[k] = v } return clone } // 注意:GameReward已经在文件前面定义,这里删除重复定义 // NewGameReward 创建新的游戏奖励 func NewGameReward(gameID string, playerID uint64, sessionID string, rewardType RewardType, itemID string, quantity int64) *GameReward { now := time.Now() return &GameReward{ RewardID: fmt.Sprintf("%s_%d_%s_%s_%d", gameID, playerID, rewardType.String(), itemID, now.Unix()), GameID: gameID, PlayerID: playerID, RewardType: rewardType, Amount: quantity, ItemID: itemID, ItemCount: int(quantity), Timestamp: now, } } // SetRarity 设置稀有度 func (gr *GameReward) SetRarity(rarity string) { // TODO: 实现稀有度设置 // gr.Rarity = rarity // gr.UpdatedAt = time.Now() } // SetSource 设置来源 func (gr *GameReward) SetSource(source string) { // TODO: 实现来源设置 // gr.Source = source // gr.UpdatedAt = time.Now() } // SetReason 设置原因 func (gr *GameReward) SetReason(reason string) { // TODO: 实现原因设置 // gr.Reason = reason // gr.UpdatedAt = time.Now() } // SetExpiration 设置过期时间 func (gr *GameReward) SetExpiration(expiresAt time.Time) { // TODO: 实现过期时间设置 // gr.ExpiresAt = &expiresAt // gr.UpdatedAt = time.Now() } // Claim 领取奖励 func (gr *GameReward) Claim() error { // TODO: 实现奖励领取 // if gr.Claimed { // return fmt.Errorf("reward already claimed") // } // if gr.IsExpired() { // return fmt.Errorf("reward has expired") // } // now := time.Now() // gr.Claimed = true // gr.ClaimedAt = &now // gr.UpdatedAt = now return nil } // IsExpired 检查是否已过期 func (gr *GameReward) IsExpired() bool { // TODO: 实现过期检查 // if gr.ExpiresAt == nil { // return false // } // return time.Now().After(*gr.ExpiresAt) return false } // IsClaimable 检查是否可领取 func (gr *GameReward) IsClaimable() bool { // TODO: 实现可领取检查 // return !gr.Claimed && !gr.IsExpired() return true } // SetMetadata 设置元数据 func (gr *GameReward) SetMetadata(key string, value interface{}) { // TODO: 实现元数据设置 // if gr.Metadata == nil { // gr.Metadata = make(map[string]interface{}) // } // gr.Metadata[key] = value // gr.UpdatedAt = time.Now() } // GetMetadata 获取元数据 func (gr *GameReward) GetMetadata(key string) (interface{}, bool) { // TODO: 实现元数据获取 // if gr.Metadata == nil { // return nil, false // } // value, exists := gr.Metadata[key] // return value, exists return nil, false } // Clone 克隆游戏奖励 func (gr *GameReward) Clone() *GameReward { clone := &GameReward{ RewardID: gr.RewardID, GameID: gr.GameID, PlayerID: gr.PlayerID, RewardType: gr.RewardType, Amount: gr.Amount, ItemID: gr.ItemID, ItemCount: gr.ItemCount, Timestamp: gr.Timestamp, } // TODO: 实现深拷贝 // 深拷贝map // for k, v := range gr.Metadata { // clone.Metadata[k] = v // } // 深拷贝指针 // if gr.ClaimedAt != nil { // claimedAt := *gr.ClaimedAt // clone.ClaimedAt = &claimedAt // } // if gr.ExpiresAt != nil { // expiresAt := *gr.ExpiresAt // clone.ExpiresAt = &expiresAt // } return clone } // GameAchievement 游戏成就实体 type GameAchievement struct { ID string `json:"id" bson:"_id"` GameID string `json:"game_id" bson:"game_id"` PlayerID uint64 `json:"player_id" bson:"player_id"` SessionID string `json:"session_id" bson:"session_id"` AchievementID string `json:"achievement_id" bson:"achievement_id"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` Category string `json:"category" bson:"category"` Rarity string `json:"rarity" bson:"rarity"` Points int64 `json:"points" bson:"points"` Progress float64 `json:"progress" bson:"progress"` MaxProgress float64 `json:"max_progress" bson:"max_progress"` Completed bool `json:"completed" bson:"completed"` CompletedAt *time.Time `json:"completed_at,omitempty" bson:"completed_at,omitempty"` Unlocked bool `json:"unlocked" bson:"unlocked"` UnlockedAt *time.Time `json:"unlocked_at,omitempty" bson:"unlocked_at,omitempty"` Conditions map[string]interface{} `json:"conditions" bson:"conditions"` Rewards []string `json:"rewards" bson:"rewards"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewGameAchievement 创建新的游戏成就 func NewGameAchievement(gameID string, playerID uint64, sessionID string, achievementID string, name string) *GameAchievement { now := time.Now() return &GameAchievement{ ID: fmt.Sprintf("%s_%d_%s_%d", gameID, playerID, achievementID, now.Unix()), GameID: gameID, PlayerID: playerID, SessionID: sessionID, AchievementID: achievementID, Name: name, Description: "", Category: "general", Rarity: "common", Points: 0, Progress: 0.0, MaxProgress: 100.0, Completed: false, Unlocked: false, Conditions: make(map[string]interface{}), Rewards: make([]string, 0), Metadata: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // UpdateProgress 更新进度 func (ga *GameAchievement) UpdateProgress(progress float64) error { if progress < 0 { return fmt.Errorf("progress cannot be negative") } if progress > ga.MaxProgress { progress = ga.MaxProgress } ga.Progress = progress // 检查是否完成 if progress >= ga.MaxProgress && !ga.Completed { ga.Complete() } ga.UpdatedAt = time.Now() return nil } // AddProgress 增加进度 func (ga *GameAchievement) AddProgress(delta float64) error { return ga.UpdateProgress(ga.Progress + delta) } // Complete 完成成就 func (ga *GameAchievement) Complete() { if ga.Completed { return } now := time.Now() ga.Completed = true ga.CompletedAt = &now ga.Progress = ga.MaxProgress ga.UpdatedAt = now } // Unlock 解锁成就 func (ga *GameAchievement) Unlock() { if ga.Unlocked { return } now := time.Now() ga.Unlocked = true ga.UnlockedAt = &now ga.UpdatedAt = now } // SetDescription 设置描述 func (ga *GameAchievement) SetDescription(description string) { ga.Description = description ga.UpdatedAt = time.Now() } // SetCategory 设置分类 func (ga *GameAchievement) SetCategory(category string) { ga.Category = category ga.UpdatedAt = time.Now() } // SetRarity 设置稀有度 func (ga *GameAchievement) SetRarity(rarity string) { ga.Rarity = rarity ga.UpdatedAt = time.Now() } // SetPoints 设置积分 func (ga *GameAchievement) SetPoints(points int64) { ga.Points = points ga.UpdatedAt = time.Now() } // SetMaxProgress 设置最大进度 func (ga *GameAchievement) SetMaxProgress(maxProgress float64) error { if maxProgress <= 0 { return fmt.Errorf("max progress must be positive") } ga.MaxProgress = maxProgress ga.UpdatedAt = time.Now() return nil } // AddReward 添加奖励 func (ga *GameAchievement) AddReward(reward string) { // 检查是否已存在 for _, existing := range ga.Rewards { if existing == reward { return // 已存在,不重复添加 } } ga.Rewards = append(ga.Rewards, reward) ga.UpdatedAt = time.Now() } // SetCondition 设置条件 func (ga *GameAchievement) SetCondition(key string, value interface{}) { if ga.Conditions == nil { ga.Conditions = make(map[string]interface{}) } ga.Conditions[key] = value ga.UpdatedAt = time.Now() } // GetCondition 获取条件 func (ga *GameAchievement) GetCondition(key string) (interface{}, bool) { if ga.Conditions == nil { return nil, false } value, exists := ga.Conditions[key] return value, exists } // SetMetadata 设置元数据 func (ga *GameAchievement) SetMetadata(key string, value interface{}) { if ga.Metadata == nil { ga.Metadata = make(map[string]interface{}) } ga.Metadata[key] = value ga.UpdatedAt = time.Now() } // GetMetadata 获取元数据 func (ga *GameAchievement) GetMetadata(key string) (interface{}, bool) { if ga.Metadata == nil { return nil, false } value, exists := ga.Metadata[key] return value, exists } // GetProgressPercentage 获取进度百分比 func (ga *GameAchievement) GetProgressPercentage() float64 { if ga.MaxProgress == 0 { return 0 } return (ga.Progress / ga.MaxProgress) * 100 } // IsCompleted 检查是否已完成 func (ga *GameAchievement) IsCompleted() bool { return ga.Completed } // IsUnlocked 检查是否已解锁 func (ga *GameAchievement) IsUnlocked() bool { return ga.Unlocked } // Clone 克隆游戏成就 func (ga *GameAchievement) Clone() *GameAchievement { clone := &GameAchievement{ ID: ga.ID, GameID: ga.GameID, PlayerID: ga.PlayerID, SessionID: ga.SessionID, AchievementID: ga.AchievementID, Name: ga.Name, Description: ga.Description, Category: ga.Category, Rarity: ga.Rarity, Points: ga.Points, Progress: ga.Progress, MaxProgress: ga.MaxProgress, Completed: ga.Completed, Unlocked: ga.Unlocked, Conditions: make(map[string]interface{}), Rewards: make([]string, len(ga.Rewards)), Metadata: make(map[string]interface{}), CreatedAt: ga.CreatedAt, UpdatedAt: ga.UpdatedAt, } // 深拷贝切片 copy(clone.Rewards, ga.Rewards) // 深拷贝map for k, v := range ga.Conditions { clone.Conditions[k] = v } for k, v := range ga.Metadata { clone.Metadata[k] = v } // 深拷贝指针 if ga.CompletedAt != nil { completedAt := *ga.CompletedAt clone.CompletedAt = &completedAt } if ga.UnlockedAt != nil { unlockedAt := *ga.UnlockedAt clone.UnlockedAt = &unlockedAt } return clone } // 常量定义 const ( // 会话相关常量 MaxSessionDuration = 24 * time.Hour // 最大会话持续时间 SessionTimeoutWarning = 5 * time.Minute // 会话超时警告时间 MaxInactiveDuration = 30 * time.Minute // 最大非活跃时间 // 分数相关常量 MaxScoreValue = int64(999999999) // 最大分数值 MinScoreValue = int64(0) // 最小分数值 DefaultMultiplier = 1.0 // 默认倍数 MaxMultiplier = 10.0 // 最大倍数 // 奖励相关常量 MaxRewardQuantity = int64(999999) // 最大奖励数量 DefaultRewardTTL = 7 * 24 * time.Hour // 默认奖励过期时间 // 成就相关常量 MaxAchievementProgress = 100.0 // 最大成就进度 MaxAchievementPoints = int64(10000) // 最大成就积分 ) // 验证函数 // ValidateGameSession 验证游戏会话 func ValidateGameSession(session *GameSession) error { if session == nil { return fmt.Errorf("session cannot be nil") } if session.GameID == "" { return fmt.Errorf("game_id cannot be empty") } if session.PlayerID == 0 { return fmt.Errorf("player_id cannot be zero") } if session.SessionToken == "" { return fmt.Errorf("session_token cannot be empty") } if !session.Status.IsValid() { return fmt.Errorf("invalid player status: %v", session.Status) } if session.Progress < 0 || session.Progress > 100 { return fmt.Errorf("progress must be between 0 and 100") } if session.Level < 1 { return fmt.Errorf("level must be positive") } return nil } // ValidateGameScore 验证游戏分数 func ValidateGameScore(score *GameScore) error { if score == nil { return fmt.Errorf("score cannot be nil") } if score.GameID == "" { return fmt.Errorf("game_id cannot be empty") } if score.PlayerID == 0 { return fmt.Errorf("player_id cannot be zero") } if !score.ScoreType.IsValid() { return fmt.Errorf("invalid score type: %v", score.ScoreType) } if score.Value < MinScoreValue || score.Value > MaxScoreValue { return fmt.Errorf("score value must be between %d and %d", MinScoreValue, MaxScoreValue) } if score.Multiplier < 0 || score.Multiplier > MaxMultiplier { return fmt.Errorf("multiplier must be between 0 and %f", MaxMultiplier) } if score.Percentile < 0 || score.Percentile > 100 { return fmt.Errorf("percentile must be between 0 and 100") } return nil } // ValidateGameReward 验证游戏奖励 func ValidateGameReward(reward *GameReward) error { if reward == nil { return fmt.Errorf("reward cannot be nil") } if reward.GameID == "" { return fmt.Errorf("game_id cannot be empty") } if reward.PlayerID == 0 { return fmt.Errorf("player_id cannot be zero") } if !reward.RewardType.IsValid() { return fmt.Errorf("invalid reward type: %v", reward.RewardType) } if reward.ItemID == "" { return fmt.Errorf("item_id cannot be empty") } if reward.Amount <= 0 || reward.Amount > MaxRewardQuantity { return fmt.Errorf("amount must be between 1 and %d", MaxRewardQuantity) } // TODO: 实现过期时间检查 // if reward.ExpiresAt != nil && reward.ExpiresAt.Before(time.Now()) { // return fmt.Errorf("expiration time cannot be in the past") // } return nil } // ValidateGameAchievement 验证游戏成就 func ValidateGameAchievement(achievement *GameAchievement) error { if achievement == nil { return fmt.Errorf("achievement cannot be nil") } if achievement.GameID == "" { return fmt.Errorf("game_id cannot be empty") } if achievement.PlayerID == 0 { return fmt.Errorf("player_id cannot be zero") } if achievement.AchievementID == "" { return fmt.Errorf("achievement_id cannot be empty") } if achievement.Name == "" { return fmt.Errorf("name cannot be empty") } if achievement.Progress < 0 || achievement.Progress > achievement.MaxProgress { return fmt.Errorf("progress must be between 0 and %f", achievement.MaxProgress) } if achievement.MaxProgress <= 0 || achievement.MaxProgress > MaxAchievementProgress { return fmt.Errorf("max_progress must be between 0 and %f", MaxAchievementProgress) } if achievement.Points < 0 || achievement.Points > MaxAchievementPoints { return fmt.Errorf("points must be between 0 and %d", MaxAchievementPoints) } return nil } ================================================ FILE: internal/domain/minigame/errors.go ================================================ package minigame import ( "fmt" "time" ) // 错误代码常量 const ( ErrorCodeInvalidState = "INVALID_STATE" ErrorCodeInsufficientPlayers = "INSUFFICIENT_PLAYERS" ) // MinigameError 小游戏错误接口 type MinigameError interface { error GetCode() string GetMessage() string GetSeverity() ErrorSeverity GetTimestamp() time.Time GetContext() map[string]interface{} SetContext(key string, value interface{}) GetCause() error IsRetryable() bool GetRetryAfter() *time.Duration } // ErrorSeverity 错误严重程度 type ErrorSeverity int32 const ( ErrorSeverityLow ErrorSeverity = iota + 1 // 低严重程度 ErrorSeverityMedium // 中等严重程度 ErrorSeverityHigh // 高严重程度 ErrorSeverityCritical // 严重程度 ) // String 返回错误严重程度的字符串表示 func (es ErrorSeverity) String() string { switch es { case ErrorSeverityLow: return "low" case ErrorSeverityMedium: return "medium" case ErrorSeverityHigh: return "high" case ErrorSeverityCritical: return "critical" default: return "unknown" } } // BaseMinigameError 基础小游戏错误 type BaseMinigameError struct { Code string `json:"code"` Message string `json:"message"` Severity ErrorSeverity `json:"severity"` Timestamp time.Time `json:"timestamp"` Context map[string]interface{} `json:"context"` Cause error `json:"cause,omitempty"` Retryable bool `json:"retryable"` RetryAfter *time.Duration `json:"retry_after,omitempty"` } // Error 实现error接口 func (e *BaseMinigameError) Error() string { if e.Cause != nil { return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // GetCode 获取错误代码 func (e *BaseMinigameError) GetCode() string { return e.Code } // GetMessage 获取错误消息 func (e *BaseMinigameError) GetMessage() string { return e.Message } // GetSeverity 获取错误严重程度 func (e *BaseMinigameError) GetSeverity() ErrorSeverity { return e.Severity } // GetTimestamp 获取错误时间戳 func (e *BaseMinigameError) GetTimestamp() time.Time { return e.Timestamp } // GetContext 获取错误上下文 func (e *BaseMinigameError) GetContext() map[string]interface{} { return e.Context } // SetContext 设置错误上下文 func (e *BaseMinigameError) SetContext(key string, value interface{}) { if e.Context == nil { e.Context = make(map[string]interface{}) } e.Context[key] = value } // GetCause 获取错误原因 func (e *BaseMinigameError) GetCause() error { return e.Cause } // IsRetryable 检查是否可重试 func (e *BaseMinigameError) IsRetryable() bool { return e.Retryable } // GetRetryAfter 获取重试延迟时间 func (e *BaseMinigameError) GetRetryAfter() *time.Duration { return e.RetryAfter } // 具体错误类型 // GameNotFoundError 游戏未找到错误 type GameNotFoundError struct { *BaseMinigameError GameID string `json:"game_id"` } // PlayerNotFoundError 玩家未找到错误 type PlayerNotFoundError struct { *BaseMinigameError PlayerID uint64 `json:"player_id"` } // SessionNotFoundError 会话未找到错误 type SessionNotFoundError struct { *BaseMinigameError SessionID string `json:"session_id"` } // ScoreNotFoundError 分数未找到错误 type ScoreNotFoundError struct { *BaseMinigameError ScoreID string `json:"score_id"` } // RewardNotFoundError 奖励未找到错误 type RewardNotFoundError struct { *BaseMinigameError RewardID string `json:"reward_id"` } // AchievementNotFoundError 成就未找到错误 type AchievementNotFoundError struct { *BaseMinigameError AchievementID string `json:"achievement_id"` } // InvalidGameTypeError 无效游戏类型错误 type InvalidGameTypeError struct { *BaseMinigameError GameType GameType `json:"game_type"` } // InvalidGameStatusError 无效游戏状态错误 type InvalidGameStatusError struct { *BaseMinigameError CurrentStatus GameStatus `json:"current_status"` TargetStatus GameStatus `json:"target_status"` } // InvalidPlayerStatusError 无效玩家状态错误 type InvalidPlayerStatusError struct { *BaseMinigameError PlayerID uint64 `json:"player_id"` CurrentStatus PlayerStatus `json:"current_status"` TargetStatus PlayerStatus `json:"target_status"` } // GameNotJoinableError 游戏不可加入错误 type GameNotJoinableError struct { *BaseMinigameError GameID string `json:"game_id"` Status GameStatus `json:"status"` Reason string `json:"reason"` } // GameFullError 游戏已满错误 type GameFullError struct { *BaseMinigameError GameID string `json:"game_id"` CurrentPlayers int32 `json:"current_players"` MaxPlayers int32 `json:"max_players"` } // PlayerAlreadyInGameError 玩家已在游戏中错误 type PlayerAlreadyInGameError struct { *BaseMinigameError GameID string `json:"game_id"` PlayerID uint64 `json:"player_id"` } // PlayerNotInGameError 玩家不在游戏中错误 type PlayerNotInGameError struct { *BaseMinigameError GameID string `json:"game_id"` PlayerID uint64 `json:"player_id"` } // GameNotRunningError 游戏未运行错误 type GameNotRunningError struct { *BaseMinigameError GameID string `json:"game_id"` Status GameStatus `json:"status"` } // InvalidScoreError 无效分数错误 type InvalidScoreError struct { *BaseMinigameError Score int64 `json:"score"` ScoreType ScoreType `json:"score_type"` Reason string `json:"reason"` } // InvalidRewardError 无效奖励错误 type InvalidRewardError struct { *BaseMinigameError RewardType RewardType `json:"reward_type"` ItemID string `json:"item_id"` Quantity int64 `json:"quantity"` Reason string `json:"reason"` } // RewardAlreadyClaimedError 奖励已领取错误 type RewardAlreadyClaimedError struct { *BaseMinigameError RewardID string `json:"reward_id"` ClaimedAt time.Time `json:"claimed_at"` } // RewardExpiredError 奖励已过期错误 type RewardExpiredError struct { *BaseMinigameError RewardID string `json:"reward_id"` ExpiresAt time.Time `json:"expires_at"` } // InvalidConfigError 无效配置错误 type InvalidConfigError struct { *BaseMinigameError ConfigField string `json:"config_field"` ConfigValue interface{} `json:"config_value"` Reason string `json:"reason"` } // InvalidSessionError 无效会话错误 type InvalidSessionError struct { *BaseMinigameError SessionID string `json:"session_id"` Reason string `json:"reason"` } // PermissionDeniedError 权限拒绝错误 type PermissionDeniedError struct { *BaseMinigameError UserID uint64 `json:"user_id"` Operation string `json:"operation"` ResourceID string `json:"resource_id"` } // RateLimitExceededError 速率限制超出错误 type RateLimitExceededError struct { *BaseMinigameError UserID uint64 `json:"user_id"` Operation string `json:"operation"` Limit int64 `json:"limit"` WindowSize time.Duration `json:"window_size"` ResetTime time.Time `json:"reset_time"` } // ConcurrencyLimitError 并发限制错误 type ConcurrencyLimitError struct { *BaseMinigameError CurrentCount int64 `json:"current_count"` MaxCount int64 `json:"max_count"` ResourceType string `json:"resource_type"` } // ValidationError 验证错误 type ValidationError struct { *BaseMinigameError Field string `json:"field"` Value interface{} `json:"value"` Rule string `json:"rule"` Reason string `json:"reason"` } // RepositoryError 仓储错误 type RepositoryError struct { *BaseMinigameError Operation string `json:"operation"` Entity string `json:"entity"` EntityID string `json:"entity_id"` } // NetworkError 网络错误 type NetworkError struct { *BaseMinigameError Endpoint string `json:"endpoint"` Method string `json:"method"` StatusCode int `json:"status_code,omitempty"` Timeout time.Duration `json:"timeout,omitempty"` } // TimeoutError 超时错误 type TimeoutError struct { *BaseMinigameError Operation string `json:"operation"` Timeout time.Duration `json:"timeout"` Elapsed time.Duration `json:"elapsed"` } // ResourceExhaustedError 资源耗尽错误 type ResourceExhaustedError struct { *BaseMinigameError ResourceType string `json:"resource_type"` Limit int64 `json:"limit"` Used int64 `json:"used"` } // InternalError 内部错误 type InternalError struct { *BaseMinigameError Component string `json:"component"` Function string `json:"function"` } // 错误代码常量 const ( // 通用错误代码 ErrorCodeUnknown = "MINIGAME_UNKNOWN" ErrorCodeInternalError = "MINIGAME_INTERNAL_ERROR" ErrorCodeValidationError = "MINIGAME_VALIDATION_ERROR" ErrorCodePermissionDenied = "MINIGAME_PERMISSION_DENIED" ErrorCodeRateLimitExceeded = "MINIGAME_RATE_LIMIT_EXCEEDED" ErrorCodeConcurrencyLimit = "MINIGAME_CONCURRENCY_LIMIT" ErrorCodeResourceExhausted = "MINIGAME_RESOURCE_EXHAUSTED" ErrorCodeTimeout = "MINIGAME_TIMEOUT" ErrorCodeNetworkError = "MINIGAME_NETWORK_ERROR" // 游戏相关错误代码 ErrorCodeGameNotFound = "MINIGAME_GAME_NOT_FOUND" ErrorCodeInvalidGameType = "MINIGAME_INVALID_GAME_TYPE" ErrorCodeInvalidGameStatus = "MINIGAME_INVALID_GAME_STATUS" ErrorCodeGameNotJoinable = "MINIGAME_GAME_NOT_JOINABLE" ErrorCodeGameFull = "MINIGAME_GAME_FULL" ErrorCodeGameNotRunning = "MINIGAME_GAME_NOT_RUNNING" ErrorCodeInvalidOperation = "MINIGAME_INVALID_OPERATION" ErrorCodeInvalidConfig = "MINIGAME_INVALID_CONFIG" // 玩家相关错误代码 ErrorCodePlayerNotFound = "MINIGAME_PLAYER_NOT_FOUND" ErrorCodeInvalidPlayer = "MINIGAME_INVALID_PLAYER" ErrorCodeInvalidPlayerStatus = "MINIGAME_INVALID_PLAYER_STATUS" ErrorCodePlayerAlreadyInGame = "MINIGAME_PLAYER_ALREADY_IN_GAME" ErrorCodePlayerNotInGame = "MINIGAME_PLAYER_NOT_IN_GAME" // 会话相关错误代码 ErrorCodeSessionNotFound = "MINIGAME_SESSION_NOT_FOUND" ErrorCodeInvalidSession = "MINIGAME_INVALID_SESSION" // 分数相关错误代码 ErrorCodeScoreNotFound = "MINIGAME_SCORE_NOT_FOUND" ErrorCodeInvalidScore = "MINIGAME_INVALID_SCORE" // 奖励相关错误代码 ErrorCodeRewardNotFound = "MINIGAME_REWARD_NOT_FOUND" ErrorCodeInvalidReward = "MINIGAME_INVALID_REWARD" ErrorCodeRewardAlreadyClaimed = "MINIGAME_REWARD_ALREADY_CLAIMED" ErrorCodeRewardExpired = "MINIGAME_REWARD_EXPIRED" // 成就相关错误代码 ErrorCodeAchievementNotFound = "MINIGAME_ACHIEVEMENT_NOT_FOUND" ErrorCodeInvalidAchievement = "MINIGAME_INVALID_ACHIEVEMENT" // 仓储相关错误代码 ErrorCodeRepositoryError = "MINIGAME_REPOSITORY_ERROR" ErrorCodeDatabaseError = "MINIGAME_DATABASE_ERROR" ErrorCodeCacheError = "MINIGAME_CACHE_ERROR" ) // 错误工厂函数 // NewMinigameError 创建基础小游戏错误 func NewMinigameError(code, message string, cause error) *BaseMinigameError { return &BaseMinigameError{ Code: code, Message: message, Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Cause: cause, Retryable: false, } } // NewMinigameInvalidStateError 创建无效状态错误 func NewMinigameInvalidStateError(gameID string, currentStatus, expectedStatus GameStatus, operation string) *BaseMinigameError { return &BaseMinigameError{ Code: ErrorCodeInvalidState, Message: fmt.Sprintf("Invalid state for operation %s: current=%s, expected=%s", operation, currentStatus, expectedStatus), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: map[string]interface{}{"game_id": gameID, "current_status": currentStatus, "expected_status": expectedStatus, "operation": operation}, Retryable: false, } } // NewMinigameInsufficientPlayersError 创建玩家不足错误 func NewMinigameInsufficientPlayersError(gameID string, currentPlayers, minPlayers int32) *BaseMinigameError { return &BaseMinigameError{ Code: ErrorCodeInsufficientPlayers, Message: fmt.Sprintf("Insufficient players: current=%d, minimum=%d", currentPlayers, minPlayers), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: map[string]interface{}{"game_id": gameID, "current_players": currentPlayers, "min_players": minPlayers}, Retryable: false, } } // NewPlayerAlreadyInGameError 创建玩家已在游戏中错误 func NewPlayerAlreadyInGameError(playerID uint64, gameID string) *BaseMinigameError { return &BaseMinigameError{ Code: ErrorCodePlayerAlreadyInGame, Message: fmt.Sprintf("Player %d is already in game %s", playerID, gameID), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: map[string]interface{}{"player_id": playerID, "game_id": gameID}, Retryable: false, } } // NewGameNotFoundError 创建游戏未找到错误 func NewGameNotFoundError(gameID string) *GameNotFoundError { return &GameNotFoundError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeGameNotFound, Message: fmt.Sprintf("Game not found: %s", gameID), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, GameID: gameID, } } // NewPlayerNotFoundError 创建玩家未找到错误 func NewPlayerNotFoundError(playerID uint64) *PlayerNotFoundError { return &PlayerNotFoundError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodePlayerNotFound, Message: fmt.Sprintf("Player not found: %d", playerID), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, PlayerID: playerID, } } // NewSessionNotFoundError 创建会话未找到错误 func NewSessionNotFoundError(sessionID string) *SessionNotFoundError { return &SessionNotFoundError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeSessionNotFound, Message: fmt.Sprintf("Session not found: %s", sessionID), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, SessionID: sessionID, } } // NewInvalidGameTypeError 创建无效游戏类型错误 func NewInvalidGameTypeError(gameType GameType) *InvalidGameTypeError { return &InvalidGameTypeError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeInvalidGameType, Message: fmt.Sprintf("Invalid game type: %v", gameType), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, GameType: gameType, } } // NewGameNotJoinableError 创建游戏不可加入错误 func NewGameNotJoinableError(gameID string, status GameStatus, reason string) *GameNotJoinableError { return &GameNotJoinableError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeGameNotJoinable, Message: fmt.Sprintf("Game %s is not joinable: %s", gameID, reason), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, GameID: gameID, Status: status, Reason: reason, } } // NewGameFullError 创建游戏已满错误 func NewGameFullError(gameID string, currentPlayers, maxPlayers int32) *GameFullError { return &GameFullError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeGameFull, Message: fmt.Sprintf("Game %s is full: %d/%d players", gameID, currentPlayers, maxPlayers), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: true, }, GameID: gameID, CurrentPlayers: currentPlayers, MaxPlayers: maxPlayers, } } // NewPlayerAlreadyInGameError 创建玩家已在游戏中错误 (duplicate removed - see line 428) // NewPlayerNotInGameError 创建玩家不在游戏中错误 func NewPlayerNotInGameError(gameID string, playerID uint64) *PlayerNotInGameError { return &PlayerNotInGameError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodePlayerNotInGame, Message: fmt.Sprintf("Player %d is not in game %s", playerID, gameID), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, GameID: gameID, PlayerID: playerID, } } // NewInvalidScoreError 创建无效分数错误 func NewInvalidScoreError(score int64, scoreType ScoreType, reason string) *InvalidScoreError { return &InvalidScoreError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeInvalidScore, Message: fmt.Sprintf("Invalid score %d for type %v: %s", score, scoreType, reason), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, Score: score, ScoreType: scoreType, Reason: reason, } } // NewRewardAlreadyClaimedError 创建奖励已领取错误 func NewRewardAlreadyClaimedError(rewardID string, claimedAt time.Time) *RewardAlreadyClaimedError { return &RewardAlreadyClaimedError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeRewardAlreadyClaimed, Message: fmt.Sprintf("Reward %s has already been claimed at %v", rewardID, claimedAt), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, RewardID: rewardID, ClaimedAt: claimedAt, } } // NewRewardExpiredError 创建奖励已过期错误 func NewRewardExpiredError(rewardID string, expiresAt time.Time) *RewardExpiredError { return &RewardExpiredError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeRewardExpired, Message: fmt.Sprintf("Reward %s has expired at %v", rewardID, expiresAt), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, RewardID: rewardID, ExpiresAt: expiresAt, } } // NewPermissionDeniedError 创建权限拒绝错误 func NewPermissionDeniedError(userID uint64, operation, resourceID string) *PermissionDeniedError { return &PermissionDeniedError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodePermissionDenied, Message: fmt.Sprintf("Permission denied for user %d to perform %s on %s", userID, operation, resourceID), Severity: ErrorSeverityHigh, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: false, }, UserID: userID, Operation: operation, ResourceID: resourceID, } } // NewRateLimitExceededError 创建速率限制超出错误 func NewRateLimitExceededError(userID uint64, operation string, limit int64, windowSize time.Duration, resetTime time.Time) *RateLimitExceededError { retryAfter := time.Until(resetTime) return &RateLimitExceededError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeRateLimitExceeded, Message: fmt.Sprintf("Rate limit exceeded for user %d on %s: %d requests per %v", userID, operation, limit, windowSize), Severity: ErrorSeverityMedium, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: true, RetryAfter: &retryAfter, }, UserID: userID, Operation: operation, Limit: limit, WindowSize: windowSize, ResetTime: resetTime, } } // NewTimeoutError 创建超时错误 func NewTimeoutError(operation string, timeout, elapsed time.Duration) *TimeoutError { return &TimeoutError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeTimeout, Message: fmt.Sprintf("Operation %s timed out after %v (elapsed: %v)", operation, timeout, elapsed), Severity: ErrorSeverityHigh, Timestamp: time.Now(), Context: make(map[string]interface{}), Retryable: true, }, Operation: operation, Timeout: timeout, Elapsed: elapsed, } } // NewRepositoryError 创建仓储错误 func NewRepositoryError(operation, entity, entityID string, cause error) *RepositoryError { return &RepositoryError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeRepositoryError, Message: fmt.Sprintf("Repository error during %s on %s %s", operation, entity, entityID), Severity: ErrorSeverityHigh, Timestamp: time.Now(), Context: make(map[string]interface{}), Cause: cause, Retryable: true, }, Operation: operation, Entity: entity, EntityID: entityID, } } // NewInternalError 创建内部错误 func NewInternalError(component, function string, cause error) *InternalError { return &InternalError{ BaseMinigameError: &BaseMinigameError{ Code: ErrorCodeInternalError, Message: fmt.Sprintf("Internal error in %s.%s", component, function), Severity: ErrorSeverityCritical, Timestamp: time.Now(), Context: make(map[string]interface{}), Cause: cause, Retryable: false, }, Component: component, Function: function, } } // 错误分类函数 // IsNotFoundError 检查是否为未找到错误 func IsNotFoundError(err error) bool { switch err.(type) { case *GameNotFoundError, *PlayerNotFoundError, *SessionNotFoundError, *ScoreNotFoundError, *RewardNotFoundError, *AchievementNotFoundError: return true default: return false } } // IsValidationError 检查是否为验证错误 func IsValidationError(err error) bool { switch err.(type) { case *InvalidGameTypeError, *InvalidGameStatusError, *InvalidPlayerStatusError, *InvalidScoreError, *InvalidRewardError, *InvalidConfigError, *InvalidSessionError, *ValidationError: return true default: return false } } // IsPermissionError 检查是否为权限错误 func IsPermissionError(err error) bool { switch err.(type) { case *PermissionDeniedError: return true default: return false } } // IsRateLimitError 检查是否为速率限制错误 func IsRateLimitError(err error) bool { switch err.(type) { case *RateLimitExceededError: return true default: return false } } // IsRetryableError 检查是否为可重试错误 func IsRetryableError(err error) bool { if minigameErr, ok := err.(MinigameError); ok { return minigameErr.IsRetryable() } return false } // IsTemporaryError 检查是否为临时错误 func IsTemporaryError(err error) bool { switch err.(type) { case *NetworkError, *TimeoutError, *RateLimitExceededError, *ConcurrencyLimitError, *ResourceExhaustedError: return true default: return false } } // IsCriticalError 检查是否为严重错误 func IsCriticalError(err error) bool { if minigameErr, ok := err.(MinigameError); ok { return minigameErr.GetSeverity() == ErrorSeverityCritical } return false } // 错误恢复策略 // ErrorRecoveryStrategy 错误恢复策略 type ErrorRecoveryStrategy int32 const ( RecoveryStrategyNone ErrorRecoveryStrategy = iota + 1 // 无恢复策略 RecoveryStrategyRetry // 重试 RecoveryStrategyFallback // 降级 RecoveryStrategyCircuitBreaker // 熔断 RecoveryStrategyGracefulDegradation // 优雅降级 ) // String 返回恢复策略的字符串表示 func (ers ErrorRecoveryStrategy) String() string { switch ers { case RecoveryStrategyNone: return "none" case RecoveryStrategyRetry: return "retry" case RecoveryStrategyFallback: return "fallback" case RecoveryStrategyCircuitBreaker: return "circuit_breaker" case RecoveryStrategyGracefulDegradation: return "graceful_degradation" default: return "unknown" } } // GetRecoveryStrategy 获取错误的恢复策略 func GetRecoveryStrategy(err error) ErrorRecoveryStrategy { switch err.(type) { case *NetworkError, *TimeoutError: return RecoveryStrategyRetry case *RateLimitExceededError: return RecoveryStrategyRetry case *ConcurrencyLimitError, *ResourceExhaustedError: return RecoveryStrategyCircuitBreaker case *GameFullError: return RecoveryStrategyFallback case *RepositoryError: return RecoveryStrategyRetry case *InternalError: return RecoveryStrategyGracefulDegradation default: return RecoveryStrategyNone } } // GetRetryDelay 获取重试延迟时间 func GetRetryDelay(err error, attempt int) time.Duration { baseDelay := time.Second maxDelay := 30 * time.Second // 指数退避 delay := time.Duration(attempt) * baseDelay if delay > maxDelay { delay = maxDelay } // 特殊错误类型的延迟调整 switch e := err.(type) { case *RateLimitExceededError: if e.GetRetryAfter() != nil { return *e.GetRetryAfter() } case *TimeoutError: // 超时错误使用更长的延迟 delay = delay * 2 case *NetworkError: // 网络错误使用较短的延迟 delay = delay / 2 } return delay } // GetMaxRetryAttempts 获取最大重试次数 func GetMaxRetryAttempts(err error) int { switch err.(type) { case *NetworkError: return 3 case *TimeoutError: return 2 case *RateLimitExceededError: return 5 case *RepositoryError: return 3 case *ResourceExhaustedError: return 1 default: return 0 } } // 错误聚合和统计 // ErrorStatistics 错误统计 type ErrorStatistics struct { TotalErrors int64 `json:"total_errors"` ErrorsByCode map[string]int64 `json:"errors_by_code"` ErrorsBySeverity map[ErrorSeverity]int64 `json:"errors_by_severity"` ErrorsByType map[string]int64 `json:"errors_by_type"` RetryableErrors int64 `json:"retryable_errors"` CriticalErrors int64 `json:"critical_errors"` LastError *time.Time `json:"last_error,omitempty"` ErrorRate float64 `json:"error_rate"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // NewErrorStatistics 创建错误统计 func NewErrorStatistics() *ErrorStatistics { now := time.Now() return &ErrorStatistics{ TotalErrors: 0, ErrorsByCode: make(map[string]int64), ErrorsBySeverity: make(map[ErrorSeverity]int64), ErrorsByType: make(map[string]int64), RetryableErrors: 0, CriticalErrors: 0, ErrorRate: 0.0, CreatedAt: now, UpdatedAt: now, } } // RecordError 记录错误 func (es *ErrorStatistics) RecordError(err error) { es.TotalErrors++ now := time.Now() es.LastError = &now es.UpdatedAt = now if minigameErr, ok := err.(MinigameError); ok { // 按错误代码统计 code := minigameErr.GetCode() es.ErrorsByCode[code]++ // 按严重程度统计 severity := minigameErr.GetSeverity() es.ErrorsBySeverity[severity]++ // 统计可重试错误 if minigameErr.IsRetryable() { es.RetryableErrors++ } // 统计严重错误 if severity == ErrorSeverityCritical { es.CriticalErrors++ } } // 按错误类型统计 errorType := fmt.Sprintf("%T", err) es.ErrorsByType[errorType]++ } // CalculateErrorRate 计算错误率 func (es *ErrorStatistics) CalculateErrorRate(totalRequests int64) { if totalRequests > 0 { es.ErrorRate = float64(es.TotalErrors) / float64(totalRequests) * 100 } else { es.ErrorRate = 0.0 } es.UpdatedAt = time.Now() } // Reset 重置统计 func (es *ErrorStatistics) Reset() { es.TotalErrors = 0 es.ErrorsByCode = make(map[string]int64) es.ErrorsBySeverity = make(map[ErrorSeverity]int64) es.ErrorsByType = make(map[string]int64) es.RetryableErrors = 0 es.CriticalErrors = 0 es.LastError = nil es.ErrorRate = 0.0 es.UpdatedAt = time.Now() } // 辅助函数 // WrapError 包装错误 func WrapError(err error, code, message string) MinigameError { if err == nil { return nil } if minigameErr, ok := err.(MinigameError); ok { return minigameErr } return NewMinigameError(code, message, err) } // UnwrapError 解包错误 func UnwrapError(err error) error { if minigameErr, ok := err.(MinigameError); ok { return minigameErr.GetCause() } return err } // FormatError 格式化错误信息 func FormatError(err error) string { if minigameErr, ok := err.(MinigameError); ok { return fmt.Sprintf("[%s] %s (severity: %s, timestamp: %v)", minigameErr.GetCode(), minigameErr.GetMessage(), minigameErr.GetSeverity(), minigameErr.GetTimestamp().Format(time.RFC3339)) } return err.Error() } // LogError 记录错误日志 func LogError(err error, context map[string]interface{}) { if minigameErr, ok := err.(MinigameError); ok { // 合并上下文 for k, v := range context { minigameErr.SetContext(k, v) } // 根据严重程度记录日志 switch minigameErr.GetSeverity() { case ErrorSeverityCritical: // 记录严重错误日志 fmt.Printf("CRITICAL ERROR: %s\n", FormatError(err)) case ErrorSeverityHigh: // 记录高级错误日志 fmt.Printf("HIGH ERROR: %s\n", FormatError(err)) case ErrorSeverityMedium: // 记录中级错误日志 fmt.Printf("MEDIUM ERROR: %s\n", FormatError(err)) case ErrorSeverityLow: // 记录低级错误日志 fmt.Printf("LOW ERROR: %s\n", FormatError(err)) } } else { // 记录普通错误日志 fmt.Printf("ERROR: %s\n", err.Error()) } } // NewMinigameValidationError 创建小游戏验证错误 func NewMinigameValidationError(message string) error { return fmt.Errorf("validation error: %s", message) } ================================================ FILE: internal/domain/minigame/events.go ================================================ package minigame import ( "context" "fmt" "time" ) // MinigameEvent 小游戏事件接口 type MinigameEvent interface { GetEventID() string GetEventType() string GetGameID() string GetPlayerID() *uint64 GetTimestamp() time.Time GetMetadata() map[string]interface{} SetMetadata(key string, value interface{}) } // BaseMinigameEvent 基础小游戏事件 type BaseMinigameEvent struct { EventID string `json:"event_id" bson:"event_id"` EventType string `json:"event_type" bson:"event_type"` GameID string `json:"game_id" bson:"game_id"` PlayerID *uint64 `json:"player_id,omitempty" bson:"player_id,omitempty"` Timestamp time.Time `json:"timestamp" bson:"timestamp"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` } // GetEventID 获取事件ID func (e *BaseMinigameEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseMinigameEvent) GetEventType() string { return e.EventType } // GetGameID 获取游戏ID func (e *BaseMinigameEvent) GetGameID() string { return e.GameID } // GetPlayerID 获取玩家ID func (e *BaseMinigameEvent) GetPlayerID() *uint64 { return e.PlayerID } // GetTimestamp 获取时间戳 func (e *BaseMinigameEvent) GetTimestamp() time.Time { return e.Timestamp } // GetMetadata 获取元数据 func (e *BaseMinigameEvent) GetMetadata() map[string]interface{} { return e.Metadata } // SetMetadata 设置元数据 func (e *BaseMinigameEvent) SetMetadata(key string, value interface{}) { if e.Metadata == nil { e.Metadata = make(map[string]interface{}) } e.Metadata[key] = value } // 游戏生命周期事件 // MinigameCreatedEvent 小游戏创建事件 type MinigameCreatedEvent struct { BaseMinigameEvent GameType GameType `json:"game_type" bson:"game_type"` CreatorID uint64 `json:"creator_id" bson:"creator_id"` } // GameStartedEvent 游戏开始事件 type GameStartedEvent struct { BaseMinigameEvent OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameEndedEvent 游戏结束事件 type GameEndedEvent struct { BaseMinigameEvent EndReason GameEndReason `json:"end_reason" bson:"end_reason"` OperatorID uint64 `json:"operator_id" bson:"operator_id"` Duration time.Duration `json:"duration" bson:"duration"` } // GamePausedEvent 游戏暂停事件 type GamePausedEvent struct { BaseMinigameEvent OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameResumedEvent 游戏恢复事件 type GameResumedEvent struct { BaseMinigameEvent OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameCancelledEvent 游戏取消事件 type GameCancelledEvent struct { BaseMinigameEvent Reason string `json:"reason" bson:"reason"` OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameResetEvent 游戏重置事件 type GameResetEvent struct { BaseMinigameEvent OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameDeletedEvent 游戏删除事件 type GameDeletedEvent struct { BaseMinigameEvent OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // 游戏状态事件 // GameStatusChangedEvent 游戏状态变更事件 type GameStatusChangedEvent struct { BaseMinigameEvent OldStatus GameStatus `json:"old_status" bson:"old_status"` NewStatus GameStatus `json:"new_status" bson:"new_status"` } // GamePhaseChangedEvent 游戏阶段变更事件 type GamePhaseChangedEvent struct { BaseMinigameEvent OldPhase GamePhase `json:"old_phase" bson:"old_phase"` NewPhase GamePhase `json:"new_phase" bson:"new_phase"` } // GameConfigUpdatedEvent 游戏配置更新事件 type GameConfigUpdatedEvent struct { BaseMinigameEvent UpdatedFields []string `json:"updated_fields" bson:"updated_fields"` OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameRulesUpdatedEvent 游戏规则更新事件 type GameRulesUpdatedEvent struct { BaseMinigameEvent UpdatedRules []string `json:"updated_rules" bson:"updated_rules"` OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // GameSettingsUpdatedEvent 游戏设置更新事件 type GameSettingsUpdatedEvent struct { BaseMinigameEvent UpdatedSettings []string `json:"updated_settings" bson:"updated_settings"` OperatorID uint64 `json:"operator_id" bson:"operator_id"` } // 玩家相关事件 // PlayerJoinedEvent 玩家加入事件 type PlayerJoinedEvent struct { BaseMinigameEvent SessionID string `json:"session_id" bson:"session_id"` } // PlayerLeftEvent 玩家离开事件 type PlayerLeftEvent struct { BaseMinigameEvent LeaveReason PlayerLeaveReason `json:"leave_reason" bson:"leave_reason"` SessionID string `json:"session_id" bson:"session_id"` } // PlayerKickedEvent 玩家被踢出事件 type PlayerKickedEvent struct { BaseMinigameEvent Reason string `json:"reason" bson:"reason"` OperatorID uint64 `json:"operator_id" bson:"operator_id"` SessionID string `json:"session_id" bson:"session_id"` } // PlayerStatusChangedEvent 玩家状态变更事件 type PlayerStatusChangedEvent struct { BaseMinigameEvent OldStatus PlayerStatus `json:"old_status" bson:"old_status"` NewStatus PlayerStatus `json:"new_status" bson:"new_status"` SessionID string `json:"session_id" bson:"session_id"` } // PlayerReadyEvent 玩家准备事件 type PlayerReadyEvent struct { BaseMinigameEvent SessionID string `json:"session_id" bson:"session_id"` } // PlayerNotReadyEvent 玩家取消准备事件 type PlayerNotReadyEvent struct { BaseMinigameEvent SessionID string `json:"session_id" bson:"session_id"` } // PlayerDisconnectedEvent 玩家断线事件 type PlayerDisconnectedEvent struct { BaseMinigameEvent Reason string `json:"reason" bson:"reason"` SessionID string `json:"session_id" bson:"session_id"` } // PlayerReconnectedEvent 玩家重连事件 type PlayerReconnectedEvent struct { BaseMinigameEvent SessionID string `json:"session_id" bson:"session_id"` } // 分数和进度事件 // ScoreUpdatedEvent 分数更新事件 type ScoreUpdatedEvent struct { BaseMinigameEvent ScoreType ScoreType `json:"score_type" bson:"score_type"` OldScore int64 `json:"old_score" bson:"old_score"` NewScore int64 `json:"new_score" bson:"new_score"` FinalScore int64 `json:"final_score" bson:"final_score"` SessionID string `json:"session_id" bson:"session_id"` } // HighScoreAchievedEvent 最高分达成事件 type HighScoreAchievedEvent struct { BaseMinigameEvent ScoreType ScoreType `json:"score_type" bson:"score_type"` Score int64 `json:"score" bson:"score"` PreviousHigh int64 `json:"previous_high" bson:"previous_high"` SessionID string `json:"session_id" bson:"session_id"` } // LevelUpEvent 升级事件 type LevelUpEvent struct { BaseMinigameEvent OldLevel int32 `json:"old_level" bson:"old_level"` NewLevel int32 `json:"new_level" bson:"new_level"` SessionID string `json:"session_id" bson:"session_id"` } // ProgressUpdatedEvent 进度更新事件 type ProgressUpdatedEvent struct { BaseMinigameEvent OldProgress float64 `json:"old_progress" bson:"old_progress"` NewProgress float64 `json:"new_progress" bson:"new_progress"` SessionID string `json:"session_id" bson:"session_id"` } // MilestoneReachedEvent 里程碑达成事件 type MilestoneReachedEvent struct { BaseMinigameEvent Milestone string `json:"milestone" bson:"milestone"` Value int64 `json:"value" bson:"value"` SessionID string `json:"session_id" bson:"session_id"` } // 奖励相关事件 // RewardGrantedEvent 奖励授予事件 type RewardGrantedEvent struct { BaseMinigameEvent RewardType RewardType `json:"reward_type" bson:"reward_type"` ItemID string `json:"item_id" bson:"item_id"` Quantity int64 `json:"quantity" bson:"quantity"` Source string `json:"source" bson:"source"` RewardID string `json:"reward_id" bson:"reward_id"` } // RewardClaimedEvent 奖励领取事件 type RewardClaimedEvent struct { BaseMinigameEvent RewardType RewardType `json:"reward_type" bson:"reward_type"` ItemID string `json:"item_id" bson:"item_id"` Quantity int64 `json:"quantity" bson:"quantity"` RewardID string `json:"reward_id" bson:"reward_id"` } // RewardExpiredEvent 奖励过期事件 type RewardExpiredEvent struct { BaseMinigameEvent RewardType RewardType `json:"reward_type" bson:"reward_type"` ItemID string `json:"item_id" bson:"item_id"` Quantity int64 `json:"quantity" bson:"quantity"` RewardID string `json:"reward_id" bson:"reward_id"` } // BonusRewardEvent 奖励加成事件 type BonusRewardEvent struct { BaseMinigameEvent BonusType string `json:"bonus_type" bson:"bonus_type"` Multiplier float64 `json:"multiplier" bson:"multiplier"` BonusAmount int64 `json:"bonus_amount" bson:"bonus_amount"` Reason string `json:"reason" bson:"reason"` } // 成就相关事件 // AchievementUnlockedEvent 成就解锁事件 type AchievementUnlockedEvent struct { BaseMinigameEvent AchievementID string `json:"achievement_id" bson:"achievement_id"` Name string `json:"name" bson:"name"` Category string `json:"category" bson:"category"` Rarity string `json:"rarity" bson:"rarity"` Points int64 `json:"points" bson:"points"` } // AchievementCompletedEvent 成就完成事件 type AchievementCompletedEvent struct { BaseMinigameEvent AchievementID string `json:"achievement_id" bson:"achievement_id"` Name string `json:"name" bson:"name"` Category string `json:"category" bson:"category"` Rarity string `json:"rarity" bson:"rarity"` Points int64 `json:"points" bson:"points"` } // AchievementProgressEvent 成就进度事件 type AchievementProgressEvent struct { BaseMinigameEvent AchievementID string `json:"achievement_id" bson:"achievement_id"` OldProgress float64 `json:"old_progress" bson:"old_progress"` NewProgress float64 `json:"new_progress" bson:"new_progress"` MaxProgress float64 `json:"max_progress" bson:"max_progress"` } // 游戏操作事件 // GameActionEvent 游戏动作事件 type GameActionEvent struct { BaseMinigameEvent Action string `json:"action" bson:"action"` Parameters map[string]interface{} `json:"parameters" bson:"parameters"` Result string `json:"result" bson:"result"` SessionID string `json:"session_id" bson:"session_id"` } // GameMoveEvent 游戏移动事件 type GameMoveEvent struct { BaseMinigameEvent MoveType string `json:"move_type" bson:"move_type"` MoveData map[string]interface{} `json:"move_data" bson:"move_data"` MoveNumber int32 `json:"move_number" bson:"move_number"` SessionID string `json:"session_id" bson:"session_id"` } // GameInputEvent 游戏输入事件 type GameInputEvent struct { BaseMinigameEvent InputType string `json:"input_type" bson:"input_type"` InputData map[string]interface{} `json:"input_data" bson:"input_data"` SessionID string `json:"session_id" bson:"session_id"` } // GameOutputEvent 游戏输出事件 type GameOutputEvent struct { BaseMinigameEvent OutputType string `json:"output_type" bson:"output_type"` OutputData map[string]interface{} `json:"output_data" bson:"output_data"` SessionID string `json:"session_id" bson:"session_id"` } // 系统事件 // GameErrorEvent 游戏错误事件 type GameErrorEvent struct { BaseMinigameEvent ErrorCode string `json:"error_code" bson:"error_code"` ErrorMessage string `json:"error_message" bson:"error_message"` ErrorType string `json:"error_type" bson:"error_type"` StackTrace string `json:"stack_trace,omitempty" bson:"stack_trace,omitempty"` SessionID string `json:"session_id,omitempty" bson:"session_id,omitempty"` } // GameWarningEvent 游戏警告事件 type GameWarningEvent struct { BaseMinigameEvent WarningCode string `json:"warning_code" bson:"warning_code"` WarningMessage string `json:"warning_message" bson:"warning_message"` WarningType string `json:"warning_type" bson:"warning_type"` SessionID string `json:"session_id,omitempty" bson:"session_id,omitempty"` } // GameMaintenanceEvent 游戏维护事件 type GameMaintenanceEvent struct { BaseMinigameEvent MaintenanceType string `json:"maintenance_type" bson:"maintenance_type"` StartTime time.Time `json:"start_time" bson:"start_time"` EndTime time.Time `json:"end_time" bson:"end_time"` Reason string `json:"reason" bson:"reason"` } // GameUpdateEvent 游戏更新事件 type GameUpdateEvent struct { BaseMinigameEvent UpdateType string `json:"update_type" bson:"update_type"` Version string `json:"version" bson:"version"` UpdateDetails string `json:"update_details" bson:"update_details"` } // 统计事件 // GameStatisticsEvent 游戏统计事件 type GameStatisticsEvent struct { BaseMinigameEvent StatisticsType string `json:"statistics_type" bson:"statistics_type"` Statistics map[string]interface{} `json:"statistics" bson:"statistics"` Period string `json:"period" bson:"period"` } // PlayerStatisticsEvent 玩家统计事件 type PlayerStatisticsEvent struct { BaseMinigameEvent StatisticsType string `json:"statistics_type" bson:"statistics_type"` Statistics map[string]interface{} `json:"statistics" bson:"statistics"` Period string `json:"period" bson:"period"` } // PlayerJoinedGameEvent 玩家加入游戏事件 type PlayerJoinedGameEvent struct { BaseMinigameEvent PlayerID uint64 `json:"player_id" bson:"player_id"` PlayerName string `json:"player_name" bson:"player_name"` } // PlayerLeftGameEvent 玩家离开游戏事件 type PlayerLeftGameEvent struct { BaseMinigameEvent PlayerID uint64 `json:"player_id" bson:"player_id"` PlayerName string `json:"player_name" bson:"player_name"` Reason string `json:"reason" bson:"reason"` } // PlayerScoreUpdatedEvent 玩家分数更新事件 type PlayerScoreUpdatedEvent struct { BaseMinigameEvent PlayerID uint64 `json:"player_id" bson:"player_id"` OldScore int64 `json:"old_score" bson:"old_score"` NewScore int64 `json:"new_score" bson:"new_score"` ScoreType ScoreType `json:"score_type" bson:"score_type"` } // GameDataUpdatedEvent 游戏数据更新事件 type GameDataUpdatedEvent struct { BaseMinigameEvent Key string `json:"key" bson:"key"` Value interface{} `json:"value" bson:"value"` } // GamePausedEvent 游戏暂停事件 (duplicate removed - see line 91) // GameResumedEvent 游戏恢复事件 (duplicate removed - see line 97) // LeaderboardUpdatedEvent 排行榜更新事件 type LeaderboardUpdatedEvent struct { BaseMinigameEvent LeaderboardType string `json:"leaderboard_type" bson:"leaderboard_type"` ScoreType ScoreType `json:"score_type" bson:"score_type"` TopPlayers []uint64 `json:"top_players" bson:"top_players"` UpdateReason string `json:"update_reason" bson:"update_reason"` } // 事件常量 const ( // 游戏生命周期事件类型 EventTypeMinigameCreated = "minigame.created" EventTypeGameStarted = "game.started" EventTypeGameEnded = "game.ended" EventTypeGamePaused = "game.paused" EventTypeGameResumed = "game.resumed" EventTypeGameCancelled = "game.cancelled" EventTypeGameReset = "game.reset" EventTypeGameDeleted = "game.deleted" // 游戏状态事件类型 EventTypeGameStatusChanged = "game.status_changed" EventTypeGamePhaseChanged = "game.phase_changed" EventTypeGameConfigUpdated = "game.config_updated" EventTypeGameRulesUpdated = "game.rules_updated" EventTypeGameSettingsUpdated = "game.settings_updated" // 玩家相关事件类型 EventTypePlayerJoined = "player.joined" EventTypePlayerLeft = "player.left" EventTypePlayerScoreUpdated = "player.score_updated" EventTypeGameDataUpdated = "game.data_updated" // EventTypeGamePaused = "game.paused" // Duplicate removed - see line 480 // EventTypeGameResumed = "game.resumed" // Duplicate removed - see line 481 EventTypePlayerKicked = "player.kicked" EventTypePlayerStatusChanged = "player.status_changed" EventTypePlayerReady = "player.ready" EventTypePlayerNotReady = "player.not_ready" EventTypePlayerDisconnected = "player.disconnected" EventTypePlayerReconnected = "player.reconnected" // 分数和进度事件类型 EventTypeScoreUpdated = "score.updated" EventTypeHighScoreAchieved = "score.high_score_achieved" EventTypeLevelUp = "progress.level_up" EventTypeProgressUpdated = "progress.updated" EventTypeMilestoneReached = "progress.milestone_reached" // 奖励相关事件类型 EventTypeRewardGranted = "reward.granted" EventTypeRewardClaimed = "reward.claimed" EventTypeRewardExpired = "reward.expired" EventTypeBonusReward = "reward.bonus" // 成就相关事件类型 EventTypeAchievementUnlocked = "achievement.unlocked" EventTypeAchievementCompleted = "achievement.completed" EventTypeAchievementProgress = "achievement.progress" // 游戏操作事件类型 EventTypeGameAction = "game.action" EventTypeGameMove = "game.move" EventTypeGameInput = "game.input" EventTypeGameOutput = "game.output" // 系统事件类型 EventTypeGameError = "system.error" EventTypeGameWarning = "system.warning" EventTypeGameMaintenance = "system.maintenance" EventTypeGameUpdate = "system.update" // 统计事件类型 EventTypeGameStatistics = "statistics.game" EventTypePlayerStatistics = "statistics.player" EventTypeLeaderboardUpdated = "statistics.leaderboard_updated" ) // 事件工厂函数 // NewMinigameCreatedEvent 创建小游戏创建事件 func NewMinigameCreatedEvent(gameID string, gameType GameType, creatorID uint64) *MinigameCreatedEvent { return &MinigameCreatedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeMinigameCreated, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, GameType: gameType, CreatorID: creatorID, } } // NewGameStartedEvent 创建游戏开始事件 func NewGameStartedEvent(gameID string, operatorID uint64) *GameStartedEvent { return &GameStartedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeGameStarted, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, OperatorID: operatorID, } } // NewGameEndedEvent 创建游戏结束事件 func NewGameEndedEvent(gameID string, endReason GameEndReason, operatorID uint64) *GameEndedEvent { return &GameEndedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeGameEnded, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, EndReason: endReason, OperatorID: operatorID, } } // NewPlayerJoinedGameEvent 创建玩家加入游戏事件 func NewPlayerJoinedGameEvent(gameID string, playerID uint64, playerName string) *PlayerJoinedGameEvent { return &PlayerJoinedGameEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypePlayerJoined, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, PlayerID: playerID, PlayerName: playerName, } } // NewPlayerLeftGameEvent 创建玩家离开游戏事件 func NewPlayerLeftGameEvent(gameID string, playerID uint64, playerName string, reason string) *PlayerLeftGameEvent { return &PlayerLeftGameEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypePlayerLeft, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, PlayerID: playerID, PlayerName: playerName, Reason: reason, } } // NewPlayerScoreUpdatedEvent 创建玩家分数更新事件 func NewPlayerScoreUpdatedEvent(gameID string, playerID uint64, oldScore, newScore int64, scoreType ScoreType) *PlayerScoreUpdatedEvent { return &PlayerScoreUpdatedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypePlayerScoreUpdated, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, PlayerID: playerID, OldScore: oldScore, NewScore: newScore, ScoreType: scoreType, } } // NewGameDataUpdatedEvent 创建游戏数据更新事件 func NewGameDataUpdatedEvent(gameID string, key string, value interface{}) *GameDataUpdatedEvent { return &GameDataUpdatedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeGameDataUpdated, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, Key: key, Value: value, } } // NewGamePausedEvent 创建游戏暂停事件 func NewGamePausedEvent(gameID string, operatorID uint64) *GamePausedEvent { return &GamePausedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeGamePaused, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, OperatorID: operatorID, } } // NewGameResumedEvent 创建游戏恢复事件 func NewGameResumedEvent(gameID string, operatorID uint64) *GameResumedEvent { return &GameResumedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeGameResumed, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, OperatorID: operatorID, } } // NewPlayerJoinedEvent 创建玩家加入事件 func NewPlayerJoinedEvent(gameID string, playerID uint64, sessionID string) *PlayerJoinedEvent { return &PlayerJoinedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypePlayerJoined, GameID: gameID, PlayerID: &playerID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, SessionID: sessionID, } } // NewPlayerLeftEvent 创建玩家离开事件 func NewPlayerLeftEvent(gameID string, playerID uint64, leaveReason PlayerLeaveReason) *PlayerLeftEvent { return &PlayerLeftEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypePlayerLeft, GameID: gameID, PlayerID: &playerID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, LeaveReason: leaveReason, } } // NewScoreUpdatedEvent 创建分数更新事件 func NewScoreUpdatedEvent(gameID string, playerID uint64, scoreType ScoreType, oldScore, newScore, finalScore int64) *ScoreUpdatedEvent { return &ScoreUpdatedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeScoreUpdated, GameID: gameID, PlayerID: &playerID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, ScoreType: scoreType, OldScore: oldScore, NewScore: newScore, FinalScore: finalScore, } } // NewRewardGrantedEvent 创建奖励授予事件 func NewRewardGrantedEvent(gameID string, playerID uint64, rewardType RewardType, itemID string, quantity int64) *RewardGrantedEvent { return &RewardGrantedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeRewardGranted, GameID: gameID, PlayerID: &playerID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, RewardType: rewardType, ItemID: itemID, Quantity: quantity, } } // NewRewardClaimedEvent 创建奖励领取事件 func NewRewardClaimedEvent(gameID string, playerID uint64, rewardType RewardType, itemID string, quantity int64) *RewardClaimedEvent { return &RewardClaimedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeRewardClaimed, GameID: gameID, PlayerID: &playerID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, RewardType: rewardType, ItemID: itemID, Quantity: quantity, } } // NewAchievementCompletedEvent 创建成就完成事件 func NewAchievementCompletedEvent(gameID string, playerID uint64, achievementID string, points int64) *AchievementCompletedEvent { return &AchievementCompletedEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeAchievementCompleted, GameID: gameID, PlayerID: &playerID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, AchievementID: achievementID, Points: points, } } // NewGameErrorEvent 创建游戏错误事件 func NewGameErrorEvent(gameID string, errorCode, errorMessage, errorType string) *GameErrorEvent { return &GameErrorEvent{ BaseMinigameEvent: BaseMinigameEvent{ EventID: generateEventID(), EventType: EventTypeGameError, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), }, ErrorCode: errorCode, ErrorMessage: errorMessage, ErrorType: errorType, } } // 事件处理器接口 // MinigameEventHandler 小游戏事件处理器接口 type MinigameEventHandler interface { Handle(ctx context.Context, event MinigameEvent) error CanHandle(eventType string) bool GetHandlerName() string } // MinigameEventBus 小游戏事件总线接口 type MinigameEventBus interface { Publish(ctx context.Context, event MinigameEvent) error Subscribe(eventType string, handler MinigameEventHandler) error Unsubscribe(eventType string, handlerName string) error Start(ctx context.Context) error Stop(ctx context.Context) error } // 事件聚合器 // EventAggregator 事件聚合器 type EventAggregator struct { Events []MinigameEvent `json:"events"` Count int64 `json:"count"` Period string `json:"period"` From time.Time `json:"from"` To time.Time `json:"to"` } // NewEventAggregator 创建事件聚合器 func NewEventAggregator(period string, from, to time.Time) *EventAggregator { return &EventAggregator{ Events: make([]MinigameEvent, 0), Count: 0, Period: period, From: from, To: to, } } // AddEvent 添加事件 func (ea *EventAggregator) AddEvent(event MinigameEvent) { ea.Events = append(ea.Events, event) ea.Count++ } // GetEventsByType 根据类型获取事件 func (ea *EventAggregator) GetEventsByType(eventType string) []MinigameEvent { var events []MinigameEvent for _, event := range ea.Events { if event.GetEventType() == eventType { events = append(events, event) } } return events } // GetEventsByPlayer 根据玩家获取事件 func (ea *EventAggregator) GetEventsByPlayer(playerID uint64) []MinigameEvent { var events []MinigameEvent for _, event := range ea.Events { if event.GetPlayerID() != nil && *event.GetPlayerID() == playerID { events = append(events, event) } } return events } // GetEventsByGame 根据游戏获取事件 func (ea *EventAggregator) GetEventsByGame(gameID string) []MinigameEvent { var events []MinigameEvent for _, event := range ea.Events { if event.GetGameID() == gameID { events = append(events, event) } } return events } // GetEventStatistics 获取事件统计 func (ea *EventAggregator) GetEventStatistics() map[string]int64 { stats := make(map[string]int64) for _, event := range ea.Events { stats[event.GetEventType()]++ } return stats } // Clear 清空事件 func (ea *EventAggregator) Clear() { ea.Events = make([]MinigameEvent, 0) ea.Count = 0 } // 辅助函数 // generateEventID 生成事件ID func generateEventID() string { return fmt.Sprintf("evt_%d_%d", time.Now().UnixNano(), time.Now().Unix()) } // IsGameLifecycleEvent 检查是否为游戏生命周期事件 func IsGameLifecycleEvent(eventType string) bool { lifecycleEvents := []string{ EventTypeMinigameCreated, EventTypeGameStarted, EventTypeGameEnded, EventTypeGamePaused, EventTypeGameResumed, EventTypeGameCancelled, EventTypeGameReset, EventTypeGameDeleted, } for _, event := range lifecycleEvents { if event == eventType { return true } } return false } // IsPlayerEvent 检查是否为玩家事件 func IsPlayerEvent(eventType string) bool { playerEvents := []string{ EventTypePlayerJoined, EventTypePlayerLeft, EventTypePlayerKicked, EventTypePlayerStatusChanged, EventTypePlayerReady, EventTypePlayerNotReady, EventTypePlayerDisconnected, EventTypePlayerReconnected, } for _, event := range playerEvents { if event == eventType { return true } } return false } // IsScoreEvent 检查是否为分数事件 func IsScoreEvent(eventType string) bool { scoreEvents := []string{ EventTypeScoreUpdated, EventTypeHighScoreAchieved, EventTypeLevelUp, EventTypeProgressUpdated, EventTypeMilestoneReached, } for _, event := range scoreEvents { if event == eventType { return true } } return false } // IsRewardEvent 检查是否为奖励事件 func IsRewardEvent(eventType string) bool { rewardEvents := []string{ EventTypeRewardGranted, EventTypeRewardClaimed, EventTypeRewardExpired, EventTypeBonusReward, } for _, event := range rewardEvents { if event == eventType { return true } } return false } // IsAchievementEvent 检查是否为成就事件 func IsAchievementEvent(eventType string) bool { achievementEvents := []string{ EventTypeAchievementUnlocked, EventTypeAchievementCompleted, EventTypeAchievementProgress, } for _, event := range achievementEvents { if event == eventType { return true } } return false } // IsSystemEvent 检查是否为系统事件 func IsSystemEvent(eventType string) bool { systemEvents := []string{ EventTypeGameError, EventTypeGameWarning, EventTypeGameMaintenance, EventTypeGameUpdate, } for _, event := range systemEvents { if event == eventType { return true } } return false } // GetEventPriority 获取事件优先级 func GetEventPriority(eventType string) int { switch eventType { case EventTypeGameError: return 1 // 最高优先级 case EventTypeGameWarning: return 2 case EventTypeGameMaintenance: return 3 case EventTypeMinigameCreated, EventTypeGameStarted, EventTypeGameEnded: return 4 case EventTypePlayerJoined, EventTypePlayerLeft: return 5 case EventTypeScoreUpdated, EventTypeHighScoreAchieved: return 6 case EventTypeRewardGranted, EventTypeRewardClaimed: return 7 case EventTypeAchievementCompleted, EventTypeAchievementUnlocked: return 8 default: return 9 // 最低优先级 } } // FilterEventsByTimeRange 根据时间范围过滤事件 func FilterEventsByTimeRange(events []MinigameEvent, from, to time.Time) []MinigameEvent { var filtered []MinigameEvent for _, event := range events { timestamp := event.GetTimestamp() if timestamp.After(from) && timestamp.Before(to) { filtered = append(filtered, event) } } return filtered } // GroupEventsByType 根据类型分组事件 func GroupEventsByType(events []MinigameEvent) map[string][]MinigameEvent { groups := make(map[string][]MinigameEvent) for _, event := range events { eventType := event.GetEventType() groups[eventType] = append(groups[eventType], event) } return groups } // GroupEventsByPlayer 根据玩家分组事件 func GroupEventsByPlayer(events []MinigameEvent) map[uint64][]MinigameEvent { groups := make(map[uint64][]MinigameEvent) for _, event := range events { if playerID := event.GetPlayerID(); playerID != nil { groups[*playerID] = append(groups[*playerID], event) } } return groups } // GroupEventsByGame 根据游戏分组事件 func GroupEventsByGame(events []MinigameEvent) map[string][]MinigameEvent { groups := make(map[string][]MinigameEvent) for _, event := range events { gameID := event.GetGameID() groups[gameID] = append(groups[gameID], event) } return groups } ================================================ FILE: internal/domain/minigame/repository.go ================================================ package minigame import ( "context" "time" ) // MinigameRepository 小游戏仓储接口 type MinigameRepository interface { // 基础CRUD操作 Save(ctx context.Context, minigame *MinigameAggregate) error FindByID(ctx context.Context, id string) (*MinigameAggregate, error) FindByIDs(ctx context.Context, ids []string) ([]*MinigameAggregate, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByQuery(ctx context.Context, query *GameQuery) ([]*MinigameAggregate, error) FindByCreator(ctx context.Context, creatorID uint64, limit, offset int32) ([]*MinigameAggregate, error) FindByType(ctx context.Context, gameType GameType, limit, offset int32) ([]*MinigameAggregate, error) FindByStatus(ctx context.Context, status GameStatus, limit, offset int32) ([]*MinigameAggregate, error) FindByPlayer(ctx context.Context, playerID uint64, limit, offset int32) ([]*MinigameAggregate, error) FindActive(ctx context.Context, limit, offset int32) ([]*MinigameAggregate, error) FindJoinable(ctx context.Context, limit, offset int32) ([]*MinigameAggregate, error) // 分页查询 FindWithPagination(ctx context.Context, filter *GameFilter, limit, offset int32) (*MinigamePaginationResult, error) // 统计操作 Count(ctx context.Context, filter *GameFilter) (int64, error) CountByCreator(ctx context.Context, creatorID uint64) (int64, error) CountByType(ctx context.Context, gameType GameType) (int64, error) CountByStatus(ctx context.Context, status GameStatus) (int64, error) GetStatistics(ctx context.Context, filter *GameFilter) (*MinigameStatistics, error) // 批量操作 SaveBatch(ctx context.Context, minigames []*MinigameAggregate) error DeleteBatch(ctx context.Context, ids []string) error UpdateStatusBatch(ctx context.Context, ids []string, status GameStatus) error // 清理操作 CleanupExpired(ctx context.Context, expiredBefore time.Time) (int64, error) CleanupFinished(ctx context.Context, finishedBefore time.Time) (int64, error) } // GameSessionRepository 游戏会话仓储接口 type GameSessionRepository interface { // 基础CRUD操作 Save(ctx context.Context, session *GameSession) error FindByID(ctx context.Context, id string) (*GameSession, error) FindByIDs(ctx context.Context, ids []string) ([]*GameSession, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByQuery(ctx context.Context, query *GameSessionQuery) ([]*GameSession, error) FindByGame(ctx context.Context, gameID string) ([]*GameSession, error) FindByPlayer(ctx context.Context, playerID uint64) ([]*GameSession, error) FindByGameAndPlayer(ctx context.Context, gameID string, playerID uint64) (*GameSession, error) FindByStatus(ctx context.Context, status PlayerStatus, limit, offset int32) ([]*GameSession, error) FindActive(ctx context.Context, limit, offset int32) ([]*GameSession, error) FindByToken(ctx context.Context, sessionToken string) (*GameSession, error) // 分页查询 FindWithPagination(ctx context.Context, query *GameSessionQuery, limit, offset int32) (*SessionPaginationResult, error) // 统计操作 Count(ctx context.Context, query *GameSessionQuery) (int64, error) CountByGame(ctx context.Context, gameID string) (int64, error) CountByPlayer(ctx context.Context, playerID uint64) (int64, error) CountByStatus(ctx context.Context, status PlayerStatus) (int64, error) GetPlayerStatistics(ctx context.Context, playerID uint64, gameType *GameType) (*PlayerStatistics, error) GetGameStatistics(ctx context.Context, gameID string) (*GameSessionStatistics, error) // 批量操作 SaveBatch(ctx context.Context, sessions []*GameSession) error DeleteBatch(ctx context.Context, ids []string) error UpdateStatusBatch(ctx context.Context, ids []string, status PlayerStatus) error // 清理操作 CleanupExpired(ctx context.Context, expiredBefore time.Time) (int64, error) CleanupInactive(ctx context.Context, inactiveBefore time.Time) (int64, error) } // GameScoreRepository 游戏分数仓储接口 type GameScoreRepository interface { // 基础CRUD操作 Save(ctx context.Context, score *GameScore) error FindByID(ctx context.Context, id string) (*GameScore, error) FindByIDs(ctx context.Context, ids []string) ([]*GameScore, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByGame(ctx context.Context, gameID string) ([]*GameScore, error) FindByPlayer(ctx context.Context, playerID uint64) ([]*GameScore, error) FindByGameAndPlayer(ctx context.Context, gameID string, playerID uint64) ([]*GameScore, error) FindByGamePlayerAndType(ctx context.Context, gameID string, playerID uint64, scoreType ScoreType) (*GameScore, error) FindByType(ctx context.Context, scoreType ScoreType, limit, offset int32) ([]*GameScore, error) FindBySession(ctx context.Context, sessionID string) ([]*GameScore, error) // 排行榜操作 FindTopScores(ctx context.Context, scoreType ScoreType, limit int32) ([]*GameScore, error) FindTopScoresByGame(ctx context.Context, gameID string, scoreType ScoreType, limit int32) ([]*GameScore, error) FindTopScoresByPlayer(ctx context.Context, playerID uint64, scoreType ScoreType, limit int32) ([]*GameScore, error) GetPlayerRank(ctx context.Context, gameID string, playerID uint64, scoreType ScoreType) (int32, error) GetScorePercentile(ctx context.Context, gameID string, score int64, scoreType ScoreType) (float64, error) // 分页查询 FindWithPagination(ctx context.Context, query *ScoreQuery, limit, offset int32) (*ScorePaginationResult, error) // 统计操作 Count(ctx context.Context, query *ScoreQuery) (int64, error) CountByGame(ctx context.Context, gameID string) (int64, error) CountByPlayer(ctx context.Context, playerID uint64) (int64, error) GetScoreStatistics(ctx context.Context, gameID string, scoreType ScoreType) (*ScoreStatistics, error) GetPlayerScoreStatistics(ctx context.Context, playerID uint64, scoreType ScoreType) (*PlayerScoreStatistics, error) // 批量操作 SaveBatch(ctx context.Context, scores []*GameScore) error DeleteBatch(ctx context.Context, ids []string) error UpdateRanksBatch(ctx context.Context, gameID string, scoreType ScoreType) error // 清理操作 CleanupOldScores(ctx context.Context, olderThan time.Time) (int64, error) } // GameRewardRepository 游戏奖励仓储接口 type GameRewardRepository interface { // 基础CRUD操作 Save(ctx context.Context, reward *GameReward) error FindByID(ctx context.Context, id string) (*GameReward, error) FindByIDs(ctx context.Context, ids []string) ([]*GameReward, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByGame(ctx context.Context, gameID string) ([]*GameReward, error) FindByPlayer(ctx context.Context, playerID uint64) ([]*GameReward, error) FindByGameAndPlayer(ctx context.Context, gameID string, playerID uint64) ([]*GameReward, error) FindByType(ctx context.Context, rewardType RewardType, limit, offset int32) ([]*GameReward, error) FindBySession(ctx context.Context, sessionID string) ([]*GameReward, error) FindClaimable(ctx context.Context, playerID uint64) ([]*GameReward, error) FindClaimed(ctx context.Context, playerID uint64, limit, offset int32) ([]*GameReward, error) FindExpired(ctx context.Context, expiredBefore time.Time) ([]*GameReward, error) // 分页查询 FindWithPagination(ctx context.Context, query *RewardQuery, limit, offset int32) (*RewardPaginationResult, error) // 统计操作 Count(ctx context.Context, query *RewardQuery) (int64, error) CountByGame(ctx context.Context, gameID string) (int64, error) CountByPlayer(ctx context.Context, playerID uint64) (int64, error) CountClaimable(ctx context.Context, playerID uint64) (int64, error) CountClaimed(ctx context.Context, playerID uint64) (int64, error) GetRewardStatistics(ctx context.Context, gameID string) (*RewardStatistics, error) GetPlayerRewardStatistics(ctx context.Context, playerID uint64) (*PlayerRewardStatistics, error) // 批量操作 SaveBatch(ctx context.Context, rewards []*GameReward) error DeleteBatch(ctx context.Context, ids []string) error ClaimBatch(ctx context.Context, ids []string, playerID uint64) error // 清理操作 CleanupExpired(ctx context.Context, expiredBefore time.Time) (int64, error) CleanupClaimed(ctx context.Context, claimedBefore time.Time) (int64, error) } // GameAchievementRepository 游戏成就仓储接口 type GameAchievementRepository interface { // 基础CRUD操作 Save(ctx context.Context, achievement *GameAchievement) error FindByID(ctx context.Context, id string) (*GameAchievement, error) FindByIDs(ctx context.Context, ids []string) ([]*GameAchievement, error) Delete(ctx context.Context, id string) error Exists(ctx context.Context, id string) (bool, error) // 查询操作 FindByGame(ctx context.Context, gameID string) ([]*GameAchievement, error) FindByPlayer(ctx context.Context, playerID uint64) ([]*GameAchievement, error) FindByGameAndPlayer(ctx context.Context, gameID string, playerID uint64) ([]*GameAchievement, error) FindByAchievementID(ctx context.Context, achievementID string) ([]*GameAchievement, error) FindByCategory(ctx context.Context, category string, limit, offset int32) ([]*GameAchievement, error) FindByRarity(ctx context.Context, rarity string, limit, offset int32) ([]*GameAchievement, error) FindCompleted(ctx context.Context, playerID uint64) ([]*GameAchievement, error) FindInProgress(ctx context.Context, playerID uint64) ([]*GameAchievement, error) FindUnlocked(ctx context.Context, playerID uint64) ([]*GameAchievement, error) // 分页查询 FindWithPagination(ctx context.Context, query *AchievementQuery, limit, offset int32) (*AchievementPaginationResult, error) // 统计操作 Count(ctx context.Context, query *AchievementQuery) (int64, error) CountByGame(ctx context.Context, gameID string) (int64, error) CountByPlayer(ctx context.Context, playerID uint64) (int64, error) CountCompleted(ctx context.Context, playerID uint64) (int64, error) CountUnlocked(ctx context.Context, playerID uint64) (int64, error) GetAchievementStatistics(ctx context.Context, gameID string) (*AchievementStatistics, error) GetPlayerAchievementStatistics(ctx context.Context, playerID uint64) (*PlayerAchievementStatistics, error) // 批量操作 SaveBatch(ctx context.Context, achievements []*GameAchievement) error DeleteBatch(ctx context.Context, ids []string) error CompleteBatch(ctx context.Context, ids []string) error UnlockBatch(ctx context.Context, ids []string) error // 清理操作 CleanupOldAchievements(ctx context.Context, olderThan time.Time) (int64, error) } // 查询条件结构体 // ScoreQuery 分数查询条件 type ScoreQuery struct { GameID *string `json:"game_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` SessionID *string `json:"session_id,omitempty"` ScoreType *ScoreType `json:"score_type,omitempty"` MinValue *int64 `json:"min_value,omitempty"` MaxValue *int64 `json:"max_value,omitempty"` MinRank *int32 `json:"min_rank,omitempty"` MaxRank *int32 `json:"max_rank,omitempty"` AchievedAfter *time.Time `json:"achieved_after,omitempty"` AchievedBefore *time.Time `json:"achieved_before,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` } // RewardQuery 奖励查询条件 type RewardQuery struct { GameID *string `json:"game_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` SessionID *string `json:"session_id,omitempty"` RewardType *RewardType `json:"reward_type,omitempty"` ItemID *string `json:"item_id,omitempty"` Rarity *string `json:"rarity,omitempty"` Source *string `json:"source,omitempty"` Claimed *bool `json:"claimed,omitempty"` Expired *bool `json:"expired,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` ExpiresAfter *time.Time `json:"expires_after,omitempty"` ExpiresBefore *time.Time `json:"expires_before,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` } // AchievementQuery 成就查询条件 type AchievementQuery struct { GameID *string `json:"game_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` SessionID *string `json:"session_id,omitempty"` AchievementID *string `json:"achievement_id,omitempty"` Category *string `json:"category,omitempty"` Rarity *string `json:"rarity,omitempty"` Completed *bool `json:"completed,omitempty"` Unlocked *bool `json:"unlocked,omitempty"` MinProgress *float64 `json:"min_progress,omitempty"` MaxProgress *float64 `json:"max_progress,omitempty"` MinPoints *int64 `json:"min_points,omitempty"` MaxPoints *int64 `json:"max_points,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` CompletedAfter *time.Time `json:"completed_after,omitempty"` CompletedBefore *time.Time `json:"completed_before,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` } // 分页结果结构体 // MinigamePaginationResult 小游戏分页结果 type MinigamePaginationResult struct { Items []*MinigameAggregate `json:"items"` Total int64 `json:"total"` Limit int32 `json:"limit"` Offset int32 `json:"offset"` HasMore bool `json:"has_more"` TotalPages int32 `json:"total_pages"` } // SessionPaginationResult 会话分页结果 type SessionPaginationResult struct { Items []*GameSession `json:"items"` Total int64 `json:"total"` Limit int32 `json:"limit"` Offset int32 `json:"offset"` HasMore bool `json:"has_more"` TotalPages int32 `json:"total_pages"` } // ScorePaginationResult 分数分页结果 type ScorePaginationResult struct { Items []*GameScore `json:"items"` Total int64 `json:"total"` Limit int32 `json:"limit"` Offset int32 `json:"offset"` HasMore bool `json:"has_more"` TotalPages int32 `json:"total_pages"` } // RewardPaginationResult 奖励分页结果 type RewardPaginationResult struct { Items []*GameReward `json:"items"` Total int64 `json:"total"` Limit int32 `json:"limit"` Offset int32 `json:"offset"` HasMore bool `json:"has_more"` TotalPages int32 `json:"total_pages"` } // AchievementPaginationResult 成就分页结果 type AchievementPaginationResult struct { Items []*GameAchievement `json:"items"` Total int64 `json:"total"` Limit int32 `json:"limit"` Offset int32 `json:"offset"` HasMore bool `json:"has_more"` TotalPages int32 `json:"total_pages"` } // 统计数据结构体 // MinigameStatistics 小游戏统计 type MinigameStatistics struct { TotalGames int64 `json:"total_games"` ActiveGames int64 `json:"active_games"` FinishedGames int64 `json:"finished_games"` CancelledGames int64 `json:"cancelled_games"` TotalPlayers int64 `json:"total_players"` ActivePlayers int64 `json:"active_players"` AveragePlayTime time.Duration `json:"average_play_time"` AverageScore float64 `json:"average_score"` GamesByType map[GameType]int64 `json:"games_by_type"` GamesByStatus map[GameStatus]int64 `json:"games_by_status"` GamesByDifficulty map[GameDifficulty]int64 `json:"games_by_difficulty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // GameSessionStatistics 游戏会话统计 type GameSessionStatistics struct { GameID string `json:"game_id"` TotalSessions int64 `json:"total_sessions"` ActiveSessions int64 `json:"active_sessions"` FinishedSessions int64 `json:"finished_sessions"` AveragePlayTime time.Duration `json:"average_play_time"` AverageScore float64 `json:"average_score"` AverageMoves float64 `json:"average_moves"` AverageLevel float64 `json:"average_level"` HighestScore int64 `json:"highest_score"` HighestLevel int32 `json:"highest_level"` TotalPlayTime time.Duration `json:"total_play_time"` TotalMoves int64 `json:"total_moves"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ScoreStatistics 分数统计 type ScoreStatistics struct { GameID string `json:"game_id"` ScoreType ScoreType `json:"score_type"` TotalScores int64 `json:"total_scores"` AverageScore float64 `json:"average_score"` HighestScore int64 `json:"highest_score"` LowestScore int64 `json:"lowest_score"` MedianScore int64 `json:"median_score"` TotalValue int64 `json:"total_value"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PlayerScoreStatistics 玩家分数统计 type PlayerScoreStatistics struct { PlayerID uint64 `json:"player_id"` ScoreType ScoreType `json:"score_type"` TotalScores int64 `json:"total_scores"` AverageScore float64 `json:"average_score"` HighestScore int64 `json:"highest_score"` LowestScore int64 `json:"lowest_score"` BestRank int32 `json:"best_rank"` CurrentRank int32 `json:"current_rank"` TotalValue int64 `json:"total_value"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // RewardStatistics 奖励统计 type RewardStatistics struct { GameID string `json:"game_id"` TotalRewards int64 `json:"total_rewards"` ClaimedRewards int64 `json:"claimed_rewards"` ExpiredRewards int64 `json:"expired_rewards"` PendingRewards int64 `json:"pending_rewards"` RewardsByType map[RewardType]int64 `json:"rewards_by_type"` RewardsByRarity map[string]int64 `json:"rewards_by_rarity"` TotalValue map[RewardType]int64 `json:"total_value"` ClaimRate float64 `json:"claim_rate"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PlayerRewardStatistics 玩家奖励统计 type PlayerRewardStatistics struct { PlayerID uint64 `json:"player_id"` TotalRewards int64 `json:"total_rewards"` ClaimedRewards int64 `json:"claimed_rewards"` ExpiredRewards int64 `json:"expired_rewards"` PendingRewards int64 `json:"pending_rewards"` RewardsByType map[RewardType]int64 `json:"rewards_by_type"` RewardsByRarity map[string]int64 `json:"rewards_by_rarity"` TotalValue map[RewardType]int64 `json:"total_value"` ClaimRate float64 `json:"claim_rate"` LastClaimedAt *time.Time `json:"last_claimed_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // AchievementStatistics 成就统计 type AchievementStatistics struct { GameID string `json:"game_id"` TotalAchievements int64 `json:"total_achievements"` CompletedAchievements int64 `json:"completed_achievements"` UnlockedAchievements int64 `json:"unlocked_achievements"` InProgressAchievements int64 `json:"in_progress_achievements"` AchievementsByCategory map[string]int64 `json:"achievements_by_category"` AchievementsByRarity map[string]int64 `json:"achievements_by_rarity"` AverageProgress float64 `json:"average_progress"` CompletionRate float64 `json:"completion_rate"` TotalPoints int64 `json:"total_points"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PlayerAchievementStatistics 玩家成就统计 type PlayerAchievementStatistics struct { PlayerID uint64 `json:"player_id"` TotalAchievements int64 `json:"total_achievements"` CompletedAchievements int64 `json:"completed_achievements"` UnlockedAchievements int64 `json:"unlocked_achievements"` InProgressAchievements int64 `json:"in_progress_achievements"` AchievementsByCategory map[string]int64 `json:"achievements_by_category"` AchievementsByRarity map[string]int64 `json:"achievements_by_rarity"` AverageProgress float64 `json:"average_progress"` CompletionRate float64 `json:"completion_rate"` TotalPoints int64 `json:"total_points"` LastCompletedAt *time.Time `json:"last_completed_at,omitempty"` LastUnlockedAt *time.Time `json:"last_unlocked_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // 辅助函数 // NewMinigamePaginationResult 创建小游戏分页结果 func NewMinigamePaginationResult(items []*MinigameAggregate, total int64, limit, offset int32) *MinigamePaginationResult { totalPages := int32((total + int64(limit) - 1) / int64(limit)) hasMore := offset+limit < int32(total) return &MinigamePaginationResult{ Items: items, Total: total, Limit: limit, Offset: offset, HasMore: hasMore, TotalPages: totalPages, } } // NewSessionPaginationResult 创建会话分页结果 func NewSessionPaginationResult(items []*GameSession, total int64, limit, offset int32) *SessionPaginationResult { totalPages := int32((total + int64(limit) - 1) / int64(limit)) hasMore := offset+limit < int32(total) return &SessionPaginationResult{ Items: items, Total: total, Limit: limit, Offset: offset, HasMore: hasMore, TotalPages: totalPages, } } // NewScorePaginationResult 创建分数分页结果 func NewScorePaginationResult(items []*GameScore, total int64, limit, offset int32) *ScorePaginationResult { totalPages := int32((total + int64(limit) - 1) / int64(limit)) hasMore := offset+limit < int32(total) return &ScorePaginationResult{ Items: items, Total: total, Limit: limit, Offset: offset, HasMore: hasMore, TotalPages: totalPages, } } // NewRewardPaginationResult 创建奖励分页结果 func NewRewardPaginationResult(items []*GameReward, total int64, limit, offset int32) *RewardPaginationResult { totalPages := int32((total + int64(limit) - 1) / int64(limit)) hasMore := offset+limit < int32(total) return &RewardPaginationResult{ Items: items, Total: total, Limit: limit, Offset: offset, HasMore: hasMore, TotalPages: totalPages, } } // NewAchievementPaginationResult 创建成就分页结果 func NewAchievementPaginationResult(items []*GameAchievement, total int64, limit, offset int32) *AchievementPaginationResult { totalPages := int32((total + int64(limit) - 1) / int64(limit)) hasMore := offset+limit < int32(total) return &AchievementPaginationResult{ Items: items, Total: total, Limit: limit, Offset: offset, HasMore: hasMore, TotalPages: totalPages, } } // NewMinigameStatistics 创建小游戏统计 func NewMinigameStatistics() *MinigameStatistics { now := time.Now() return &MinigameStatistics{ TotalGames: 0, ActiveGames: 0, FinishedGames: 0, CancelledGames: 0, TotalPlayers: 0, ActivePlayers: 0, AveragePlayTime: 0, AverageScore: 0.0, GamesByType: make(map[GameType]int64), GamesByStatus: make(map[GameStatus]int64), GamesByDifficulty: make(map[GameDifficulty]int64), CreatedAt: now, UpdatedAt: now, } } // NewGameSessionStatistics 创建游戏会话统计 func NewGameSessionStatistics(gameID string) *GameSessionStatistics { now := time.Now() return &GameSessionStatistics{ GameID: gameID, TotalSessions: 0, ActiveSessions: 0, FinishedSessions: 0, AveragePlayTime: 0, AverageScore: 0.0, AverageMoves: 0.0, AverageLevel: 0.0, HighestScore: 0, HighestLevel: 0, TotalPlayTime: 0, TotalMoves: 0, CreatedAt: now, UpdatedAt: now, } } // NewScoreStatistics 创建分数统计 func NewScoreStatistics(gameID string, scoreType ScoreType) *ScoreStatistics { now := time.Now() return &ScoreStatistics{ GameID: gameID, ScoreType: scoreType, TotalScores: 0, AverageScore: 0.0, HighestScore: 0, LowestScore: 0, MedianScore: 0, TotalValue: 0, CreatedAt: now, UpdatedAt: now, } } // NewPlayerScoreStatistics 创建玩家分数统计 func NewPlayerScoreStatistics(playerID uint64, scoreType ScoreType) *PlayerScoreStatistics { now := time.Now() return &PlayerScoreStatistics{ PlayerID: playerID, ScoreType: scoreType, TotalScores: 0, AverageScore: 0.0, HighestScore: 0, LowestScore: 0, BestRank: 0, CurrentRank: 0, TotalValue: 0, CreatedAt: now, UpdatedAt: now, } } // NewRewardStatistics 创建奖励统计 func NewRewardStatistics(gameID string) *RewardStatistics { now := time.Now() return &RewardStatistics{ GameID: gameID, TotalRewards: 0, ClaimedRewards: 0, ExpiredRewards: 0, PendingRewards: 0, RewardsByType: make(map[RewardType]int64), RewardsByRarity: make(map[string]int64), TotalValue: make(map[RewardType]int64), ClaimRate: 0.0, CreatedAt: now, UpdatedAt: now, } } // NewPlayerRewardStatistics 创建玩家奖励统计 func NewPlayerRewardStatistics(playerID uint64) *PlayerRewardStatistics { now := time.Now() return &PlayerRewardStatistics{ PlayerID: playerID, TotalRewards: 0, ClaimedRewards: 0, ExpiredRewards: 0, PendingRewards: 0, RewardsByType: make(map[RewardType]int64), RewardsByRarity: make(map[string]int64), TotalValue: make(map[RewardType]int64), ClaimRate: 0.0, CreatedAt: now, UpdatedAt: now, } } // NewAchievementStatistics 创建成就统计 func NewAchievementStatistics(gameID string) *AchievementStatistics { now := time.Now() return &AchievementStatistics{ GameID: gameID, TotalAchievements: 0, CompletedAchievements: 0, UnlockedAchievements: 0, InProgressAchievements: 0, AchievementsByCategory: make(map[string]int64), AchievementsByRarity: make(map[string]int64), AverageProgress: 0.0, CompletionRate: 0.0, TotalPoints: 0, CreatedAt: now, UpdatedAt: now, } } // NewPlayerAchievementStatistics 创建玩家成就统计 func NewPlayerAchievementStatistics(playerID uint64) *PlayerAchievementStatistics { now := time.Now() return &PlayerAchievementStatistics{ PlayerID: playerID, TotalAchievements: 0, CompletedAchievements: 0, UnlockedAchievements: 0, InProgressAchievements: 0, AchievementsByCategory: make(map[string]int64), AchievementsByRarity: make(map[string]int64), AverageProgress: 0.0, CompletionRate: 0.0, TotalPoints: 0, CreatedAt: now, UpdatedAt: now, } } ================================================ FILE: internal/domain/minigame/service.go ================================================ package minigame import ( "context" "fmt" "time" ) // MinigameService 小游戏领域服务 type MinigameService struct { minigameRepo MinigameRepository sessionRepo GameSessionRepository scoreRepo GameScoreRepository rewardRepo GameRewardRepository achievementRepo GameAchievementRepository eventBus MinigameEventBus } // NewMinigameService 创建小游戏服务 func NewMinigameService( minigameRepo MinigameRepository, sessionRepo GameSessionRepository, scoreRepo GameScoreRepository, rewardRepo GameRewardRepository, achievementRepo GameAchievementRepository, eventBus MinigameEventBus, ) *MinigameService { return &MinigameService{ minigameRepo: minigameRepo, sessionRepo: sessionRepo, scoreRepo: scoreRepo, rewardRepo: rewardRepo, achievementRepo: achievementRepo, eventBus: eventBus, } } // CreateMinigame 创建小游戏 func (s *MinigameService) CreateMinigame(ctx context.Context, gameType GameType, creatorID uint64, config *GameConfig) (*MinigameAggregate, error) { // 验证游戏类型 if !gameType.IsValid() { return nil, NewMinigameError(ErrorCodeInvalidGameType, "Invalid game type", fmt.Errorf("invalid game type: %v", gameType)) } // 验证创建者 if creatorID == 0 { return nil, NewMinigameError(ErrorCodeInvalidPlayer, "Invalid creator", fmt.Errorf("creator_id cannot be zero")) } // 验证配置 if config == nil { config = NewGameConfig() } if err := s.validateGameConfig(config); err != nil { return nil, NewMinigameError(ErrorCodeInvalidConfig, "Invalid game config", err) } // 生成游戏ID gameID := fmt.Sprintf("game_%d_%d", creatorID, time.Now().Unix()) // 创建小游戏聚合 minigame := NewMinigameAggregate(gameID, gameType, GameCategoryNormal, creatorID) // TODO: 实现SetConfig方法 // minigame.SetConfig(config.Clone()) // 保存到仓储 if err := s.minigameRepo.Save(ctx, minigame); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save minigame", err) } // 发布事件 event := NewMinigameCreatedEvent(minigame.ID, minigame.GameType, creatorID) s.eventBus.Publish(ctx, event) return minigame, nil } // StartGame 开始游戏 func (s *MinigameService) StartGame(ctx context.Context, gameID string, operatorID uint64) error { // 获取游戏 minigame, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 检查权限 if !s.canOperateGame(minigame, operatorID) { return NewMinigameError(ErrorCodePermissionDenied, "Permission denied", fmt.Errorf("user %d cannot operate game %s", operatorID, gameID)) } // 开始游戏 if err := minigame.StartGame(); err != nil { return NewMinigameError(ErrorCodeInvalidOperation, "Cannot start game", err) } // 保存更新 if err := s.minigameRepo.Save(ctx, minigame); err != nil { return NewMinigameError(ErrorCodeRepositoryError, "Failed to save minigame", err) } // 发布事件 event := NewGameStartedEvent(gameID, operatorID) s.eventBus.Publish(ctx, event) return nil } // EndGame 结束游戏 func (s *MinigameService) EndGame(ctx context.Context, gameID string, operatorID uint64, reason GameEndReason) error { // 获取游戏 minigame, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 检查权限 if !s.canOperateGame(minigame, operatorID) { return NewMinigameError(ErrorCodePermissionDenied, "Permission denied", fmt.Errorf("user %d cannot operate game %s", operatorID, gameID)) } // 结束游戏 if err := minigame.EndGame(reason); err != nil { return NewMinigameError(ErrorCodeInvalidOperation, "Cannot end game", err) } // 处理游戏结束后的逻辑 if err := s.handleGameEnd(ctx, minigame); err != nil { return err } // 保存更新 if err := s.minigameRepo.Save(ctx, minigame); err != nil { return NewMinigameError(ErrorCodeRepositoryError, "Failed to save minigame", err) } // 发布事件 event := NewGameEndedEvent(gameID, reason, operatorID) s.eventBus.Publish(ctx, event) return nil } // JoinGame 加入游戏 func (s *MinigameService) JoinGame(ctx context.Context, gameID string, playerID uint64, sessionToken string) (*GameSession, error) { // 获取游戏 minigame, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return nil, NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 检查是否可以加入 // TODO: 实现CanJoin方法 // if !minigame.CanJoin() { // return nil, NewMinigameError(ErrorCodeGameNotJoinable, "Game is not joinable", fmt.Errorf("game %s is not joinable", gameID)) // } // 检查玩家是否已在游戏中 // TODO: 实现HasPlayer方法 // if minigame.HasPlayer(playerID) { // return nil, NewMinigameError(ErrorCodePlayerAlreadyInGame, "Player already in game", fmt.Errorf("player %d already in game %s", playerID, gameID)) // } // 检查游戏是否已满 // TODO: 实现IsFull方法 // if minigame.IsFull() { // return nil, NewMinigameError(ErrorCodeGameFull, "Game is full", fmt.Errorf("game %s is full", gameID)) // } // 创建游戏会话 session := NewGameSession(gameID, playerID, sessionToken) // 验证会话 if err := ValidateGameSession(session); err != nil { return nil, NewMinigameError(ErrorCodeInvalidSession, "Invalid session", err) } // 加入游戏 // TODO: 实现AddPlayer方法,需要传递正确的参数 // if err := minigame.AddPlayer(playerID, session); err != nil { // return nil, NewMinigameError(ErrorCodeInvalidOperation, "Cannot add player", err) // } // 保存会话 if err := s.sessionRepo.Save(ctx, session); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save session", err) } // 保存游戏更新 if err := s.minigameRepo.Save(ctx, minigame); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save minigame", err) } // 发布事件 event := NewPlayerJoinedEvent(gameID, playerID, session.ID) s.eventBus.Publish(ctx, event) return session, nil } // LeaveGame 离开游戏 func (s *MinigameService) LeaveGame(ctx context.Context, gameID string, playerID uint64, reason PlayerLeaveReason) error { // 获取游戏 minigame, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 检查玩家是否在游戏中 // TODO: 实现HasPlayer方法 // if !minigame.HasPlayer(playerID) { // return NewMinigameError(ErrorCodePlayerNotInGame, "Player not in game", fmt.Errorf("player %d not in game %s", playerID, gameID)) // } // 获取会话 session, err := s.sessionRepo.FindByGameAndPlayer(ctx, gameID, playerID) if err != nil { return NewMinigameError(ErrorCodeSessionNotFound, "Session not found", err) } // 离开游戏 if err := session.Leave(reason); err != nil { return NewMinigameError(ErrorCodeInvalidOperation, "Cannot leave game", err) } // 从游戏中移除玩家 if err := minigame.RemovePlayer(playerID, reason); err != nil { return NewMinigameError(ErrorCodeInvalidOperation, "Cannot remove player", err) } // 保存会话更新 if err := s.sessionRepo.Save(ctx, session); err != nil { return NewMinigameError(ErrorCodeRepositoryError, "Failed to save session", err) } // 保存游戏更新 if err := s.minigameRepo.Save(ctx, minigame); err != nil { return NewMinigameError(ErrorCodeRepositoryError, "Failed to save minigame", err) } // 发布事件 event := NewPlayerLeftEvent(gameID, playerID, reason) s.eventBus.Publish(ctx, event) return nil } // UpdatePlayerScore 更新玩家分数 func (s *MinigameService) UpdatePlayerScore(ctx context.Context, gameID string, playerID uint64, scoreType ScoreType, value int64) (*GameScore, error) { // 获取游戏 minigame, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return nil, NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 检查游戏状态 // TODO: 实现IsRunning方法 // if !minigame.IsRunning() { // return nil, NewMinigameError(ErrorCodeGameNotRunning, "Game is not running", fmt.Errorf("game %s is not running", gameID)) // } // 检查玩家是否在游戏中 // TODO: 实现HasPlayer方法 // if !minigame.HasPlayer(playerID) { // return nil, NewMinigameError(ErrorCodePlayerNotInGame, "Player not in game", fmt.Errorf("player %d not in game %s", playerID, gameID)) // } // 获取会话 session, err := s.sessionRepo.FindByGameAndPlayer(ctx, gameID, playerID) if err != nil { return nil, NewMinigameError(ErrorCodeSessionNotFound, "Session not found", err) } // 更新会话分数 session.UpdateScore(value) // 创建或更新分数记录 score, err := s.scoreRepo.FindByGamePlayerAndType(ctx, gameID, playerID, scoreType) if err != nil { // 创建新分数记录 score = NewGameScore(gameID, playerID, session.ID, scoreType) } score.UpdateValue(value) // 应用难度倍数 if minigame.Config != nil { multiplier := minigame.Config.Difficulty.GetScoreMultiplier() score.SetMultiplier(multiplier) } // 验证分数 if err := ValidateGameScore(score); err != nil { return nil, NewMinigameError(ErrorCodeInvalidScore, "Invalid score", err) } // 保存分数 if err := s.scoreRepo.Save(ctx, score); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save score", err) } // 保存会话更新 if err := s.sessionRepo.Save(ctx, session); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save session", err) } // 更新游戏统计 // TODO: 实现UpdatePlayerScore方法,需要传递ScoreType参数 // minigame.UpdatePlayerScore(playerID, value, ScoreTypePoints) // 保存游戏更新 if err := s.minigameRepo.Save(ctx, minigame); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save minigame", err) } // 发布事件 // TODO: 实现NewScoreUpdatedEvent,需要传递正确的参数 // event := NewScoreUpdatedEvent(gameID, playerID, scoreType, value, score.FinalScore, 0) // s.eventBus.Publish(ctx, event) // 检查成就 go s.checkAchievements(context.Background(), gameID, playerID, session) return score, nil } // GrantReward 授予奖励 func (s *MinigameService) GrantReward(ctx context.Context, gameID string, playerID uint64, rewardType RewardType, itemID string, quantity int64, source string) (*GameReward, error) { // 获取游戏 _, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return nil, NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 检查玩家是否参与过游戏 // TODO: 实现HasPlayer方法 // if !minigame.HasPlayer(playerID) { // return nil, NewMinigameError(ErrorCodePlayerNotInGame, "Player not in game", fmt.Errorf("player %d not in game %s", playerID, gameID)) // } // 获取会话 session, err := s.sessionRepo.FindByGameAndPlayer(ctx, gameID, playerID) if err != nil { return nil, NewMinigameError(ErrorCodeSessionNotFound, "Session not found", err) } // 创建奖励 reward := NewGameReward(gameID, playerID, session.ID, rewardType, itemID, quantity) reward.SetSource(source) reward.SetReason(fmt.Sprintf("Game reward from %s", source)) // 设置过期时间 expiresAt := time.Now().Add(DefaultRewardTTL) reward.SetExpiration(expiresAt) // 验证奖励 if err := ValidateGameReward(reward); err != nil { return nil, NewMinigameError(ErrorCodeInvalidReward, "Invalid reward", err) } // 保存奖励 if err := s.rewardRepo.Save(ctx, reward); err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to save reward", err) } // 发布事件 event := NewRewardGrantedEvent(gameID, playerID, rewardType, itemID, quantity) s.eventBus.Publish(ctx, event) return reward, nil } // ClaimReward 领取奖励 func (s *MinigameService) ClaimReward(ctx context.Context, rewardID string, playerID uint64) error { // 获取奖励 reward, err := s.rewardRepo.FindByID(ctx, rewardID) if err != nil { return NewMinigameError(ErrorCodeRewardNotFound, "Reward not found", err) } // 检查玩家权限 if reward.PlayerID != playerID { return NewMinigameError(ErrorCodePermissionDenied, "Permission denied", fmt.Errorf("player %d cannot claim reward %s", playerID, rewardID)) } // 领取奖励 if err := reward.Claim(); err != nil { return NewMinigameError(ErrorCodeInvalidOperation, "Cannot claim reward", err) } // 保存更新 if err := s.rewardRepo.Save(ctx, reward); err != nil { return NewMinigameError(ErrorCodeRepositoryError, "Failed to save reward", err) } // 发布事件 // TODO: 实现NewRewardClaimedEvent,使用正确的字段 // event := NewRewardClaimedEvent(reward.GameID, playerID, reward.RewardType, reward.ItemID, reward.Amount) // s.eventBus.Publish(ctx, event) return nil } // GetGameLeaderboard 获取游戏排行榜 func (s *MinigameService) GetGameLeaderboard(ctx context.Context, gameID string, scoreType ScoreType, limit int32) ([]*GameScore, error) { // 获取游戏 _, err := s.minigameRepo.FindByID(ctx, gameID) if err != nil { return nil, NewMinigameError(ErrorCodeGameNotFound, "Game not found", err) } // 获取排行榜 scores, err := s.scoreRepo.FindTopScoresByGame(ctx, gameID, scoreType, limit) if err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to get leaderboard", err) } // 更新排名 for i, score := range scores { rank := int32(i + 1) percentile := float64(rank) / float64(len(scores)) * 100 score.SetRank(rank, percentile) } return scores, nil } // GetPlayerGameHistory 获取玩家游戏历史 func (s *MinigameService) GetPlayerGameHistory(ctx context.Context, playerID uint64, gameType *GameType, limit int32, offset int32) ([]*GameSession, error) { // 构建查询条件 query := &GameSessionQuery{ PlayerID: &playerID, GameType: gameType, Limit: &limit, Offset: &offset, OrderBy: "created_at", OrderDesc: true, } // 获取会话历史 sessions, err := s.sessionRepo.FindByQuery(ctx, query) if err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to get game history", err) } return sessions, nil } // GetPlayerStatistics 获取玩家统计 func (s *MinigameService) GetPlayerStatistics(ctx context.Context, playerID uint64, gameType *GameType) (*PlayerStatistics, error) { // 获取玩家会话统计 stats, err := s.sessionRepo.GetPlayerStatistics(ctx, playerID, gameType) if err != nil { return nil, NewMinigameError(ErrorCodeRepositoryError, "Failed to get player statistics", err) } return stats, nil } // 私有方法 // validateGameConfig 验证游戏配置 func (s *MinigameService) validateGameConfig(config *GameConfig) error { if config.MaxPlayers <= 0 { return fmt.Errorf("max_players must be positive") } if config.MinPlayers <= 0 { return fmt.Errorf("min_players must be positive") } if config.MinPlayers > config.MaxPlayers { return fmt.Errorf("min_players cannot be greater than max_players") } if config.MaxDuration <= 0 { return fmt.Errorf("max_duration must be positive") } if config.MinDuration <= 0 { return fmt.Errorf("min_duration must be positive") } if config.MinDuration > config.MaxDuration { return fmt.Errorf("min_duration cannot be greater than max_duration") } if !config.Difficulty.IsValid() { return fmt.Errorf("invalid difficulty: %v", config.Difficulty) } return nil } // canOperateGame 检查是否可以操作游戏 func (s *MinigameService) canOperateGame(minigame *MinigameAggregate, operatorID uint64) bool { // 创建者可以操作 if minigame.CreatorID == operatorID { return true } // TODO: 添加其他权限检查逻辑,如管理员权限等 return false } // handleGameEnd 处理游戏结束 func (s *MinigameService) handleGameEnd(ctx context.Context, minigame *MinigameAggregate) error { // 结算所有玩家会话 for _, player := range minigame.Players { session, err := s.sessionRepo.FindByGameAndPlayer(ctx, minigame.ID, player.PlayerID) if err != nil { continue // 忽略错误,继续处理其他玩家 } // 更新会话状态 if session.IsActive() { session.UpdateStatus(PlayerStatusFinished) s.sessionRepo.Save(ctx, session) } // 发放完成奖励 go s.grantCompletionRewards(context.Background(), minigame.ID, player.PlayerID, session) } return nil } // grantCompletionRewards 发放完成奖励 func (s *MinigameService) grantCompletionRewards(ctx context.Context, gameID string, playerID uint64, session *GameSession) { // 基础完成奖励 baseReward := int64(100) // 根据分数计算奖励倍数 multiplier := 1.0 if session.Score > 1000 { multiplier = 1.5 } if session.Score > 5000 { multiplier = 2.0 } finalReward := int64(float64(baseReward) * multiplier) // 发放金币奖励 s.GrantReward(ctx, gameID, playerID, RewardTypeCoin, "coins", finalReward, "game_completion") // 发放经验奖励 expReward := finalReward / 2 s.GrantReward(ctx, gameID, playerID, RewardTypeExp, "exp", expReward, "game_completion") } // checkAchievements 检查成就 func (s *MinigameService) checkAchievements(ctx context.Context, gameID string, playerID uint64, session *GameSession) { // 获取玩家成就 achievements, err := s.achievementRepo.FindByGameAndPlayer(ctx, gameID, playerID) if err != nil { return } // 检查各种成就条件 for _, achievement := range achievements { if achievement.IsCompleted() { continue } // 根据成就类型检查条件 switch achievement.Category { case "score": s.checkScoreAchievement(ctx, achievement, session) case "time": s.checkTimeAchievement(ctx, achievement, session) case "moves": s.checkMovesAchievement(ctx, achievement, session) case "level": s.checkLevelAchievement(ctx, achievement, session) } // 保存成就更新 if achievement.IsCompleted() { s.achievementRepo.Save(ctx, achievement) // 发布成就完成事件 event := NewAchievementCompletedEvent(gameID, playerID, achievement.AchievementID, achievement.Points) s.eventBus.Publish(ctx, event) // 发放成就奖励 go s.grantAchievementRewards(context.Background(), gameID, playerID, achievement) } } } // checkScoreAchievement 检查分数成就 func (s *MinigameService) checkScoreAchievement(ctx context.Context, achievement *GameAchievement, session *GameSession) { targetScore, ok := achievement.GetCondition("target_score") if !ok { return } target, ok := targetScore.(float64) if !ok { return } if float64(session.Score) >= target { achievement.Complete() } else { progress := float64(session.Score) / target * 100 achievement.UpdateProgress(progress) } } // checkTimeAchievement 检查时间成就 func (s *MinigameService) checkTimeAchievement(ctx context.Context, achievement *GameAchievement, session *GameSession) { targetTime, ok := achievement.GetCondition("target_time") if !ok { return } target, ok := targetTime.(float64) if !ok { return } playTimeSeconds := session.PlayTime.Seconds() if playTimeSeconds >= target { achievement.Complete() } else { progress := playTimeSeconds / target * 100 achievement.UpdateProgress(progress) } } // checkMovesAchievement 检查移动成就 func (s *MinigameService) checkMovesAchievement(ctx context.Context, achievement *GameAchievement, session *GameSession) { targetMoves, ok := achievement.GetCondition("target_moves") if !ok { return } target, ok := targetMoves.(float64) if !ok { return } if float64(session.Moves) >= target { achievement.Complete() } else { progress := float64(session.Moves) / target * 100 achievement.UpdateProgress(progress) } } // checkLevelAchievement 检查等级成就 func (s *MinigameService) checkLevelAchievement(ctx context.Context, achievement *GameAchievement, session *GameSession) { targetLevel, ok := achievement.GetCondition("target_level") if !ok { return } target, ok := targetLevel.(float64) if !ok { return } if float64(session.Level) >= target { achievement.Complete() } else { progress := float64(session.Level) / target * 100 achievement.UpdateProgress(progress) } } // grantAchievementRewards 发放成就奖励 func (s *MinigameService) grantAchievementRewards(ctx context.Context, gameID string, playerID uint64, achievement *GameAchievement) { // 发放成就积分奖励 if achievement.Points > 0 { s.GrantReward(ctx, gameID, playerID, RewardTypeCoin, "coins", achievement.Points, "achievement") } // 发放其他奖励 for _, rewardID := range achievement.Rewards { // 根据奖励ID发放对应奖励 // 这里可以根据具体的奖励系统实现 s.GrantReward(ctx, gameID, playerID, RewardTypeItem, rewardID, 1, "achievement") } } // 常量定义 const ( // 服务相关常量 // DefaultGameDuration = 30 * time.Minute // Moved to aggregate.go MaxConcurrentGames = 1000 // 最大并发游戏数 MaxPlayersPerGame = 100 // 每个游戏最大玩家数 // 奖励相关常量 BaseCompletionReward = int64(100) // 基础完成奖励 MaxRewardMultiplier = 5.0 // 最大奖励倍数 // 成就相关常量 MaxAchievementsPerGame = 50 // 每个游戏最大成就数 ) // 辅助结构体 // GameSessionQuery 游戏会话查询条件 type GameSessionQuery struct { GameID *string `json:"game_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` GameType *GameType `json:"game_type,omitempty"` Status *PlayerStatus `json:"status,omitempty"` StartedAfter *time.Time `json:"started_after,omitempty"` StartedBefore *time.Time `json:"started_before,omitempty"` EndedAfter *time.Time `json:"ended_after,omitempty"` EndedBefore *time.Time `json:"ended_before,omitempty"` MinScore *int64 `json:"min_score,omitempty"` MaxScore *int64 `json:"max_score,omitempty"` MinLevel *int32 `json:"min_level,omitempty"` MaxLevel *int32 `json:"max_level,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` Limit *int32 `json:"limit,omitempty"` Offset *int32 `json:"offset,omitempty"` } // GetPlayerID 获取玩家ID func (q *GameSessionQuery) GetPlayerID() uint64 { if q.PlayerID != nil { return *q.PlayerID } return 0 } // GetMinigameID 获取小游戏ID func (q *GameSessionQuery) GetMinigameID() string { if q.GameID != nil { return *q.GameID } return "" } // GetStatus 获取状态 func (q *GameSessionQuery) GetStatus() *PlayerStatus { return q.Status } // GetSort 获取排序字段 func (q *GameSessionQuery) GetSort() string { return q.OrderBy } // GetSortOrder 获取排序顺序 func (q *GameSessionQuery) GetSortOrder() bool { return q.OrderDesc } // GetLimit 获取限制数量 func (q *GameSessionQuery) GetLimit() int32 { if q.Limit != nil { return *q.Limit } return 0 } // GetOffset 获取偏移量 func (q *GameSessionQuery) GetOffset() int32 { if q.Offset != nil { return *q.Offset } return 0 } // PlayerStatistics 玩家统计 type PlayerStatistics struct { PlayerID uint64 `json:"player_id"` TotalGames int64 `json:"total_games"` CompletedGames int64 `json:"completed_games"` WonGames int64 `json:"won_games"` TotalScore int64 `json:"total_score"` HighestScore int64 `json:"highest_score"` AverageScore float64 `json:"average_score"` TotalPlayTime time.Duration `json:"total_play_time"` AveragePlayTime time.Duration `json:"average_play_time"` TotalMoves int64 `json:"total_moves"` AverageMoves float64 `json:"average_moves"` HighestLevel int32 `json:"highest_level"` TotalAchievements int64 `json:"total_achievements"` TotalRewards int64 `json:"total_rewards"` WinRate float64 `json:"win_rate"` CompletionRate float64 `json:"completion_rate"` LastPlayedAt *time.Time `json:"last_played_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // NewPlayerStatistics 创建玩家统计 func NewPlayerStatistics(playerID uint64) *PlayerStatistics { now := time.Now() return &PlayerStatistics{ PlayerID: playerID, TotalGames: 0, CompletedGames: 0, WonGames: 0, TotalScore: 0, HighestScore: 0, AverageScore: 0.0, TotalPlayTime: 0, AveragePlayTime: 0, TotalMoves: 0, AverageMoves: 0.0, HighestLevel: 0, TotalAchievements: 0, TotalRewards: 0, WinRate: 0.0, CompletionRate: 0.0, CreatedAt: now, UpdatedAt: now, } } // UpdateStatistics 更新统计数据 func (ps *PlayerStatistics) UpdateStatistics(session *GameSession) { ps.TotalGames++ if session.IsFinished() { ps.CompletedGames++ } ps.TotalScore += session.Score if session.Score > ps.HighestScore { ps.HighestScore = session.Score } ps.TotalPlayTime += session.PlayTime ps.TotalMoves += int64(session.Moves) if session.Level > ps.HighestLevel { ps.HighestLevel = session.Level } // 重新计算平均值 if ps.TotalGames > 0 { ps.AverageScore = float64(ps.TotalScore) / float64(ps.TotalGames) ps.AveragePlayTime = ps.TotalPlayTime / time.Duration(ps.TotalGames) ps.AverageMoves = float64(ps.TotalMoves) / float64(ps.TotalGames) ps.CompletionRate = float64(ps.CompletedGames) / float64(ps.TotalGames) * 100 } ps.UpdatedAt = time.Now() } ================================================ FILE: internal/domain/minigame/types.go ================================================ package minigame import ( "fmt" "time" ) // GameCategory 游戏分类 type GameCategory string const ( GameCategoryNormal GameCategory = "normal" GameCategoryCompetitive GameCategory = "competitive" GameCategoryCasual GameCategory = "casual" GameCategoryRanked GameCategory = "ranked" ) // GameResult 游戏结果 type GameResult struct { GameID string `json:"game_id"` WinnerID uint64 `json:"winner_id"` WinnerName string `json:"winner_name"` FinalScore int64 `json:"final_score"` CompletedAt time.Time `json:"completed_at"` Rank int `json:"rank"` Score int64 `json:"score"` IsWinner bool `json:"is_winner"` PlayerID uint64 `json:"player_id"` } // GamePlayer 游戏玩家 type GamePlayer struct { PlayerID uint64 `json:"player_id"` Username string `json:"username"` Score int64 `json:"score"` JoinTime time.Time `json:"join_time"` IsActive bool `json:"is_active"` IsWinner bool `json:"is_winner"` Rank int `json:"rank"` } // Clone 克隆游戏玩家 func (gp *GamePlayer) Clone() *GamePlayer { return &GamePlayer{ PlayerID: gp.PlayerID, Username: gp.Username, Score: gp.Score, JoinTime: gp.JoinTime, IsActive: gp.IsActive, IsWinner: gp.IsWinner, Rank: gp.Rank, } } // GameData 游戏数据 type GameData struct { GameID string `json:"game_id"` GameType GameType `json:"game_type"` Config map[string]interface{} `json:"config"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` MaxPlayers int `json:"max_players"` MinPlayers int `json:"min_players"` IsActive bool `json:"is_active"` IsCompleted bool `json:"is_completed"` } // RewardPool 奖励池 type RewardPool struct { PoolID string `json:"pool_id"` GameID string `json:"game_id"` TotalReward int64 `json:"total_reward"` Rewards []Reward `json:"rewards"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // Reward 奖励 type Reward struct { RewardID string `json:"reward_id"` PlayerID string `json:"player_id"` RewardType string `json:"reward_type"` Amount int64 `json:"amount"` ItemID string `json:"item_id,omitempty"` ItemCount int `json:"item_count,omitempty"` GameID string `json:"game_id"` Timestamp time.Time `json:"timestamp"` } // GameStatistics 游戏统计 type GameStatistics struct { GameID string `json:"game_id"` TotalPlayers int `json:"total_players"` ActivePlayers int `json:"active_players"` CompletedGames int `json:"completed_games"` AverageScore float64 `json:"average_score"` HighestScore int64 `json:"highest_score"` LowestScore int64 `json:"lowest_score"` AverageDuration time.Duration `json:"average_duration"` AverageGameDuration float64 `json:"average_game_duration"` TotalGames int `json:"total_games"` LastPlayedAt time.Time `json:"last_played_at"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // NewGameStatistics 创建游戏统计信息 func NewGameStatistics() *GameStatistics { now := time.Now() return &GameStatistics{ CreatedAt: now, UpdatedAt: now, } } // NewGameData 创建游戏数据 func NewGameData() *GameData { now := time.Now() return &GameData{ StartTime: now, Config: make(map[string]interface{}), } } // generateScoreID 生成分数ID func generateScoreID() string { return fmt.Sprintf("score_%d", time.Now().UnixNano()) } // SetData 设置游戏数据 func (gd *GameData) SetData(key string, value interface{}) { if gd.Config == nil { gd.Config = make(map[string]interface{}) } gd.Config[key] = value } // GetData 获取游戏数据 func (gd *GameData) GetData(key string) (interface{}, bool) { if gd.Config == nil { return nil, false } value, exists := gd.Config[key] return value, exists } // Clone 克隆游戏数据 func (gd *GameData) Clone() *GameData { clone := &GameData{ GameID: gd.GameID, GameType: gd.GameType, StartTime: gd.StartTime, EndTime: gd.EndTime, Duration: gd.Duration, MaxPlayers: gd.MaxPlayers, MinPlayers: gd.MinPlayers, Config: make(map[string]interface{}), } // 复制配置 for k, v := range gd.Config { clone.Config[k] = v } return clone } // GetTotalPlays 获取总游戏次数 func (gs *GameStatistics) GetTotalPlays() int { return gs.TotalGames } // GetTotalPlayers 获取总玩家数 func (gs *GameStatistics) GetTotalPlayers() int { return gs.TotalPlayers } // GetAverageScore 获取平均分数 func (gs *GameStatistics) GetAverageScore() float64 { return gs.AverageScore } // GetHighestScore 获取最高分数 func (gs *GameStatistics) GetHighestScore() int64 { return gs.HighestScore } // GetAverageTime 获取平均游戏时长 func (gs *GameStatistics) GetAverageTime() time.Duration { return gs.AverageDuration } // GetCompletionRate 获取完成率 func (gs *GameStatistics) GetCompletionRate() float64 { if gs.TotalGames == 0 { return 0.0 } return float64(gs.CompletedGames) / float64(gs.TotalGames) } // Clone 克隆游戏统计信息 func (gs *GameStatistics) Clone() *GameStatistics { return &GameStatistics{ GameID: gs.GameID, TotalPlayers: gs.TotalPlayers, ActivePlayers: gs.ActivePlayers, CompletedGames: gs.CompletedGames, AverageScore: gs.AverageScore, HighestScore: gs.HighestScore, AverageDuration: gs.AverageDuration, LastPlayedAt: gs.LastPlayedAt, CreatedAt: gs.CreatedAt, UpdatedAt: gs.UpdatedAt, } } // CalculateRewards 计算奖励 func (rp *RewardPool) CalculateRewards(rank int, score int64, isWinner bool) []Reward { // 简单的奖励计算逻辑 rewards := make([]Reward, 0) if isWinner { // 获胜者获得基础奖励 rewards = append(rewards, Reward{ RewardType: "experience", Amount: int64(100 * rank), }) } // 根据分数给予额外奖励 if score > 1000 { rewards = append(rewards, Reward{ RewardType: "coin", Amount: score / 10, }) } return rewards } // Clone 克隆奖励池 func (rp *RewardPool) Clone() *RewardPool { clone := &RewardPool{ PoolID: rp.PoolID, GameID: rp.GameID, TotalReward: rp.TotalReward, CreatedAt: rp.CreatedAt, UpdatedAt: rp.UpdatedAt, Rewards: make([]Reward, len(rp.Rewards)), } // 复制奖励列表 for i, reward := range rp.Rewards { clone.Rewards[i] = reward } return clone } // 注意:GameReward已经在entity.go中定义,不需要重复定义 // 注意:GameStatistics和GameResult已经在文件开头定义,这里只是添加了缺失的字段 ================================================ FILE: internal/domain/minigame/value_object.go ================================================ package minigame import ( "fmt" "time" ) // 小游戏类型相关值对象 // GameType 游戏类型 type GameType int32 const ( GameTypeSaveDog GameType = iota + 1 // 拯救小狗 GameTypePuzzle // 拼图游戏 GameTypeRacing // 赛车游戏 GameTypeMemory // 记忆游戏 GameTypeMatch // 消除游戏 GameTypeJump // 跳跃游戏 GameTypeShoot // 射击游戏 GameTypeStrategy // 策略游戏 GameTypeCard // 卡牌游戏 GameTypeCustom // 自定义游戏 ) // String 返回游戏类型的字符串表示 func (gt GameType) String() string { switch gt { case GameTypeSaveDog: return "save_dog" case GameTypePuzzle: return "puzzle" case GameTypeRacing: return "racing" case GameTypeMemory: return "memory" case GameTypeMatch: return "match" case GameTypeJump: return "jump" case GameTypeShoot: return "shoot" case GameTypeStrategy: return "strategy" case GameTypeCard: return "card" case GameTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查游戏类型是否有效 func (gt GameType) IsValid() bool { return gt >= GameTypeSaveDog && gt <= GameTypeCustom } // 注意:GameCategory已经在types.go中定义,这里删除重复定义 // String 返回游戏分类的字符串表示 func (gc GameCategory) String() string { switch gc { case GameCategoryNormal: return "normal" case GameCategoryCompetitive: return "competitive" case GameCategoryCasual: return "casual" case GameCategoryRanked: return "ranked" default: return "unknown" } } // IsValid 检查游戏分类是否有效 func (gc GameCategory) IsValid() bool { switch gc { case GameCategoryNormal, GameCategoryCompetitive, GameCategoryCasual, GameCategoryRanked: return true default: return false } } // GameStatus 游戏状态 type GameStatus int32 const ( GameStatusWaiting GameStatus = iota + 1 // 等待开始 GameStatusRunning // 进行中 GameStatusPaused // 暂停 GameStatusFinished // 已结束 GameStatusCancelled // 已取消 GameStatusError // 错误状态 ) // String 返回游戏状态的字符串表示 func (gs GameStatus) String() string { switch gs { case GameStatusWaiting: return "waiting" case GameStatusRunning: return "running" case GameStatusPaused: return "paused" case GameStatusFinished: return "finished" case GameStatusCancelled: return "cancelled" case GameStatusError: return "error" default: return "unknown" } } // IsValid 检查游戏状态是否有效 func (gs GameStatus) IsValid() bool { return gs >= GameStatusWaiting && gs <= GameStatusError } // CanTransitionTo 检查是否可以转换到目标状态 func (gs GameStatus) CanTransitionTo(target GameStatus) bool { switch gs { case GameStatusWaiting: return target == GameStatusRunning || target == GameStatusCancelled case GameStatusRunning: return target == GameStatusPaused || target == GameStatusFinished || target == GameStatusCancelled || target == GameStatusError case GameStatusPaused: return target == GameStatusRunning || target == GameStatusFinished || target == GameStatusCancelled case GameStatusFinished, GameStatusCancelled, GameStatusError: return false // 终态,不能转换 default: return false } } // GamePhase 游戏阶段 type GamePhase int32 const ( GamePhaseWaiting GamePhase = iota + 1 // 等待阶段 GamePhaseStarting // 开始阶段 GamePhaseRunning // 运行阶段 GamePhasePaused // 暂停阶段 GamePhaseEnding // 结束阶段 GamePhaseFinished // 完成阶段 ) // String 返回游戏阶段的字符串表示 func (gp GamePhase) String() string { switch gp { case GamePhaseWaiting: return "waiting" case GamePhaseStarting: return "starting" case GamePhaseRunning: return "running" case GamePhasePaused: return "paused" case GamePhaseEnding: return "ending" case GamePhaseFinished: return "finished" default: return "unknown" } } // IsValid 检查游戏阶段是否有效 func (gp GamePhase) IsValid() bool { return gp >= GamePhaseWaiting && gp <= GamePhaseFinished } // GameEndReason 游戏结束原因 type GameEndReason int32 const ( GameEndReasonCompleted GameEndReason = iota + 1 // 正常完成 GameEndReasonTimeout // 超时 GameEndReasonCancelled // 取消 GameEndReasonInsufficientPlayers // 玩家不足 GameEndReasonError // 错误 GameEndReasonForceQuit // 强制退出 GameEndReasonNetworkError // 网络错误 GameEndReasonSystemMaintenance // 系统维护 ) // String 返回游戏结束原因的字符串表示 func (ger GameEndReason) String() string { switch ger { case GameEndReasonCompleted: return "completed" case GameEndReasonTimeout: return "timeout" case GameEndReasonCancelled: return "cancelled" case GameEndReasonInsufficientPlayers: return "insufficient_players" case GameEndReasonError: return "error" case GameEndReasonForceQuit: return "force_quit" case GameEndReasonNetworkError: return "network_error" case GameEndReasonSystemMaintenance: return "system_maintenance" default: return "unknown" } } // IsValid 检查游戏结束原因是否有效 func (ger GameEndReason) IsValid() bool { return ger >= GameEndReasonCompleted && ger <= GameEndReasonSystemMaintenance } // PlayerStatus 玩家状态 type PlayerStatus int32 const ( PlayerStatusWaiting PlayerStatus = iota + 1 // 等待中 PlayerStatusReady // 准备就绪 PlayerStatusPlaying // 游戏中 PlayerStatusPaused // 暂停 PlayerStatusFinished // 已完成 PlayerStatusLeft // 已离开 PlayerStatusDisconnected // 断线 PlayerStatusKicked // 被踢出 ) // String 返回玩家状态的字符串表示 func (ps PlayerStatus) String() string { switch ps { case PlayerStatusWaiting: return "waiting" case PlayerStatusReady: return "ready" case PlayerStatusPlaying: return "playing" case PlayerStatusPaused: return "paused" case PlayerStatusFinished: return "finished" case PlayerStatusLeft: return "left" case PlayerStatusDisconnected: return "disconnected" case PlayerStatusKicked: return "kicked" default: return "unknown" } } // IsValid 检查玩家状态是否有效 func (ps PlayerStatus) IsValid() bool { return ps >= PlayerStatusWaiting && ps <= PlayerStatusKicked } // PlayerLeaveReason 玩家离开原因 type PlayerLeaveReason int32 const ( PlayerLeaveReasonVoluntary PlayerLeaveReason = iota + 1 // 主动离开 PlayerLeaveReasonKicked // 被踢出 PlayerLeaveReasonDisconnected // 断线 PlayerLeaveReasonTimeout // 超时 PlayerLeaveReasonError // 错误 PlayerLeaveReasonGameEnded // 游戏结束 ) // String 返回玩家离开原因的字符串表示 func (plr PlayerLeaveReason) String() string { switch plr { case PlayerLeaveReasonVoluntary: return "voluntary" case PlayerLeaveReasonKicked: return "kicked" case PlayerLeaveReasonDisconnected: return "disconnected" case PlayerLeaveReasonTimeout: return "timeout" case PlayerLeaveReasonError: return "error" case PlayerLeaveReasonGameEnded: return "game_ended" default: return "unknown" } } // IsValid 检查玩家离开原因是否有效 func (plr PlayerLeaveReason) IsValid() bool { return plr >= PlayerLeaveReasonVoluntary && plr <= PlayerLeaveReasonGameEnded } // ScoreType 分数类型 type ScoreType int32 const ( ScoreTypePoints ScoreType = iota + 1 // 积分 ScoreTypeTime // 时间 ScoreTypeDistance // 距离 ScoreTypeAccuracy // 准确度 ScoreTypeCombo // 连击 ScoreTypeLevel // 等级 ScoreTypeProgress // 进度 ScoreTypeCustom // 自定义 ) // String 返回分数类型的字符串表示 func (st ScoreType) String() string { switch st { case ScoreTypePoints: return "points" case ScoreTypeTime: return "time" case ScoreTypeDistance: return "distance" case ScoreTypeAccuracy: return "accuracy" case ScoreTypeCombo: return "combo" case ScoreTypeLevel: return "level" case ScoreTypeProgress: return "progress" case ScoreTypeCustom: return "custom" default: return "unknown" } } // IsValid 检查分数类型是否有效 func (st ScoreType) IsValid() bool { return st >= ScoreTypePoints && st <= ScoreTypeCustom } // 注意:RewardType已经在entity.go中定义,这里删除重复定义 // String 返回奖励类型的字符串表示 func (rt RewardType) String() string { switch rt { case RewardTypeCoin: return "coin" case RewardTypeExp: return "exp" case RewardTypeItem: return "item" case RewardTypeCurrency: return "currency" default: return "unknown" } } // IsValid 检查奖励类型是否有效 func (rt RewardType) IsValid() bool { switch rt { case RewardTypeCoin, RewardTypeExp, RewardTypeItem, RewardTypeCurrency: return true default: return false } } // 游戏配置相关值对象 // GameConfig 游戏配置 type GameConfig struct { MaxPlayers int32 `json:"max_players" bson:"max_players"` MinPlayers int32 `json:"min_players" bson:"min_players"` MaxDuration time.Duration `json:"max_duration" bson:"max_duration"` MinDuration time.Duration `json:"min_duration" bson:"min_duration"` AutoStart bool `json:"auto_start" bson:"auto_start"` AutoEnd bool `json:"auto_end" bson:"auto_end"` AllowSpectators bool `json:"allow_spectators" bson:"allow_spectators"` AllowReconnect bool `json:"allow_reconnect" bson:"allow_reconnect"` Difficulty GameDifficulty `json:"difficulty" bson:"difficulty"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewGameConfig 创建新的游戏配置 func NewGameConfig() *GameConfig { now := time.Now() return &GameConfig{ MaxPlayers: 10, MinPlayers: 1, MaxDuration: 30 * time.Minute, MinDuration: 1 * time.Minute, AutoStart: false, AutoEnd: true, AllowSpectators: true, AllowReconnect: true, Difficulty: GameDifficultyNormal, CreatedAt: now, UpdatedAt: now, } } // Clone 克隆游戏配置 func (gc *GameConfig) Clone() *GameConfig { return &GameConfig{ MaxPlayers: gc.MaxPlayers, MinPlayers: gc.MinPlayers, MaxDuration: gc.MaxDuration, MinDuration: gc.MinDuration, AutoStart: gc.AutoStart, AutoEnd: gc.AutoEnd, AllowSpectators: gc.AllowSpectators, AllowReconnect: gc.AllowReconnect, Difficulty: gc.Difficulty, CreatedAt: gc.CreatedAt, UpdatedAt: gc.UpdatedAt, } } // GameDifficulty 游戏难度 type GameDifficulty int32 const ( GameDifficultyEasy GameDifficulty = iota + 1 // 简单 GameDifficultyNormal // 普通 GameDifficultyHard // 困难 GameDifficultyExpert // 专家 GameDifficultyCustom // 自定义 ) // String 返回游戏难度的字符串表示 func (gd GameDifficulty) String() string { switch gd { case GameDifficultyEasy: return "easy" case GameDifficultyNormal: return "normal" case GameDifficultyHard: return "hard" case GameDifficultyExpert: return "expert" case GameDifficultyCustom: return "custom" default: return "unknown" } } // IsValid 检查游戏难度是否有效 func (gd GameDifficulty) IsValid() bool { return gd >= GameDifficultyEasy && gd <= GameDifficultyCustom } // GetScoreMultiplier 获取分数倍数 func (gd GameDifficulty) GetScoreMultiplier() float64 { switch gd { case GameDifficultyEasy: return 0.8 case GameDifficultyNormal: return 1.0 case GameDifficultyHard: return 1.5 case GameDifficultyExpert: return 2.0 case GameDifficultyCustom: return 1.0 default: return 1.0 } } // GameRules 游戏规则 type GameRules struct { WinConditions []WinCondition `json:"win_conditions" bson:"win_conditions"` LoseConditions []LoseCondition `json:"lose_conditions" bson:"lose_conditions"` ScoringRules []ScoringRule `json:"scoring_rules" bson:"scoring_rules"` TimeLimit *time.Duration `json:"time_limit,omitempty" bson:"time_limit,omitempty"` MoveLimit *int32 `json:"move_limit,omitempty" bson:"move_limit,omitempty"` SpecialRules map[string]interface{} `json:"special_rules" bson:"special_rules"` Penalties []Penalty `json:"penalties" bson:"penalties"` Bonuses []Bonus `json:"bonuses" bson:"bonuses"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewGameRules 创建新的游戏规则 func NewGameRules() *GameRules { now := time.Now() return &GameRules{ WinConditions: make([]WinCondition, 0), LoseConditions: make([]LoseCondition, 0), ScoringRules: make([]ScoringRule, 0), SpecialRules: make(map[string]interface{}), Penalties: make([]Penalty, 0), Bonuses: make([]Bonus, 0), CreatedAt: now, UpdatedAt: now, } } // Clone 克隆游戏规则 func (gr *GameRules) Clone() *GameRules { clone := &GameRules{ WinConditions: make([]WinCondition, len(gr.WinConditions)), LoseConditions: make([]LoseCondition, len(gr.LoseConditions)), ScoringRules: make([]ScoringRule, len(gr.ScoringRules)), SpecialRules: make(map[string]interface{}), Penalties: make([]Penalty, len(gr.Penalties)), Bonuses: make([]Bonus, len(gr.Bonuses)), CreatedAt: gr.CreatedAt, UpdatedAt: gr.UpdatedAt, } // 深拷贝切片 copy(clone.WinConditions, gr.WinConditions) copy(clone.LoseConditions, gr.LoseConditions) copy(clone.ScoringRules, gr.ScoringRules) copy(clone.Penalties, gr.Penalties) copy(clone.Bonuses, gr.Bonuses) // 深拷贝map for k, v := range gr.SpecialRules { clone.SpecialRules[k] = v } // 深拷贝指针 if gr.TimeLimit != nil { timeLimit := *gr.TimeLimit clone.TimeLimit = &timeLimit } if gr.MoveLimit != nil { moveLimit := *gr.MoveLimit clone.MoveLimit = &moveLimit } return clone } // ToMap 转换为映射 func (gr *GameRules) ToMap() map[string]interface{} { return map[string]interface{}{ "win_conditions": gr.WinConditions, "lose_conditions": gr.LoseConditions, "scoring_rules": gr.ScoringRules, "time_limit": gr.TimeLimit, "move_limit": gr.MoveLimit, "special_rules": gr.SpecialRules, "penalties": gr.Penalties, "bonuses": gr.Bonuses, "created_at": gr.CreatedAt, "updated_at": gr.UpdatedAt, } } // WinCondition 胜利条件 type WinCondition struct { Type string `json:"type" bson:"type"` Description string `json:"description" bson:"description"` Target interface{} `json:"target" bson:"target"` Operator string `json:"operator" bson:"operator"` Priority int32 `json:"priority" bson:"priority"` } // LoseCondition 失败条件 type LoseCondition struct { Type string `json:"type" bson:"type"` Description string `json:"description" bson:"description"` Target interface{} `json:"target" bson:"target"` Operator string `json:"operator" bson:"operator"` Priority int32 `json:"priority" bson:"priority"` } // ScoringRule 计分规则 type ScoringRule struct { Action string `json:"action" bson:"action"` Points int64 `json:"points" bson:"points"` Multiplier float64 `json:"multiplier" bson:"multiplier"` Description string `json:"description" bson:"description"` } // Penalty 惩罚 type Penalty struct { Trigger string `json:"trigger" bson:"trigger"` Penalty int64 `json:"penalty" bson:"penalty"` Description string `json:"description" bson:"description"` } // Bonus 奖励 type Bonus struct { Trigger string `json:"trigger" bson:"trigger"` Bonus int64 `json:"bonus" bson:"bonus"` Multiplier float64 `json:"multiplier" bson:"multiplier"` Description string `json:"description" bson:"description"` } // GameSettings 游戏设置 type GameSettings struct { SoundEnabled bool `json:"sound_enabled" bson:"sound_enabled"` MusicEnabled bool `json:"music_enabled" bson:"music_enabled"` EffectsEnabled bool `json:"effects_enabled" bson:"effects_enabled"` Language string `json:"language" bson:"language"` Theme string `json:"theme" bson:"theme"` Quality GameQuality `json:"quality" bson:"quality"` Controls map[string]interface{} `json:"controls" bson:"controls"` CustomSettings map[string]interface{} `json:"custom_settings" bson:"custom_settings"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // NewGameSettings 创建新的游戏设置 func NewGameSettings() *GameSettings { now := time.Now() return &GameSettings{ SoundEnabled: true, MusicEnabled: true, EffectsEnabled: true, Language: "zh-CN", Theme: "default", Quality: GameQualityMedium, Controls: make(map[string]interface{}), CustomSettings: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // Clone 克隆游戏设置 func (gs *GameSettings) Clone() *GameSettings { clone := &GameSettings{ SoundEnabled: gs.SoundEnabled, MusicEnabled: gs.MusicEnabled, EffectsEnabled: gs.EffectsEnabled, Language: gs.Language, Theme: gs.Theme, Quality: gs.Quality, Controls: make(map[string]interface{}), CustomSettings: make(map[string]interface{}), CreatedAt: gs.CreatedAt, UpdatedAt: gs.UpdatedAt, } // 深拷贝map for k, v := range gs.Controls { clone.Controls[k] = v } for k, v := range gs.CustomSettings { clone.CustomSettings[k] = v } return clone } // ToMap 转换为映射 func (gs *GameSettings) ToMap() map[string]interface{} { return map[string]interface{}{ "sound_enabled": gs.SoundEnabled, "music_enabled": gs.MusicEnabled, "effects_enabled": gs.EffectsEnabled, "language": gs.Language, "theme": gs.Theme, "quality": gs.Quality.String(), "controls": gs.Controls, "custom_settings": gs.CustomSettings, "created_at": gs.CreatedAt, "updated_at": gs.UpdatedAt, } } // GameQuality 游戏质量 type GameQuality int32 const ( GameQualityLow GameQuality = iota + 1 // 低质量 GameQualityMedium // 中等质量 GameQualityHigh // 高质量 GameQualityUltra // 超高质量 ) // String 返回游戏质量的字符串表示 func (gq GameQuality) String() string { switch gq { case GameQualityLow: return "low" case GameQualityMedium: return "medium" case GameQualityHigh: return "high" case GameQualityUltra: return "ultra" default: return "unknown" } } // IsValid 检查游戏质量是否有效 func (gq GameQuality) IsValid() bool { return gq >= GameQualityLow && gq <= GameQualityUltra } // 游戏查询相关值对象 // GameQuery 游戏查询条件 type GameQuery struct { GameID *string `json:"game_id,omitempty"` GameType *GameType `json:"game_type,omitempty"` Category *GameCategory `json:"category,omitempty"` Status *GameStatus `json:"status,omitempty"` CreatorID *uint64 `json:"creator_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` MinPlayers *int32 `json:"min_players,omitempty"` MaxPlayers *int32 `json:"max_players,omitempty"` MinDuration *time.Duration `json:"min_duration,omitempty"` MaxDuration *time.Duration `json:"max_duration,omitempty"` StartedAfter *time.Time `json:"started_after,omitempty"` StartedBefore *time.Time `json:"started_before,omitempty"` EndedAfter *time.Time `json:"ended_after,omitempty"` EndedBefore *time.Time `json:"ended_before,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` Keywords []string `json:"keywords,omitempty"` Tags []string `json:"tags,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` Offset int `json:"offset,omitempty"` Limit int `json:"limit,omitempty"` } // GameFilter 游戏过滤器 type GameFilter struct { IncludeFinished bool `json:"include_finished"` IncludeCancelled bool `json:"include_cancelled"` OnlyActive bool `json:"only_active"` OnlyJoinable bool `json:"only_joinable"` ExcludeGameIDs []string `json:"exclude_game_ids,omitempty"` ExcludePlayerIDs []uint64 `json:"exclude_player_ids,omitempty"` MinScore *int64 `json:"min_score,omitempty"` MaxScore *int64 `json:"max_score,omitempty"` Difficulties []GameDifficulty `json:"difficulties,omitempty"` CustomFilters map[string]interface{} `json:"custom_filters,omitempty"` } // NewGameFilter 创建新的游戏过滤器 func NewGameFilter() *GameFilter { return &GameFilter{ IncludeFinished: false, IncludeCancelled: false, OnlyActive: true, OnlyJoinable: false, ExcludeGameIDs: make([]string, 0), ExcludePlayerIDs: make([]uint64, 0), Difficulties: make([]GameDifficulty, 0), CustomFilters: make(map[string]interface{}), } } // 游戏操作相关值对象 // GameOperation 游戏操作类型 type GameOperation int32 const ( GameOperationStart GameOperation = iota + 1 // 开始游戏 GameOperationPause // 暂停游戏 GameOperationResume // 恢复游戏 GameOperationEnd // 结束游戏 GameOperationCancel // 取消游戏 GameOperationReset // 重置游戏 GameOperationKick // 踢出玩家 GameOperationJoin // 加入游戏 GameOperationLeave // 离开游戏 ) // String 返回游戏操作的字符串表示 func (go_ GameOperation) String() string { switch go_ { case GameOperationStart: return "start" case GameOperationPause: return "pause" case GameOperationResume: return "resume" case GameOperationEnd: return "end" case GameOperationCancel: return "cancel" case GameOperationReset: return "reset" case GameOperationKick: return "kick" case GameOperationJoin: return "join" case GameOperationLeave: return "leave" default: return "unknown" } } // IsValid 检查游戏操作是否有效 func (go_ GameOperation) IsValid() bool { return go_ >= GameOperationStart && go_ <= GameOperationLeave } // RequiresPermission 检查操作是否需要权限 func (go_ GameOperation) RequiresPermission() bool { switch go_ { case GameOperationStart, GameOperationPause, GameOperationResume, GameOperationEnd, GameOperationCancel, GameOperationReset, GameOperationKick: return true default: return false } } // GameOperationResult 游戏操作结果 type GameOperationResult struct { Success bool `json:"success"` Operation GameOperation `json:"operation"` GameID string `json:"game_id"` PlayerID *uint64 `json:"player_id,omitempty"` OldStatus *GameStatus `json:"old_status,omitempty"` NewStatus *GameStatus `json:"new_status,omitempty"` AffectedCount int64 `json:"affected_count"` Message string `json:"message"` Error string `json:"error,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` Timestamp time.Time `json:"timestamp"` Duration time.Duration `json:"duration"` } // NewGameOperationResult 创建游戏操作结果 func NewGameOperationResult(operation GameOperation, gameID string, success bool) *GameOperationResult { return &GameOperationResult{ Success: success, Operation: operation, GameID: gameID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), } } // SetPlayerInfo 设置玩家信息 func (gor *GameOperationResult) SetPlayerInfo(playerID uint64) { gor.PlayerID = &playerID } // SetStatusChange 设置状态变更 func (gor *GameOperationResult) SetStatusChange(oldStatus, newStatus GameStatus) { gor.OldStatus = &oldStatus gor.NewStatus = &newStatus } // SetError 设置错误信息 func (gor *GameOperationResult) SetError(err error) { gor.Success = false gor.Error = err.Error() } // SetMessage 设置消息 func (gor *GameOperationResult) SetMessage(message string) { gor.Message = message } // SetDuration 设置持续时间 func (gor *GameOperationResult) SetDuration(start time.Time) { gor.Duration = time.Since(start) } // AddMetadata 添加元数据 func (gor *GameOperationResult) AddMetadata(key string, value interface{}) { if gor.Metadata == nil { gor.Metadata = make(map[string]interface{}) } gor.Metadata[key] = value } // 验证函数 // ValidateGameQuery 验证游戏查询 func ValidateGameQuery(query *GameQuery) error { if query == nil { return fmt.Errorf("query cannot be nil") } if query.Limit <= 0 { return fmt.Errorf("limit must be positive") } if query.Limit > 1000 { return fmt.Errorf("limit cannot exceed 1000") } if query.Offset < 0 { return fmt.Errorf("offset cannot be negative") } if query.MinPlayers != nil && query.MaxPlayers != nil && *query.MinPlayers > *query.MaxPlayers { return fmt.Errorf("min_players cannot be greater than max_players") } if query.MinDuration != nil && query.MaxDuration != nil && *query.MinDuration > *query.MaxDuration { return fmt.Errorf("min_duration cannot be greater than max_duration") } if query.StartedAfter != nil && query.StartedBefore != nil && query.StartedAfter.After(*query.StartedBefore) { return fmt.Errorf("started_after cannot be after started_before") } if query.EndedAfter != nil && query.EndedBefore != nil && query.EndedAfter.After(*query.EndedBefore) { return fmt.Errorf("ended_after cannot be after ended_before") } if query.CreatedAfter != nil && query.CreatedBefore != nil && query.CreatedAfter.After(*query.CreatedBefore) { return fmt.Errorf("created_after cannot be after created_before") } return nil } // 辅助函数 // GetGameTypeByString 根据字符串获取游戏类型 func GetGameTypeByString(s string) (GameType, error) { switch s { case "save_dog": return GameTypeSaveDog, nil case "puzzle": return GameTypePuzzle, nil case "racing": return GameTypeRacing, nil case "memory": return GameTypeMemory, nil case "match": return GameTypeMatch, nil case "jump": return GameTypeJump, nil case "shoot": return GameTypeShoot, nil case "strategy": return GameTypeStrategy, nil case "card": return GameTypeCard, nil case "custom": return GameTypeCustom, nil default: return 0, fmt.Errorf("unknown game type: %s", s) } } // GetGameCategoryByString 根据字符串获取游戏分类 func GetGameCategoryByString(s string) (GameCategory, error) { switch s { case "normal": return GameCategoryNormal, nil case "competitive": return GameCategoryCompetitive, nil case "casual": return GameCategoryCasual, nil case "ranked": return GameCategoryRanked, nil default: return "", fmt.Errorf("unknown game category: %s", s) } } // GetGameStatusByString 根据字符串获取游戏状态 func GetGameStatusByString(s string) (GameStatus, error) { switch s { case "waiting": return GameStatusWaiting, nil case "running": return GameStatusRunning, nil case "paused": return GameStatusPaused, nil case "finished": return GameStatusFinished, nil case "cancelled": return GameStatusCancelled, nil case "error": return GameStatusError, nil default: return 0, fmt.Errorf("unknown game status: %s", s) } } // GetGameDifficultyByString 根据字符串获取游戏难度 func GetGameDifficultyByString(s string) (GameDifficulty, error) { switch s { case "easy": return GameDifficultyEasy, nil case "normal": return GameDifficultyNormal, nil case "hard": return GameDifficultyHard, nil case "expert": return GameDifficultyExpert, nil case "custom": return GameDifficultyCustom, nil default: return 0, fmt.Errorf("unknown game difficulty: %s", s) } } // GetRewardTypeByString 根据字符串获取奖励类型 func GetRewardTypeByString(s string) (RewardType, error) { switch s { case "coin": return RewardTypeCoin, nil case "exp": return RewardTypeExp, nil case "item": return RewardTypeItem, nil case "currency": return RewardTypeCurrency, nil default: return RewardTypeCoin, fmt.Errorf("unknown reward type: %s", s) } } // SessionStatus 会话状态 type SessionStatus int32 const ( SessionStatusActive SessionStatus = iota + 1 // 活跃 SessionStatusExpired // 过期 SessionStatusCompleted // 完成 SessionStatusCancelled // 取消 ) // String 返回会话状态的字符串表示 func (ss SessionStatus) String() string { switch ss { case SessionStatusActive: return "active" case SessionStatusExpired: return "expired" case SessionStatusCompleted: return "completed" case SessionStatusCancelled: return "cancelled" default: return "unknown" } } // IsValid 检查会话状态是否有效 func (ss SessionStatus) IsValid() bool { return ss >= SessionStatusActive && ss <= SessionStatusCancelled } // ParseSessionStatus 根据字符串解析会话状态 func ParseSessionStatus(s string) SessionStatus { switch s { case "active": return SessionStatusActive case "expired": return SessionStatusExpired case "completed": return SessionStatusCompleted case "cancelled": return SessionStatusCancelled default: return SessionStatusActive // 默认为活跃状态 } } ================================================ FILE: internal/domain/npc/aggregate.go ================================================ package npc import ( "time" ) // NPCAggregate NPC聚合�? type NPCAggregate struct { id string name string description string npcType NPCType status NPCStatus location *Location attributes *NPCAttributes behavior *NPCBehavior dialogues map[string]*Dialogue quests map[string]*Quest shop *Shop relationships map[string]*Relationship schedule *NPCSchedule createdAt time.Time updatedAt time.Time version int events []DomainEvent } // NewNPCAggregate 创建NPC聚合�? func NewNPCAggregate(id, name, description string, npcType NPCType) *NPCAggregate { now := time.Now() return &NPCAggregate{ id: id, name: name, description: description, npcType: npcType, status: NPCStatusActive, location: NewLocation(0, 0, 0, "default", "default"), attributes: NewNPCAttributes(), behavior: NewNPCBehavior(), dialogues: make(map[string]*Dialogue), quests: make(map[string]*Quest), relationships: make(map[string]*Relationship), schedule: NewNPCSchedule(), createdAt: now, updatedAt: now, version: 1, events: make([]DomainEvent, 0), } } // GetID gets ID func (n *NPCAggregate) GetID() string { return n.id } // GetName gets name func (n *NPCAggregate) GetName() string { return n.name } // GetDescription gets description func (n *NPCAggregate) GetDescription() string { return n.description } // GetType gets type func (n *NPCAggregate) GetType() NPCType { return n.npcType } // GetStatus 获取状�? func (n *NPCAggregate) GetStatus() NPCStatus { return n.status } // GetLocation gets location func (n *NPCAggregate) GetLocation() *Location { return n.location } // GetAttributes 获取属�? func (n *NPCAggregate) GetAttributes() *NPCAttributes { return n.attributes } // GetBehavior gets behavior func (n *NPCAggregate) GetBehavior() *NPCBehavior { return n.behavior } // GetShop 获取商店 func (n *NPCAggregate) GetShop() *Shop { return n.shop } // GetSchedule 获取日程 func (n *NPCAggregate) GetSchedule() *NPCSchedule { return n.schedule } // GetVersion 获取版本 func (n *NPCAggregate) GetVersion() int { return n.version } // GetEvents 获取领域事件 func (n *NPCAggregate) GetEvents() []DomainEvent { return n.events } // ClearEvents 清除领域事件 func (n *NPCAggregate) ClearEvents() { n.events = make([]DomainEvent, 0) } // SetName 设置名称 func (n *NPCAggregate) SetName(name string) error { if name == "" { return ErrInvalidNPCName } oldName := n.name n.name = name n.updatedAt = time.Now() n.version++ // 发布名称变更事件 event := NewNPCNameChangedEvent(n.id, oldName, name) n.addEvent(event) return nil } // SetDescription 设置描述 func (n *NPCAggregate) SetDescription(description string) { n.description = description n.updatedAt = time.Now() n.version++ } // SetStatus 设置状�? func (n *NPCAggregate) SetStatus(status NPCStatus) error { if !status.IsValid() { return ErrInvalidNPCStatus } oldStatus := n.status n.status = status n.updatedAt = time.Now() n.version++ // 发布状态变更事�? event := NewNPCStatusChangedEvent(n.id, oldStatus, status, "status_changed", "system") n.addEvent(event) return nil } // MoveTo 移动到指定位�? func (n *NPCAggregate) MoveTo(location *Location) error { if location == nil { return ErrInvalidLocation } // 检查是否可以移�? if !n.CanMove() { return ErrNPCCannotMove } oldLocation := n.location n.location = location n.updatedAt = time.Now() n.version++ // 发布移动事件 event := NewNPCMovedEvent(n.id, oldLocation, location) n.addEvent(event) return nil } // CanMove 检查是否可以移�? func (n *NPCAggregate) CanMove() bool { return n.status == NPCStatusActive && n.behavior.CanMoveNow() } // AddDialogue 添加对话 func (n *NPCAggregate) AddDialogue(dialogue *Dialogue) error { if dialogue == nil { return ErrInvalidDialogue } if _, exists := n.dialogues[dialogue.GetID()]; exists { return ErrDialogueAlreadyExists } n.dialogues[dialogue.GetID()] = dialogue n.updatedAt = time.Now() n.version++ // 发布对话添加事件 event := NewDialogueAddedEvent(n.id, dialogue.GetID()) n.addEvent(event) return nil } // RemoveDialogue 移除对话 func (n *NPCAggregate) RemoveDialogue(dialogueID string) error { _, exists := n.dialogues[dialogueID] if !exists { return ErrDialogueNotFound } delete(n.dialogues, dialogueID) n.updatedAt = time.Now() n.version++ // 发布对话移除事件 event := NewDialogueRemovedEvent(n.id, dialogueID) n.addEvent(event) return nil } // StartDialogue 开始对�? func (n *NPCAggregate) StartDialogue(dialogueID, playerID string) (*DialogueSession, error) { dialogue, exists := n.dialogues[dialogueID] if !exists { return nil, ErrDialogueNotFound } // 检查对话条�? if !dialogue.CanStart(playerID) { return nil, ErrDialogueConditionsNotMet } // 创建对话会话 session := NewDialogueSession(n.id, dialogueID, playerID) n.updatedAt = time.Now() n.version++ // 发布对话开始事�? event := NewDialogueStartedEvent(n.id, playerID, dialogueID, session.GetID()) n.addEvent(event) return session, nil } // AddQuest 添加任务 func (n *NPCAggregate) AddQuest(quest *Quest) error { if quest == nil { return ErrInvalidQuest } if _, exists := n.quests[quest.GetID()]; exists { return ErrQuestAlreadyExists } n.quests[quest.GetID()] = quest n.updatedAt = time.Now() n.version++ // 发布任务添加事件 event := NewQuestAddedEvent(n.id, quest.GetID()) n.addEvent(event) return nil } // RemoveQuest 移除任务 func (n *NPCAggregate) RemoveQuest(questID string) error { _, exists := n.quests[questID] if !exists { return ErrQuestNotFound } delete(n.quests, questID) n.updatedAt = time.Now() n.version++ // 发布任务移除事件 event := NewQuestRemovedEvent(n.id, questID) n.addEvent(event) return nil } // GiveQuest 给予任务 func (n *NPCAggregate) GiveQuest(questID, playerID string) (*QuestInstance, error) { quest, exists := n.quests[questID] if !exists { return nil, ErrQuestNotFound } // 检查任务条�? if !quest.CanAccept(playerID) { return nil, ErrQuestConditionsNotMet } // 创建任务实例 instance := NewQuestInstance(questID, playerID, n.id) n.updatedAt = time.Now() n.version++ // 发布任务给予事件 event := NewQuestGivenEvent(n.id, questID, playerID) n.addEvent(event) return instance, nil } // SetShop 设置商店 func (n *NPCAggregate) SetShop(shop *Shop) error { if shop == nil { return ErrInvalidShop } // 检查NPC类型是否支持商店 if !n.npcType.CanHaveShop() { return ErrNPCCannotHaveShop } n.shop = shop n.updatedAt = time.Now() n.version++ // 发布商店设置事件 event := NewShopSetEvent(n.id, shop.GetID()) n.addEvent(event) return nil } // Trade 交易 func (n *NPCAggregate) Trade(playerID string, tradeRequest *TradeRequest) (*TradeResult, error) { if n.shop == nil { return nil, ErrNPCHasNoShop } // 执行交易 result, err := n.shop.ExecuteTrade(playerID, tradeRequest) if err != nil { return nil, err } n.updatedAt = time.Now() n.version++ // 发布交易事件 event := NewTradeExecutedEvent(n.id, playerID, tradeRequest, result) n.addEvent(event) return result, nil } // UpdateRelationship 更新关系 func (n *NPCAggregate) UpdateRelationship(playerID string, change int, reason string) error { relationship, exists := n.relationships[playerID] if !exists { relationship = NewRelationship(playerID, n.id) n.relationships[playerID] = relationship } oldLevel := relationship.GetLevel() err := relationship.ChangeValue(change, reason) if err != nil { return err } n.updatedAt = time.Now() n.version++ // 如果关系等级发生变化,发布事�? if relationship.GetLevel() != oldLevel { oldValue := relationship.GetValue() - change newValue := relationship.GetValue() event := NewRelationshipChangedEvent(n.id, playerID, oldValue, newValue, oldLevel, relationship.GetLevel(), RelationshipChangeTypeIncrease, reason) n.addEvent(event) } return nil } // GetRelationship 获取关系 func (n *NPCAggregate) GetRelationship(playerID string) *Relationship { return n.relationships[playerID] } // GetDialogue 获取对话 func (n *NPCAggregate) GetDialogue(dialogueID string) (*Dialogue, error) { dialogue, exists := n.dialogues[dialogueID] if !exists { return nil, ErrDialogueNotFound } return dialogue, nil } // GetAllDialogues 获取所有对�? func (n *NPCAggregate) GetAllDialogues() map[string]*Dialogue { return n.dialogues } // GetAvailableDialogues 获取可用对话 func (n *NPCAggregate) GetAvailableDialogues(playerID string) []*Dialogue { var available []*Dialogue for _, dialogue := range n.dialogues { if dialogue.CanStart(playerID) { available = append(available, dialogue) } } return available } // GetQuest 获取任务 func (n *NPCAggregate) GetQuest(questID string) (*Quest, error) { quest, exists := n.quests[questID] if !exists { return nil, ErrQuestNotFound } return quest, nil } // GetAllQuests 获取所有任�? func (n *NPCAggregate) GetAllQuests() map[string]*Quest { return n.quests } // GetAvailableQuests 获取可用任务 func (n *NPCAggregate) GetAvailableQuests(playerID string) []*Quest { var available []*Quest for _, quest := range n.quests { if quest.CanAccept(playerID) { available = append(available, quest) } } return available } // Update 更新NPC状�? func (n *NPCAggregate) Update(deltaTime time.Duration) { // 更新行为 n.behavior.Update(deltaTime) // 更新日程 n.schedule.Update(time.Now()) // 更新商店(如果有�? if n.shop != nil { n.shop.Update(deltaTime) } n.updatedAt = time.Now() n.version++ } // IsActive 检查是否激�? func (n *NPCAggregate) IsActive() bool { return n.status == NPCStatusActive } // CanInteract 检查是否可以交�? func (n *NPCAggregate) CanInteract(playerID string) bool { if !n.IsActive() { return false } // 检查关系是否允许交�? relationship := n.GetRelationship(playerID) if relationship != nil && relationship.GetLevel() == RelationshipLevelHostile { return false } return true } // GetInteractionOptions 获取交互选项 func (n *NPCAggregate) GetInteractionOptions(playerID string) []InteractionOption { var options []InteractionOption if !n.CanInteract(playerID) { return options } // 添加对话选项 availableDialogues := n.GetAvailableDialogues(playerID) for _, dialogue := range availableDialogues { options = append(options, InteractionOption{ Type: InteractionTypeDialogue, ID: dialogue.GetID(), Name: dialogue.GetName(), Description: dialogue.GetDescription(), }) } // 添加任务选项 availableQuests := n.GetAvailableQuests(playerID) for _, quest := range availableQuests { options = append(options, InteractionOption{ Type: InteractionTypeQuest, ID: quest.GetID(), Name: quest.GetName(), Description: quest.GetDescription(), }) } // 添加商店选项 if n.shop != nil && n.shop.IsOpen() { options = append(options, InteractionOption{ Type: InteractionTypeShop, ID: n.shop.GetID(), Name: "商店", Description: "浏览商品", }) } return options } // Activate 激活NPC func (n *NPCAggregate) Activate() error { return n.SetStatus(NPCStatusActive) } // Deactivate 停用NPC func (n *NPCAggregate) Deactivate() error { return n.SetStatus(NPCStatusInactive) } // Hide 隐藏NPC func (n *NPCAggregate) Hide() error { return n.SetStatus(NPCStatusHidden) } // Busy 设置忙碌状�? func (n *NPCAggregate) Busy() error { return n.SetStatus(NPCStatusBusy) } // GetStatistics 获取统计信息 func (n *NPCAggregate) GetStatistics() *NPCStatistics { totalDialogues := len(n.dialogues) totalQuests := len(n.quests) totalRelationships := len(n.relationships) var averageRelationship float64 if totalRelationships > 0 { sum := 0 for _, rel := range n.relationships { sum += rel.GetValue() } averageRelationship = float64(sum) / float64(totalRelationships) } return &NPCStatistics{ NPCID: n.id, Name: n.name, Type: n.npcType, Status: n.status, TotalDialogues: totalDialogues, TotalQuests: totalQuests, TotalRelationships: totalRelationships, AverageRelationship: averageRelationship, CreatedAt: n.createdAt, LastActiveAt: n.updatedAt, } } // addEvent 添加领域事件 func (n *NPCAggregate) addEvent(event DomainEvent) { n.events = append(n.events, event) } // ToMap 转换为映�? func (n *NPCAggregate) ToMap() map[string]interface{} { return map[string]interface{}{ "id": n.id, "name": n.name, "description": n.description, "type": n.npcType.String(), "status": n.status.String(), "location": n.location.ToMap(), "attributes": n.attributes.ToMap(), "dialogues": len(n.dialogues), "quests": len(n.quests), "relationships": len(n.relationships), "has_shop": n.shop != nil, "created_at": n.createdAt, "updated_at": n.updatedAt, "version": n.version, } } // HasDialogue 检查是否有对话 func (n *NPCAggregate) HasDialogue() bool { return len(n.dialogues) > 0 } // HasQuests 检查是否有任务 func (n *NPCAggregate) HasQuests() bool { return len(n.quests) > 0 } // HasShop 检查是否有商店 func (n *NPCAggregate) HasShop() bool { return n.shop != nil } // GetCreatedAt 获取创建时间 func (n *NPCAggregate) GetCreatedAt() time.Time { return n.createdAt } // GetUpdatedAt 获取更新时间 func (n *NPCAggregate) GetUpdatedAt() time.Time { return n.updatedAt } // InteractionOption 交互选项 type InteractionOption struct { Type InteractionType ID string Name string Description string Icon string Enabled bool } // InteractionType 交互类型 type InteractionType int const ( InteractionTypeDialogue InteractionType = iota + 1 // 对话 InteractionTypeQuest // 任务 InteractionTypeShop // 商店 InteractionTypeTrade // 交易 InteractionTypeService // 服务 ) // String 返回交互类型字符�? func (it InteractionType) String() string { switch it { case InteractionTypeDialogue: return "dialogue" case InteractionTypeQuest: return "quest" case InteractionTypeShop: return "shop" case InteractionTypeTrade: return "trade" case InteractionTypeService: return "service" default: return "unknown" } } // NPCStatistics NPC统计信息 type NPCStatistics struct { NPCID string Name string Type NPCType Status NPCStatus TotalDialogues int TotalQuests int TotalRelationships int AverageRelationship float64 CreatedAt time.Time LastActiveAt time.Time } // NewNPCStatistics 创建NPC统计信息 func NewNPCStatistics(npcID string) *NPCStatistics { return &NPCStatistics{ NPCID: npcID, CreatedAt: time.Now(), } } // AddDialogueSession 添加对话会话 func (ns *NPCStatistics) AddDialogueSession(playerID string, duration time.Duration) { ns.TotalDialogues++ ns.LastActiveAt = time.Now() } // UpdateLastInteractionTime 更新最后交互时间 func (ns *NPCStatistics) UpdateLastInteractionTime(t time.Time) { ns.LastActiveAt = t } // AddQuestCompletion 添加任务完成 func (ns *NPCStatistics) AddQuestCompletion(playerID string, questID string, totalValue int) { ns.TotalQuests++ ns.LastActiveAt = time.Now() } // 相关错误定义 var ( // 这些错误已经在errors.go中定义,注释掉重复声明 // ErrInvalidNPCName = fmt.Errorf("invalid NPC name") // ErrInvalidNPCStatus = fmt.Errorf("invalid NPC status") // ErrInvalidLocation = fmt.Errorf("invalid location") // ErrNPCCannotMove = fmt.Errorf("NPC cannot move") // ErrInvalidDialogue = fmt.Errorf("invalid dialogue") // ErrDialogueAlreadyExists = fmt.Errorf("dialogue already exists") // ErrDialogueNotFound = fmt.Errorf("dialogue not found") // ErrDialogueConditionsNotMet = fmt.Errorf("dialogue conditions not met") // ErrInvalidQuest = fmt.Errorf("invalid quest") // ErrQuestAlreadyExists = fmt.Errorf("quest already exists") // ErrQuestNotFound = fmt.Errorf("quest not found") // ErrQuestConditionsNotMet = fmt.Errorf("quest conditions not met") // ErrInvalidShop = fmt.Errorf("invalid shop") // ErrNPCCannotHaveShop = fmt.Errorf("NPC cannot have shop") // ErrNPCHasNoShop = fmt.Errorf("NPC has no shop") ) ================================================ FILE: internal/domain/npc/entity.go ================================================ package npc import ( "fmt" "time" ) // Dialogue 对话实体 type Dialogue struct { id string name string description string type_ DialogueType nodes map[string]*DialogueNode startNodeID string conditions []*DialogueCondition rewards *DialogueReward cooldown time.Duration lastUsed map[string]time.Time maxUses int useCount map[string]int createdAt time.Time updatedAt time.Time } // NewDialogue 创建对话 func NewDialogue(id, name, description string, dialogueType DialogueType) *Dialogue { now := time.Now() return &Dialogue{ id: id, name: name, description: description, type_: dialogueType, nodes: make(map[string]*DialogueNode), conditions: make([]*DialogueCondition, 0), lastUsed: make(map[string]time.Time), useCount: make(map[string]int), maxUses: -1, // 无限制 createdAt: now, updatedAt: now, } } // GetID 获取ID func (d *Dialogue) GetID() string { return d.id } // GetName 获取名称 func (d *Dialogue) GetName() string { return d.name } // GetDescription 获取描述 func (d *Dialogue) GetDescription() string { return d.description } // GetType 获取类型 func (d *Dialogue) GetType() DialogueType { return d.type_ } // AddNode 添加对话节点 func (d *Dialogue) AddNode(node *DialogueNode) { d.nodes[node.GetID()] = node d.updatedAt = time.Now() } // SetStartNode 设置开始节点 func (d *Dialogue) SetStartNode(nodeID string) error { if _, exists := d.nodes[nodeID]; !exists { return fmt.Errorf("node not found: %s", nodeID) } d.startNodeID = nodeID d.updatedAt = time.Now() return nil } // GetStartNode 获取开始节点 func (d *Dialogue) GetStartNode() *DialogueNode { return d.nodes[d.startNodeID] } // GetNode 获取节点 func (d *Dialogue) GetNode(nodeID string) *DialogueNode { return d.nodes[nodeID] } // AddCondition 添加条件 func (d *Dialogue) AddCondition(condition *DialogueCondition) { d.conditions = append(d.conditions, condition) d.updatedAt = time.Now() } // CanStart 检查是否可以开始 func (d *Dialogue) CanStart(playerID string) bool { // 检查使用次数 if d.maxUses > 0 && d.useCount[playerID] >= d.maxUses { return false } // 检查冷却时间 if d.cooldown > 0 { if lastUsed, exists := d.lastUsed[playerID]; exists { if time.Since(lastUsed) < d.cooldown { return false } } } // 检查条件 for _, condition := range d.conditions { if !condition.Check(playerID) { return false } } return true } // Use 使用对话 func (d *Dialogue) Use(playerID string) { d.lastUsed[playerID] = time.Now() d.useCount[playerID]++ d.updatedAt = time.Now() } // SetReward 设置奖励 func (d *Dialogue) SetReward(reward *DialogueReward) { d.rewards = reward d.updatedAt = time.Now() } // GetReward 获取奖励 func (d *Dialogue) GetReward() *DialogueReward { return d.rewards } // DialogueNode 对话节点 type DialogueNode struct { id string text string speaker string options []*DialogueOption actions []*DialogueAction nextNode string } // NewDialogueNode 创建对话节点 func NewDialogueNode(id, text, speaker string) *DialogueNode { return &DialogueNode{ id: id, text: text, speaker: speaker, options: make([]*DialogueOption, 0), actions: make([]*DialogueAction, 0), } } // GetID 获取ID func (dn *DialogueNode) GetID() string { return dn.id } // GetText 获取文本 func (dn *DialogueNode) GetText() string { return dn.text } // GetSpeaker 获取说话者 func (dn *DialogueNode) GetSpeaker() string { return dn.speaker } // GetOptions 获取选项 func (dn *DialogueNode) GetOptions() []*DialogueOption { return dn.options } // AddOption 添加选项 func (dn *DialogueNode) AddOption(option *DialogueOption) { dn.options = append(dn.options, option) } // AddAction 添加动作 func (dn *DialogueNode) AddAction(action *DialogueAction) { dn.actions = append(dn.actions, action) } // ExecuteActions 执行动作 func (dn *DialogueNode) ExecuteActions(playerID string) error { for _, action := range dn.actions { if err := action.Execute(playerID); err != nil { return err } } return nil } // SetNextNode 设置下一个节点 func (dn *DialogueNode) SetNextNode(nodeID string) { dn.nextNode = nodeID } // GetNextNode 获取下一个节点 func (dn *DialogueNode) GetNextNode() string { return dn.nextNode } // DialogueOption 对话选项 type DialogueOption struct { id string text string targetNode string conditions []*DialogueCondition actions []*DialogueAction } // NewDialogueOption 创建对话选项 func NewDialogueOption(id, text, targetNode string) *DialogueOption { return &DialogueOption{ id: id, text: text, targetNode: targetNode, conditions: make([]*DialogueCondition, 0), actions: make([]*DialogueAction, 0), } } // GetID 获取ID func (do *DialogueOption) GetID() string { return do.id } // GetText 获取文本 func (do *DialogueOption) GetText() string { return do.text } // GetTargetNode 获取目标节点 func (do *DialogueOption) GetTargetNode() string { return do.targetNode } // IsAvailable 检查是否可用 func (do *DialogueOption) IsAvailable(playerID string) bool { for _, condition := range do.conditions { if !condition.Check(playerID) { return false } } return true } // Execute 执行选项 func (do *DialogueOption) Execute(playerID string) error { for _, action := range do.actions { if err := action.Execute(playerID); err != nil { return err } } return nil } // DialogueCondition 对话条件 type DialogueCondition struct { type_ ConditionType key string operator string value interface{} message string } // NewDialogueCondition 创建对话条件 func NewDialogueCondition(conditionType ConditionType, key, operator string, value interface{}, message string) *DialogueCondition { return &DialogueCondition{ type_: conditionType, key: key, operator: operator, value: value, message: message, } } // Check 检查条件 func (dc *DialogueCondition) Check(playerID string) bool { // 这里应该根据条件类型和玩家数据进行检查 // 简化实现,总是返回true return true } // DialogueAction 对话动作 type DialogueAction struct { type_ ActionType parameters map[string]interface{} } // NewDialogueAction 创建对话动作 func NewDialogueAction(actionType ActionType, parameters map[string]interface{}) *DialogueAction { return &DialogueAction{ type_: actionType, parameters: parameters, } } // Execute 执行动作 func (da *DialogueAction) Execute(playerID string) error { // 这里应该根据动作类型执行相应的操作 // 简化实现,直接返回nil return nil } // DialogueReward 对话奖励 type DialogueReward struct { gold int experience int items map[string]int special map[string]interface{} } // NewDialogueReward 创建对话奖励 func NewDialogueReward() *DialogueReward { return &DialogueReward{ items: make(map[string]int), special: make(map[string]interface{}), } } // AddGold 添加金币奖励 func (dr *DialogueReward) AddGold(amount int) { dr.gold += amount } // AddExperience 添加经验奖励 func (dr *DialogueReward) AddExperience(amount int) { dr.experience += amount } // AddItem 添加物品奖励 func (dr *DialogueReward) AddItem(itemID string, quantity int) { dr.items[itemID] = quantity } // DialogueSession 对话会话 type DialogueSession struct { npcID string dialogueID string playerID string currentNode string startTime time.Time lastUpdate time.Time context map[string]interface{} active bool } // NewDialogueSession 创建对话会话 func NewDialogueSession(npcID, dialogueID, playerID string) *DialogueSession { now := time.Now() return &DialogueSession{ npcID: npcID, dialogueID: dialogueID, playerID: playerID, startTime: now, lastUpdate: now, context: make(map[string]interface{}), active: true, } } // GetNPCID 获取NPC ID func (ds *DialogueSession) GetNPCID() string { return ds.npcID } // GetDialogueID 获取对话ID func (ds *DialogueSession) GetDialogueID() string { return ds.dialogueID } // GetPlayerID 获取玩家ID func (ds *DialogueSession) GetPlayerID() string { return ds.playerID } // GetCurrentNode 获取当前节点 func (ds *DialogueSession) GetCurrentNode() string { return ds.currentNode } // SetCurrentNode 设置当前节点 func (ds *DialogueSession) SetCurrentNode(nodeID string) { ds.currentNode = nodeID ds.lastUpdate = time.Now() } // IsActive 检查是否激活 func (ds *DialogueSession) IsActive() bool { return ds.active } // End 结束会话 func (ds *DialogueSession) End() { ds.active = false ds.lastUpdate = time.Now() } // GetDuration 获取持续时间 func (ds *DialogueSession) GetDuration() time.Duration { return ds.lastUpdate.Sub(ds.startTime) } // GetID 获取会话ID func (ds *DialogueSession) GetID() string { return ds.npcID + "_" + ds.dialogueID + "_" + ds.playerID } // GetCurrentNodeID 获取当前节点ID func (ds *DialogueSession) GetCurrentNodeID() string { return ds.currentNode } // GetStartTime 获取开始时间 func (ds *DialogueSession) GetStartTime() time.Time { return ds.startTime } // GetEndTime 获取结束时间 func (ds *DialogueSession) GetEndTime() time.Time { return ds.lastUpdate } // GetContext 获取上下文 func (ds *DialogueSession) GetContext() map[string]interface{} { return ds.context } // Quest 任务实体 type Quest struct { id string name string description string type_ QuestType objectives []*QuestObjective rewards *QuestReward prerequisites []*QuestPrerequisite timeLimit time.Duration repeatable bool dailyReset bool createdAt time.Time updatedAt time.Time } // NewQuest 创建任务 func NewQuest(id, name, description string, questType QuestType) *Quest { now := time.Now() return &Quest{ id: id, name: name, description: description, type_: questType, objectives: make([]*QuestObjective, 0), prerequisites: make([]*QuestPrerequisite, 0), createdAt: now, updatedAt: now, } } // GetID 获取ID func (q *Quest) GetID() string { return q.id } // GetName 获取名称 func (q *Quest) GetName() string { return q.name } // GetDescription 获取描述 func (q *Quest) GetDescription() string { return q.description } // GetType 获取类型 func (q *Quest) GetType() QuestType { return q.type_ } // AddObjective 添加目标 func (q *Quest) AddObjective(objective *QuestObjective) { q.objectives = append(q.objectives, objective) q.updatedAt = time.Now() } // GetObjectives 获取目标 func (q *Quest) GetObjectives() []*QuestObjective { return q.objectives } // SetReward 设置奖励 func (q *Quest) SetReward(reward *QuestReward) { q.rewards = reward q.updatedAt = time.Now() } // GetReward 获取奖励 func (q *Quest) GetReward() *QuestReward { return q.rewards } // AddPrerequisite 添加前置条件 func (q *Quest) AddPrerequisite(prerequisite *QuestPrerequisite) { q.prerequisites = append(q.prerequisites, prerequisite) q.updatedAt = time.Now() } // CanAccept 检查是否可以接受 func (q *Quest) CanAccept(playerID string) bool { // 检查前置条件 for _, prerequisite := range q.prerequisites { if !prerequisite.Check(playerID) { return false } } return true } // SetTimeLimit 设置时间限制 func (q *Quest) SetTimeLimit(duration time.Duration) { q.timeLimit = duration q.updatedAt = time.Now() } // SetRepeatable 设置是否可重复 func (q *Quest) SetRepeatable(repeatable bool) { q.repeatable = repeatable q.updatedAt = time.Now() } // SetDailyReset 设置每日重置 func (q *Quest) SetDailyReset(dailyReset bool) { q.dailyReset = dailyReset q.updatedAt = time.Now() } // QuestObjective 任务目标 type QuestObjective struct { id string description string type_ ObjectiveType target string required int optional bool } // NewQuestObjective 创建任务目标 func NewQuestObjective(id, description string, objectiveType ObjectiveType, target string, required int) *QuestObjective { return &QuestObjective{ id: id, description: description, type_: objectiveType, target: target, required: required, } } // GetID 获取ID func (qo *QuestObjective) GetID() string { return qo.id } // GetDescription 获取描述 func (qo *QuestObjective) GetDescription() string { return qo.description } // GetType 获取类型 func (qo *QuestObjective) GetType() ObjectiveType { return qo.type_ } // GetTarget 获取目标 func (qo *QuestObjective) GetTarget() string { return qo.target } // GetRequired 获取需求数量 func (qo *QuestObjective) GetRequired() int { return qo.required } // IsOptional 检查是否可选 func (qo *QuestObjective) IsOptional() bool { return qo.optional } // SetOptional 设置可选 func (qo *QuestObjective) SetOptional(optional bool) { qo.optional = optional } // QuestReward 任务奖励 type QuestReward struct { gold int experience int items map[string]int special map[string]interface{} choices []*RewardChoice } // NewQuestReward 创建任务奖励 func NewQuestReward() *QuestReward { return &QuestReward{ items: make(map[string]int), special: make(map[string]interface{}), choices: make([]*RewardChoice, 0), } } // AddGold 添加金币奖励 func (qr *QuestReward) AddGold(amount int) { qr.gold += amount } // AddExperience 添加经验奖励 func (qr *QuestReward) AddExperience(amount int) { qr.experience += amount } // AddItem 添加物品奖励 func (qr *QuestReward) AddItem(itemID string, quantity int) { qr.items[itemID] = quantity } // AddChoice 添加选择奖励 func (qr *QuestReward) AddChoice(choice *RewardChoice) { qr.choices = append(qr.choices, choice) } // GetTotalValue 获取总价值 func (qr *QuestReward) GetTotalValue() int { return qr.gold + qr.experience*10 } // RewardChoice 奖励选择 type RewardChoice struct { id string name string description string items map[string]int } // NewRewardChoice 创建奖励选择 func NewRewardChoice(id, name, description string) *RewardChoice { return &RewardChoice{ id: id, name: name, description: description, items: make(map[string]int), } } // QuestPrerequisite 任务前置条件 type QuestPrerequisite struct { type_ PrerequisiteType key string operator string value interface{} message string } // NewQuestPrerequisite 创建任务前置条件 func NewQuestPrerequisite(prerequisiteType PrerequisiteType, key, operator string, value interface{}, message string) *QuestPrerequisite { return &QuestPrerequisite{ type_: prerequisiteType, key: key, operator: operator, value: value, message: message, } } // Check 检查前置条件 func (qp *QuestPrerequisite) Check(playerID string) bool { // 这里应该根据前置条件类型和玩家数据进行检查 // 简化实现,总是返回true return true } // QuestInstance 任务实例 type QuestInstance struct { questID string playerID string npcID string status QuestStatus progress map[string]int startTime time.Time deadline time.Time completedAt time.Time rewardGiven bool } // NewQuestInstance 创建任务实例 func NewQuestInstance(questID, playerID, npcID string) *QuestInstance { now := time.Now() return &QuestInstance{ questID: questID, playerID: playerID, npcID: npcID, status: QuestStatusActive, progress: make(map[string]int), startTime: now, } } // GetQuestID 获取任务ID func (qi *QuestInstance) GetQuestID() string { return qi.questID } // GetPlayerID 获取玩家ID func (qi *QuestInstance) GetPlayerID() string { return qi.playerID } // GetNPCID 获取NPC ID func (qi *QuestInstance) GetNPCID() string { return qi.npcID } // GetStatus 获取状态 func (qi *QuestInstance) GetStatus() QuestStatus { return qi.status } // UpdateProgress 更新进度 func (qi *QuestInstance) UpdateProgress(objectiveID string, amount int) { qi.progress[objectiveID] += amount } // GetProgress 获取进度 func (qi *QuestInstance) GetProgress(objectiveID string) int { return qi.progress[objectiveID] } // Complete 完成任务 func (qi *QuestInstance) Complete() { qi.status = QuestStatusCompleted qi.completedAt = time.Now() } // Fail 失败任务 func (qi *QuestInstance) Fail() { qi.status = QuestStatusFailed } // GetCompletedAt 获取完成时间 func (qi *QuestInstance) GetCompletedAt() time.Time { return qi.completedAt } // GiveReward 给予奖励 func (qi *QuestInstance) GiveReward() { qi.rewardGiven = true } // IsRewardGiven 检查是否已给予奖励 func (qi *QuestInstance) IsRewardGiven() bool { return qi.rewardGiven } // SetDeadline 设置截止时间 func (qi *QuestInstance) SetDeadline(deadline time.Time) { qi.deadline = deadline } // IsExpired 检查是否过期 func (qi *QuestInstance) IsExpired() bool { return !qi.deadline.IsZero() && time.Now().After(qi.deadline) } // Shop 商店实体 type Shop struct { id string name string description string items map[string]*ShopItem schedule *ShopSchedule discounts map[string]*Discount currency string createdAt time.Time updatedAt time.Time } // NewShop 创建商店 func NewShop(id, name, description string) *Shop { now := time.Now() return &Shop{ id: id, name: name, description: description, items: make(map[string]*ShopItem), schedule: NewShopSchedule(), discounts: make(map[string]*Discount), currency: "gold", createdAt: now, updatedAt: now, } } // GetID 获取ID func (s *Shop) GetID() string { return s.id } // GetName 获取名称 func (s *Shop) GetName() string { return s.name } // AddItem 添加商品 func (s *Shop) AddItem(item *ShopItem) { s.items[item.GetID()] = item s.updatedAt = time.Now() } // RemoveItem 移除商品 func (s *Shop) RemoveItem(itemID string) { delete(s.items, itemID) s.updatedAt = time.Now() } // GetItem 获取商品 func (s *Shop) GetItem(itemID string) *ShopItem { return s.items[itemID] } // GetAllItems 获取所有商品 func (s *Shop) GetAllItems() map[string]*ShopItem { return s.items } // GetAvailableItems 获取可用商品 func (s *Shop) GetAvailableItems() []*ShopItem { var available []*ShopItem for _, item := range s.items { if item.IsAvailable() { available = append(available, item) } } return available } // IsOpen 检查是否开放 func (s *Shop) IsOpen() bool { return s.schedule.IsOpen(time.Now()) } // ExecuteTrade 执行交易 func (s *Shop) ExecuteTrade(playerID string, request *TradeRequest) (*TradeResult, error) { if !s.IsOpen() { return nil, fmt.Errorf("shop is closed") } item := s.GetItem(request.ItemID) if item == nil { return nil, fmt.Errorf("item not found") } if !item.IsAvailable() { return nil, fmt.Errorf("item not available") } if request.Quantity > item.GetStock() { return nil, fmt.Errorf("insufficient stock") } // 计算价格(包括折扣) totalPrice := item.GetPrice() * request.Quantity if discount := s.getDiscount(playerID, request.ItemID); discount != nil { totalPrice = discount.Apply(totalPrice) } // 执行交易 item.Purchase(request.Quantity) s.updatedAt = time.Now() return &TradeResult{ ItemID: request.ItemID, Quantity: request.Quantity, TotalPrice: totalPrice, Success: true, Timestamp: time.Now(), }, nil } // getDiscount 获取折扣 func (s *Shop) getDiscount(playerID, itemID string) *Discount { // 简化实现,返回nil return nil } // Update 更新商店 func (s *Shop) Update(deltaTime time.Duration) { // 更新商品库存等 for _, item := range s.items { item.Update(deltaTime) } s.updatedAt = time.Now() } // ShopItem 商店商品 type ShopItem struct { id string name string description string price int stock int maxStock int restockRate int lastRestock time.Time available bool } // NewShopItem 创建商店商品 func NewShopItem(id, name, description string, price, stock int) *ShopItem { return &ShopItem{ id: id, name: name, description: description, price: price, stock: stock, maxStock: stock, lastRestock: time.Now(), available: true, } } // GetID 获取ID func (si *ShopItem) GetID() string { return si.id } // GetName 获取名称 func (si *ShopItem) GetName() string { return si.name } // GetPrice 获取价格 func (si *ShopItem) GetPrice() int { return si.price } // GetStock 获取库存 func (si *ShopItem) GetStock() int { return si.stock } // IsAvailable 检查是否可用 func (si *ShopItem) IsAvailable() bool { return si.available && si.stock > 0 } // Purchase 购买 func (si *ShopItem) Purchase(quantity int) { si.stock -= quantity if si.stock < 0 { si.stock = 0 } } // Restock 补货 func (si *ShopItem) Restock(quantity int) { si.stock += quantity if si.stock > si.maxStock { si.stock = si.maxStock } si.lastRestock = time.Now() } // Update 更新商品 func (si *ShopItem) Update(deltaTime time.Duration) { // 自动补货逻辑 if si.restockRate > 0 && si.stock < si.maxStock { if time.Since(si.lastRestock) >= time.Hour { si.Restock(si.restockRate) } } } // TradeRequest 交易请求 type TradeRequest struct { ItemID string Quantity int PlayerID string } // TradeResult 交易结果 type TradeResult struct { ItemID string Quantity int TotalPrice int Success bool Message string Timestamp time.Time } // Discount 折扣 type Discount struct { id string name string type_ DiscountType value float64 conditions []*DiscountCondition startTime time.Time endTime time.Time } // NewDiscount 创建折扣 func NewDiscount(id, name string, discountType DiscountType, value float64) *Discount { return &Discount{ id: id, name: name, type_: discountType, value: value, conditions: make([]*DiscountCondition, 0), } } // Apply 应用折扣 func (d *Discount) Apply(originalPrice int) int { switch d.type_ { case DiscountTypePercentage: return int(float64(originalPrice) * (1.0 - d.value/100.0)) case DiscountTypeFixed: result := originalPrice - int(d.value) if result < 0 { return 0 } return result default: return originalPrice } } // IsValid 检查是否有效 func (d *Discount) IsValid() bool { now := time.Now() return (d.startTime.IsZero() || now.After(d.startTime)) && (d.endTime.IsZero() || now.Before(d.endTime)) } // DiscountCondition 折扣条件 type DiscountCondition struct { type_ ConditionType key string operator string value interface{} } // NewDiscountCondition 创建折扣条件 func NewDiscountCondition(conditionType ConditionType, key, operator string, value interface{}) *DiscountCondition { return &DiscountCondition{ type_: conditionType, key: key, operator: operator, value: value, } } // Check 检查条件 func (dc *DiscountCondition) Check(playerID string) bool { // 这里应该根据条件类型和玩家数据进行检查 // 简化实现,总是返回true return true } ================================================ FILE: internal/domain/npc/errors.go ================================================ package npc import ( "fmt" "strings" "time" ) // 基础错误变量 var ( // NPC相关错误 ErrNPCNotFound = fmt.Errorf("NPC not found") ErrNPCAlreadyExists = fmt.Errorf("NPC already exists") ErrNPCInvalidID = fmt.Errorf("invalid NPC ID") ErrNPCInvalidName = fmt.Errorf("invalid NPC name") ErrInvalidNPCName = fmt.Errorf("invalid NPC name") ErrNPCInvalidType = fmt.Errorf("invalid NPC type") ErrNPCInvalidStatus = fmt.Errorf("invalid NPC status") ErrInvalidNPCStatus = fmt.Errorf("invalid NPC status") ErrNPCInvalidLocation = fmt.Errorf("invalid NPC location") ErrInvalidLocation = fmt.Errorf("invalid location") ErrNPCCannotMove = fmt.Errorf("NPC cannot move") ErrNPCNotActive = fmt.Errorf("NPC is not active") ErrNPCBusy = fmt.Errorf("NPC is busy") ErrNPCUnavailable = fmt.Errorf("NPC is unavailable") ErrNPCMaxInteractions = fmt.Errorf("NPC has reached maximum interactions") // 对话相关错误 ErrDialogueNotFound = fmt.Errorf("dialogue not found") ErrDialogueAlreadyExists = fmt.Errorf("dialogue already exists") ErrDialogueInvalidID = fmt.Errorf("invalid dialogue ID") ErrDialogueInvalidType = fmt.Errorf("invalid dialogue type") ErrDialogueNotAvailable = fmt.Errorf("dialogue not available") ErrDialogueConditionFailed = fmt.Errorf("dialogue condition not met") ErrDialogueConditionsNotMet = fmt.Errorf("dialogue conditions not met") ErrDialogueSessionExpired = fmt.Errorf("dialogue session expired") ErrDialogueSessionNotFound = fmt.Errorf("dialogue session not found") ErrDialogueInProgress = fmt.Errorf("dialogue already in progress") ErrDialogueNodeNotFound = fmt.Errorf("dialogue node not found") ErrDialogueInvalidChoice = fmt.Errorf("invalid dialogue choice") ErrInvalidDialogue = fmt.Errorf("invalid dialogue") ErrInvalidQuest = fmt.Errorf("invalid quest") ErrInvalidShop = fmt.Errorf("invalid shop") ErrNPCCannotHaveShop = fmt.Errorf("NPC cannot have shop") ErrQuestConditionsNotMet = fmt.Errorf("quest conditions not met") ErrNPCHasNoShop = fmt.Errorf("NPC has no shop") // 任务相关错误 ErrQuestNotFound = fmt.Errorf("quest not found") ErrQuestAlreadyExists = fmt.Errorf("quest already exists") ErrQuestInvalidID = fmt.Errorf("invalid quest ID") ErrQuestInvalidType = fmt.Errorf("invalid quest type") ErrQuestNotAvailable = fmt.Errorf("quest not available") ErrQuestAlreadyAccepted = fmt.Errorf("quest already accepted") ErrQuestAlreadyCompleted = fmt.Errorf("quest already completed") ErrQuestNotAccepted = fmt.Errorf("quest not accepted") ErrQuestNotCompleted = fmt.Errorf("quest not completed") ErrQuestConditionFailed = fmt.Errorf("quest condition not met") ErrQuestObjectiveNotFound = fmt.Errorf("quest objective not found") ErrQuestRewardClaimed = fmt.Errorf("quest reward already claimed") ErrQuestExpired = fmt.Errorf("quest has expired") ErrQuestCooldown = fmt.Errorf("quest is on cooldown") ErrQuestMaxAttempts = fmt.Errorf("quest maximum attempts reached") // 商店相关错误 ErrShopNotFound = fmt.Errorf("shop not found") ErrShopAlreadyExists = fmt.Errorf("shop already exists") ErrShopInvalidID = fmt.Errorf("invalid shop ID") ErrShopNotOpen = fmt.Errorf("shop is not open") ErrShopItemNotFound = fmt.Errorf("shop item not found") ErrShopItemOutOfStock = fmt.Errorf("shop item out of stock") ErrShopInsufficientFunds = fmt.Errorf("insufficient funds") ErrShopInvalidQuantity = fmt.Errorf("invalid quantity") ErrShopInvalidPrice = fmt.Errorf("invalid price") ErrShopTransactionFailed = fmt.Errorf("shop transaction failed") ErrShopInventoryFull = fmt.Errorf("shop inventory is full") // 关系相关错误 ErrRelationshipNotFound = fmt.Errorf("relationship not found") ErrRelationshipAlreadyExists = fmt.Errorf("relationship already exists") ErrRelationshipInvalidValue = fmt.Errorf("invalid relationship value") ErrRelationshipInvalidLevel = fmt.Errorf("invalid relationship level") ErrRelationshipMaxValue = fmt.Errorf("relationship value at maximum") ErrRelationshipMinValue = fmt.Errorf("relationship value at minimum") ErrRelationshipLocked = fmt.Errorf("relationship is locked") // 行为相关错误 ErrBehaviorNotFound = fmt.Errorf("behavior not found") ErrBehaviorInvalidType = fmt.Errorf("invalid behavior type") ErrBehaviorInvalidState = fmt.Errorf("invalid behavior state") ErrBehaviorCooldown = fmt.Errorf("behavior is on cooldown") ErrBehaviorConditionFailed = fmt.Errorf("behavior condition not met") // 位置相关错误 ErrLocationInvalid = fmt.Errorf("invalid location") ErrLocationOutOfBounds = fmt.Errorf("location out of bounds") ErrLocationNotAccessible = fmt.Errorf("location not accessible") ErrLocationOccupied = fmt.Errorf("location is occupied") // 权限相关错误 ErrPermissionDenied = fmt.Errorf("permission denied") ErrUnauthorized = fmt.Errorf("unauthorized access") ErrInsufficientLevel = fmt.Errorf("insufficient level") ErrInsufficientReputation = fmt.Errorf("insufficient reputation") // 系统相关错误 ErrSystemBusy = fmt.Errorf("system is busy") ErrSystemMaintenance = fmt.Errorf("system under maintenance") ErrRateLimitExceeded = fmt.Errorf("rate limit exceeded") ErrTimeout = fmt.Errorf("operation timeout") ErrConcurrencyConflict = fmt.Errorf("concurrency conflict") ) // NPCError NPC错误类型 type NPCError struct { Code string Message string Details map[string]interface{} Cause error Timestamp time.Time Context map[string]string } // Error 实现error接口 func (e *NPCError) Error() string { if e.Cause != nil { return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // Unwrap 解包错误 func (e *NPCError) Unwrap() error { return e.Cause } // WithDetail 添加详细信息 func (e *NPCError) WithDetail(key string, value interface{}) *NPCError { if e.Details == nil { e.Details = make(map[string]interface{}) } e.Details[key] = value return e } // WithContext 添加上下文信息 func (e *NPCError) WithContext(key, value string) *NPCError { if e.Context == nil { e.Context = make(map[string]string) } e.Context[key] = value return e } // WithCause 添加原因错误 func (e *NPCError) WithCause(cause error) *NPCError { e.Cause = cause return e } // ValidationError 验证错误 type ValidationError struct { Field string Value interface{} Rule string Message string } // Error 实现error接口 func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed for field '%s': %s (value: %v, rule: %s)", e.Field, e.Message, e.Value, e.Rule) } // ValidationErrors 多个验证错误 type ValidationErrors struct { Errors []ValidationError } // Error 实现error接口 func (e *ValidationErrors) Error() string { if len(e.Errors) == 0 { return "no validation errors" } var messages []string for _, err := range e.Errors { messages = append(messages, err.Error()) } return fmt.Sprintf("validation errors: %s", strings.Join(messages, "; ")) } // Add 添加验证错误 func (e *ValidationErrors) Add(field, rule, message string, value interface{}) { e.Errors = append(e.Errors, ValidationError{ Field: field, Value: value, Rule: rule, Message: message, }) } // HasErrors 是否有错误 func (e *ValidationErrors) HasErrors() bool { return len(e.Errors) > 0 } // BusinessRuleError 业务规则错误 type BusinessRuleError struct { Rule string Description string Violation string Context map[string]interface{} } // Error 实现error接口 func (e *BusinessRuleError) Error() string { return fmt.Sprintf("business rule violation '%s': %s (%s)", e.Rule, e.Description, e.Violation) } // WithContext 添加上下文 func (e *BusinessRuleError) WithContext(key string, value interface{}) *BusinessRuleError { if e.Context == nil { e.Context = make(map[string]interface{}) } e.Context[key] = value return e } // ConcurrencyError 并发错误 type ConcurrencyError struct { Resource string Operation string ConflictID string ExpectedVersion int ActualVersion int } // Error 实现error接口 func (e *ConcurrencyError) Error() string { return fmt.Sprintf("concurrency conflict on %s during %s: expected version %d, got %d (conflict ID: %s)", e.Resource, e.Operation, e.ExpectedVersion, e.ActualVersion, e.ConflictID) } // TimeoutError 超时错误 type TimeoutError struct { Operation string Timeout time.Duration Elapsed time.Duration } // Error 实现error接口 func (e *TimeoutError) Error() string { return fmt.Sprintf("operation '%s' timed out after %v (timeout: %v)", e.Operation, e.Elapsed, e.Timeout) } // RateLimitError 限流错误 type RateLimitError struct { Resource string Limit int Window time.Duration RetryAfter time.Duration CurrentRate int } // Error 实现error接口 func (e *RateLimitError) Error() string { return fmt.Sprintf("rate limit exceeded for %s: %d/%d requests in %v, retry after %v", e.Resource, e.CurrentRate, e.Limit, e.Window, e.RetryAfter) } // 错误工厂函数 // NewNPCError 创建NPC错误 func NewNPCError(code, message string) *NPCError { return &NPCError{ Code: code, Message: message, Timestamp: time.Now(), } } // NewValidationError 创建验证错误 func NewValidationError(field, rule, message string, value interface{}) *ValidationError { return &ValidationError{ Field: field, Value: value, Rule: rule, Message: message, } } // NewValidationErrors 创建验证错误集合 func NewValidationErrors() *ValidationErrors { return &ValidationErrors{ Errors: make([]ValidationError, 0), } } // NewBusinessRuleError 创建业务规则错误 func NewBusinessRuleError(rule, description, violation string) *BusinessRuleError { return &BusinessRuleError{ Rule: rule, Description: description, Violation: violation, } } // NewConcurrencyError 创建并发错误 func NewConcurrencyError(resource, operation, conflictID string, expectedVersion, actualVersion int) *ConcurrencyError { return &ConcurrencyError{ Resource: resource, Operation: operation, ConflictID: conflictID, ExpectedVersion: expectedVersion, ActualVersion: actualVersion, } } // NewTimeoutError 创建超时错误 func NewTimeoutError(operation string, timeout, elapsed time.Duration) *TimeoutError { return &TimeoutError{ Operation: operation, Timeout: timeout, Elapsed: elapsed, } } // NewRateLimitError 创建限流错误 func NewRateLimitError(resource string, limit int, window, retryAfter time.Duration, currentRate int) *RateLimitError { return &RateLimitError{ Resource: resource, Limit: limit, Window: window, RetryAfter: retryAfter, CurrentRate: currentRate, } } // 错误检查函数 // IsNPCError 检查是否为NPC错误 func IsNPCError(err error) bool { _, ok := err.(*NPCError) return ok } // IsValidationError 检查是否为验证错误 func IsValidationError(err error) bool { _, ok := err.(*ValidationError) if ok { return true } _, ok = err.(*ValidationErrors) return ok } // IsBusinessRuleError 检查是否为业务规则错误 func IsBusinessRuleError(err error) bool { _, ok := err.(*BusinessRuleError) return ok } // IsConcurrencyError 检查是否为并发错误 func IsConcurrencyError(err error) bool { _, ok := err.(*ConcurrencyError) return ok } // IsTimeoutError 检查是否为超时错误 func IsTimeoutError(err error) bool { _, ok := err.(*TimeoutError) return ok } // IsRateLimitError 检查是否为限流错误 func IsRateLimitError(err error) bool { _, ok := err.(*RateLimitError) return ok } // 错误分类函数 // ErrorCategory 错误类别 type ErrorCategory string const ( ErrorCategoryValidation ErrorCategory = "validation" ErrorCategoryBusinessRule ErrorCategory = "business_rule" ErrorCategoryNotFound ErrorCategory = "not_found" ErrorCategoryConflict ErrorCategory = "conflict" ErrorCategoryPermission ErrorCategory = "permission" ErrorCategorySystem ErrorCategory = "system" ErrorCategoryTimeout ErrorCategory = "timeout" ErrorCategoryRateLimit ErrorCategory = "rate_limit" ErrorCategoryUnknown ErrorCategory = "unknown" ) // CategorizeError 错误分类 func CategorizeError(err error) ErrorCategory { if err == nil { return ErrorCategoryUnknown } switch { case IsValidationError(err): return ErrorCategoryValidation case IsBusinessRuleError(err): return ErrorCategoryBusinessRule case IsConcurrencyError(err): return ErrorCategoryConflict case IsTimeoutError(err): return ErrorCategoryTimeout case IsRateLimitError(err): return ErrorCategoryRateLimit default: // 根据错误消息进行分类 msg := err.Error() switch { case strings.Contains(msg, "not found"): return ErrorCategoryNotFound case strings.Contains(msg, "permission"), strings.Contains(msg, "unauthorized"): return ErrorCategoryPermission case strings.Contains(msg, "system"), strings.Contains(msg, "maintenance"): return ErrorCategorySystem default: return ErrorCategoryUnknown } } } // 错误恢复策略 // RecoveryStrategy 恢复策略 type RecoveryStrategy string const ( RecoveryStrategyRetry RecoveryStrategy = "retry" RecoveryStrategyFallback RecoveryStrategy = "fallback" RecoveryStrategyCircuit RecoveryStrategy = "circuit_breaker" RecoveryStrategyIgnore RecoveryStrategy = "ignore" RecoveryStrategyEscalate RecoveryStrategy = "escalate" ) // GetRecoveryStrategy 获取恢复策略 func GetRecoveryStrategy(err error) RecoveryStrategy { category := CategorizeError(err) switch category { case ErrorCategoryTimeout, ErrorCategoryRateLimit: return RecoveryStrategyRetry case ErrorCategorySystem: return RecoveryStrategyCircuit case ErrorCategoryValidation, ErrorCategoryBusinessRule: return RecoveryStrategyEscalate case ErrorCategoryNotFound: return RecoveryStrategyFallback case ErrorCategoryConflict: return RecoveryStrategyRetry default: return RecoveryStrategyEscalate } } // 错误统计 // ErrorStats 错误统计 type ErrorStats struct { TotalErrors int64 ErrorsByType map[string]int64 ErrorsByCode map[string]int64 LastError error LastErrorTime time.Time ErrorRate float64 RecoveryRate float64 } // NewErrorStats 创建错误统计 func NewErrorStats() *ErrorStats { return &ErrorStats{ ErrorsByType: make(map[string]int64), ErrorsByCode: make(map[string]int64), } } // RecordError 记录错误 func (s *ErrorStats) RecordError(err error) { s.TotalErrors++ s.LastError = err s.LastErrorTime = time.Now() // 按类型统计 category := string(CategorizeError(err)) s.ErrorsByType[category]++ // 按错误码统计 if npcErr, ok := err.(*NPCError); ok { s.ErrorsByCode[npcErr.Code]++ } else { s.ErrorsByCode["unknown"]++ } } // GetMostCommonError 获取最常见错误 func (s *ErrorStats) GetMostCommonError() (string, int64) { var maxType string var maxCount int64 for errorType, count := range s.ErrorsByType { if count > maxCount { maxType = errorType maxCount = count } } return maxType, maxCount } // 错误处理器 // ErrorHandler 错误处理器接口 type ErrorHandler interface { Handle(err error) error CanHandle(err error) bool GetHandlerName() string } // DefaultErrorHandler 默认错误处理器 type DefaultErrorHandler struct { name string handlers map[ErrorCategory]func(error) error } // NewDefaultErrorHandler 创建默认错误处理器 func NewDefaultErrorHandler(name string) *DefaultErrorHandler { return &DefaultErrorHandler{ name: name, handlers: make(map[ErrorCategory]func(error) error), } } // RegisterHandler 注册处理器 func (h *DefaultErrorHandler) RegisterHandler(category ErrorCategory, handler func(error) error) { h.handlers[category] = handler } // Handle 处理错误 func (h *DefaultErrorHandler) Handle(err error) error { category := CategorizeError(err) if handler, exists := h.handlers[category]; exists { return handler(err) } return err } // CanHandle 是否可以处理 func (h *DefaultErrorHandler) CanHandle(err error) bool { category := CategorizeError(err) _, exists := h.handlers[category] return exists } // GetHandlerName 获取处理器名称 func (h *DefaultErrorHandler) GetHandlerName() string { return h.name } // 错误上下文 // ErrorContext 错误上下文 type ErrorContext struct { OperationID string UserID string NPCID string RequestID string SessionID string Timestamp time.Time StackTrace string AdditionalInfo map[string]interface{} } // NewErrorContext 创建错误上下文 func NewErrorContext() *ErrorContext { return &ErrorContext{ Timestamp: time.Now(), AdditionalInfo: make(map[string]interface{}), } } // WithOperation 设置操作ID func (c *ErrorContext) WithOperation(operationID string) *ErrorContext { c.OperationID = operationID return c } // WithUser 设置用户ID func (c *ErrorContext) WithUser(userID string) *ErrorContext { c.UserID = userID return c } // WithNPC 设置NPC ID func (c *ErrorContext) WithNPC(npcID string) *ErrorContext { c.NPCID = npcID return c } // WithRequest 设置请求ID func (c *ErrorContext) WithRequest(requestID string) *ErrorContext { c.RequestID = requestID return c } // WithSession 设置会话ID func (c *ErrorContext) WithSession(sessionID string) *ErrorContext { c.SessionID = sessionID return c } // WithInfo 添加额外信息 func (c *ErrorContext) WithInfo(key string, value interface{}) *ErrorContext { c.AdditionalInfo[key] = value return c } // 辅助函数 // WrapError 包装错误 func WrapError(err error, message string) error { if err == nil { return nil } return fmt.Errorf("%s: %w", message, err) } // WrapErrorWithContext 带上下文包装错误 func WrapErrorWithContext(err error, message string, context *ErrorContext) error { if err == nil { return nil } npcErr := NewNPCError("WRAPPED_ERROR", message).WithCause(err) if context != nil { npcErr.WithContext("operation_id", context.OperationID) npcErr.WithContext("user_id", context.UserID) npcErr.WithContext("npc_id", context.NPCID) npcErr.WithContext("request_id", context.RequestID) npcErr.WithContext("session_id", context.SessionID) } return npcErr } // IsRetryableError 是否可重试错误 func IsRetryableError(err error) bool { strategy := GetRecoveryStrategy(err) return strategy == RecoveryStrategyRetry } // IsFatalError 是否致命错误 func IsFatalError(err error) bool { strategy := GetRecoveryStrategy(err) return strategy == RecoveryStrategyEscalate } // GetErrorSeverity 获取错误严重程度 func GetErrorSeverity(err error) string { category := CategorizeError(err) switch category { case ErrorCategoryValidation: return "low" case ErrorCategoryNotFound, ErrorCategoryRateLimit: return "medium" case ErrorCategoryBusinessRule, ErrorCategoryConflict: return "high" case ErrorCategoryPermission, ErrorCategorySystem, ErrorCategoryTimeout: return "critical" default: return "unknown" } } // IsNotFoundError 检查是否为未找到错误 func IsNotFoundError(err error) bool { if err == nil { return false } category := CategorizeError(err) return category == ErrorCategoryNotFound } ================================================ FILE: internal/domain/npc/events.go ================================================ package npc import ( "fmt" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetVersion() int GetData() map[string]interface{} Validate() error } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string EventType string AggregateID string OccurredAt time.Time Version int Data map[string]interface{} } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetVersion 获取版本 func (e *BaseDomainEvent) GetVersion() int { return e.Version } // GetData 获取数据 func (e *BaseDomainEvent) GetData() map[string]interface{} { return e.Data } // Validate 验证事件 func (e *BaseDomainEvent) Validate() error { if e.EventID == "" { return fmt.Errorf("event ID cannot be empty") } if e.EventType == "" { return fmt.Errorf("event type cannot be empty") } if e.AggregateID == "" { return fmt.Errorf("aggregate ID cannot be empty") } if e.OccurredAt.IsZero() { return fmt.Errorf("occurred at cannot be zero") } return nil } // NPC相关事件 // NPCCreatedEvent NPC创建事件 type NPCCreatedEvent struct { *BaseDomainEvent NPCID string Name string Type NPCType Location *Location CreatedBy string } // NewNPCCreatedEvent 创建NPC创建事件 func NewNPCCreatedEvent(npcID, name string, npcType NPCType, location *Location, createdBy string) *NPCCreatedEvent { return &NPCCreatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("npc_created_%d", time.Now().UnixNano()), EventType: "NPCCreated", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "name": name, "type": npcType, "location": location, "created_by": createdBy, }, }, NPCID: npcID, Name: name, Type: npcType, Location: location, CreatedBy: createdBy, } } // NPCStatusChangedEvent NPC状态变更事件 type NPCStatusChangedEvent struct { *BaseDomainEvent NPCID string OldStatus NPCStatus NewStatus NPCStatus Reason string ChangedBy string } // NewNPCStatusChangedEvent 创建NPC状态变更事件 func NewNPCStatusChangedEvent(npcID string, oldStatus, newStatus NPCStatus, reason, changedBy string) *NPCStatusChangedEvent { return &NPCStatusChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("npc_status_changed_%d", time.Now().UnixNano()), EventType: "NPCStatusChanged", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "old_status": oldStatus, "new_status": newStatus, "reason": reason, "changed_by": changedBy, }, }, NPCID: npcID, OldStatus: oldStatus, NewStatus: newStatus, Reason: reason, ChangedBy: changedBy, } } // NPCLocationChangedEvent NPC位置变更事件 type NPCLocationChangedEvent struct { *BaseDomainEvent NPCID string OldLocation *Location NewLocation *Location MoveReason string } // NewNPCLocationChangedEvent 创建NPC位置变更事件 func NewNPCLocationChangedEvent(npcID string, oldLocation, newLocation *Location, moveReason string) *NPCLocationChangedEvent { return &NPCLocationChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("npc_location_changed_%d", time.Now().UnixNano()), EventType: "NPCLocationChanged", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "old_location": oldLocation, "new_location": newLocation, "move_reason": moveReason, }, }, NPCID: npcID, OldLocation: oldLocation, NewLocation: newLocation, MoveReason: moveReason, } } // 对话相关事件 // DialogueStartedEvent 对话开始事件 type DialogueStartedEvent struct { *BaseDomainEvent NPCID string PlayerID string DialogueID string SessionID string } // NewDialogueStartedEvent 创建对话开始事件 func NewDialogueStartedEvent(npcID, playerID, dialogueID, sessionID string) *DialogueStartedEvent { return &DialogueStartedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("dialogue_started_%d", time.Now().UnixNano()), EventType: "DialogueStarted", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "player_id": playerID, "dialogue_id": dialogueID, "session_id": sessionID, }, }, NPCID: npcID, PlayerID: playerID, DialogueID: dialogueID, SessionID: sessionID, } } // DialogueEndedEvent 对话结束事件 type DialogueEndedEvent struct { *BaseDomainEvent NPCID string PlayerID string DialogueID string SessionID string Duration time.Duration EndReason string } // NewDialogueEndedEvent 创建对话结束事件 func NewDialogueEndedEvent(npcID, playerID, dialogueID, sessionID string, duration time.Duration, endReason string) *DialogueEndedEvent { return &DialogueEndedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("dialogue_ended_%d", time.Now().UnixNano()), EventType: "DialogueEnded", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "player_id": playerID, "dialogue_id": dialogueID, "session_id": sessionID, "duration": duration, "end_reason": endReason, }, }, NPCID: npcID, PlayerID: playerID, DialogueID: dialogueID, SessionID: sessionID, Duration: duration, EndReason: endReason, } } // 任务相关事件 // QuestAcceptedEvent 任务接受事件 type QuestAcceptedEvent struct { *BaseDomainEvent NPCID string PlayerID string QuestID string } // NewQuestAcceptedEvent 创建任务接受事件 func NewQuestAcceptedEvent(npcID, playerID, questID string) *QuestAcceptedEvent { return &QuestAcceptedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("quest_accepted_%d", time.Now().UnixNano()), EventType: "QuestAccepted", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "player_id": playerID, "quest_id": questID, }, }, NPCID: npcID, PlayerID: playerID, QuestID: questID, } } // QuestCompletedEvent 任务完成事件 type QuestCompletedEvent struct { *BaseDomainEvent NPCID string PlayerID string QuestID string Rewards []QuestReward Completion time.Duration } // NewQuestCompletedEvent 创建任务完成事件 func NewQuestCompletedEvent(npcID, playerID, questID string, rewards []QuestReward, completion time.Duration) *QuestCompletedEvent { return &QuestCompletedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("quest_completed_%d", time.Now().UnixNano()), EventType: "QuestCompleted", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "player_id": playerID, "quest_id": questID, "rewards": rewards, "completion": completion, }, }, NPCID: npcID, PlayerID: playerID, QuestID: questID, Rewards: rewards, Completion: completion, } } // 商店相关事件 // TradeCompletedEvent 交易完成事件 type TradeCompletedEvent struct { *BaseDomainEvent NPCID string PlayerID string ShopID string ItemID string Quantity int Price int TotalPrice int } // NewTradeCompletedEvent 创建交易完成事件 func NewTradeCompletedEvent(npcID, playerID, shopID, itemID string, quantity, price, totalPrice int) *TradeCompletedEvent { return &TradeCompletedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("trade_completed_%d", time.Now().UnixNano()), EventType: "TradeCompleted", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "player_id": playerID, "shop_id": shopID, "item_id": itemID, "quantity": quantity, "price": price, "total_price": totalPrice, }, }, NPCID: npcID, PlayerID: playerID, ShopID: shopID, ItemID: itemID, Quantity: quantity, Price: price, TotalPrice: totalPrice, } } // 关系相关事件 // RelationshipChangedEvent 关系变更事件 type RelationshipChangedEvent struct { *BaseDomainEvent NPCID string PlayerID string OldValue int NewValue int OldLevel RelationshipLevel NewLevel RelationshipLevel ChangeType RelationshipChangeType Reason string } // NewRelationshipChangedEvent 创建关系变更事件 func NewRelationshipChangedEvent(npcID, playerID string, oldValue, newValue int, oldLevel, newLevel RelationshipLevel, changeType RelationshipChangeType, reason string) *RelationshipChangedEvent { return &RelationshipChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("relationship_changed_%d", time.Now().UnixNano()), EventType: "RelationshipChanged", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "npc_id": npcID, "player_id": playerID, "old_value": oldValue, "new_value": newValue, "old_level": oldLevel, "new_level": newLevel, "change_type": changeType, "reason": reason, }, }, NPCID: npcID, PlayerID: playerID, OldValue: oldValue, NewValue: newValue, OldLevel: oldLevel, NewLevel: newLevel, ChangeType: changeType, Reason: reason, } } // 事件处理器接口 // EventHandler 事件处理器接口 type EventHandler interface { Handle(event DomainEvent) error CanHandle(eventType string) bool GetHandlerName() string } // EventBus 事件总线接口 type EventBus interface { // 发布事件 Publish(event DomainEvent) error PublishBatch(events []DomainEvent) error // 订阅事件 Subscribe(eventType string, handler EventHandler) error Unsubscribe(eventType string, handlerName string) error // 获取订阅者 GetSubscribers(eventType string) []EventHandler // 启动和停止 Start() error Stop() error // 健康检查 HealthCheck() error } // EventStore 事件存储接口 type EventStore interface { // 保存事件 Save(event DomainEvent) error SaveBatch(events []DomainEvent) error // 查询事件 FindByAggregateID(aggregateID string) ([]DomainEvent, error) FindByEventType(eventType string) ([]DomainEvent, error) FindByTimeRange(start, end time.Time) ([]DomainEvent, error) // 分页查询 FindWithPagination(query *EventQuery) (*EventPageResult, error) // 统计 Count() (int64, error) CountByType(eventType string) (int64, error) CountByAggregateID(aggregateID string) (int64, error) // 清理 CleanupOldEvents(before time.Time) (int64, error) } // EventQuery 事件查询条件 type EventQuery struct { AggregateID string EventType string StartTime *time.Time EndTime *time.Time OrderBy string OrderDesc bool Offset int Limit int } // EventPageResult 事件分页结果 type EventPageResult struct { Events []DomainEvent Total int64 Offset int Limit int HasMore bool } // 事件验证器 // EventValidator 事件验证器接口 type EventValidator interface { Validate(event DomainEvent) error ValidateType(eventType string) error ValidateData(eventType string, data map[string]interface{}) error } // DefaultEventValidator 默认事件验证器 type DefaultEventValidator struct { validationRules map[string]func(DomainEvent) error } // NewDefaultEventValidator 创建默认事件验证器 func NewDefaultEventValidator() *DefaultEventValidator { return &DefaultEventValidator{ validationRules: make(map[string]func(DomainEvent) error), } } // RegisterRule 注册验证规则 func (v *DefaultEventValidator) RegisterRule(eventType string, rule func(DomainEvent) error) { v.validationRules[eventType] = rule } // Validate 验证事件 func (v *DefaultEventValidator) Validate(event DomainEvent) error { // 基础验证 if err := event.Validate(); err != nil { return err } // 类型特定验证 if rule, exists := v.validationRules[event.GetEventType()]; exists { return rule(event) } return nil } // ValidateType 验证事件类型 func (v *DefaultEventValidator) ValidateType(eventType string) error { if eventType == "" { return fmt.Errorf("event type cannot be empty") } return nil } // ValidateData 验证事件数据 func (v *DefaultEventValidator) ValidateData(eventType string, data map[string]interface{}) error { if data == nil { return fmt.Errorf("event data cannot be nil") } return nil } // 事件监控器 // EventMonitor 事件监控器接口 type EventMonitor interface { // 记录事件指标 RecordEvent(event DomainEvent) error RecordEventProcessed(eventType string, duration time.Duration) error RecordEventFailed(eventType string, err error) error // 获取指标 GetEventCount(eventType string) (int64, error) GetProcessingTime(eventType string) (time.Duration, error) GetFailureRate(eventType string) (float64, error) // 健康检查 GetHealthStatus() (*EventHealthStatus, error) } // EventHealthStatus 事件健康状态 type EventHealthStatus struct { TotalEvents int64 ProcessedEvents int64 FailedEvents int64 AverageLatency time.Duration ErrorRate float64 LastEventTime time.Time Status string } // 事件重放器 // EventReplayer 事件重放器接口 type EventReplayer interface { // 重放事件 Replay(aggregateID string, fromVersion int) error ReplayAll(fromTime time.Time) error ReplayByType(eventType string, fromTime time.Time) error // 重建聚合 RebuildAggregate(aggregateID string) error RebuildAllAggregates() error // 快照管理 CreateSnapshot(aggregateID string) error LoadFromSnapshot(aggregateID string) error } // 事件投影器 // EventProjector 事件投影器接口 type EventProjector interface { // 处理事件 Project(event DomainEvent) error ProjectBatch(events []DomainEvent) error // 重建投影 Rebuild() error RebuildFrom(fromTime time.Time) error // 获取投影名称 GetProjectionName() string // 健康检查 HealthCheck() error } // 事件序列化器 // EventSerializer 事件序列化器接口 type EventSerializer interface { // 序列化 Serialize(event DomainEvent) ([]byte, error) SerializeBatch(events []DomainEvent) ([]byte, error) // 反序列化 Deserialize(data []byte) (DomainEvent, error) DeserializeBatch(data []byte) ([]DomainEvent, error) // 获取内容类型 GetContentType() string } // 事件过滤器 // EventFilter 事件过滤器接口 type EventFilter interface { // 过滤事件 Filter(event DomainEvent) bool // 获取过滤器名称 GetFilterName() string } // EventTypeFilter 事件类型过滤器 type EventTypeFilter struct { allowedTypes map[string]bool } // NewEventTypeFilter 创建事件类型过滤器 func NewEventTypeFilter(allowedTypes []string) *EventTypeFilter { typeMap := make(map[string]bool) for _, eventType := range allowedTypes { typeMap[eventType] = true } return &EventTypeFilter{ allowedTypes: typeMap, } } // Filter 过滤事件 func (f *EventTypeFilter) Filter(event DomainEvent) bool { return f.allowedTypes[event.GetEventType()] } // GetFilterName 获取过滤器名称 func (f *EventTypeFilter) GetFilterName() string { return "EventTypeFilter" } // 事件聚合器 // EventAggregator 事件聚合器接口 type EventAggregator interface { // 聚合事件 Aggregate(events []DomainEvent) (map[string]interface{}, error) // 获取聚合器名称 GetAggregatorName() string } // 事件调度器 // EventScheduler 事件调度器接口 type EventScheduler interface { // 调度事件 Schedule(event DomainEvent, delay time.Duration) error ScheduleAt(event DomainEvent, at time.Time) error // 取消调度 Cancel(eventID string) error // 获取调度状态 GetScheduledEvents() ([]ScheduledEvent, error) // 启动和停止 Start() error Stop() error } // ScheduledEvent 调度事件 type ScheduledEvent struct { ID string Event DomainEvent ScheduledAt time.Time ExecuteAt time.Time Status string RetryCount int MaxRetries int LastError string } // 事件工厂 // EventFactory 事件工厂接口 type EventFactory interface { // 创建事件 CreateEvent(eventType string, aggregateID string, data map[string]interface{}) (DomainEvent, error) // 注册事件类型 RegisterEventType(eventType string, factory func(string, map[string]interface{}) (DomainEvent, error)) error // 获取支持的事件类型 GetSupportedEventTypes() []string } // NPCNameChangedEvent NPC名称变更事件 type NPCNameChangedEvent struct { *BaseDomainEvent OldName string NewName string } // NewNPCNameChangedEvent 创建NPC名称变更事件 func NewNPCNameChangedEvent(npcID, oldName, newName string) *NPCNameChangedEvent { return &NPCNameChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("npc_name_changed_%d", time.Now().UnixNano()), EventType: "NPCNameChanged", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: make(map[string]interface{}), }, OldName: oldName, NewName: newName, } } // NPCMovedEvent NPC移动事件 type NPCMovedEvent struct { *BaseDomainEvent OldLocation *Location NewLocation *Location } // NewNPCMovedEvent 创建NPC移动事件 func NewNPCMovedEvent(npcID string, oldLocation, newLocation *Location) *NPCMovedEvent { return &NPCMovedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("npc_moved_%d", time.Now().UnixNano()), EventType: "NPCMoved", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: make(map[string]interface{}), }, OldLocation: oldLocation, NewLocation: newLocation, } } // NewDialogueAddedEvent 创建对话添加事件 func NewDialogueAddedEvent(npcID, dialogueID string) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("dialogue_added_%d", time.Now().UnixNano()), EventType: "DialogueAdded", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "dialogue_id": dialogueID, }, } } // NewDialogueRemovedEvent 创建对话移除事件 func NewDialogueRemovedEvent(npcID, dialogueID string) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("dialogue_removed_%d", time.Now().UnixNano()), EventType: "DialogueRemoved", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "dialogue_id": dialogueID, }, } } // 注意:NewDialogueStartedEvent已经在文件前面定义,这里删除重复定义 // NewQuestAddedEvent 创建任务添加事件 func NewQuestAddedEvent(npcID, questID string) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("quest_added_%d", time.Now().UnixNano()), EventType: "QuestAdded", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "quest_id": questID, }, } } // NewQuestRemovedEvent 创建任务移除事件 func NewQuestRemovedEvent(npcID, questID string) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("quest_removed_%d", time.Now().UnixNano()), EventType: "QuestRemoved", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "quest_id": questID, }, } } // NewQuestGivenEvent 创建任务给予事件 func NewQuestGivenEvent(npcID, questID, playerID string) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("quest_given_%d", time.Now().UnixNano()), EventType: "QuestGiven", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "quest_id": questID, "player_id": playerID, }, } } // NewShopSetEvent 创建商店设置事件 func NewShopSetEvent(npcID, shopID string) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("shop_set_%d", time.Now().UnixNano()), EventType: "ShopSet", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "shop_id": shopID, }, } } // NewTradeExecutedEvent 创建交易执行事件 func NewTradeExecutedEvent(npcID, playerID string, tradeRequest interface{}, result interface{}) *BaseDomainEvent { return &BaseDomainEvent{ EventID: fmt.Sprintf("trade_executed_%d", time.Now().UnixNano()), EventType: "TradeExecuted", AggregateID: npcID, OccurredAt: time.Now(), Version: 1, Data: map[string]interface{}{ "player_id": playerID, "request": tradeRequest, "result": result, }, } } ================================================ FILE: internal/domain/npc/repository.go ================================================ package npc import ( "fmt" "time" ) // NPCRepository NPC仓储接口 type NPCRepository interface { // 基础CRUD操作 Save(npc *NPCAggregate) error FindByID(id string) (*NPCAggregate, error) FindByType(npcType NPCType) ([]*NPCAggregate, error) FindByStatus(status NPCStatus) ([]*NPCAggregate, error) Update(npc *NPCAggregate) error Delete(id string) error // 位置相关查询 FindByLocation(location *Location, radius float64) ([]*NPCAggregate, error) FindByRegion(region string) ([]*NPCAggregate, error) FindByZone(zone string) ([]*NPCAggregate, error) // 分页查询 FindWithPagination(query *NPCQuery) (*NPCPageResult, error) // 统计操作 Count() (int64, error) CountByType(npcType NPCType) (int64, error) CountByStatus(status NPCStatus) (int64, error) CountByRegion(region string) (int64, error) // 批量操作 SaveBatch(npcs []*NPCAggregate) error DeleteBatch(ids []string) error // 高级查询 FindActiveNPCs() ([]*NPCAggregate, error) FindNPCsWithShops() ([]*NPCAggregate, error) FindNPCsWithQuests() ([]*NPCAggregate, error) FindNearbyNPCs(location *Location, radius float64, npcType NPCType) ([]*NPCAggregate, error) } // DialogueRepository 对话仓储接口 type DialogueRepository interface { // 基础CRUD操作 Save(dialogue *Dialogue) error FindByID(id string) (*Dialogue, error) FindByNPC(npcID string) ([]*Dialogue, error) FindByType(dialogueType DialogueType) ([]*Dialogue, error) Update(dialogue *Dialogue) error Delete(id string) error // 分页查询 FindWithPagination(query *DialogueQuery) (*DialoguePageResult, error) // 统计操作 Count() (int64, error) CountByType(dialogueType DialogueType) (int64, error) CountByNPC(npcID string) (int64, error) // 会话相关 SaveSession(session *DialogueSession) error FindSession(npcID, playerID string) (*DialogueSession, error) FindActiveSessions(playerID string) ([]*DialogueSession, error) DeleteSession(npcID, playerID string) error CleanupExpiredSessions() error } // QuestRepository 任务仓储接口 type QuestRepository interface { // 基础CRUD操作 Save(quest *Quest) error FindByID(id string) (*Quest, error) FindByNPC(npcID string) ([]*Quest, error) FindByType(questType QuestType) ([]*Quest, error) Update(quest *Quest) error Delete(id string) error // 分页查询 FindWithPagination(query *QuestQuery) (*QuestPageResult, error) // 统计操作 Count() (int64, error) CountByType(questType QuestType) (int64, error) CountByNPC(npcID string) (int64, error) // 任务实例相关 SaveInstance(instance *QuestInstance) error FindInstance(questID, playerID string) (*QuestInstance, error) FindInstancesByPlayer(playerID string) ([]*QuestInstance, error) FindInstancesByQuest(questID string) ([]*QuestInstance, error) FindInstancesByStatus(status QuestStatus) ([]*QuestInstance, error) UpdateInstance(instance *QuestInstance) error DeleteInstance(questID, playerID string) error // 任务进度 UpdateProgress(questID, playerID, objectiveID string, progress int) error GetProgress(questID, playerID string) (map[string]int, error) // 任务完成统计 GetCompletionStats(questID string) (*QuestCompletionStats, error) GetPlayerQuestStats(playerID string) (*PlayerQuestStats, error) } // ShopRepository 商店仓储接口 type ShopRepository interface { // 基础CRUD操作 Save(shop *Shop) error FindByID(id string) (*Shop, error) FindByNPC(npcID string) (*Shop, error) Update(shop *Shop) error Delete(id string) error // 商品相关 SaveItem(shopID string, item *ShopItem) error FindItem(shopID, itemID string) (*ShopItem, error) FindItemsByShop(shopID string) ([]*ShopItem, error) UpdateItem(shopID string, item *ShopItem) error DeleteItem(shopID, itemID string) error // 交易记录 SaveTradeRecord(record *TradeRecord) error FindTradeRecords(shopID string, limit int) ([]*TradeRecord, error) FindPlayerTradeRecords(playerID string, limit int) ([]*TradeRecord, error) // 统计操作 Count() (int64, error) GetShopStats(shopID string) (*ShopStatistics, error) GetTradeStats(shopID string, startTime, endTime time.Time) (*TradeStatistics, error) } // RelationshipRepository 关系仓储接口 type RelationshipRepository interface { // 基础CRUD操作 Save(relationship *Relationship) error FindByID(playerID, npcID string) (*Relationship, error) FindByPlayer(playerID string) ([]*Relationship, error) FindByNPC(npcID string) ([]*Relationship, error) Update(relationship *Relationship) error Delete(playerID, npcID string) error // 关系等级查询 FindByLevel(level RelationshipLevel) ([]*Relationship, error) FindByValueRange(minValue, maxValue int) ([]*Relationship, error) // 分页查询 FindWithPagination(query *RelationshipQuery) (*RelationshipPageResult, error) // 统计操作 Count() (int64, error) CountByLevel(level RelationshipLevel) (int64, error) GetAverageRelationship(npcID string) (float64, error) // 关系历史 SaveRelationshipEvent(event *RelationshipEvent) error FindRelationshipHistory(playerID, npcID string, limit int) ([]*RelationshipEvent, error) // 排行榜 GetTopRelationships(npcID string, limit int) ([]*Relationship, error) GetPlayerRanking(playerID, npcID string) (int, error) } // NPCStatisticsRepository NPC统计仓储接口 type NPCStatisticsRepository interface { // 保存统计数据 SaveStatistics(stats *NPCStatistics) error UpdateStatistics(stats *NPCStatistics) error // 查询统计数据 FindStatistics(npcID string) (*NPCStatistics, error) FindStatisticsByType(npcType NPCType) ([]*NPCStatistics, error) // 全局统计 GetGlobalStatistics() (*GlobalNPCStatistics, error) GetTypeStatistics(npcType NPCType) (*TypeNPCStatistics, error) // 趋势分析 GetInteractionTrend(npcID string, days int) ([]*InteractionTrendData, error) GetPopularityTrend(npcType NPCType, days int) ([]*PopularityTrendData, error) // 活跃度统计 GetActiveNPCCount(timeRange time.Duration) (int64, error) GetMostActiveNPCs(limit int) ([]*NPCStatistics, error) } // 查询条件结构体 // NPCQuery NPC查询条件 type NPCQuery struct { Name string Type *NPCType Status *NPCStatus Region string Zone string Location *Location Radius *float64 HasShop *bool HasQuests *bool CreatedAfter *time.Time CreatedBefore *time.Time UpdatedAfter *time.Time UpdatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // DialogueQuery 对话查询条件 type DialogueQuery struct { NPCID string Type *DialogueType PlayerID string Available *bool CreatedAfter *time.Time CreatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // QuestQuery 任务查询条件 type QuestQuery struct { NPCID string Type *QuestType PlayerID string Status *QuestStatus Repeatable *bool DailyReset *bool CreatedAfter *time.Time CreatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // RelationshipQuery 关系查询条件 type RelationshipQuery struct { PlayerID string NPCID string Level *RelationshipLevel MinValue *int MaxValue *int CreatedAfter *time.Time CreatedBefore *time.Time UpdatedAfter *time.Time UpdatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // 分页结果结构体 // NPCPageResult NPC分页结果 type NPCPageResult struct { Items []*NPCAggregate Total int64 Offset int Limit int HasMore bool } // DialoguePageResult 对话分页结果 type DialoguePageResult struct { Items []*Dialogue Total int64 Offset int Limit int HasMore bool } // QuestPageResult 任务分页结果 type QuestPageResult struct { Items []*Quest Total int64 Offset int Limit int HasMore bool } // RelationshipPageResult 关系分页结果 type RelationshipPageResult struct { Items []*Relationship Total int64 Offset int Limit int HasMore bool } // 统计数据结构体 // QuestCompletionStats 任务完成统计 type QuestCompletionStats struct { QuestID string TotalAttempts int64 TotalCompleted int64 CompletionRate float64 AverageTime time.Duration LastCompleted time.Time } // PlayerQuestStats 玩家任务统计 type PlayerQuestStats struct { PlayerID string TotalQuests int64 CompletedQuests int64 FailedQuests int64 ActiveQuests int64 CompletionRate float64 AverageTime time.Duration FavoriteType QuestType LastQuestTime time.Time } // ShopStatistics 商店统计 type ShopStatistics struct { ShopID string TotalTrades int64 TotalRevenue int64 TotalItems int64 PopularItem string AveragePrice float64 LastTradeTime time.Time CreatedAt time.Time } // TradeStatistics 交易统计 type TradeStatistics struct { ShopID string PeriodStart time.Time PeriodEnd time.Time TotalTrades int64 TotalRevenue int64 TotalItems int64 TopItems []string TopCustomers []string } // GlobalNPCStatistics 全局NPC统计 type GlobalNPCStatistics struct { TotalNPCs int64 ActiveNPCs int64 NPCsByType map[NPCType]int64 NPCsByStatus map[NPCStatus]int64 TotalDialogues int64 TotalQuests int64 TotalShops int64 TotalRelationships int64 AverageRelationship float64 MostPopularNPC string MostActiveRegion string UpdatedAt time.Time } // TypeNPCStatistics 类型NPC统计 type TypeNPCStatistics struct { NPCType NPCType TotalCount int64 ActiveCount int64 AverageLevel float64 TotalDialogues int64 TotalQuests int64 TotalShops int64 AverageRelationship float64 MostPopularNPC string UpdatedAt time.Time } // InteractionTrendData 交互趋势数据 type InteractionTrendData struct { Date time.Time DialogueCount int64 QuestCount int64 TradeCount int64 UniqueVisitors int64 } // PopularityTrendData 受欢迎程度趋势数据 type PopularityTrendData struct { Date time.Time NPCType NPCType InteractionCount int64 UniquePlayers int64 AverageRating float64 } // TradeRecord 交易记录 type TradeRecord struct { ID string ShopID string PlayerID string ItemID string Quantity int Price int TotalPrice int Timestamp time.Time } // NewTradeRecord 创建交易记录 func NewTradeRecord(shopID, playerID, itemID string, quantity, price int) *TradeRecord { return &TradeRecord{ ID: fmt.Sprintf("trade_%d", time.Now().UnixNano()), ShopID: shopID, PlayerID: playerID, ItemID: itemID, Quantity: quantity, Price: price, TotalPrice: quantity * price, Timestamp: time.Now(), } } // 缓存接口 // NPCCacheRepository NPC缓存仓储接口 type NPCCacheRepository interface { // NPC缓存 SetNPC(id string, npc *NPCAggregate, ttl time.Duration) error GetNPC(id string) (*NPCAggregate, error) DeleteNPC(id string) error // 对话缓存 SetDialogue(id string, dialogue *Dialogue, ttl time.Duration) error GetDialogue(id string) (*Dialogue, error) DeleteDialogue(id string) error // 任务缓存 SetQuest(id string, quest *Quest, ttl time.Duration) error GetQuest(id string) (*Quest, error) DeleteQuest(id string) error // 关系缓存 SetRelationship(playerID, npcID string, relationship *Relationship, ttl time.Duration) error GetRelationship(playerID, npcID string) (*Relationship, error) DeleteRelationship(playerID, npcID string) error // 会话缓存 SetSession(npcID, playerID string, session *DialogueSession, ttl time.Duration) error GetSession(npcID, playerID string) (*DialogueSession, error) DeleteSession(npcID, playerID string) error // 统计缓存 SetStatistics(key string, stats interface{}, ttl time.Duration) error GetStatistics(key string, result interface{}) error DeleteStatistics(key string) error // 位置索引缓存 SetLocationIndex(region string, npcs []*NPCAggregate, ttl time.Duration) error GetLocationIndex(region string) ([]*NPCAggregate, error) DeleteLocationIndex(region string) error // 批量操作 SetBatch(items map[string]interface{}, ttl time.Duration) error GetBatch(keys []string) (map[string]interface{}, error) DeleteBatch(keys []string) error // 缓存管理 Clear() error Exists(key string) (bool, error) SetTTL(key string, ttl time.Duration) error GetTTL(key string) (time.Duration, error) } // 事务接口 // NPCTransactionRepository NPC事务仓储接口 type NPCTransactionRepository interface { // 事务管理 BeginTransaction() (NPCTransaction, error) CommitTransaction(tx NPCTransaction) error RollbackTransaction(tx NPCTransaction) error // 在事务中执行操作 ExecuteInTransaction(fn func(tx NPCTransaction) error) error } // NPCTransaction NPC事务接口 type NPCTransaction interface { // NPC操作 SaveNPC(npc *NPCAggregate) error UpdateNPC(npc *NPCAggregate) error DeleteNPC(id string) error // 对话操作 SaveDialogue(dialogue *Dialogue) error UpdateDialogue(dialogue *Dialogue) error DeleteDialogue(id string) error // 任务操作 SaveQuest(quest *Quest) error UpdateQuest(quest *Quest) error DeleteQuest(id string) error // 任务实例操作 SaveQuestInstance(instance *QuestInstance) error UpdateQuestInstance(instance *QuestInstance) error DeleteQuestInstance(questID, playerID string) error // 关系操作 SaveRelationship(relationship *Relationship) error UpdateRelationship(relationship *Relationship) error DeleteRelationship(playerID, npcID string) error // 商店操作 SaveShop(shop *Shop) error UpdateShop(shop *Shop) error DeleteShop(id string) error // 交易记录 SaveTradeRecord(record *TradeRecord) error // 统计操作 UpdateStatistics(stats *NPCStatistics) error // 事务状态 IsActive() bool GetID() string } // 仓储工厂接口 // NPCRepositoryFactory NPC仓储工厂接口 type NPCRepositoryFactory interface { // 创建仓储实例 CreateNPCRepository() NPCRepository CreateDialogueRepository() DialogueRepository CreateQuestRepository() QuestRepository CreateShopRepository() ShopRepository CreateRelationshipRepository() RelationshipRepository CreateStatisticsRepository() NPCStatisticsRepository CreateCacheRepository() NPCCacheRepository CreateTransactionRepository() NPCTransactionRepository // 健康检查 HealthCheck() error // 关闭连接 Close() error } // 搜索接口 // NPCSearchRepository NPC搜索仓储接口 type NPCSearchRepository interface { // 全文搜索 SearchNPCs(query string, filters map[string]interface{}) ([]*NPCAggregate, error) SearchDialogues(query string, filters map[string]interface{}) ([]*Dialogue, error) SearchQuests(query string, filters map[string]interface{}) ([]*Quest, error) // 地理搜索 SearchNearbyNPCs(location *Location, radius float64, filters map[string]interface{}) ([]*NPCAggregate, error) // 智能推荐 RecommendNPCs(playerID string, limit int) ([]*NPCAggregate, error) RecommendQuests(playerID string, limit int) ([]*Quest, error) RecommendDialogues(playerID string, npcID string, limit int) ([]*Dialogue, error) // 索引管理 RebuildIndex() error UpdateIndex(entityType string, entityID string, data interface{}) error DeleteFromIndex(entityType string, entityID string) error } ================================================ FILE: internal/domain/npc/service.go ================================================ package npc import ( "fmt" "math/rand" "time" ) // NPCService NPC领域服务 type NPCService struct { npcTemplates map[NPCType]*NPCTemplate dialogueTemplates map[DialogueType]*DialogueTemplate questTemplates map[QuestType]*QuestTemplate behaviorRules map[BehaviorType]*BehaviorRule relationshipRules *RelationshipRules aiEngine *AIEngine } // NewNPCService 创建NPC服务 func NewNPCService() *NPCService { service := &NPCService{ npcTemplates: make(map[NPCType]*NPCTemplate), dialogueTemplates: make(map[DialogueType]*DialogueTemplate), questTemplates: make(map[QuestType]*QuestTemplate), behaviorRules: make(map[BehaviorType]*BehaviorRule), relationshipRules: NewRelationshipRules(), aiEngine: NewAIEngine(), } // 初始化默认模板和规则 service.initializeDefaultTemplates() service.initializeBehaviorRules() return service } // CreateNPC 创建NPC func (s *NPCService) CreateNPC(id, name, description string, npcType NPCType, location *Location) (*NPCAggregate, error) { if id == "" || name == "" { return nil, fmt.Errorf("invalid parameters for NPC creation") } npc := NewNPCAggregate(id, name, description, npcType) // 设置位置 if location != nil { npc.MoveTo(location) } // 应用模板 if template, exists := s.npcTemplates[npcType]; exists { s.applyTemplate(npc, template) } // 生成默认对话 defaultDialogues := s.generateDefaultDialogues(npcType) for _, dialogue := range defaultDialogues { npc.AddDialogue(dialogue) } // 生成默认任务(如果适用) if npcType.CanGiveQuests() { defaultQuests := s.generateDefaultQuests(npcType) for _, quest := range defaultQuests { npc.AddQuest(quest) } } // 创建商店(如果适用) if npcType.CanHaveShop() { shop := s.createDefaultShop(npcType, id) npc.SetShop(shop) } return npc, nil } // GenerateDialogue 生成对话 func (s *NPCService) GenerateDialogue(dialogueType DialogueType, npcType NPCType, context map[string]interface{}) (*Dialogue, error) { template, exists := s.dialogueTemplates[dialogueType] if !exists { return nil, fmt.Errorf("dialogue template not found for type: %s", dialogueType.String()) } // 生成唯一ID id := fmt.Sprintf("dialogue_%s_%s_%d", dialogueType.String(), npcType.String(), time.Now().UnixNano()) // 根据模板创建对话 dialogue := NewDialogue( id, template.GenerateName(npcType, context), template.GenerateDescription(npcType, context), dialogueType, ) // 生成对话节点 nodes := template.GenerateNodes(npcType, context) for _, node := range nodes { dialogue.AddNode(node) } // 设置开始节点 if len(nodes) > 0 { dialogue.SetStartNode(nodes[0].GetID()) } // 添加条件 conditions := template.GenerateConditions(npcType, context) for _, condition := range conditions { dialogue.AddCondition(condition) } // 设置奖励 if reward := template.GenerateReward(npcType, context); reward != nil { dialogue.SetReward(reward) } return dialogue, nil } // GenerateQuest 生成任务 func (s *NPCService) GenerateQuest(questType QuestType, npcType NPCType, playerLevel int) (*Quest, error) { template, exists := s.questTemplates[questType] if !exists { return nil, fmt.Errorf("quest template not found for type: %s", questType.String()) } // 生成唯一ID id := fmt.Sprintf("quest_%s_%s_%d", questType.String(), npcType.String(), time.Now().UnixNano()) // 根据模板创建任务 quest := NewQuest( id, template.GenerateName(npcType, playerLevel), template.GenerateDescription(npcType, playerLevel), questType, ) // 生成目标 objectives := template.GenerateObjectives(npcType, playerLevel) for _, objective := range objectives { quest.AddObjective(objective) } // 设置奖励 reward := template.GenerateReward(npcType, playerLevel) quest.SetReward(reward) // 添加前置条件 prerequisites := template.GeneratePrerequisites(npcType, playerLevel) for _, prerequisite := range prerequisites { quest.AddPrerequisite(prerequisite) } // 设置时间限制 if timeLimit := template.GetTimeLimit(playerLevel); timeLimit > 0 { quest.SetTimeLimit(timeLimit) } // 设置重复性 quest.SetRepeatable(template.IsRepeatable()) quest.SetDailyReset(template.IsDailyReset()) return quest, nil } // ProcessDialogue 处理对话 func (s *NPCService) ProcessDialogue(npc *NPCAggregate, playerID string, dialogueID string, optionID string) (*DialogueResponse, error) { dialogue, err := npc.GetDialogue(dialogueID) if err != nil { return nil, err } // 检查是否可以开始对话 if !dialogue.CanStart(playerID) { return nil, fmt.Errorf("cannot start dialogue") } // 开始对话会话 session, err := npc.StartDialogue(dialogueID, playerID) if err != nil { return nil, err } // 获取当前节点 currentNode := dialogue.GetStartNode() if session.GetCurrentNode() != "" { currentNode = dialogue.GetNode(session.GetCurrentNode()) } if currentNode == nil { return nil, fmt.Errorf("dialogue node not found") } // 处理选项 if optionID != "" { for _, option := range currentNode.GetOptions() { if option.GetID() == optionID && option.IsAvailable(playerID) { // 执行选项动作 if err := option.Execute(playerID); err != nil { return nil, err } // 移动到目标节点 if option.GetTargetNode() != "" { currentNode = dialogue.GetNode(option.GetTargetNode()) session.SetCurrentNode(option.GetTargetNode()) } break } } } // 执行节点动作 if err := currentNode.ExecuteActions(playerID); err != nil { return nil, err } // 使用对话 dialogue.Use(playerID) // 创建响应 response := &DialogueResponse{ NPCID: npc.GetID(), DialogueID: dialogueID, NodeID: currentNode.GetID(), Text: currentNode.GetText(), Speaker: currentNode.GetSpeaker(), Options: s.filterAvailableOptions(currentNode.GetOptions(), playerID), CanContinue: currentNode.GetNextNode() != "", Completed: currentNode.GetNextNode() == "", } return response, nil } // UpdateNPCBehavior 更新NPC行为 func (s *NPCService) UpdateNPCBehavior(npc *NPCAggregate, deltaTime time.Duration) error { behavior := npc.GetBehavior() behaviorRule, exists := s.behaviorRules[behavior.Type] if !exists { return fmt.Errorf("behavior rule not found for type: %s", behavior.Type.String()) } // 应用行为规则 behaviorRule.Apply(npc, deltaTime) // 更新NPC npc.Update(deltaTime) return nil } // CalculateRelationshipChange 计算关系变化 func (s *NPCService) CalculateRelationshipChange(npc *NPCAggregate, playerID string, action string, context map[string]interface{}) int { return s.relationshipRules.CalculateChange(npc.GetType(), action, context) } // GenerateAIResponse 生成AI响应 func (s *NPCService) GenerateAIResponse(npc *NPCAggregate, playerID string, input string) (string, error) { return s.aiEngine.GenerateResponse(npc, playerID, input) } // ValidateQuestCompletion 验证任务完成 func (s *NPCService) ValidateQuestCompletion(quest *Quest, questInstance *QuestInstance, playerData map[string]interface{}) bool { objectives := quest.GetObjectives() for _, objective := range objectives { if !objective.IsOptional() { required := objective.GetRequired() current := questInstance.GetProgress(objective.GetID()) if current < required { return false } } } return true } // GetRecommendedQuests 获取推荐任务 func (s *NPCService) GetRecommendedQuests(npc *NPCAggregate, playerID string, playerLevel int) []*Quest { availableQuests := npc.GetAvailableQuests(playerID) var recommended []*Quest for _, quest := range availableQuests { // 根据玩家等级和任务类型推荐 if s.isQuestRecommended(quest, playerLevel) { recommended = append(recommended, quest) } } return recommended } // GetOptimalDialogue 获取最佳对话 func (s *NPCService) GetOptimalDialogue(npc *NPCAggregate, playerID string, context map[string]interface{}) *Dialogue { availableDialogues := npc.GetAvailableDialogues(playerID) // 根据上下文选择最合适的对话 for _, dialogue := range availableDialogues { if s.isDialogueOptimal(dialogue, context) { return dialogue } } // 返回默认对话 if len(availableDialogues) > 0 { return availableDialogues[0] } return nil } // 私有方法 // applyTemplate 应用模板 func (s *NPCService) applyTemplate(npc *NPCAggregate, template *NPCTemplate) { // 设置属性 attributes := npc.GetAttributes() attributes.SetLevel(template.Level) attributes.Strength = template.Attributes.Strength attributes.Agility = template.Attributes.Agility attributes.Intelligence = template.Attributes.Intelligence attributes.Charisma = template.Attributes.Charisma attributes.Luck = template.Attributes.Luck attributes.MoveSpeed = template.Attributes.MoveSpeed attributes.ViewRange = template.Attributes.ViewRange attributes.HearRange = template.Attributes.HearRange // 设置行为 behavior := npc.GetBehavior() behavior.SetBehaviorType(template.DefaultBehavior) behavior.MoveSpeed = template.Attributes.MoveSpeed behavior.CanMove = template.CanMove behavior.CanTalk = template.CanTalk behavior.CanFight = template.CanFight // 设置巡逻点 for _, point := range template.PatrolPoints { behavior.AddPatrolPoint(point) } } // generateDefaultDialogues 生成默认对话 func (s *NPCService) generateDefaultDialogues(npcType NPCType) []*Dialogue { var dialogues []*Dialogue // 生成问候对话 if greeting, err := s.GenerateDialogue(DialogueTypeGreeting, npcType, nil); err == nil { dialogues = append(dialogues, greeting) } // 根据NPC类型生成特定对话 switch npcType { case NPCTypeMerchant: if trade, err := s.GenerateDialogue(DialogueTypeTrade, npcType, nil); err == nil { dialogues = append(dialogues, trade) } case NPCTypeQuestGiver: if quest, err := s.GenerateDialogue(DialogueTypeQuest, npcType, nil); err == nil { dialogues = append(dialogues, quest) } case NPCTypeVillager: if info, err := s.GenerateDialogue(DialogueTypeInformation, npcType, nil); err == nil { dialogues = append(dialogues, info) } if rumor, err := s.GenerateDialogue(DialogueTypeRumor, npcType, nil); err == nil { dialogues = append(dialogues, rumor) } } return dialogues } // generateDefaultQuests 生成默认任务 func (s *NPCService) generateDefaultQuests(npcType NPCType) []*Quest { var quests []*Quest // 根据NPC类型生成不同的任务 switch npcType { case NPCTypeQuestGiver: // 生成各种类型的任务 questTypes := []QuestType{QuestTypeKill, QuestTypeCollect, QuestTypeDeliver} for _, questType := range questTypes { if quest, err := s.GenerateQuest(questType, npcType, 1); err == nil { quests = append(quests, quest) } } case NPCTypeVillager: // 生成简单任务 if quest, err := s.GenerateQuest(QuestTypeTalk, npcType, 1); err == nil { quests = append(quests, quest) } case NPCTypeGuard: // 生成巡逻或保护任务 if quest, err := s.GenerateQuest(QuestTypeEscort, npcType, 1); err == nil { quests = append(quests, quest) } } return quests } // createDefaultShop 创建默认商店 func (s *NPCService) createDefaultShop(npcType NPCType, npcID string) *Shop { shopID := fmt.Sprintf("shop_%s_%s", npcType.String(), npcID) shop := NewShop(shopID, fmt.Sprintf("%s商店", npcType.String()), "默认商店") // 根据NPC类型添加商品 switch npcType { case NPCTypeMerchant: // 添加一般商品 shop.AddItem(NewShopItem("item_potion_health", "生命药水", "恢复生命值", 50, 10)) shop.AddItem(NewShopItem("item_potion_mana", "法力药水", "恢复法力值", 30, 10)) case NPCTypeBlacksmith: // 添加武器装备 shop.AddItem(NewShopItem("weapon_sword", "铁剑", "基础武器", 100, 5)) shop.AddItem(NewShopItem("armor_leather", "皮甲", "基础护甲", 80, 5)) case NPCTypeInnkeeper: // 添加食物和住宿 shop.AddItem(NewShopItem("food_bread", "面包", "基础食物", 10, 20)) shop.AddItem(NewShopItem("service_room", "房间", "住宿服务", 25, 10)) } return shop } // filterAvailableOptions 过滤可用选项 func (s *NPCService) filterAvailableOptions(options []*DialogueOption, playerID string) []*DialogueOption { var available []*DialogueOption for _, option := range options { if option.IsAvailable(playerID) { available = append(available, option) } } return available } // isQuestRecommended 检查任务是否推荐 func (s *NPCService) isQuestRecommended(quest *Quest, playerLevel int) bool { // 简单的推荐逻辑:任务类型和玩家等级匹配 switch quest.GetType() { case QuestTypeDaily: return true // 日常任务总是推荐 case QuestTypeKill, QuestTypeCollect: return playerLevel >= 5 // 需要一定等级 case QuestTypeDeliver, QuestTypeTalk: return playerLevel >= 1 // 低等级任务 default: return playerLevel >= 10 // 高等级任务 } } // isDialogueOptimal 检查对话是否最佳 func (s *NPCService) isDialogueOptimal(dialogue *Dialogue, context map[string]interface{}) bool { // 根据上下文判断对话是否合适 if mood, exists := context["mood"]; exists { if mood == "friendly" && dialogue.GetType() == DialogueTypeGreeting { return true } if mood == "business" && dialogue.GetType() == DialogueTypeTrade { return true } } // 默认返回false,使用第一个可用对话 return false } // initializeDefaultTemplates 初始化默认模板 func (s *NPCService) initializeDefaultTemplates() { // 初始化NPC模板 s.npcTemplates[NPCTypeVillager] = &NPCTemplate{ Level: 1, Attributes: NewNPCAttributes(), DefaultBehavior: BehaviorTypeWander, CanMove: true, CanTalk: true, CanFight: false, PatrolPoints: make([]*Location, 0), } s.npcTemplates[NPCTypeMerchant] = &NPCTemplate{ Level: 5, Attributes: NewNPCAttributes(), DefaultBehavior: BehaviorTypeStationary, CanMove: false, CanTalk: true, CanFight: false, PatrolPoints: make([]*Location, 0), } s.npcTemplates[NPCTypeGuard] = &NPCTemplate{ Level: 10, Attributes: NewNPCAttributes(), DefaultBehavior: BehaviorTypePatrol, CanMove: true, CanTalk: true, CanFight: true, PatrolPoints: make([]*Location, 0), } // 初始化对话模板 s.dialogueTemplates[DialogueTypeGreeting] = &DialogueTemplate{ Name: "问候", Description: "基础问候对话", Nodes: make([]*DialogueNodeTemplate, 0), } s.dialogueTemplates[DialogueTypeTrade] = &DialogueTemplate{ Name: "交易", Description: "商店交易对话", Nodes: make([]*DialogueNodeTemplate, 0), } // 初始化任务模板 s.questTemplates[QuestTypeKill] = &QuestTemplate{ Name: "击杀任务", Description: "击杀指定目标", Objectives: make([]*QuestObjectiveTemplate, 0), BaseReward: NewQuestReward(), TimeLimit: time.Hour * 24, Repeatable: false, DailyReset: false, } s.questTemplates[QuestTypeCollect] = &QuestTemplate{ Name: "收集任务", Description: "收集指定物品", Objectives: make([]*QuestObjectiveTemplate, 0), BaseReward: NewQuestReward(), TimeLimit: time.Hour * 12, Repeatable: true, DailyReset: true, } } // initializeBehaviorRules 初始化行为规则 func (s *NPCService) initializeBehaviorRules() { s.behaviorRules[BehaviorTypeIdle] = &BehaviorRule{ Type: BehaviorTypeIdle, Description: "空闲行为", ApplyFunc: func(npc *NPCAggregate, deltaTime time.Duration) { // 空闲状态不需要特殊处理 }, } s.behaviorRules[BehaviorTypePatrol] = &BehaviorRule{ Type: BehaviorTypePatrol, Description: "巡逻行为", ApplyFunc: func(npc *NPCAggregate, deltaTime time.Duration) { behavior := npc.GetBehavior() behavior.Update(deltaTime) }, } s.behaviorRules[BehaviorTypeWander] = &BehaviorRule{ Type: BehaviorTypeWander, Description: "漫游行为", ApplyFunc: func(npc *NPCAggregate, deltaTime time.Duration) { behavior := npc.GetBehavior() behavior.Update(deltaTime) // 随机移动逻辑 if rand.Float64() < 0.1 { // 10%概率改变方向 currentLocation := npc.GetLocation() newX := currentLocation.X + (rand.Float64()-0.5)*10 newY := currentLocation.Y + (rand.Float64()-0.5)*10 newLocation := NewLocation(newX, newY, currentLocation.Z, currentLocation.Region, currentLocation.Zone) npc.MoveTo(newLocation) } }, } s.behaviorRules[BehaviorTypeStationary] = &BehaviorRule{ Type: BehaviorTypeStationary, Description: "固定行为", ApplyFunc: func(npc *NPCAggregate, deltaTime time.Duration) { // 固定位置,不移动 }, } } // 辅助结构体 // NPCTemplate NPC模板 type NPCTemplate struct { Level int Attributes *NPCAttributes DefaultBehavior BehaviorType CanMove bool CanTalk bool CanFight bool PatrolPoints []*Location } // DialogueTemplate 对话模板 type DialogueTemplate struct { Name string Description string Nodes []*DialogueNodeTemplate } // GenerateName 生成名称 func (dt *DialogueTemplate) GenerateName(npcType NPCType, context map[string]interface{}) string { return fmt.Sprintf("%s - %s", dt.Name, npcType.String()) } // GenerateDescription 生成描述 func (dt *DialogueTemplate) GenerateDescription(npcType NPCType, context map[string]interface{}) string { return fmt.Sprintf("%s (%s)", dt.Description, npcType.String()) } // GenerateNodes 生成节点 func (dt *DialogueTemplate) GenerateNodes(npcType NPCType, context map[string]interface{}) []*DialogueNode { var nodes []*DialogueNode // 根据模板生成节点 for i, nodeTemplate := range dt.Nodes { nodeID := fmt.Sprintf("node_%d", i) node := NewDialogueNode(nodeID, nodeTemplate.Text, nodeTemplate.Speaker) nodes = append(nodes, node) } // 如果没有模板节点,生成默认节点 if len(nodes) == 0 { defaultText := "你好,我是NPC。" // 默认文本 defaultNode := NewDialogueNode("node_0", defaultText, "NPC") nodes = append(nodes, defaultNode) } return nodes } // GenerateConditions 生成条件 func (dt *DialogueTemplate) GenerateConditions(npcType NPCType, context map[string]interface{}) []*DialogueCondition { // 简化实现,返回空条件 return make([]*DialogueCondition, 0) } // GenerateReward 生成奖励 func (dt *DialogueTemplate) GenerateReward(npcType NPCType, context map[string]interface{}) *DialogueReward { // 简化实现,返回nil return nil } // getDefaultDialogueText 获取默认对话文本 func (s *NPCService) getDefaultDialogueText(npcType NPCType) string { switch npcType { case NPCTypeVillager: return "你好,旅行者!欢迎来到我们的村庄。" case NPCTypeMerchant: return "欢迎光临!我这里有各种商品,看看有什么需要的吗?" case NPCTypeGuard: return "站住!请出示你的通行证。" case NPCTypeQuestGiver: return "勇敢的冒险者,我这里有个任务需要你的帮助。" default: return "你好。" } } // DialogueNodeTemplate 对话节点模板 type DialogueNodeTemplate struct { Text string Speaker string Options []*DialogueOptionTemplate } // DialogueOptionTemplate 对话选项模板 type DialogueOptionTemplate struct { Text string TargetNode string Conditions []*DialogueCondition Actions []*DialogueAction } // QuestTemplate 任务模板 type QuestTemplate struct { Name string Description string Objectives []*QuestObjectiveTemplate BaseReward *QuestReward TimeLimit time.Duration Repeatable bool DailyReset bool } // GenerateName 生成名称 func (qt *QuestTemplate) GenerateName(npcType NPCType, playerLevel int) string { return fmt.Sprintf("%s (Lv.%d)", qt.Name, playerLevel) } // GenerateDescription 生成描述 func (qt *QuestTemplate) GenerateDescription(npcType NPCType, playerLevel int) string { return fmt.Sprintf("%s - 适合等级 %d 的玩家", qt.Description, playerLevel) } // GenerateObjectives 生成目标 func (qt *QuestTemplate) GenerateObjectives(npcType NPCType, playerLevel int) []*QuestObjective { var objectives []*QuestObjective for i, objTemplate := range qt.Objectives { objID := fmt.Sprintf("obj_%d", i) objective := NewQuestObjective( objID, objTemplate.Description, objTemplate.Type, objTemplate.Target, objTemplate.Required, ) objectives = append(objectives, objective) } return objectives } // GenerateReward 生成奖励 func (qt *QuestTemplate) GenerateReward(npcType NPCType, playerLevel int) *QuestReward { reward := NewQuestReward() // 根据玩家等级调整奖励 multiplier := float64(playerLevel) reward.AddGold(int(float64(qt.BaseReward.gold) * multiplier)) reward.AddExperience(int(float64(qt.BaseReward.experience) * multiplier)) // 复制物品奖励 for itemID, quantity := range qt.BaseReward.items { reward.AddItem(itemID, quantity) } return reward } // GeneratePrerequisites 生成前置条件 func (qt *QuestTemplate) GeneratePrerequisites(npcType NPCType, playerLevel int) []*QuestPrerequisite { // 简化实现,返回空前置条件 return make([]*QuestPrerequisite, 0) } // GetTimeLimit 获取时间限制 func (qt *QuestTemplate) GetTimeLimit(playerLevel int) time.Duration { return qt.TimeLimit } // IsRepeatable 检查是否可重复 func (qt *QuestTemplate) IsRepeatable() bool { return qt.Repeatable } // IsDailyReset 检查是否每日重置 func (qt *QuestTemplate) IsDailyReset() bool { return qt.DailyReset } // QuestObjectiveTemplate 任务目标模板 type QuestObjectiveTemplate struct { Description string Type ObjectiveType Target string Required int } // BehaviorRule 行为规则 type BehaviorRule struct { Type BehaviorType Description string ApplyFunc func(*NPCAggregate, time.Duration) } // Apply 应用规则 func (br *BehaviorRule) Apply(npc *NPCAggregate, deltaTime time.Duration) { if br.ApplyFunc != nil { br.ApplyFunc(npc, deltaTime) } } // RelationshipRules 关系规则 type RelationshipRules struct { rules map[string]*RelationshipRule } // NewRelationshipRules 创建关系规则 func NewRelationshipRules() *RelationshipRules { rules := &RelationshipRules{ rules: make(map[string]*RelationshipRule), } // 初始化默认规则 rules.rules["quest_complete"] = &RelationshipRule{Action: "quest_complete", BaseChange: 10} rules.rules["quest_fail"] = &RelationshipRule{Action: "quest_fail", BaseChange: -5} rules.rules["trade"] = &RelationshipRule{Action: "trade", BaseChange: 1} rules.rules["dialogue"] = &RelationshipRule{Action: "dialogue", BaseChange: 1} return rules } // CalculateChange 计算关系变化 func (rr *RelationshipRules) CalculateChange(npcType NPCType, action string, context map[string]interface{}) int { rule, exists := rr.rules[action] if !exists { return 0 } change := rule.BaseChange // 根据NPC类型调整 switch npcType { case NPCTypeMerchant: if action == "trade" { change *= 2 // 商人更重视交易 } case NPCTypeQuestGiver: if action == "quest_complete" { change *= 2 // 任务发布者更重视任务完成 } } return change } // RelationshipRule 关系规则 type RelationshipRule struct { Action string BaseChange int } // AIEngine AI引擎 type AIEngine struct { responseTemplates map[string][]string } // NewAIEngine 创建AI引擎 func NewAIEngine() *AIEngine { engine := &AIEngine{ responseTemplates: make(map[string][]string), } // 初始化响应模板 engine.responseTemplates["greeting"] = []string{ "你好!", "欢迎!", "很高兴见到你!", } engine.responseTemplates["farewell"] = []string{ "再见!", "祝你好运!", "期待下次见面!", } return engine } // GenerateResponse 生成响应 func (ai *AIEngine) GenerateResponse(npc *NPCAggregate, playerID string, input string) (string, error) { // 简化的AI响应逻辑 if templates, exists := ai.responseTemplates["greeting"]; exists { index := rand.Intn(len(templates)) return templates[index], nil } return "我不明白你在说什么。", nil } // StartDialogue 开始对话 func (s *NPCService) StartDialogue(playerID string, npcAggregate *NPCAggregate) (*DialogueSession, error) { // 获取NPC的第一个对话 dialogues := npcAggregate.dialogues if len(dialogues) == 0 { return nil, fmt.Errorf("NPC has no dialogues") } // 选择第一个对话 var firstDialogue *Dialogue for _, d := range dialogues { if d.CanStart(playerID) { firstDialogue = d break } } if firstDialogue == nil { return nil, fmt.Errorf("no available dialogues for player") } // 创建对话会话 session, err := npcAggregate.StartDialogue(firstDialogue.GetID(), playerID) if err != nil { return nil, err } return session, nil } // ProcessDialogueChoice 处理对话选择 func (s *NPCService) ProcessDialogueChoice(session *DialogueSession, choiceID int) (*DialogueResponse, error) { // 简化实现:返回一个响应 response := &DialogueResponse{ NPCID: session.GetNPCID(), DialogueID: session.GetDialogueID(), NodeID: session.GetCurrentNodeID(), Text: "谢谢你的选择。", Options: make([]*DialogueOption, 0), CanContinue: false, Completed: true, } return response, nil } // EndDialogue 结束对话 func (s *NPCService) EndDialogue(session *DialogueSession) error { session.End() return nil } // IsQuestAvailable 检查任务是否可用 func (s *NPCService) IsQuestAvailable(playerID string, quest *Quest) bool { return quest.CanAccept(playerID) } // AcceptQuest 接受任务 func (s *NPCService) AcceptQuest(playerID string, quest *Quest) (*QuestInstance, error) { if !quest.CanAccept(playerID) { return nil, fmt.Errorf("quest not available") } instance := NewQuestInstance(quest.GetID(), playerID, "") return instance, nil } // CompleteQuest 完成任务 func (s *NPCService) CompleteQuest(questInstance *QuestInstance) (*QuestReward, error) { if questInstance.GetStatus() != QuestStatusCompleted { return nil, fmt.Errorf("quest not completed") } reward := NewQuestReward() reward.AddGold(100) reward.AddExperience(50) return reward, nil } // BuyItem 购买物品 func (s *NPCService) BuyItem(playerID string, shop *Shop, itemID string, quantity int) (interface{}, error) { // 简化实现 result := map[string]interface{}{ "success": true, "itemID": itemID, "quantity": quantity, } return result, nil } // DialogueResponse 对话响应 type DialogueResponse struct { NPCID string DialogueID string NodeID string Text string Speaker string Options []*DialogueOption CanContinue bool Completed bool } // GetNodeID 获取节点ID func (dr *DialogueResponse) GetNodeID() string { return dr.NodeID } // GetText 获取文本 func (dr *DialogueResponse) GetText() string { return dr.Text } // GetChoices 获取选项 func (dr *DialogueResponse) GetChoices() []*DialogueOption { return dr.Options } ================================================ FILE: internal/domain/npc/value_object.go ================================================ package npc import ( "math" "time" ) // NPCType NPC类型 type NPCType int const ( NPCTypeVillager NPCType = iota + 1 // 村民 NPCTypeMerchant // 商人 NPCTypeGuard // 守卫 NPCTypeQuestGiver // 任务发布者 NPCTypeTrainer // 训练师 NPCTypeBlacksmith // 铁匠 NPCTypeInnkeeper // 旅店老板 NPCTypeLibrarian // 图书管理员 NPCTypeHealer // 治疗师 NPCTypeBanker // 银行家 NPCTypeSpecial // 特殊NPC ) // String 返回类型字符串 func (nt NPCType) String() string { switch nt { case NPCTypeVillager: return "villager" case NPCTypeMerchant: return "merchant" case NPCTypeGuard: return "guard" case NPCTypeQuestGiver: return "quest_giver" case NPCTypeTrainer: return "trainer" case NPCTypeBlacksmith: return "blacksmith" case NPCTypeInnkeeper: return "innkeeper" case NPCTypeLibrarian: return "librarian" case NPCTypeHealer: return "healer" case NPCTypeBanker: return "banker" case NPCTypeSpecial: return "special" default: return "unknown" } } // IsValid 检查类型是否有效 func (nt NPCType) IsValid() bool { return nt >= NPCTypeVillager && nt <= NPCTypeSpecial } // CanHaveShop 检查是否可以拥有商店 func (nt NPCType) CanHaveShop() bool { switch nt { case NPCTypeMerchant, NPCTypeBlacksmith, NPCTypeInnkeeper, NPCTypeBanker: return true default: return false } } // CanGiveQuests 检查是否可以发布任务 func (nt NPCType) CanGiveQuests() bool { switch nt { case NPCTypeQuestGiver, NPCTypeVillager, NPCTypeGuard, NPCTypeSpecial: return true default: return false } } // GetDefaultBehavior 获取默认行为 func (nt NPCType) GetDefaultBehavior() BehaviorType { switch nt { case NPCTypeGuard: return BehaviorTypePatrol case NPCTypeMerchant, NPCTypeBlacksmith, NPCTypeInnkeeper: return BehaviorTypeStationary case NPCTypeVillager: return BehaviorTypeWander default: return BehaviorTypeIdle } } // NPCStatus NPC状态 type NPCStatus int const ( NPCStatusActive NPCStatus = iota + 1 // 激活 NPCStatusInactive // 未激活 NPCStatusHidden // 隐藏 NPCStatusBusy // 忙碌 NPCStatusSleeping // 睡眠 NPCStatusDead // 死亡 ) // String 返回状态字符串 func (ns NPCStatus) String() string { switch ns { case NPCStatusActive: return "active" case NPCStatusInactive: return "inactive" case NPCStatusHidden: return "hidden" case NPCStatusBusy: return "busy" case NPCStatusSleeping: return "sleeping" case NPCStatusDead: return "dead" default: return "unknown" } } // IsValid 检查状态是否有效 func (ns NPCStatus) IsValid() bool { return ns >= NPCStatusActive && ns <= NPCStatusDead } // CanInteract 检查是否可以交互 func (ns NPCStatus) CanInteract() bool { return ns == NPCStatusActive } // IsVisible 检查是否可见 func (ns NPCStatus) IsVisible() bool { return ns != NPCStatusHidden && ns != NPCStatusDead } // Location 位置值对象 type Location struct { X float64 Y float64 Z float64 Region string Zone string } // NewLocation 创建位置 func NewLocation(x, y, z float64, region, zone string) *Location { return &Location{ X: x, Y: y, Z: z, Region: region, Zone: zone, } } // DistanceTo 计算到另一个位置的距离 func (l *Location) DistanceTo(other *Location) float64 { dx := l.X - other.X dy := l.Y - other.Y dz := l.Z - other.Z return math.Sqrt(dx*dx + dy*dy + dz*dz) } // IsWithinRange 检查是否在指定范围内 func (l *Location) IsWithinRange(other *Location, range_ float64) bool { return l.DistanceTo(other) <= range_ } // MoveTo 移动到指定位置 func (l *Location) MoveTo(x, y, z float64) { l.X = x l.Y = y l.Z = z } // ToMap 转换为映射 func (l *Location) ToMap() map[string]interface{} { return map[string]interface{}{ "x": l.X, "y": l.Y, "z": l.Z, "region": l.Region, "zone": l.Zone, } } // GetX 获取X坐标 func (l *Location) GetX() float64 { return l.X } // GetY 获取Y坐标 func (l *Location) GetY() float64 { return l.Y } // GetZ 获取Z坐标 func (l *Location) GetZ() float64 { return l.Z } // GetRegion 获取区域 func (l *Location) GetRegion() string { return l.Region } // GetZone 获取区域 func (l *Location) GetZone() string { return l.Zone } // NPCAttributes NPC属性值对象 type NPCAttributes struct { Level int Health int MaxHealth int Mana int MaxMana int Strength int Agility int Intelligence int Charisma int Luck int MoveSpeed float64 ViewRange float64 HearRange float64 } // NewNPCAttributes 创建NPC属性 func NewNPCAttributes() *NPCAttributes { return &NPCAttributes{ Level: 1, Health: 100, MaxHealth: 100, Mana: 50, MaxMana: 50, Strength: 10, Agility: 10, Intelligence: 10, Charisma: 10, Luck: 10, MoveSpeed: 1.0, ViewRange: 10.0, HearRange: 5.0, } } // SetLevel 设置等级 func (na *NPCAttributes) SetLevel(level int) { na.Level = level // 根据等级调整其他属性 na.MaxHealth = 100 + (level-1)*20 na.Health = na.MaxHealth na.MaxMana = 50 + (level-1)*10 na.Mana = na.MaxMana } // Heal 治疗 func (na *NPCAttributes) Heal(amount int) { na.Health += amount if na.Health > na.MaxHealth { na.Health = na.MaxHealth } } // TakeDamage 受到伤害 func (na *NPCAttributes) TakeDamage(damage int) { na.Health -= damage if na.Health < 0 { na.Health = 0 } } // IsAlive 检查是否存活 func (na *NPCAttributes) IsAlive() bool { return na.Health > 0 } // GetHealthPercentage 获取生命值百分比 func (na *NPCAttributes) GetHealthPercentage() float64 { if na.MaxHealth == 0 { return 0 } return float64(na.Health) / float64(na.MaxHealth) } // GetManaPercentage 获取法力值百分比 func (na *NPCAttributes) GetManaPercentage() float64 { if na.MaxMana == 0 { return 0 } return float64(na.Mana) / float64(na.MaxMana) } // ToMap 转换为映射 func (na *NPCAttributes) ToMap() map[string]interface{} { return map[string]interface{}{ "level": na.Level, "health": na.Health, "max_health": na.MaxHealth, "mana": na.Mana, "max_mana": na.MaxMana, "strength": na.Strength, "agility": na.Agility, "intelligence": na.Intelligence, "charisma": na.Charisma, "luck": na.Luck, "move_speed": na.MoveSpeed, "view_range": na.ViewRange, "hear_range": na.HearRange, } } // GetLevel 获取等级 func (na *NPCAttributes) GetLevel() int { return na.Level } // GetHealth 获取生命值 func (na *NPCAttributes) GetHealth() int { return na.Health } // GetMaxHealth 获取最大生命值 func (na *NPCAttributes) GetMaxHealth() int { return na.MaxHealth } // GetAttack 获取攻击力 func (na *NPCAttributes) GetAttack() int { return na.Strength } // GetDefense 获取防御力 func (na *NPCAttributes) GetDefense() int { return na.Agility } // GetSpeed 获取速度 func (na *NPCAttributes) GetSpeed() float64 { return na.MoveSpeed } // GetIntelligence 获取智力 func (na *NPCAttributes) GetIntelligence() int { return na.Intelligence } // NPCBehavior NPC行为值对象 type NPCBehavior struct { Type BehaviorType State BehaviorState PatrolPoints []*Location CurrentPoint int Target *Location MoveSpeed float64 PauseTime time.Duration LastMove time.Time CanMove bool CanTalk bool CanFight bool } // NewNPCBehavior 创建NPC行为 func NewNPCBehavior() *NPCBehavior { return &NPCBehavior{ Type: BehaviorTypeIdle, State: BehaviorStateIdle, PatrolPoints: make([]*Location, 0), MoveSpeed: 1.0, PauseTime: time.Second * 3, LastMove: time.Now(), CanMove: true, CanTalk: true, CanFight: false, } } // SetBehaviorType 设置行为类型 func (nb *NPCBehavior) SetBehaviorType(behaviorType BehaviorType) { nb.Type = behaviorType nb.State = BehaviorStateIdle } // AddPatrolPoint 添加巡逻点 func (nb *NPCBehavior) AddPatrolPoint(location *Location) { nb.PatrolPoints = append(nb.PatrolPoints, location) } // GetNextPatrolPoint 获取下一个巡逻点 func (nb *NPCBehavior) GetNextPatrolPoint() *Location { if len(nb.PatrolPoints) == 0 { return nil } nb.CurrentPoint = (nb.CurrentPoint + 1) % len(nb.PatrolPoints) return nb.PatrolPoints[nb.CurrentPoint] } // SetTarget 设置目标 func (nb *NPCBehavior) SetTarget(target *Location) { nb.Target = target nb.State = BehaviorStateMoving } // ClearTarget 清除目标 func (nb *NPCBehavior) ClearTarget() { nb.Target = nil nb.State = BehaviorStateIdle } // CanMoveNow 检查是否可以移动 func (nb *NPCBehavior) CanMoveNow() bool { return nb.CanMove && nb.State != BehaviorStatePaused } // Update 更新行为 func (nb *NPCBehavior) Update(deltaTime time.Duration) { switch nb.Type { case BehaviorTypePatrol: nb.updatePatrol(deltaTime) case BehaviorTypeWander: nb.updateWander(deltaTime) case BehaviorTypeFollow: nb.updateFollow(deltaTime) default: nb.updateIdle(deltaTime) } } // updatePatrol 更新巡逻行为 func (nb *NPCBehavior) updatePatrol(deltaTime time.Duration) { if len(nb.PatrolPoints) == 0 { return } switch nb.State { case BehaviorStateIdle: if time.Since(nb.LastMove) >= nb.PauseTime { nb.SetTarget(nb.GetNextPatrolPoint()) } case BehaviorStateMoving: // 移动逻辑在这里实现 nb.LastMove = time.Now() } } // updateWander 更新漫游行为 func (nb *NPCBehavior) updateWander(deltaTime time.Duration) { // 简化的漫游逻辑 if nb.State == BehaviorStateIdle && time.Since(nb.LastMove) >= nb.PauseTime { // 随机选择一个方向移动 nb.State = BehaviorStateMoving nb.LastMove = time.Now() } } // updateFollow 更新跟随行为 func (nb *NPCBehavior) updateFollow(deltaTime time.Duration) { // 跟随目标的逻辑 if nb.Target != nil { nb.State = BehaviorStateMoving } } // updateIdle 更新空闲行为 func (nb *NPCBehavior) updateIdle(deltaTime time.Duration) { // 空闲状态不需要特殊处理 } // GetCurrentAction 获取当前动作 func (nb *NPCBehavior) GetCurrentAction() BehaviorType { return nb.Type } // GetNextAction 获取下一个动作 func (nb *NPCBehavior) GetNextAction() BehaviorType { return nb.Type } // GetCooldown 获取冷却时间 func (nb *NPCBehavior) GetCooldown() time.Duration { return nb.PauseTime } // IsActive 是否激活 func (nb *NPCBehavior) IsActive() bool { return nb.State != BehaviorStateIdle && nb.State != BehaviorStatePaused } // BehaviorType 行为类型 type BehaviorType int const ( BehaviorTypeIdle BehaviorType = iota + 1 // 空闲 BehaviorTypePatrol // 巡逻 BehaviorTypeWander // 漫游 BehaviorTypeFollow // 跟随 BehaviorTypeStationary // 固定 BehaviorTypeAggressive // 攻击性 BehaviorTypeDefensive // 防御性 ) // String 返回行为类型字符串 func (bt BehaviorType) String() string { switch bt { case BehaviorTypeIdle: return "idle" case BehaviorTypePatrol: return "patrol" case BehaviorTypeWander: return "wander" case BehaviorTypeFollow: return "follow" case BehaviorTypeStationary: return "stationary" case BehaviorTypeAggressive: return "aggressive" case BehaviorTypeDefensive: return "defensive" default: return "unknown" } } // BehaviorState 行为状态 type BehaviorState int const ( BehaviorStateIdle BehaviorState = iota + 1 // 空闲 BehaviorStateMoving // 移动中 BehaviorStatePaused // 暂停 BehaviorStateWaiting // 等待 BehaviorStateActing // 执行动作 ) // String 返回行为状态字符串 func (bs BehaviorState) String() string { switch bs { case BehaviorStateIdle: return "idle" case BehaviorStateMoving: return "moving" case BehaviorStatePaused: return "paused" case BehaviorStateWaiting: return "waiting" case BehaviorStateActing: return "acting" default: return "unknown" } } // Relationship 关系值对象 type Relationship struct { PlayerID string NPCID string Value int Level RelationshipLevel History []*RelationshipEvent CreatedAt time.Time UpdatedAt time.Time } // NewRelationship 创建关系 func NewRelationship(playerID, npcID string) *Relationship { now := time.Now() return &Relationship{ PlayerID: playerID, NPCID: npcID, Value: 0, Level: RelationshipLevelNeutral, History: make([]*RelationshipEvent, 0), CreatedAt: now, UpdatedAt: now, } } // GetValue 获取关系值 func (r *Relationship) GetValue() int { return r.Value } // GetLevel 获取关系等级 func (r *Relationship) GetLevel() RelationshipLevel { return r.Level } // ChangeValue 改变关系值 func (r *Relationship) ChangeValue(change int, reason string) error { oldValue := r.Value oldLevel := r.Level r.Value += change // 限制关系值范围 if r.Value > 1000 { r.Value = 1000 } else if r.Value < -1000 { r.Value = -1000 } // 更新关系等级 r.updateLevel() // 记录历史 event := &RelationshipEvent{ Reason: reason, Change: change, OldValue: oldValue, NewValue: r.Value, OldLevel: oldLevel, NewLevel: r.Level, Timestamp: time.Now(), } r.History = append(r.History, event) r.UpdatedAt = time.Now() return nil } // updateLevel 更新关系等级 func (r *Relationship) updateLevel() { switch { case r.Value >= 500: r.Level = RelationshipLevelRevered case r.Value >= 200: r.Level = RelationshipLevelFriendly case r.Value >= 50: r.Level = RelationshipLevelLiked case r.Value >= -50: r.Level = RelationshipLevelNeutral case r.Value >= -200: r.Level = RelationshipLevelDisliked case r.Value >= -500: r.Level = RelationshipLevelUnfriendly default: r.Level = RelationshipLevelHostile } } // GetRecentHistory 获取最近的历史记录 func (r *Relationship) GetRecentHistory(limit int) []*RelationshipEvent { if len(r.History) <= limit { return r.History } return r.History[len(r.History)-limit:] } // RelationshipLevel 关系等级 type RelationshipLevel int const ( RelationshipLevelHostile RelationshipLevel = iota + 1 // 敌对 RelationshipLevelUnfriendly // 不友好 RelationshipLevelDisliked // 不喜欢 RelationshipLevelNeutral // 中立 RelationshipLevelLiked // 喜欢 RelationshipLevelFriendly // 友好 RelationshipLevelRevered // 崇敬 ) // String 返回关系等级字符串 func (rl RelationshipLevel) String() string { switch rl { case RelationshipLevelHostile: return "hostile" case RelationshipLevelUnfriendly: return "unfriendly" case RelationshipLevelDisliked: return "disliked" case RelationshipLevelNeutral: return "neutral" case RelationshipLevelLiked: return "liked" case RelationshipLevelFriendly: return "friendly" case RelationshipLevelRevered: return "revered" default: return "unknown" } } // GetColor 获取关系等级颜色 func (rl RelationshipLevel) GetColor() string { switch rl { case RelationshipLevelHostile: return "red" case RelationshipLevelUnfriendly: return "orange" case RelationshipLevelDisliked: return "yellow" case RelationshipLevelNeutral: return "white" case RelationshipLevelLiked: return "lightgreen" case RelationshipLevelFriendly: return "green" case RelationshipLevelRevered: return "gold" default: return "gray" } } // RelationshipEvent 关系事件 type RelationshipEvent struct { Reason string Change int OldValue int NewValue int OldLevel RelationshipLevel NewLevel RelationshipLevel Timestamp time.Time } // RelationshipChangeType 关系变更类型 type RelationshipChangeType int const ( RelationshipChangeTypeIncrease RelationshipChangeType = iota + 1 // 增加 RelationshipChangeTypeDecrease // 减少 RelationshipChangeTypeReset // 重置 RelationshipChangeTypeSet // 设置 ) // String 返回变更类型字符串 func (rct RelationshipChangeType) String() string { switch rct { case RelationshipChangeTypeIncrease: return "increase" case RelationshipChangeTypeDecrease: return "decrease" case RelationshipChangeTypeReset: return "reset" case RelationshipChangeTypeSet: return "set" default: return "unknown" } } // NPCSchedule NPC日程值对象 type NPCSchedule struct { ScheduleItems []*ScheduleItem CurrentItem *ScheduleItem TimeZone string } // NewNPCSchedule 创建NPC日程 func NewNPCSchedule() *NPCSchedule { return &NPCSchedule{ ScheduleItems: make([]*ScheduleItem, 0), TimeZone: "UTC", } } // AddScheduleItem 添加日程项 func (ns *NPCSchedule) AddScheduleItem(item *ScheduleItem) { ns.ScheduleItems = append(ns.ScheduleItems, item) } // GetCurrentItem 获取当前日程项 func (ns *NPCSchedule) GetCurrentItem(currentTime time.Time) *ScheduleItem { for _, item := range ns.ScheduleItems { if item.IsActive(currentTime) { return item } } return nil } // Update 更新日程 func (ns *NPCSchedule) Update(currentTime time.Time) { ns.CurrentItem = ns.GetCurrentItem(currentTime) } // ScheduleItem 日程项 type ScheduleItem struct { ID string Name string Description string StartTime time.Time EndTime time.Time DayOfWeek []time.Weekday Location *Location Behavior BehaviorType Actions []string Priority int } // NewScheduleItem 创建日程项 func NewScheduleItem(id, name, description string, startTime, endTime time.Time) *ScheduleItem { return &ScheduleItem{ ID: id, Name: name, Description: description, StartTime: startTime, EndTime: endTime, DayOfWeek: make([]time.Weekday, 0), Actions: make([]string, 0), Priority: 1, } } // IsActive 检查是否激活 func (si *ScheduleItem) IsActive(currentTime time.Time) bool { // 检查星期几 if len(si.DayOfWeek) > 0 { currentWeekday := currentTime.Weekday() found := false for _, weekday := range si.DayOfWeek { if weekday == currentWeekday { found = true break } } if !found { return false } } // 检查时间范围 currentHour := currentTime.Hour() currentMinute := currentTime.Minute() currentTimeOfDay := currentHour*60 + currentMinute startTimeOfDay := si.StartTime.Hour()*60 + si.StartTime.Minute() endTimeOfDay := si.EndTime.Hour()*60 + si.EndTime.Minute() return currentTimeOfDay >= startTimeOfDay && currentTimeOfDay <= endTimeOfDay } // AddDayOfWeek 添加星期几 func (si *ScheduleItem) AddDayOfWeek(weekday time.Weekday) { si.DayOfWeek = append(si.DayOfWeek, weekday) } // AddAction 添加动作 func (si *ScheduleItem) AddAction(action string) { si.Actions = append(si.Actions, action) } // ShopSchedule 商店日程值对象 type ShopSchedule struct { OpenTime time.Time CloseTime time.Time DaysOpen []time.Weekday Holidays []time.Time SpecialHours map[string]*SpecialHours } // NewShopSchedule 创建商店日程 func NewShopSchedule() *ShopSchedule { return &ShopSchedule{ OpenTime: time.Date(0, 1, 1, 9, 0, 0, 0, time.UTC), // 9:00 AM CloseTime: time.Date(0, 1, 1, 18, 0, 0, 0, time.UTC), // 6:00 PM DaysOpen: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday}, Holidays: make([]time.Time, 0), SpecialHours: make(map[string]*SpecialHours), } } // IsOpen 检查是否开放 func (ss *ShopSchedule) IsOpen(currentTime time.Time) bool { // 检查是否为假日 for _, holiday := range ss.Holidays { if currentTime.YearDay() == holiday.YearDay() && currentTime.Year() == holiday.Year() { return false } } // 检查特殊时间 dateKey := currentTime.Format("2006-01-02") if specialHours, exists := ss.SpecialHours[dateKey]; exists { return specialHours.IsOpen(currentTime) } // 检查星期几 currentWeekday := currentTime.Weekday() found := false for _, weekday := range ss.DaysOpen { if weekday == currentWeekday { found = true break } } if !found { return false } // 检查营业时间 currentHour := currentTime.Hour() currentMinute := currentTime.Minute() currentTimeOfDay := currentHour*60 + currentMinute openTimeOfDay := ss.OpenTime.Hour()*60 + ss.OpenTime.Minute() closeTimeOfDay := ss.CloseTime.Hour()*60 + ss.CloseTime.Minute() return currentTimeOfDay >= openTimeOfDay && currentTimeOfDay <= closeTimeOfDay } // AddHoliday 添加假日 func (ss *ShopSchedule) AddHoliday(holiday time.Time) { ss.Holidays = append(ss.Holidays, holiday) } // SetSpecialHours 设置特殊时间 func (ss *ShopSchedule) SetSpecialHours(date string, specialHours *SpecialHours) { ss.SpecialHours[date] = specialHours } // SpecialHours 特殊时间 type SpecialHours struct { OpenTime time.Time CloseTime time.Time Closed bool } // NewSpecialHours 创建特殊时间 func NewSpecialHours(openTime, closeTime time.Time, closed bool) *SpecialHours { return &SpecialHours{ OpenTime: openTime, CloseTime: closeTime, Closed: closed, } } // IsOpen 检查是否开放 func (sh *SpecialHours) IsOpen(currentTime time.Time) bool { if sh.Closed { return false } currentHour := currentTime.Hour() currentMinute := currentTime.Minute() currentTimeOfDay := currentHour*60 + currentMinute openTimeOfDay := sh.OpenTime.Hour()*60 + sh.OpenTime.Minute() closeTimeOfDay := sh.CloseTime.Hour()*60 + sh.CloseTime.Minute() return currentTimeOfDay >= openTimeOfDay && currentTimeOfDay <= closeTimeOfDay } // 枚举类型定义 // DialogueType 对话类型 type DialogueType int const ( DialogueTypeGreeting DialogueType = iota + 1 // 问候 DialogueTypeInformation // 信息 DialogueTypeQuest // 任务 DialogueTypeTrade // 交易 DialogueTypeRumor // 传言 DialogueTypeStory // 故事 DialogueTypeSpecial // 特殊 ) // String 返回对话类型字符串 func (dt DialogueType) String() string { switch dt { case DialogueTypeGreeting: return "greeting" case DialogueTypeInformation: return "information" case DialogueTypeQuest: return "quest" case DialogueTypeTrade: return "trade" case DialogueTypeRumor: return "rumor" case DialogueTypeStory: return "story" case DialogueTypeSpecial: return "special" default: return "unknown" } } // QuestType 任务类型 type QuestType int const ( QuestTypeKill QuestType = iota + 1 // 击杀 QuestTypeCollect // 收集 QuestTypeDeliver // 运送 QuestTypeEscort // 护送 QuestTypeExplore // 探索 QuestTypeTalk // 对话 QuestTypeCraft // 制作 QuestTypeDaily // 日常 QuestTypeWeekly // 周常 QuestTypeSpecial // 特殊 ) // String 返回任务类型字符串 func (qt QuestType) String() string { switch qt { case QuestTypeKill: return "kill" case QuestTypeCollect: return "collect" case QuestTypeDeliver: return "deliver" case QuestTypeEscort: return "escort" case QuestTypeExplore: return "explore" case QuestTypeTalk: return "talk" case QuestTypeCraft: return "craft" case QuestTypeDaily: return "daily" case QuestTypeWeekly: return "weekly" case QuestTypeSpecial: return "special" default: return "unknown" } } // QuestStatus 任务状态 type QuestStatus int const ( QuestStatusActive QuestStatus = iota + 1 // 激活 QuestStatusCompleted // 完成 QuestStatusFailed // 失败 QuestStatusAbandoned // 放弃 QuestStatusExpired // 过期 ) // String 返回任务状态字符串 func (qs QuestStatus) String() string { switch qs { case QuestStatusActive: return "active" case QuestStatusCompleted: return "completed" case QuestStatusFailed: return "failed" case QuestStatusAbandoned: return "abandoned" case QuestStatusExpired: return "expired" default: return "unknown" } } // ObjectiveType 目标类型 type ObjectiveType int const ( ObjectiveTypeKill ObjectiveType = iota + 1 // 击杀 ObjectiveTypeCollect // 收集 ObjectiveTypeDeliver // 运送 ObjectiveTypeReach // 到达 ObjectiveTypeInteract // 交互 ObjectiveTypeWait // 等待 ObjectiveTypeDefend // 防御 ObjectiveTypeEscape // 逃脱 ) // String 返回目标类型字符串 func (ot ObjectiveType) String() string { switch ot { case ObjectiveTypeKill: return "kill" case ObjectiveTypeCollect: return "collect" case ObjectiveTypeDeliver: return "deliver" case ObjectiveTypeReach: return "reach" case ObjectiveTypeInteract: return "interact" case ObjectiveTypeWait: return "wait" case ObjectiveTypeDefend: return "defend" case ObjectiveTypeEscape: return "escape" default: return "unknown" } } // ConditionType 条件类型 type ConditionType int const ( ConditionTypeLevel ConditionType = iota + 1 // 等级 ConditionTypeItem // 物品 ConditionTypeQuest // 任务 ConditionTypeRelationship // 关系 ConditionTypeTime // 时间 ConditionTypeLocation // 位置 ConditionTypeAttribute // 属性 ConditionTypeCustom // 自定义 ) // String 返回条件类型字符串 func (ct ConditionType) String() string { switch ct { case ConditionTypeLevel: return "level" case ConditionTypeItem: return "item" case ConditionTypeQuest: return "quest" case ConditionTypeRelationship: return "relationship" case ConditionTypeTime: return "time" case ConditionTypeLocation: return "location" case ConditionTypeAttribute: return "attribute" case ConditionTypeCustom: return "custom" default: return "unknown" } } // ActionType 动作类型 type ActionType int const ( ActionTypeGiveItem ActionType = iota + 1 // 给予物品 ActionTypeTakeItem // 拿取物品 ActionTypeGiveGold // 给予金币 ActionTypeTakeGold // 拿取金币 ActionTypeGiveExperience // 给予经验 ActionTypeStartQuest // 开始任务 ActionTypeCompleteQuest // 完成任务 ActionTypeChangeRelationship // 改变关系 ActionTypeTeleport // 传送 ActionTypeCustom // 自定义 ) // String 返回动作类型字符串 func (at ActionType) String() string { switch at { case ActionTypeGiveItem: return "give_item" case ActionTypeTakeItem: return "take_item" case ActionTypeGiveGold: return "give_gold" case ActionTypeTakeGold: return "take_gold" case ActionTypeGiveExperience: return "give_experience" case ActionTypeStartQuest: return "start_quest" case ActionTypeCompleteQuest: return "complete_quest" case ActionTypeChangeRelationship: return "change_relationship" case ActionTypeTeleport: return "teleport" case ActionTypeCustom: return "custom" default: return "unknown" } } // PrerequisiteType 前置条件类型 type PrerequisiteType int const ( PrerequisiteTypeLevel PrerequisiteType = iota + 1 // 等级 PrerequisiteTypeQuest // 任务 PrerequisiteTypeItem // 物品 PrerequisiteTypeRelationship // 关系 PrerequisiteTypeAttribute // 属性 PrerequisiteTypeTime // 时间 PrerequisiteTypeCustom // 自定义 ) // String 返回前置条件类型字符串 func (pt PrerequisiteType) String() string { switch pt { case PrerequisiteTypeLevel: return "level" case PrerequisiteTypeQuest: return "quest" case PrerequisiteTypeItem: return "item" case PrerequisiteTypeRelationship: return "relationship" case PrerequisiteTypeAttribute: return "attribute" case PrerequisiteTypeTime: return "time" case PrerequisiteTypeCustom: return "custom" default: return "unknown" } } // DiscountType 折扣类型 type DiscountType int const ( DiscountTypePercentage DiscountType = iota + 1 // 百分比 DiscountTypeFixed // 固定金额 ) // String 返回折扣类型字符串 func (dt DiscountType) String() string { switch dt { case DiscountTypePercentage: return "percentage" case DiscountTypeFixed: return "fixed" default: return "unknown" } } ================================================ FILE: internal/domain/pet/aggregate.go ================================================ package pet import ( "fmt" "time" ) // PetAggregate 宠物聚合根 type PetAggregate struct { id string playerID string configID uint32 name string category PetCategory star uint32 level uint32 experience uint64 state PetState attributes *PetAttributes skills []*PetSkill bonds *PetBonds skins []*PetSkin reviveTime time.Time createdAt time.Time updatedAt time.Time version int } // NewPetAggregate 创建新宠物聚合根 func NewPetAggregate(playerID string, configID uint32, name string, category PetCategory) *PetAggregate { now := time.Now() return &PetAggregate{ id: fmt.Sprintf("pet_%d", now.UnixNano()), playerID: playerID, configID: configID, name: name, category: category, star: 1, level: 1, experience: 0, state: PetStateIdle, attributes: NewPetAttributes(), skills: make([]*PetSkill, 0), bonds: NewPetBonds(), skins: make([]*PetSkin, 0), createdAt: now, updatedAt: now, version: 1, } } // GetID 获取宠物ID func (p *PetAggregate) GetID() string { return p.id } // GetPlayerID 获取玩家ID func (p *PetAggregate) GetPlayerID() string { return p.playerID } // GetConfigID 获取配置ID func (p *PetAggregate) GetConfigID() uint32 { return p.configID } // GetName 获取宠物名称 func (p *PetAggregate) GetName() string { return p.name } // SetName 设置宠物名称 func (p *PetAggregate) SetName(name string) error { if name == "" { return ErrInvalidPetName } p.name = name p.updatedAt = time.Now() p.version++ return nil } // GetCategory 获取宠物类别 func (p *PetAggregate) GetCategory() PetCategory { return p.category } // GetStar 获取宠物星级 func (p *PetAggregate) GetStar() uint32 { return p.star } // GetLevel 获取宠物等级 func (p *PetAggregate) GetLevel() uint32 { return p.level } // GetExperience 获取宠物经验 func (p *PetAggregate) GetExperience() uint64 { return p.experience } // GetState 获取宠物状态 func (p *PetAggregate) GetState() PetState { return p.state } // GetAttributes 获取宠物属性 func (p *PetAggregate) GetAttributes() *PetAttributes { return p.attributes } // GetSkills 获取宠物技能 func (p *PetAggregate) GetSkills() []*PetSkill { return p.skills } // GetBonds 获取宠物羁绊 func (p *PetAggregate) GetBonds() *PetBonds { return p.bonds } // GetSkins 获取宠物皮肤 func (p *PetAggregate) GetSkins() []*PetSkin { return p.skins } // GetReviveTime 获取复活时间 func (p *PetAggregate) GetReviveTime() time.Time { return p.reviveTime } // GetCreatedAt 获取创建时间 func (p *PetAggregate) GetCreatedAt() time.Time { return p.createdAt } // GetUpdatedAt 获取更新时间 func (p *PetAggregate) GetUpdatedAt() time.Time { return p.updatedAt } // GetVersion 获取版本号 func (p *PetAggregate) GetVersion() int { return p.version } // AddExperience 增加经验 func (p *PetAggregate) AddExperience(exp uint64) error { if p.state == PetStateDead { return ErrPetIsDead } p.experience += exp p.updatedAt = time.Now() p.version++ // 检查是否可以升级 if p.canLevelUp() { return p.levelUp() } return nil } // LevelUp 升级 func (p *PetAggregate) levelUp() error { if p.level >= MaxPetLevel { return ErrMaxLevelReached } p.level++ p.attributes.UpgradeOnLevelUp(p.level) p.updatedAt = time.Now() p.version++ return nil } // canLevelUp 检查是否可以升级 func (p *PetAggregate) canLevelUp() bool { requiredExp := CalculateRequiredExperience(p.level) return p.experience >= requiredExp && p.level < MaxPetLevel } // UpgradeStar 升星 func (p *PetAggregate) UpgradeStar() error { if p.star >= MaxPetStar { return ErrMaxStarReached } p.star++ p.attributes.UpgradeOnStarUp(p.star) p.updatedAt = time.Now() p.version++ return nil } // ChangeState 改变状态 func (p *PetAggregate) ChangeState(newState PetState) error { if !p.canChangeState(newState) { return ErrInvalidStateTransition } p.state = newState p.updatedAt = time.Now() p.version++ // 如果是死亡状态,设置复活时间 if newState == PetStateDead { p.reviveTime = time.Now().Add(DefaultReviveTime) } return nil } // canChangeState 检查是否可以改变状态 func (p *PetAggregate) canChangeState(newState PetState) bool { switch p.state { case PetStateIdle: return newState == PetStateBattle || newState == PetStateTraining || newState == PetStateDead case PetStateBattle: return newState == PetStateIdle || newState == PetStateDead case PetStateTraining: return newState == PetStateIdle case PetStateDead: return newState == PetStateIdle && time.Now().After(p.reviveTime) default: return false } } // Revive 复活宠物 func (p *PetAggregate) Revive() error { if p.state != PetStateDead { return ErrPetNotDead } if time.Now().Before(p.reviveTime) { return ErrReviveTimeNotReached } p.state = PetStateIdle p.reviveTime = time.Time{} p.updatedAt = time.Now() p.version++ return nil } // InstantRevive 立即复活(消耗道具) func (p *PetAggregate) InstantRevive() error { if p.state != PetStateDead { return ErrPetNotDead } p.state = PetStateIdle p.reviveTime = time.Time{} p.updatedAt = time.Now() p.version++ return nil } // AddSkill 添加技能 func (p *PetAggregate) AddSkill(skill *PetSkill) error { if len(p.skills) >= MaxPetSkills { return ErrMaxSkillsReached } // 检查是否已存在相同技能 for _, existingSkill := range p.skills { if existingSkill.GetSkillID() == skill.GetSkillID() { return ErrSkillAlreadyExists } } p.skills = append(p.skills, skill) p.updatedAt = time.Now() p.version++ return nil } // RemoveSkill 移除技能 func (p *PetAggregate) RemoveSkill(skillID string) error { for i, skill := range p.skills { if skill.GetSkillID() == skillID { p.skills = append(p.skills[:i], p.skills[i+1:]...) p.updatedAt = time.Now() p.version++ return nil } } return ErrSkillNotFound } // UpgradeSkill 升级技能 func (p *PetAggregate) UpgradeSkill(skillID string) error { for _, skill := range p.skills { if skill.GetSkillID() == skillID { if err := skill.Upgrade(); err != nil { return err } p.updatedAt = time.Now() p.version++ return nil } } return ErrSkillNotFound } // AddSkin 添加皮肤 func (p *PetAggregate) AddSkin(skin *PetSkin) error { // 检查是否已拥有该皮肤 for _, existingSkin := range p.skins { if existingSkin.GetSkinID() == skin.GetSkinID() { return ErrSkinAlreadyOwned } } p.skins = append(p.skins, skin) p.updatedAt = time.Now() p.version++ return nil } // EquipSkin 装备皮肤 func (p *PetAggregate) EquipSkin(skinID string) error { // 先取消当前装备的皮肤 for _, skin := range p.skins { if skin.IsEquipped() { skin.Unequip() } } // 装备新皮肤 for _, skin := range p.skins { if skin.GetSkinID() == skinID { if err := skin.Equip(); err != nil { return err } p.updatedAt = time.Now() p.version++ return nil } } return ErrSkinNotOwned } // Feed 喂食 func (p *PetAggregate) Feed(foodType FoodType, amount int) error { if p.state == PetStateDead { return ErrPetIsDead } if amount <= 0 { return ErrInvalidAmount } // 根据食物类型增加不同属性 switch foodType { case FoodTypeExperience: return p.AddExperience(uint64(amount * ExperienceFoodValue)) case FoodTypeHealth: p.attributes.AddHealth(int64(amount * HealthFoodValue)) case FoodTypeAttack: p.attributes.AddAttack(int64(amount * AttackFoodValue)) case FoodTypeDefense: p.attributes.AddDefense(int64(amount * DefenseFoodValue)) default: return ErrInvalidFoodType } p.updatedAt = time.Now() p.version++ return nil } // Train 训练 func (p *PetAggregate) Train(trainingType TrainingType, duration time.Duration) error { if p.state != PetStateIdle { return ErrPetNotIdle } p.state = PetStateTraining p.updatedAt = time.Now() p.version++ // 训练完成后的效果将在训练结束时处理 return nil } // FinishTraining 完成训练 func (p *PetAggregate) FinishTraining(trainingType TrainingType) error { if p.state != PetStateTraining { return ErrPetNotTraining } // 根据训练类型获得不同收益 switch trainingType { case TrainingTypeExperience: p.AddExperience(TrainingExperienceGain) case TrainingTypeAttribute: p.attributes.AddRandomAttribute(TrainingAttributeGain) case TrainingTypeSkill: // 随机提升一个技能经验 if len(p.skills) > 0 { randomSkill := p.skills[0] // 简化实现,实际应该随机选择 randomSkill.AddExperience(TrainingSkillExpGain) } } p.state = PetStateIdle p.updatedAt = time.Now() p.version++ return nil } // EnterBattle 进入战斗 func (p *PetAggregate) EnterBattle() error { if p.state != PetStateIdle { return ErrPetNotIdle } if p.attributes.GetHealth() <= 0 { return ErrPetIsDead } p.state = PetStateBattle p.updatedAt = time.Now() p.version++ return nil } // ExitBattle 退出战斗 func (p *PetAggregate) ExitBattle(isDead bool) error { if p.state != PetStateBattle { return ErrPetNotInBattle } if isDead { p.state = PetStateDead p.reviveTime = time.Now().Add(DefaultReviveTime) } else { p.state = PetStateIdle } p.updatedAt = time.Now() p.version++ return nil } // ActivateBond 激活羁绊 func (p *PetAggregate) ActivateBond(bondID string) error { return p.bonds.ActivateBond(bondID) } // DeactivateBond 取消羁绊 func (p *PetAggregate) DeactivateBond(bondID string) error { return p.bonds.DeactivateBond(bondID) } // GetTotalPower 获取总战力 func (p *PetAggregate) GetTotalPower() int64 { basePower := p.attributes.CalculatePower() bondBonus := p.bonds.GetPowerBonus() skinBonus := p.getEquippedSkinBonus() return basePower + bondBonus + skinBonus } // getEquippedSkinBonus 获取装备皮肤加成 func (p *PetAggregate) getEquippedSkinBonus() int64 { for _, skin := range p.skins { if skin.IsEquipped() { return skin.GetPowerBonus() } } return 0 } // IsAlive 是否存活 func (p *PetAggregate) IsAlive() bool { return p.state != PetStateDead } // IsIdle 是否空闲 func (p *PetAggregate) IsIdle() bool { return p.state == PetStateIdle } // CanRevive 是否可以复活 func (p *PetAggregate) CanRevive() bool { return p.state == PetStateDead && time.Now().After(p.reviveTime) } // GetReviveTimeRemaining 获取剩余复活时间 func (p *PetAggregate) GetReviveTimeRemaining() time.Duration { if p.state != PetStateDead { return 0 } remaining := p.reviveTime.Sub(time.Now()) if remaining < 0 { return 0 } return remaining } // Validate 验证宠物数据 func (p *PetAggregate) Validate() error { if p.id == "" { return ErrInvalidPetID } if p.playerID == "" { return ErrInvalidPlayerID } if p.name == "" { return ErrInvalidPetName } if p.level < 1 || p.level > MaxPetLevel { return ErrInvalidPetLevel } if p.star < 1 || p.star > MaxPetStar { return ErrInvalidPetStar } if p.attributes == nil { return ErrInvalidPetAttributes } return nil } // 常量定义 const ( MaxPetLevel = 100 MaxPetStar = 5 MaxPetSkills = 4 DefaultReviveTime = 30 * time.Minute // 食物价值 ExperienceFoodValue = 100 HealthFoodValue = 50 AttackFoodValue = 10 DefenseFoodValue = 10 // 训练收益 TrainingExperienceGain = 500 TrainingAttributeGain = 20 TrainingSkillExpGain = 100 ) // CalculateRequiredExperience 计算升级所需经验 func CalculateRequiredExperience(level uint32) uint64 { // 简化的经验计算公式 return uint64(level * level * 100) } // ReconstructPetAggregate 从持久化数据重建宠物聚合根 func ReconstructPetAggregate( id string, playerID string, configID uint32, name string, category PetCategory, star uint32, level uint32, experience uint64, state PetState, attributes *PetAttributes, skills []*PetSkill, bonds *PetBonds, skins []*PetSkin, reviveTime time.Time, createdAt time.Time, updatedAt time.Time, version int, ) *PetAggregate { return &PetAggregate{ id: id, playerID: playerID, configID: configID, name: name, category: category, star: star, level: level, experience: experience, state: state, attributes: attributes, skills: skills, bonds: bonds, skins: skins, reviveTime: reviveTime, createdAt: createdAt, updatedAt: updatedAt, version: version, } } ================================================ FILE: internal/domain/pet/entity.go ================================================ package pet import ( "fmt" "time" ) // PetFragment 宠物碎片实体 type PetFragment struct { id string playerID string fragmentID uint32 relatedPetID uint32 quantity uint64 createdAt time.Time updatedAt time.Time } // NewPetFragment 创建新的宠物碎片 func NewPetFragment(playerID string, fragmentID, relatedPetID uint32, quantity uint64) *PetFragment { now := time.Now() return &PetFragment{ id: fmt.Sprintf("fragment_%d", now.UnixNano()), playerID: playerID, fragmentID: fragmentID, relatedPetID: relatedPetID, quantity: quantity, createdAt: now, updatedAt: now, } } // GetID 获取碎片ID func (pf *PetFragment) GetID() string { return pf.id } // GetPlayerID 获取玩家ID func (pf *PetFragment) GetPlayerID() string { return pf.playerID } // GetFragmentID 获取碎片配置ID func (pf *PetFragment) GetFragmentID() uint32 { return pf.fragmentID } // GetRelatedPetID 获取关联宠物ID func (pf *PetFragment) GetRelatedPetID() uint32 { return pf.relatedPetID } // GetQuantity 获取数量 func (pf *PetFragment) GetQuantity() uint64 { return pf.quantity } // AddQuantity 增加数量 func (pf *PetFragment) AddQuantity(amount uint64) { pf.quantity += amount pf.updatedAt = time.Now() } // ConsumeQuantity 消耗数量 func (pf *PetFragment) ConsumeQuantity(amount uint64) error { if pf.quantity < amount { return ErrInsufficientFragments } pf.quantity -= amount pf.updatedAt = time.Now() return nil } // CanSummon 是否可以召唤宠物 func (pf *PetFragment) CanSummon(requiredQuantity uint64) bool { return pf.quantity >= requiredQuantity } // GetCreatedAt 获取创建时间 func (pf *PetFragment) GetCreatedAt() time.Time { return pf.createdAt } // GetUpdatedAt 获取更新时间 func (pf *PetFragment) GetUpdatedAt() time.Time { return pf.updatedAt } // PetSkin 宠物皮肤实体 type PetSkin struct { id string skinID string name string rarity PetRarity equipped bool powerBonus int64 attributeBonus map[string]float64 unlocked bool unlockTime time.Time createdAt time.Time updatedAt time.Time } // NewPetSkin 创建新的宠物皮肤 func NewPetSkin(skinID, name string, rarity PetRarity, powerBonus int64) *PetSkin { now := time.Now() return &PetSkin{ id: fmt.Sprintf("skin_%d", now.UnixNano()), skinID: skinID, name: name, rarity: rarity, equipped: false, powerBonus: powerBonus, attributeBonus: make(map[string]float64), unlocked: false, createdAt: now, updatedAt: now, } } // GetID 获取皮肤实体ID func (ps *PetSkin) GetID() string { return ps.id } // GetSkinID 获取皮肤配置ID func (ps *PetSkin) GetSkinID() string { return ps.skinID } // GetName 获取皮肤名称 func (ps *PetSkin) GetName() string { return ps.name } // GetRarity 获取稀有度 func (ps *PetSkin) GetRarity() PetRarity { return ps.rarity } // IsEquipped 是否已装备 func (ps *PetSkin) IsEquipped() bool { return ps.equipped } // GetPowerBonus 获取战力加成 func (ps *PetSkin) GetPowerBonus() int64 { return ps.powerBonus } // GetAttributeBonus 获取属性加成 func (ps *PetSkin) GetAttributeBonus() map[string]float64 { return ps.attributeBonus } // IsUnlocked 是否已解锁 func (ps *PetSkin) IsUnlocked() bool { return ps.unlocked } // Unlock 解锁皮肤 func (ps *PetSkin) Unlock() error { if ps.unlocked { return ErrSkinAlreadyUnlocked } ps.unlocked = true ps.unlockTime = time.Now() ps.updatedAt = time.Now() return nil } // Equip 装备皮肤 func (ps *PetSkin) Equip() error { if !ps.unlocked { return ErrSkinNotUnlocked } if ps.equipped { return ErrSkinAlreadyEquipped } ps.equipped = true ps.updatedAt = time.Now() return nil } // Unequip 卸下皮肤 func (ps *PetSkin) Unequip() { ps.equipped = false ps.updatedAt = time.Now() } // SetAttributeBonus 设置属性加成 func (ps *PetSkin) SetAttributeBonus(attribute string, bonus float64) { ps.attributeBonus[attribute] = bonus ps.updatedAt = time.Now() } // GetUnlockTime 获取解锁时间 func (ps *PetSkin) GetUnlockTime() time.Time { return ps.unlockTime } // GetCreatedAt 获取创建时间 func (ps *PetSkin) GetCreatedAt() time.Time { return ps.createdAt } // GetUpdatedAt 获取更新时间 func (ps *PetSkin) GetUpdatedAt() time.Time { return ps.updatedAt } // PetSkill 宠物技能实体 type PetSkill struct { id string skillID string name string level uint32 experience uint64 cooldown time.Duration lastUsed time.Time skillType SkillType damage int64 effects []SkillEffect createdAt time.Time updatedAt time.Time } // SkillType 技能类型 type SkillType int const ( SkillTypeAttack SkillType = 1 // 攻击技能 SkillTypeDefense SkillType = 2 // 防御技能 SkillTypeHeal SkillType = 3 // 治疗技能 SkillTypeBuff SkillType = 4 // 增益技能 SkillTypeDebuff SkillType = 5 // 减益技能 ) // SkillEffect 技能效果 type SkillEffect struct { EffectType string Value float64 Duration time.Duration } // NewPetSkill 创建新的宠物技能 func NewPetSkill(skillID, name string, skillType SkillType, cooldown time.Duration, damage int64, description string) *PetSkill { now := time.Now() return &PetSkill{ id: fmt.Sprintf("skill_%d", now.UnixNano()), skillID: skillID, name: name, level: 1, experience: 0, cooldown: cooldown, skillType: skillType, damage: damage, effects: make([]SkillEffect, 0), createdAt: now, updatedAt: now, } } // GetDescription 获取技能描述 func (ps *PetSkill) GetDescription() string { return fmt.Sprintf("%s - Level %d", ps.name, ps.level) } // GetType 获取技能类型(为了兼容性) func (ps *PetSkill) GetType() SkillType { return ps.skillType } // GetID 获取技能实体ID func (ps *PetSkill) GetID() string { return ps.id } // GetSkillID 获取技能配置ID func (ps *PetSkill) GetSkillID() string { return ps.skillID } // GetName 获取技能名称 func (ps *PetSkill) GetName() string { return ps.name } // GetLevel 获取技能等级 func (ps *PetSkill) GetLevel() uint32 { return ps.level } // GetExperience 获取技能经验 func (ps *PetSkill) GetExperience() uint64 { return ps.experience } // GetCooldown 获取冷却时间 func (ps *PetSkill) GetCooldown() time.Duration { return ps.cooldown } // GetLastUsed 获取上次使用时间 func (ps *PetSkill) GetLastUsed() time.Time { return ps.lastUsed } // GetSkillType 获取技能类型 func (ps *PetSkill) GetSkillType() SkillType { return ps.skillType } // GetDamage 获取技能伤害 func (ps *PetSkill) GetDamage() int64 { return ps.damage } // GetEffects 获取技能效果 func (ps *PetSkill) GetEffects() []SkillEffect { return ps.effects } // AddExperience 增加技能经验 func (ps *PetSkill) AddExperience(exp uint64) { ps.experience += exp ps.updatedAt = time.Now() // 检查是否可以升级 if ps.canLevelUp() { ps.levelUp() } } // canLevelUp 检查是否可以升级 func (ps *PetSkill) canLevelUp() bool { requiredExp := ps.calculateRequiredExperience() return ps.experience >= requiredExp && ps.level < MaxSkillLevel } // levelUp 技能升级 func (ps *PetSkill) levelUp() { ps.level++ ps.damage = int64(float64(ps.damage) * 1.1) // 每级增加10%伤害 ps.updatedAt = time.Now() } // calculateRequiredExperience 计算升级所需经验 func (ps *PetSkill) calculateRequiredExperience() uint64 { return uint64(ps.level * ps.level * 50) } // Upgrade 升级技能 func (ps *PetSkill) Upgrade() error { if ps.level >= MaxSkillLevel { return ErrMaxSkillLevelReached } if !ps.canLevelUp() { return ErrInsufficientSkillExperience } ps.levelUp() return nil } // Use 使用技能 func (ps *PetSkill) Use() error { if !ps.IsReady() { return ErrSkillOnCooldown } ps.lastUsed = time.Now() ps.updatedAt = time.Now() return nil } // IsReady 技能是否准备就绪 func (ps *PetSkill) IsReady() bool { return time.Since(ps.lastUsed) >= ps.cooldown } // GetRemainingCooldown 获取剩余冷却时间 func (ps *PetSkill) GetRemainingCooldown() time.Duration { elapsed := time.Since(ps.lastUsed) if elapsed >= ps.cooldown { return 0 } return ps.cooldown - elapsed } // AddEffect 添加技能效果 func (ps *PetSkill) AddEffect(effect SkillEffect) { ps.effects = append(ps.effects, effect) ps.updatedAt = time.Now() } // GetCreatedAt 获取创建时间 func (ps *PetSkill) GetCreatedAt() time.Time { return ps.createdAt } // GetUpdatedAt 获取更新时间 func (ps *PetSkill) GetUpdatedAt() time.Time { return ps.updatedAt } // PetBonds 宠物羁绊实体 type PetBonds struct { id string activeBonds []*ActiveBond bondPoints int64 bondEffects map[string]*BondEffect createdAt time.Time updatedAt time.Time } // ActiveBond 激活的羁绊 type ActiveBond struct { bondID string name string level uint32 effect string activatedAt time.Time } // NewActiveBond 创建新的激活羁绊 func NewActiveBond(bondID, name string, level uint32, effect string) *ActiveBond { return &ActiveBond{ bondID: bondID, name: name, level: level, effect: effect, activatedAt: time.Now(), } } // GetBondID 获取羁绊ID func (ab *ActiveBond) GetBondID() string { return ab.bondID } // GetName 获取羁绊名称 func (ab *ActiveBond) GetName() string { return ab.name } // GetLevel 获取羁绊等级 func (ab *ActiveBond) GetLevel() uint32 { return ab.level } // GetEffect 获取羁绊效果 func (ab *ActiveBond) GetEffect() string { return ab.effect } // GetActivatedAt 获取激活时间 func (ab *ActiveBond) GetActivatedAt() time.Time { return ab.activatedAt } // BondEffect 羁绊效果 type BondEffect struct { BondID string Name string Description string PowerBonus int64 Attributes map[string]float64 Active bool ActivatedAt time.Time } // NewPetBonds 创建新的宠物羁绊 func NewPetBonds() *PetBonds { now := time.Now() return &PetBonds{ id: fmt.Sprintf("bonds_%d", now.UnixNano()), activeBonds: make([]*ActiveBond, 0), bondPoints: 0, bondEffects: make(map[string]*BondEffect), createdAt: now, updatedAt: now, } } // GetID 获取羁绊ID func (pb *PetBonds) GetID() string { return pb.id } // GetActiveBonds 获取激活的羁绊 func (pb *PetBonds) GetActiveBonds() []*ActiveBond { return pb.activeBonds } // GetBondPoints 获取羁绊点数 func (pb *PetBonds) GetBondPoints() int64 { return pb.bondPoints } // AddBondPoints 增加羁绊点数 func (pb *PetBonds) AddBondPoints(points int64) { pb.bondPoints += points pb.updatedAt = time.Now() } // AddActiveBond 添加激活羁绊 func (pb *PetBonds) AddActiveBond(bond *ActiveBond) { pb.activeBonds = append(pb.activeBonds, bond) pb.updatedAt = time.Now() } // GetBondEffects 获取羁绊效果 func (pb *PetBonds) GetBondEffects() map[string]*BondEffect { return pb.bondEffects } // ActivateBond 激活羁绊 func (pb *PetBonds) ActivateBond(bondID string) error { // 检查是否已激活 for _, activeBond := range pb.activeBonds { if activeBond.GetBondID() == bondID { return ErrBondAlreadyActive } } // 检查是否达到最大激活数量 if len(pb.activeBonds) >= MaxActiveBonds { return ErrMaxActiveBondsReached } // 创建新的激活羁绊 bond := NewActiveBond(bondID, "Bond", 1, "Effect") pb.activeBonds = append(pb.activeBonds, bond) // 激活羁绊效果 if effect, exists := pb.bondEffects[bondID]; exists { effect.Active = true effect.ActivatedAt = time.Now() } pb.updatedAt = time.Now() return nil } // DeactivateBond 取消羁绊 func (pb *PetBonds) DeactivateBond(bondID string) error { // 查找并移除激活的羁绊 for i, activeBond := range pb.activeBonds { if activeBond.GetBondID() == bondID { pb.activeBonds = append(pb.activeBonds[:i], pb.activeBonds[i+1:]...) // 取消羁绊效果 if effect, exists := pb.bondEffects[bondID]; exists { effect.Active = false } pb.updatedAt = time.Now() return nil } } return ErrBondNotActive } // AddBondEffect 添加羁绊效果 func (pb *PetBonds) AddBondEffect(effect *BondEffect) { pb.bondEffects[effect.BondID] = effect pb.updatedAt = time.Now() } // GetPowerBonus 获取羁绊战力加成 func (pb *PetBonds) GetPowerBonus() int64 { var totalBonus int64 for _, bond := range pb.activeBonds { if effect, exists := pb.bondEffects[bond.bondID]; exists && effect.Active { totalBonus += effect.PowerBonus } } return totalBonus } // GetAttributeBonus 获取羁绊属性加成 func (pb *PetBonds) GetAttributeBonus() map[string]float64 { attributeBonus := make(map[string]float64) for _, bond := range pb.activeBonds { if effect, exists := pb.bondEffects[bond.bondID]; exists && effect.Active { for attr, bonus := range effect.Attributes { attributeBonus[attr] += bonus } } } return attributeBonus } // IsBondActive 检查羁绊是否激活 func (pb *PetBonds) IsBondActive(bondID string) bool { for _, activeBond := range pb.activeBonds { if activeBond.GetBondID() == bondID { return true } } return false } // GetActiveCount 获取激活羁绊数量 func (pb *PetBonds) GetActiveCount() int { return len(pb.activeBonds) } // GetCreatedAt 获取创建时间 func (pb *PetBonds) GetCreatedAt() time.Time { return pb.createdAt } // GetUpdatedAt 获取更新时间 func (pb *PetBonds) GetUpdatedAt() time.Time { return pb.updatedAt } // PetPictorial 宠物图鉴实体 type PetPictorial struct { id string playerID string petConfigID uint32 unlocked bool highestLevel uint32 highestStar uint32 firstSeen time.Time lastSeen time.Time createdAt time.Time updatedAt time.Time } // NewPetPictorial 创建新的宠物图鉴 func NewPetPictorial(playerID string, petConfigID uint32) *PetPictorial { now := time.Now() return &PetPictorial{ id: fmt.Sprintf("pictorial_%d", now.UnixNano()), playerID: playerID, petConfigID: petConfigID, unlocked: false, createdAt: now, updatedAt: now, } } // GetID 获取图鉴ID func (pp *PetPictorial) GetID() string { return pp.id } // GetPlayerID 获取玩家ID func (pp *PetPictorial) GetPlayerID() string { return pp.playerID } // GetPetConfigID 获取宠物配置ID func (pp *PetPictorial) GetPetConfigID() uint32 { return pp.petConfigID } // IsUnlocked 是否已解锁 func (pp *PetPictorial) IsUnlocked() bool { return pp.unlocked } // GetHighestLevel 获取最高等级 func (pp *PetPictorial) GetHighestLevel() uint32 { return pp.highestLevel } // GetHighestStar 获取最高星级 func (pp *PetPictorial) GetHighestStar() uint32 { return pp.highestStar } // Unlock 解锁图鉴 func (pp *PetPictorial) Unlock() { if !pp.unlocked { pp.unlocked = true pp.firstSeen = time.Now() } pp.lastSeen = time.Now() pp.updatedAt = time.Now() } // UpdateRecord 更新记录 func (pp *PetPictorial) UpdateRecord(level, star uint32) { if level > pp.highestLevel { pp.highestLevel = level } if star > pp.highestStar { pp.highestStar = star } pp.lastSeen = time.Now() pp.updatedAt = time.Now() } // GetFirstSeen 获取首次见到时间 func (pp *PetPictorial) GetFirstSeen() time.Time { return pp.firstSeen } // GetLastSeen 获取最后见到时间 func (pp *PetPictorial) GetLastSeen() time.Time { return pp.lastSeen } // GetCreatedAt 获取创建时间 func (pp *PetPictorial) GetCreatedAt() time.Time { return pp.createdAt } // GetUpdatedAt 获取更新时间 func (pp *PetPictorial) GetUpdatedAt() time.Time { return pp.updatedAt } // 常量定义 const ( MaxSkillLevel = 10 MaxActiveBonds = 3 ) ================================================ FILE: internal/domain/pet/errors.go ================================================ package pet import ( "errors" "fmt" ) // 宠物领域错误定义 // 常用错误变量 var ( ErrPetNotFound = errors.New("pet not found") ) // PetError 宠物错误基础接口 type PetError interface { error GetCode() string GetMessage() string GetDetails() map[string]interface{} IsRetryable() bool GetSeverity() ErrorSeverity } // ErrorSeverity 错误严重程度 type ErrorSeverity int const ( ErrorSeverityLow ErrorSeverity = iota ErrorSeverityMedium ErrorSeverityHigh ErrorSeverityCritical ) // String 返回错误严重程度的字符串表示 func (s ErrorSeverity) String() string { switch s { case ErrorSeverityLow: return "low" case ErrorSeverityMedium: return "medium" case ErrorSeverityHigh: return "high" case ErrorSeverityCritical: return "critical" default: return "unknown" } } // BasePetError 宠物错误基础结构 type BasePetError struct { Code string `json:"code"` Message string `json:"message"` Details map[string]interface{} `json:"details"` Retryable bool `json:"retryable"` Severity ErrorSeverity `json:"severity"` } // Error 实现error接口 func (e *BasePetError) Error() string { return fmt.Sprintf("[%s] %s", e.Code, e.Message) } // GetCode 获取错误代码 func (e *BasePetError) GetCode() string { return e.Code } // GetMessage 获取错误消息 func (e *BasePetError) GetMessage() string { return e.Message } // GetDetails 获取错误详情 func (e *BasePetError) GetDetails() map[string]interface{} { return e.Details } // IsRetryable 是否可重试 func (e *BasePetError) IsRetryable() bool { return e.Retryable } // GetSeverity 获取错误严重程度 func (e *BasePetError) GetSeverity() ErrorSeverity { return e.Severity } // 宠物相关错误 // PetNotFoundError 宠物未找到错误 type PetNotFoundError struct { *BasePetError PetID string `json:"pet_id"` } // NewPetNotFoundError 创建宠物未找到错误 func NewPetNotFoundError(petID string) *PetNotFoundError { return &PetNotFoundError{ BasePetError: &BasePetError{ Code: "PET_NOT_FOUND", Message: fmt.Sprintf("Pet with ID %s not found", petID), Details: map[string]interface{}{"pet_id": petID}, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, } } // PetAlreadyExistsError 宠物已存在错误 type PetAlreadyExistsError struct { *BasePetError PetID string `json:"pet_id"` } // NewPetAlreadyExistsError 创建宠物已存在错误 func NewPetAlreadyExistsError(petID string) *PetAlreadyExistsError { return &PetAlreadyExistsError{ BasePetError: &BasePetError{ Code: "PET_ALREADY_EXISTS", Message: fmt.Sprintf("Pet with ID %s already exists", petID), Details: map[string]interface{}{"pet_id": petID}, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, } } // PetInvalidStateError 宠物状态无效错误 type PetInvalidStateError struct { *BasePetError PetID string `json:"pet_id"` CurrentState PetState `json:"current_state"` RequiredState PetState `json:"required_state"` Operation string `json:"operation"` } // NewPetInvalidStateError 创建宠物状态无效错误 func NewPetInvalidStateError(petID string, currentState, requiredState PetState, operation string) *PetInvalidStateError { return &PetInvalidStateError{ BasePetError: &BasePetError{ Code: "PET_INVALID_STATE", Message: fmt.Sprintf("Pet %s is in state %s, but %s is required for operation %s", petID, currentState, requiredState, operation), Details: map[string]interface{}{ "pet_id": petID, "current_state": currentState, "required_state": requiredState, "operation": operation, }, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, CurrentState: currentState, RequiredState: requiredState, Operation: operation, } } // PetMaxLevelReachedError 宠物达到最大等级错误 type PetMaxLevelReachedError struct { *BasePetError PetID string `json:"pet_id"` MaxLevel uint32 `json:"max_level"` } // NewPetMaxLevelReachedError 创建宠物达到最大等级错误 func NewPetMaxLevelReachedError(petID string, maxLevel uint32) *PetMaxLevelReachedError { return &PetMaxLevelReachedError{ BasePetError: &BasePetError{ Code: "PET_MAX_LEVEL_REACHED", Message: fmt.Sprintf("Pet %s has reached maximum level %d", petID, maxLevel), Details: map[string]interface{}{"pet_id": petID, "max_level": maxLevel}, Retryable: false, Severity: ErrorSeverityLow, }, PetID: petID, MaxLevel: maxLevel, } } // PetInsufficientExperienceError 宠物经验不足错误 type PetInsufficientExperienceError struct { *BasePetError PetID string `json:"pet_id"` CurrentExperience uint64 `json:"current_experience"` RequiredExperience uint64 `json:"required_experience"` } // NewPetInsufficientExperienceError 创建宠物经验不足错误 func NewPetInsufficientExperienceError(petID string, current, required uint64) *PetInsufficientExperienceError { return &PetInsufficientExperienceError{ BasePetError: &BasePetError{ Code: "PET_INSUFFICIENT_EXPERIENCE", Message: fmt.Sprintf("Pet %s has insufficient experience: %d/%d", petID, current, required), Details: map[string]interface{}{ "pet_id": petID, "current_experience": current, "required_experience": required, }, Retryable: false, Severity: ErrorSeverityLow, }, PetID: petID, CurrentExperience: current, RequiredExperience: required, } } // PetDeadError 宠物死亡错误 type PetDeadError struct { *BasePetError PetID string `json:"pet_id"` Operation string `json:"operation"` } // NewPetDeadError 创建宠物死亡错误 func NewPetDeadError(petID, operation string) *PetDeadError { return &PetDeadError{ BasePetError: &BasePetError{ Code: "PET_DEAD", Message: fmt.Sprintf("Pet %s is dead and cannot perform operation: %s", petID, operation), Details: map[string]interface{}{"pet_id": petID, "operation": operation}, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, Operation: operation, } } // 宠物技能相关错误 // PetSkillNotFoundError 宠物技能未找到错误 type PetSkillNotFoundError struct { *BasePetError PetID string `json:"pet_id"` SkillID string `json:"skill_id"` } // NewPetSkillNotFoundError 创建宠物技能未找到错误 func NewPetSkillNotFoundError(petID, skillID string) *PetSkillNotFoundError { return &PetSkillNotFoundError{ BasePetError: &BasePetError{ Code: "PET_SKILL_NOT_FOUND", Message: fmt.Sprintf("Skill %s not found for pet %s", skillID, petID), Details: map[string]interface{}{"pet_id": petID, "skill_id": skillID}, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, SkillID: skillID, } } // PetSkillOnCooldownError 宠物技能冷却中错误 type PetSkillOnCooldownError struct { *BasePetError PetID string `json:"pet_id"` SkillID string `json:"skill_id"` RemainingCooldown int64 `json:"remaining_cooldown"` } // NewPetSkillOnCooldownError 创建宠物技能冷却中错误 func NewPetSkillOnCooldownError(petID, skillID string, remainingCooldown int64) *PetSkillOnCooldownError { return &PetSkillOnCooldownError{ BasePetError: &BasePetError{ Code: "PET_SKILL_ON_COOLDOWN", Message: fmt.Sprintf("Skill %s for pet %s is on cooldown for %d seconds", skillID, petID, remainingCooldown), Details: map[string]interface{}{ "pet_id": petID, "skill_id": skillID, "remaining_cooldown": remainingCooldown, }, Retryable: true, Severity: ErrorSeverityLow, }, PetID: petID, SkillID: skillID, RemainingCooldown: remainingCooldown, } } // PetSkillMaxLevelError 宠物技能达到最大等级错误 type PetSkillMaxLevelError struct { *BasePetError PetID string `json:"pet_id"` SkillID string `json:"skill_id"` MaxLevel uint32 `json:"max_level"` } // NewPetSkillMaxLevelError 创建宠物技能达到最大等级错误 func NewPetSkillMaxLevelError(petID, skillID string, maxLevel uint32) *PetSkillMaxLevelError { return &PetSkillMaxLevelError{ BasePetError: &BasePetError{ Code: "PET_SKILL_MAX_LEVEL", Message: fmt.Sprintf("Skill %s for pet %s has reached maximum level %d", skillID, petID, maxLevel), Details: map[string]interface{}{"pet_id": petID, "skill_id": skillID, "max_level": maxLevel}, Retryable: false, Severity: ErrorSeverityLow, }, PetID: petID, SkillID: skillID, MaxLevel: maxLevel, } } // 宠物碎片相关错误 // PetFragmentNotFoundError 宠物碎片未找到错误 type PetFragmentNotFoundError struct { *BasePetError PlayerID string `json:"player_id"` FragmentID uint32 `json:"fragment_id"` } // NewPetFragmentNotFoundError 创建宠物碎片未找到错误 func NewPetFragmentNotFoundError(playerID string, fragmentID uint32) *PetFragmentNotFoundError { return &PetFragmentNotFoundError{ BasePetError: &BasePetError{ Code: "PET_FRAGMENT_NOT_FOUND", Message: fmt.Sprintf("Fragment %d not found for player %s", fragmentID, playerID), Details: map[string]interface{}{"player_id": playerID, "fragment_id": fragmentID}, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, FragmentID: fragmentID, } } // PetFragmentInsufficientError 宠物碎片不足错误 type PetFragmentInsufficientError struct { *BasePetError PlayerID string `json:"player_id"` FragmentID uint32 `json:"fragment_id"` CurrentQuantity uint64 `json:"current_quantity"` RequiredQuantity uint64 `json:"required_quantity"` } // NewPetFragmentInsufficientError 创建宠物碎片不足错误 func NewPetFragmentInsufficientError(playerID string, fragmentID uint32, current, required uint64) *PetFragmentInsufficientError { return &PetFragmentInsufficientError{ BasePetError: &BasePetError{ Code: "PET_FRAGMENT_INSUFFICIENT", Message: fmt.Sprintf("Insufficient fragments %d for player %s: %d/%d", fragmentID, playerID, current, required), Details: map[string]interface{}{ "player_id": playerID, "fragment_id": fragmentID, "current_quantity": current, "required_quantity": required, }, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, FragmentID: fragmentID, CurrentQuantity: current, RequiredQuantity: required, } } // 宠物皮肤相关错误 // PetSkinNotFoundError 宠物皮肤未找到错误 type PetSkinNotFoundError struct { *BasePetError SkinID string `json:"skin_id"` } // NewPetSkinNotFoundError 创建宠物皮肤未找到错误 func NewPetSkinNotFoundError(skinID string) *PetSkinNotFoundError { return &PetSkinNotFoundError{ BasePetError: &BasePetError{ Code: "PET_SKIN_NOT_FOUND", Message: fmt.Sprintf("Pet skin %s not found", skinID), Details: map[string]interface{}{"skin_id": skinID}, Retryable: false, Severity: ErrorSeverityMedium, }, SkinID: skinID, } } // PetSkinNotUnlockedError 宠物皮肤未解锁错误 type PetSkinNotUnlockedError struct { *BasePetError SkinID string `json:"skin_id"` PlayerID string `json:"player_id"` } // NewPetSkinNotUnlockedError 创建宠物皮肤未解锁错误 func NewPetSkinNotUnlockedError(skinID, playerID string) *PetSkinNotUnlockedError { return &PetSkinNotUnlockedError{ BasePetError: &BasePetError{ Code: "PET_SKIN_NOT_UNLOCKED", Message: fmt.Sprintf("Pet skin %s is not unlocked for player %s", skinID, playerID), Details: map[string]interface{}{"skin_id": skinID, "player_id": playerID}, Retryable: false, Severity: ErrorSeverityMedium, }, SkinID: skinID, PlayerID: playerID, } } // PetSkinIncompatibleError 宠物皮肤不兼容错误 type PetSkinIncompatibleError struct { *BasePetError PetID string `json:"pet_id"` SkinID string `json:"skin_id"` PetCategory PetCategory `json:"pet_category"` SkinCategory PetCategory `json:"skin_category"` } // NewPetSkinIncompatibleError 创建宠物皮肤不兼容错误 func NewPetSkinIncompatibleError(petID, skinID string, petCategory, skinCategory PetCategory) *PetSkinIncompatibleError { return &PetSkinIncompatibleError{ BasePetError: &BasePetError{ Code: "PET_SKIN_INCOMPATIBLE", Message: fmt.Sprintf("Skin %s (category: %s) is incompatible with pet %s (category: %s)", skinID, skinCategory, petID, petCategory), Details: map[string]interface{}{ "pet_id": petID, "skin_id": skinID, "pet_category": petCategory, "skin_category": skinCategory, }, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, SkinID: skinID, PetCategory: petCategory, SkinCategory: skinCategory, } } // 宠物羁绊相关错误 // PetBondNotFoundError 宠物羁绊未找到错误 type PetBondNotFoundError struct { *BasePetError BondID string `json:"bond_id"` } // NewPetBondNotFoundError 创建宠物羁绊未找到错误 func NewPetBondNotFoundError(bondID string) *PetBondNotFoundError { return &PetBondNotFoundError{ BasePetError: &BasePetError{ Code: "PET_BOND_NOT_FOUND", Message: fmt.Sprintf("Pet bond %s not found", bondID), Details: map[string]interface{}{"bond_id": bondID}, Retryable: false, Severity: ErrorSeverityMedium, }, BondID: bondID, } } // PetBondRequirementsNotMetError 宠物羁绊条件未满足错误 type PetBondRequirementsNotMetError struct { *BasePetError BondID string `json:"bond_id"` RequiredPets []string `json:"required_pets"` CurrentPets []string `json:"current_pets"` MissingPets []string `json:"missing_pets"` } // NewPetBondRequirementsNotMetError 创建宠物羁绊条件未满足错误 func NewPetBondRequirementsNotMetError(bondID string, required, current, missing []string) *PetBondRequirementsNotMetError { return &PetBondRequirementsNotMetError{ BasePetError: &BasePetError{ Code: "PET_BOND_REQUIREMENTS_NOT_MET", Message: fmt.Sprintf("Bond %s requirements not met, missing pets: %v", bondID, missing), Details: map[string]interface{}{ "bond_id": bondID, "required_pets": required, "current_pets": current, "missing_pets": missing, }, Retryable: false, Severity: ErrorSeverityMedium, }, BondID: bondID, RequiredPets: required, CurrentPets: current, MissingPets: missing, } } // 宠物图鉴相关错误 // PetPictorialNotFoundError 宠物图鉴未找到错误 type PetPictorialNotFoundError struct { *BasePetError PlayerID string `json:"player_id"` PetConfigID uint32 `json:"pet_config_id"` } // NewPetPictorialNotFoundError 创建宠物图鉴未找到错误 func NewPetPictorialNotFoundError(playerID string, petConfigID uint32) *PetPictorialNotFoundError { return &PetPictorialNotFoundError{ BasePetError: &BasePetError{ Code: "PET_PICTORIAL_NOT_FOUND", Message: fmt.Sprintf("Pet pictorial for config %d not found for player %s", petConfigID, playerID), Details: map[string]interface{}{"player_id": playerID, "pet_config_id": petConfigID}, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, PetConfigID: petConfigID, } } // PetPictorialAlreadyUnlockedError 宠物图鉴已解锁错误 type PetPictorialAlreadyUnlockedError struct { *BasePetError PlayerID string `json:"player_id"` PetConfigID uint32 `json:"pet_config_id"` } // NewPetPictorialAlreadyUnlockedError 创建宠物图鉴已解锁错误 func NewPetPictorialAlreadyUnlockedError(playerID string, petConfigID uint32) *PetPictorialAlreadyUnlockedError { return &PetPictorialAlreadyUnlockedError{ BasePetError: &BasePetError{ Code: "PET_PICTORIAL_ALREADY_UNLOCKED", Message: fmt.Sprintf("Pet pictorial for config %d is already unlocked for player %s", petConfigID, playerID), Details: map[string]interface{}{"player_id": playerID, "pet_config_id": petConfigID}, Retryable: false, Severity: ErrorSeverityLow, }, PlayerID: playerID, PetConfigID: petConfigID, } } // 资源相关错误 // PetInsufficientResourcesError 宠物资源不足错误 type PetInsufficientResourcesError struct { *BasePetError PlayerID string `json:"player_id"` ResourceType string `json:"resource_type"` CurrentAmount int64 `json:"current_amount"` RequiredAmount int64 `json:"required_amount"` Operation string `json:"operation"` AdditionalCosts map[string]int64 `json:"additional_costs,omitempty"` } // NewPetInsufficientResourcesError 创建宠物资源不足错误 func NewPetInsufficientResourcesError(playerID, resourceType string, current, required int64, operation string) *PetInsufficientResourcesError { return &PetInsufficientResourcesError{ BasePetError: &BasePetError{ Code: "PET_INSUFFICIENT_RESOURCES", Message: fmt.Sprintf("Insufficient %s for player %s: %d/%d (operation: %s)", resourceType, playerID, current, required, operation), Details: map[string]interface{}{ "player_id": playerID, "resource_type": resourceType, "current_amount": current, "required_amount": required, "operation": operation, }, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, ResourceType: resourceType, CurrentAmount: current, RequiredAmount: required, Operation: operation, } } // 配置相关错误 // PetConfigNotFoundError 宠物配置未找到错误 type PetConfigNotFoundError struct { *BasePetError ConfigID uint32 `json:"config_id"` } // NewPetConfigNotFoundError 创建宠物配置未找到错误 func NewPetConfigNotFoundError(configID uint32) *PetConfigNotFoundError { return &PetConfigNotFoundError{ BasePetError: &BasePetError{ Code: "PET_CONFIG_NOT_FOUND", Message: fmt.Sprintf("Pet configuration %d not found", configID), Details: map[string]interface{}{"config_id": configID}, Retryable: false, Severity: ErrorSeverityHigh, }, ConfigID: configID, } } // PetConfigInvalidError 宠物配置无效错误 type PetConfigInvalidError struct { *BasePetError ConfigID uint32 `json:"config_id"` Reason string `json:"reason"` } // NewPetConfigInvalidError 创建宠物配置无效错误 func NewPetConfigInvalidError(configID uint32, reason string) *PetConfigInvalidError { return &PetConfigInvalidError{ BasePetError: &BasePetError{ Code: "PET_CONFIG_INVALID", Message: fmt.Sprintf("Pet configuration %d is invalid: %s", configID, reason), Details: map[string]interface{}{"config_id": configID, "reason": reason}, Retryable: false, Severity: ErrorSeverityHigh, }, ConfigID: configID, Reason: reason, } } // 业务逻辑错误 // PetOperationNotAllowedError 宠物操作不允许错误 type PetOperationNotAllowedError struct { *BasePetError PetID string `json:"pet_id"` Operation string `json:"operation"` Reason string `json:"reason"` } // NewPetOperationNotAllowedError 创建宠物操作不允许错误 func NewPetOperationNotAllowedError(petID, operation, reason string) *PetOperationNotAllowedError { return &PetOperationNotAllowedError{ BasePetError: &BasePetError{ Code: "PET_OPERATION_NOT_ALLOWED", Message: fmt.Sprintf("Operation %s not allowed for pet %s: %s", operation, petID, reason), Details: map[string]interface{}{"pet_id": petID, "operation": operation, "reason": reason}, Retryable: false, Severity: ErrorSeverityMedium, }, PetID: petID, Operation: operation, Reason: reason, } } // PetLimitExceededError 宠物限制超出错误 type PetLimitExceededError struct { *BasePetError PlayerID string `json:"player_id"` CurrentCount int32 `json:"current_count"` MaxCount int32 `json:"max_count"` LimitType string `json:"limit_type"` } // NewPetLimitExceededError 创建宠物限制超出错误 func NewPetLimitExceededError(playerID string, current, max int32, limitType string) *PetLimitExceededError { return &PetLimitExceededError{ BasePetError: &BasePetError{ Code: "PET_LIMIT_EXCEEDED", Message: fmt.Sprintf("%s limit exceeded for player %s: %d/%d", limitType, playerID, current, max), Details: map[string]interface{}{ "player_id": playerID, "current_count": current, "max_count": max, "limit_type": limitType, }, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, CurrentCount: current, MaxCount: max, LimitType: limitType, } } // 系统错误 // PetSystemError 宠物系统错误 type PetSystemError struct { *BasePetError SystemComponent string `json:"system_component"` InternalError error `json:"internal_error,omitempty"` } // NewPetSystemError 创建宠物系统错误 func NewPetSystemError(component, message string, internalErr error) *PetSystemError { return &PetSystemError{ BasePetError: &BasePetError{ Code: "PET_SYSTEM_ERROR", Message: fmt.Sprintf("System error in %s: %s", component, message), Details: map[string]interface{}{"system_component": component}, Retryable: true, Severity: ErrorSeverityCritical, }, SystemComponent: component, InternalError: internalErr, } } // PetDatabaseError 宠物数据库错误 type PetDatabaseError struct { *BasePetError Operation string `json:"operation"` Table string `json:"table"` InternalError error `json:"internal_error,omitempty"` } // NewPetDatabaseError 创建宠物数据库错误 func NewPetDatabaseError(operation, table, message string, internalErr error) *PetDatabaseError { return &PetDatabaseError{ BasePetError: &BasePetError{ Code: "PET_DATABASE_ERROR", Message: fmt.Sprintf("Database error during %s on table %s: %s", operation, table, message), Details: map[string]interface{}{"operation": operation, "table": table}, Retryable: true, Severity: ErrorSeverityHigh, }, Operation: operation, Table: table, InternalError: internalErr, } } // PetCacheError 宠物缓存错误 type PetCacheError struct { *BasePetError Operation string `json:"operation"` Key string `json:"key"` InternalError error `json:"internal_error,omitempty"` } // NewPetCacheError 创建宠物缓存错误 func NewPetCacheError(operation, key, message string, internalErr error) *PetCacheError { return &PetCacheError{ BasePetError: &BasePetError{ Code: "PET_CACHE_ERROR", Message: fmt.Sprintf("Cache error during %s for key %s: %s", operation, key, message), Details: map[string]interface{}{"operation": operation, "key": key}, Retryable: true, Severity: ErrorSeverityMedium, }, Operation: operation, Key: key, InternalError: internalErr, } } // 验证错误 // PetValidationError 宠物验证错误 type PetValidationError struct { *BasePetError Field string `json:"field"` Value interface{} `json:"value"` Constraint string `json:"constraint"` ValidationRule string `json:"validation_rule"` } // NewPetValidationError 创建宠物验证错误 func NewPetValidationError(field string, value interface{}, constraint, rule string) *PetValidationError { return &PetValidationError{ BasePetError: &BasePetError{ Code: "PET_VALIDATION_ERROR", Message: fmt.Sprintf("Validation failed for field %s: %s (rule: %s)", field, constraint, rule), Details: map[string]interface{}{ "field": field, "value": value, "constraint": constraint, "validation_rule": rule, }, Retryable: false, Severity: ErrorSeverityMedium, }, Field: field, Value: value, Constraint: constraint, ValidationRule: rule, } } // 错误代码常量 const ( // 宠物相关错误代码 ErrCodePetNotFound = "PET_NOT_FOUND" ErrCodePetAlreadyExists = "PET_ALREADY_EXISTS" ErrCodePetInvalidState = "PET_INVALID_STATE" ErrCodePetMaxLevelReached = "PET_MAX_LEVEL_REACHED" ErrCodePetInsufficientExp = "PET_INSUFFICIENT_EXPERIENCE" ErrCodePetDead = "PET_DEAD" // 技能相关错误代码 ErrCodePetSkillNotFound = "PET_SKILL_NOT_FOUND" ErrCodePetSkillOnCooldown = "PET_SKILL_ON_COOLDOWN" ErrCodePetSkillMaxLevel = "PET_SKILL_MAX_LEVEL" // 碎片相关错误代码 ErrCodePetFragmentNotFound = "PET_FRAGMENT_NOT_FOUND" ErrCodePetFragmentInsufficient = "PET_FRAGMENT_INSUFFICIENT" // 皮肤相关错误代码 ErrCodePetSkinNotFound = "PET_SKIN_NOT_FOUND" ErrCodePetSkinNotUnlocked = "PET_SKIN_NOT_UNLOCKED" ErrCodePetSkinIncompatible = "PET_SKIN_INCOMPATIBLE" // 羁绊相关错误代码 ErrCodePetBondNotFound = "PET_BOND_NOT_FOUND" ErrCodePetBondRequirementsNotMet = "PET_BOND_REQUIREMENTS_NOT_MET" // 图鉴相关错误代码 ErrCodePetPictorialNotFound = "PET_PICTORIAL_NOT_FOUND" ErrCodePetPictorialAlreadyUnlocked = "PET_PICTORIAL_ALREADY_UNLOCKED" // 资源相关错误代码 ErrCodePetInsufficientResources = "PET_INSUFFICIENT_RESOURCES" // 配置相关错误代码 ErrCodePetConfigNotFound = "PET_CONFIG_NOT_FOUND" ErrCodePetConfigInvalid = "PET_CONFIG_INVALID" // 业务逻辑错误代码 ErrCodePetOperationNotAllowed = "PET_OPERATION_NOT_ALLOWED" ErrCodePetLimitExceeded = "PET_LIMIT_EXCEEDED" // 系统错误代码 ErrCodePetSystemError = "PET_SYSTEM_ERROR" ErrCodePetDatabaseError = "PET_DATABASE_ERROR" ErrCodePetCacheError = "PET_CACHE_ERROR" // 验证错误代码 ErrCodePetValidationError = "PET_VALIDATION_ERROR" ) // 错误工具函数 // IsPetError 检查是否为宠物错误 func IsPetError(err error) bool { _, ok := err.(PetError) return ok } // GetPetErrorCode 获取宠物错误代码 func GetPetErrorCode(err error) string { if petErr, ok := err.(PetError); ok { return petErr.GetCode() } return "" } // IsRetryablePetError 检查是否为可重试的宠物错误 func IsRetryablePetError(err error) bool { if petErr, ok := err.(PetError); ok { return petErr.IsRetryable() } return false } // GetPetErrorSeverity 获取宠物错误严重程度 func GetPetErrorSeverity(err error) ErrorSeverity { if petErr, ok := err.(PetError); ok { return petErr.GetSeverity() } return ErrorSeverityLow } // WrapPetError 包装宠物错误 func WrapPetError(err error, code, message string) PetError { return &BasePetError{ Code: code, Message: fmt.Sprintf("%s: %v", message, err), Details: map[string]interface{}{"wrapped_error": err.Error()}, Retryable: IsRetryablePetError(err), Severity: GetPetErrorSeverity(err), } } // FormatPetError 格式化宠物错误 func FormatPetError(err PetError) string { return fmt.Sprintf("[%s][%s] %s", err.GetSeverity(), err.GetCode(), err.GetMessage()) } // LogPetError 记录宠物错误(占位符函数) func LogPetError(err PetError) { // 实现错误日志记录逻辑 fmt.Printf("PET_ERROR: %s\n", FormatPetError(err)) } // 添加缺失的错误定义 var ( ErrInvalidPetName = fmt.Errorf("invalid pet name") ErrPetIsDead = fmt.Errorf("pet is dead") ErrMaxLevelReached = fmt.Errorf("max level reached") ErrMaxStarReached = fmt.Errorf("max star reached") ErrInvalidStateTransition = fmt.Errorf("invalid state transition") ErrPetNotDead = fmt.Errorf("pet is not dead") ErrReviveTimeNotReached = fmt.Errorf("revive time not reached") ErrMaxSkillsReached = fmt.Errorf("max skills reached") ErrSkillAlreadyExists = fmt.Errorf("skill already exists") ErrSkillNotFound = fmt.Errorf("skill not found") ErrSkinAlreadyOwned = fmt.Errorf("skin already owned") ErrSkinNotOwned = fmt.Errorf("skin not owned") ErrInvalidAmount = fmt.Errorf("invalid amount") ErrInvalidFoodType = fmt.Errorf("invalid food type") ErrPetNotIdle = fmt.Errorf("pet is not idle") ErrPetNotTraining = fmt.Errorf("pet is not training") ErrPetNotInBattle = fmt.Errorf("pet is not in battle") ErrInvalidPetID = fmt.Errorf("invalid pet ID") ErrInvalidPlayerID = fmt.Errorf("invalid player ID") ErrInvalidPetLevel = fmt.Errorf("invalid pet level") ErrInvalidPetStar = fmt.Errorf("invalid pet star") ErrInvalidPetAttributes = fmt.Errorf("invalid pet attributes") ErrInsufficientFragments = fmt.Errorf("insufficient fragments") ErrSkinAlreadyUnlocked = fmt.Errorf("skin already unlocked") ErrSkinNotUnlocked = fmt.Errorf("skin not unlocked") ErrSkinAlreadyEquipped = fmt.Errorf("skin already equipped") ErrMaxSkillLevelReached = fmt.Errorf("max skill level reached") ErrInsufficientSkillExperience = fmt.Errorf("insufficient skill experience") ErrSkillOnCooldown = fmt.Errorf("skill on cooldown") ErrBondAlreadyActive = fmt.Errorf("bond already active") ErrMaxActiveBondsReached = fmt.Errorf("max active bonds reached") ErrBondNotActive = fmt.Errorf("bond not active") ErrPetTemplateNotFound = fmt.Errorf("pet template not found") ErrNoFragmentsProvided = fmt.Errorf("no fragments provided") ErrFragmentMismatch = fmt.Errorf("fragment mismatch") ErrFragmentTemplateNotFound = fmt.Errorf("fragment template not found") ErrInsufficientEvolutionMaterials = fmt.Errorf("insufficient evolution materials") ErrInvalidTrainingType = fmt.Errorf("invalid training type") ErrNoAvailableTemplates = fmt.Errorf("no available templates") ErrDifferentOwners = fmt.Errorf("different owners") ErrInsufficientBreedingLevel = fmt.Errorf("insufficient breeding level") ) ================================================ FILE: internal/domain/pet/events.go ================================================ package pet import ( "fmt" "time" ) // 宠物相关事件定义 // PetEvent 宠物事件基础接口 type PetEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetPlayerID() string GetTimestamp() time.Time GetVersion() int GetMetadata() map[string]interface{} } // BasePetEvent 宠物事件基础结构 type BasePetEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` PlayerID string `json:"player_id"` Timestamp time.Time `json:"timestamp"` Version int `json:"version"` Metadata map[string]interface{} `json:"metadata"` } // GetEventID 获取事件ID func (e *BasePetEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BasePetEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合ID func (e *BasePetEvent) GetAggregateID() string { return e.AggregateID } // GetPlayerID 获取玩家ID func (e *BasePetEvent) GetPlayerID() string { return e.PlayerID } // GetTimestamp 获取时间戳 func (e *BasePetEvent) GetTimestamp() time.Time { return e.Timestamp } // GetVersion 获取版本 func (e *BasePetEvent) GetVersion() int { return e.Version } // GetMetadata 获取元数据 func (e *BasePetEvent) GetMetadata() map[string]interface{} { return e.Metadata } // 宠物生命周期事件 // PetCreatedEvent 宠物创建事件 type PetCreatedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` ConfigID uint32 `json:"config_id"` Category PetCategory `json:"category"` Rarity PetRarity `json:"rarity"` InitialLevel uint32 `json:"initial_level"` InitialStar uint32 `json:"initial_star"` CreationMethod string `json:"creation_method"` // "summon", "fragment", "purchase", "reward" } // PetDeletedEvent 宠物删除事件 type PetDeletedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` FinalLevel uint32 `json:"final_level"` FinalStar uint32 `json:"final_star"` FinalPower int64 `json:"final_power"` DeleteReason string `json:"delete_reason"` } // PetRenamedEvent 宠物重命名事件 type PetRenamedEvent struct { BasePetEvent PetID string `json:"pet_id"` OldName string `json:"old_name"` NewName string `json:"new_name"` } // 宠物成长事件 // PetLevelUpEvent 宠物升级事件 type PetLevelUpEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` OldLevel uint32 `json:"old_level"` NewLevel uint32 `json:"new_level"` ExperienceGained uint64 `json:"experience_gained"` PowerIncrease int64 `json:"power_increase"` AttributeChanges map[string]int64 `json:"attribute_changes"` } // PetStarUpEvent 宠物升星事件 type PetStarUpEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` OldStar uint32 `json:"old_star"` NewStar uint32 `json:"new_star"` MaterialsUsed []MaterialUsed `json:"materials_used"` PowerIncrease int64 `json:"power_increase"` NewSkillsUnlocked []string `json:"new_skills_unlocked"` } // PetEvolvedEvent 宠物进化事件 type PetEvolvedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` OldConfigID uint32 `json:"old_config_id"` NewConfigID uint32 `json:"new_config_id"` OldCategory PetCategory `json:"old_category"` NewCategory PetCategory `json:"new_category"` MaterialsUsed []MaterialUsed `json:"materials_used"` PowerIncrease int64 `json:"power_increase"` NewAbilities []string `json:"new_abilities"` } // 宠物状态事件 // PetStateChangedEvent 宠物状态改变事件 type PetStateChangedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` OldState PetState `json:"old_state"` NewState PetState `json:"new_state"` Reason string `json:"reason"` Duration *time.Duration `json:"duration,omitempty"` } // PetMoodChangedEvent 宠物心情改变事件 type PetMoodChangedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` OldMood PetMood `json:"old_mood"` NewMood PetMood `json:"new_mood"` Reason string `json:"reason"` MoodValue int32 `json:"mood_value"` } // PetHealthChangedEvent 宠物健康改变事件 type PetHealthChangedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` OldHealth uint32 `json:"old_health"` NewHealth uint32 `json:"new_health"` MaxHealth uint32 `json:"max_health"` Reason string `json:"reason"` IsCritical bool `json:"is_critical"` } // 宠物互动事件 // PetFedEvent 宠物喂食事件 type PetFedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` FoodType FoodType `json:"food_type"` FoodQuantity uint32 `json:"food_quantity"` SatietyGained uint32 `json:"satiety_gained"` MoodChange int32 `json:"mood_change"` ExperienceGained uint64 `json:"experience_gained"` SpecialEffect string `json:"special_effect,omitempty"` } // PetTrainedEvent 宠物训练事件 type PetTrainedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` TrainingType TrainingType `json:"training_type"` TrainingDuration time.Duration `json:"training_duration"` ExperienceGained uint64 `json:"experience_gained"` AttributeGains map[string]int64 `json:"attribute_gains"` SkillProgress map[string]float64 `json:"skill_progress"` TrainingCost int64 `json:"training_cost"` } // PetPlayedEvent 宠物游戏事件 type PetPlayedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` GameType string `json:"game_type"` GameDuration time.Duration `json:"game_duration"` MoodIncrease int32 `json:"mood_increase"` BondIncrease int32 `json:"bond_increase"` Rewards []GameReward `json:"rewards"` } // 宠物技能事件 // PetSkillLearnedEvent 宠物学习技能事件 type PetSkillLearnedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` SkillID string `json:"skill_id"` SkillName string `json:"skill_name"` SkillLevel uint32 `json:"skill_level"` LearnMethod string `json:"learn_method"` // "level_up", "training", "item", "evolution" Cost int64 `json:"cost"` } // PetSkillUpgradedEvent 宠物技能升级事件 type PetSkillUpgradedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` SkillID string `json:"skill_id"` SkillName string `json:"skill_name"` OldLevel uint32 `json:"old_level"` NewLevel uint32 `json:"new_level"` PowerIncrease int64 `json:"power_increase"` UpgradeCost int64 `json:"upgrade_cost"` } // PetSkillUsedEvent 宠物使用技能事件 type PetSkillUsedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` SkillID string `json:"skill_id"` SkillName string `json:"skill_name"` TargetID string `json:"target_id,omitempty"` DamageDealt int64 `json:"damage_dealt"` Effects []SkillEffect `json:"effects"` CooldownTime time.Duration `json:"cooldown_time"` Context string `json:"context"` // "battle", "training", "exploration" } // 宠物装备事件 // PetSkinEquippedEvent 宠物装备皮肤事件 type PetSkinEquippedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` SkinID string `json:"skin_id"` SkinName string `json:"skin_name"` OldSkinID string `json:"old_skin_id,omitempty"` PowerBonus int64 `json:"power_bonus"` SpecialEffects []string `json:"special_effects"` } // PetSkinUnequippedEvent 宠物卸下皮肤事件 type PetSkinUnequippedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` SkinID string `json:"skin_id"` SkinName string `json:"skin_name"` PowerLoss int64 `json:"power_loss"` } // 宠物羁绊事件 // PetBondActivatedEvent 宠物羁绊激活事件 type PetBondActivatedEvent struct { BasePetEvent BondID string `json:"bond_id"` BondName string `json:"bond_name"` PetIDs []string `json:"pet_ids"` BondLevel uint32 `json:"bond_level"` BonusEffects []BondEffect `json:"bonus_effects"` PowerBonus int64 `json:"power_bonus"` } // PetBondUpgradedEvent 宠物羁绊升级事件 type PetBondUpgradedEvent struct { BasePetEvent BondID string `json:"bond_id"` BondName string `json:"bond_name"` OldLevel uint32 `json:"old_level"` NewLevel uint32 `json:"new_level"` PetIDs []string `json:"pet_ids"` NewEffects []BondEffect `json:"new_effects"` PowerIncrease int64 `json:"power_increase"` } // PetBondDeactivatedEvent 宠物羁绊失效事件 type PetBondDeactivatedEvent struct { BasePetEvent BondID string `json:"bond_id"` BondName string `json:"bond_name"` PetIDs []string `json:"pet_ids"` Reason string `json:"reason"` PowerLoss int64 `json:"power_loss"` } // 宠物碎片事件 // PetFragmentObtainedEvent 获得宠物碎片事件 type PetFragmentObtainedEvent struct { BasePetEvent FragmentID uint32 `json:"fragment_id"` RelatedPetID uint32 `json:"related_pet_id"` Quantity uint64 `json:"quantity"` Source string `json:"source"` // "battle", "shop", "event", "decompose" TotalQuantity uint64 `json:"total_quantity"` } // PetFragmentUsedEvent 使用宠物碎片事件 type PetFragmentUsedEvent struct { BasePetEvent FragmentID uint32 `json:"fragment_id"` RelatedPetID uint32 `json:"related_pet_id"` QuantityUsed uint64 `json:"quantity_used"` Purpose string `json:"purpose"` // "summon", "upgrade", "evolution" RemainingQuantity uint64 `json:"remaining_quantity"` Result string `json:"result"` } // 宠物图鉴事件 // PetPictorialUnlockedEvent 宠物图鉴解锁事件 type PetPictorialUnlockedEvent struct { BasePetEvent PetConfigID uint32 `json:"pet_config_id"` PetName string `json:"pet_name"` Category PetCategory `json:"category"` Rarity PetRarity `json:"rarity"` UnlockMethod string `json:"unlock_method"` // "obtain", "encounter", "complete" Rewards []PictorialReward `json:"rewards"` } // PetPictorialUpdatedEvent 宠物图鉴更新事件 type PetPictorialUpdatedEvent struct { BasePetEvent PetConfigID uint32 `json:"pet_config_id"` OldHighestLevel uint32 `json:"old_highest_level"` NewHighestLevel uint32 `json:"new_highest_level"` OldStar uint32 `json:"old_star"` NewStar uint32 `json:"new_star"` ProgressRewards []PictorialReward `json:"progress_rewards"` } // 宠物战斗事件 // PetBattleStartedEvent 宠物战斗开始事件 type PetBattleStartedEvent struct { BasePetEvent BattleID string `json:"battle_id"` BattleType string `json:"battle_type"` // "pve", "pvp", "arena", "boss" Participants []BattleParticipant `json:"participants"` BattleMode string `json:"battle_mode"` Location string `json:"location"` } // PetBattleEndedEvent 宠物战斗结束事件 type PetBattleEndedEvent struct { BasePetEvent BattleID string `json:"battle_id"` Result string `json:"result"` // "victory", "defeat", "draw" Duration time.Duration `json:"duration"` Participants []BattleResult `json:"participants"` Rewards []BattleReward `json:"rewards"` Experience uint64 `json:"experience"` } // PetDefeatedEvent 宠物被击败事件 type PetDefeatedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` BattleID string `json:"battle_id"` DefeatedBy string `json:"defeated_by"` FinalDamage int64 `json:"final_damage"` ReviveTime *time.Time `json:"revive_time,omitempty"` } // 宠物探索事件 // PetExplorationStartedEvent 宠物探索开始事件 type PetExplorationStartedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` ExplorationID string `json:"exploration_id"` Location string `json:"location"` Duration time.Duration `json:"duration"` ExpectedRewards []string `json:"expected_rewards"` } // PetExplorationCompletedEvent 宠物探索完成事件 type PetExplorationCompletedEvent struct { BasePetEvent PetID string `json:"pet_id"` PetName string `json:"pet_name"` ExplorationID string `json:"exploration_id"` Location string `json:"location"` ActualDuration time.Duration `json:"actual_duration"` Success bool `json:"success"` Rewards []ExplorationReward `json:"rewards"` Experience uint64 `json:"experience"` Encounters []string `json:"encounters"` } // 系统事件 // PetSystemMaintenanceEvent 宠物系统维护事件 type PetSystemMaintenanceEvent struct { BasePetEvent MaintenanceType string `json:"maintenance_type"` // "daily", "weekly", "emergency" AffectedFeatures []string `json:"affected_features"` Duration time.Duration `json:"duration"` Compensation []SystemReward `json:"compensation"` } // PetDataMigrationEvent 宠物数据迁移事件 type PetDataMigrationEvent struct { BasePetEvent MigrationType string `json:"migration_type"` FromVersion string `json:"from_version"` ToVersion string `json:"to_version"` AffectedRecords int64 `json:"affected_records"` MigrationStatus string `json:"migration_status"` Errors []string `json:"errors,omitempty"` } // 事件相关的辅助结构体 // MaterialUsed 使用的材料 type MaterialUsed struct { MaterialID string `json:"material_id"` MaterialName string `json:"material_name"` Quantity uint64 `json:"quantity"` MaterialType string `json:"material_type"` } // GameReward 游戏奖励 type GameReward struct { RewardType string `json:"reward_type"` RewardID string `json:"reward_id"` Quantity uint64 `json:"quantity"` Rarity string `json:"rarity"` } // SkillEffect and BondEffect are defined in entity.go // PictorialReward 图鉴奖励 type PictorialReward struct { RewardType string `json:"reward_type"` RewardID string `json:"reward_id"` Quantity uint64 `json:"quantity"` Description string `json:"description"` } // BattleParticipant 战斗参与者 type BattleParticipant struct { PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` PetIDs []string `json:"pet_ids"` TeamPower int64 `json:"team_power"` Formation string `json:"formation"` } // BattleResult 战斗结果 type BattleResult struct { PlayerID string `json:"player_id"` Result string `json:"result"` DamageDealt int64 `json:"damage_dealt"` DamageReceived int64 `json:"damage_received"` PetsLost int32 `json:"pets_lost"` MVPPetID string `json:"mvp_pet_id"` } // BattleReward 战斗奖励 type BattleReward struct { RewardType string `json:"reward_type"` RewardID string `json:"reward_id"` Quantity uint64 `json:"quantity"` BonusMultiplier float64 `json:"bonus_multiplier"` } // ExplorationReward 探索奖励 type ExplorationReward struct { RewardType string `json:"reward_type"` RewardID string `json:"reward_id"` Quantity uint64 `json:"quantity"` Rarity string `json:"rarity"` FindChance float64 `json:"find_chance"` } // SystemReward 系统奖励 type SystemReward struct { RewardType string `json:"reward_type"` RewardID string `json:"reward_id"` Quantity uint64 `json:"quantity"` Reason string `json:"reason"` ExpireTime *time.Time `json:"expire_time,omitempty"` } // 事件常量 const ( // 生命周期事件类型 EventTypePetCreated = "pet.created" EventTypePetDeleted = "pet.deleted" EventTypePetRenamed = "pet.renamed" // 成长事件类型 EventTypePetLevelUp = "pet.level_up" EventTypePetStarUp = "pet.star_up" EventTypePetEvolved = "pet.evolved" // 状态事件类型 EventTypePetStateChanged = "pet.state_changed" EventTypePetMoodChanged = "pet.mood_changed" EventTypePetHealthChanged = "pet.health_changed" // 互动事件类型 EventTypePetFed = "pet.fed" EventTypePetTrained = "pet.trained" EventTypePetPlayed = "pet.played" // 技能事件类型 EventTypePetSkillLearned = "pet.skill_learned" EventTypePetSkillUpgraded = "pet.skill_upgraded" EventTypePetSkillUsed = "pet.skill_used" // 装备事件类型 EventTypePetSkinEquipped = "pet.skin_equipped" EventTypePetSkinUnequipped = "pet.skin_unequipped" // 羁绊事件类型 EventTypePetBondActivated = "pet.bond_activated" EventTypePetBondUpgraded = "pet.bond_upgraded" EventTypePetBondDeactivated = "pet.bond_deactivated" // 碎片事件类型 EventTypePetFragmentObtained = "pet.fragment_obtained" EventTypePetFragmentUsed = "pet.fragment_used" // 图鉴事件类型 EventTypePetPictorialUnlocked = "pet.pictorial_unlocked" EventTypePetPictorialUpdated = "pet.pictorial_updated" // 战斗事件类型 EventTypePetBattleStarted = "pet.battle_started" EventTypePetBattleEnded = "pet.battle_ended" EventTypePetDefeated = "pet.defeated" // 探索事件类型 EventTypePetExplorationStarted = "pet.exploration_started" EventTypePetExplorationCompleted = "pet.exploration_completed" // 系统事件类型 EventTypePetSystemMaintenance = "pet.system_maintenance" EventTypePetDataMigration = "pet.data_migration" ) // 事件工厂函数 // NewPetCreatedEvent 创建宠物创建事件 func NewPetCreatedEvent(petID, playerID, petName string, configID uint32, category PetCategory, rarity PetRarity, level, star uint32, method string) *PetCreatedEvent { return &PetCreatedEvent{ BasePetEvent: BasePetEvent{ EventID: generateEventID(), EventType: EventTypePetCreated, AggregateID: petID, PlayerID: playerID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PetID: petID, PetName: petName, ConfigID: configID, Category: category, Rarity: rarity, InitialLevel: level, InitialStar: star, CreationMethod: method, } } // NewPetLevelUpEvent 创建宠物升级事件 func NewPetLevelUpEvent(petID, playerID, petName string, oldLevel, newLevel uint32, expGained uint64, powerIncrease int64, attrChanges map[string]int64) *PetLevelUpEvent { return &PetLevelUpEvent{ BasePetEvent: BasePetEvent{ EventID: generateEventID(), EventType: EventTypePetLevelUp, AggregateID: petID, PlayerID: playerID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PetID: petID, PetName: petName, OldLevel: oldLevel, NewLevel: newLevel, ExperienceGained: expGained, PowerIncrease: powerIncrease, AttributeChanges: attrChanges, } } // NewPetSkillLearnedEvent 创建宠物学习技能事件 func NewPetSkillLearnedEvent(petID, playerID, petName, skillID, skillName string, skillLevel uint32, method string, cost int64) *PetSkillLearnedEvent { return &PetSkillLearnedEvent{ BasePetEvent: BasePetEvent{ EventID: generateEventID(), EventType: EventTypePetSkillLearned, AggregateID: petID, PlayerID: playerID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PetID: petID, PetName: petName, SkillID: skillID, SkillName: skillName, SkillLevel: skillLevel, LearnMethod: method, Cost: cost, } } // 事件处理器接口 // PetEventHandler 宠物事件处理器接口 type PetEventHandler interface { Handle(event PetEvent) error CanHandle(eventType string) bool GetHandlerName() string } // PetEventBus 宠物事件总线接口 type PetEventBus interface { // 发布事件 Publish(event PetEvent) error PublishBatch(events []PetEvent) error // 订阅事件 Subscribe(eventType string, handler PetEventHandler) error Unsubscribe(eventType string, handlerName string) error // 事件存储 Store(event PetEvent) error GetEvents(aggregateID string, fromVersion int) ([]PetEvent, error) GetEventsByType(eventType string, limit int) ([]PetEvent, error) GetEventsByPlayer(playerID string, limit int) ([]PetEvent, error) // 事件重放 Replay(aggregateID string, fromVersion int, handler PetEventHandler) error // 快照管理 CreateSnapshot(aggregateID string, version int, data interface{}) error GetSnapshot(aggregateID string) (interface{}, int, error) // 事件清理 CleanupEvents(beforeTime time.Time) error ArchiveEvents(beforeTime time.Time) error } // 辅助函数 // generateEventID 生成事件ID func generateEventID() string { // 实现事件ID生成逻辑 return fmt.Sprintf("pet_event_%d", time.Now().UnixNano()) } // ValidateEvent 验证事件 func ValidateEvent(event PetEvent) error { if event.GetEventID() == "" { return fmt.Errorf("event ID cannot be empty") } if event.GetEventType() == "" { return fmt.Errorf("event type cannot be empty") } if event.GetAggregateID() == "" { return fmt.Errorf("aggregate ID cannot be empty") } if event.GetPlayerID() == "" { return fmt.Errorf("player ID cannot be empty") } if event.GetTimestamp().IsZero() { return fmt.Errorf("timestamp cannot be zero") } return nil } // SerializeEvent 序列化事件 func SerializeEvent(event PetEvent) ([]byte, error) { // 实现事件序列化逻辑 return nil, nil } // DeserializeEvent 反序列化事件 func DeserializeEvent(data []byte, eventType string) (PetEvent, error) { // 实现事件反序列化逻辑 return nil, nil } ================================================ FILE: internal/domain/pet/repository.go ================================================ package pet import ( "time" ) // PetRepository 宠物仓储接口 type PetRepository interface { // 基础CRUD操作 Save(pet *PetAggregate) error FindByID(id string) (*PetAggregate, error) FindByPlayer(playerID string) ([]*PetAggregate, error) FindByPlayerAndCategory(playerID string, category PetCategory) ([]*PetAggregate, error) Update(pet *PetAggregate) error Delete(id string) error // 分页查询 FindWithPagination(query *PetQuery) (*PetPageResult, error) // 统计操作 Count() (int64, error) CountByPlayer(playerID string) (int64, error) CountByCategory(category PetCategory) (int64, error) // 状态查询 FindByState(state PetState) ([]*PetAggregate, error) FindActiveByPlayer(playerID string) ([]*PetAggregate, error) FindDeadPets() ([]*PetAggregate, error) // 等级和星级查询 FindByLevelRange(minLevel, maxLevel uint32) ([]*PetAggregate, error) FindByStarRange(minStar, maxStar uint32) ([]*PetAggregate, error) // 批量操作 SaveBatch(pets []*PetAggregate) error DeleteBatch(ids []string) error // 高级查询 FindTopPetsByPower(limit int) ([]*PetAggregate, error) FindRecentlyCreated(duration time.Duration) ([]*PetAggregate, error) } // PetFragmentRepository 宠物碎片仓储接口 type PetFragmentRepository interface { // 基础CRUD操作 Save(fragment *PetFragment) error FindByID(id string) (*PetFragment, error) FindByPlayer(playerID string) ([]*PetFragment, error) FindByPlayerAndFragmentID(playerID string, fragmentID uint32) (*PetFragment, error) Update(fragment *PetFragment) error Delete(id string) error // 分页查询 FindWithPagination(query *FragmentQuery) (*FragmentPageResult, error) // 统计操作 Count() (int64, error) CountByPlayer(playerID string) (int64, error) GetTotalQuantityByPlayer(playerID string, fragmentID uint32) (uint64, error) // 碎片相关查询 FindByRelatedPet(relatedPetID uint32) ([]*PetFragment, error) FindSufficientFragments(playerID string, fragmentID uint32, requiredQuantity uint64) ([]*PetFragment, error) // 批量操作 SaveBatch(fragments []*PetFragment) error UpdateBatch(fragments []*PetFragment) error } // PetSkinRepository 宠物皮肤仓储接口 type PetSkinRepository interface { // 基础CRUD操作 Save(skin *PetSkin) error FindByID(id string) (*PetSkin, error) FindBySkinID(skinID string) (*PetSkin, error) Update(skin *PetSkin) error Delete(id string) error // 分页查询 FindWithPagination(query *SkinQuery) (*SkinPageResult, error) // 统计操作 Count() (int64, error) CountByRarity(rarity PetRarity) (int64, error) // 皮肤相关查询 FindByRarity(rarity PetRarity) ([]*PetSkin, error) FindUnlockedSkins() ([]*PetSkin, error) FindEquippedSkins() ([]*PetSkin, error) // 批量操作 SaveBatch(skins []*PetSkin) error UpdateBatch(skins []*PetSkin) error } // PetSkillRepository 宠物技能仓储接口 type PetSkillRepository interface { // 基础CRUD操作 Save(skill *PetSkill) error FindByID(id string) (*PetSkill, error) FindBySkillID(skillID string) (*PetSkill, error) Update(skill *PetSkill) error Delete(id string) error // 分页查询 FindWithPagination(query *SkillQuery) (*SkillPageResult, error) // 统计操作 Count() (int64, error) CountByType(skillType SkillType) (int64, error) // 技能相关查询 FindByType(skillType SkillType) ([]*PetSkill, error) FindByLevelRange(minLevel, maxLevel uint32) ([]*PetSkill, error) FindReadySkills() ([]*PetSkill, error) // 批量操作 SaveBatch(skills []*PetSkill) error UpdateBatch(skills []*PetSkill) error } // PetBondsRepository 宠物羁绊仓储接口 type PetBondsRepository interface { // 基础CRUD操作 Save(bonds *PetBonds) error FindByID(id string) (*PetBonds, error) Update(bonds *PetBonds) error Delete(id string) error // 羁绊相关查询 FindActiveBonds() ([]*PetBonds, error) FindByBondID(bondID string) ([]*PetBonds, error) // 统计操作 Count() (int64, error) CountActiveBonds() (int64, error) } // PetPictorialRepository 宠物图鉴仓储接口 type PetPictorialRepository interface { // 基础CRUD操作 Save(pictorial *PetPictorial) error FindByID(id string) (*PetPictorial, error) FindByPlayer(playerID string) ([]*PetPictorial, error) FindByPlayerAndPetConfig(playerID string, petConfigID uint32) (*PetPictorial, error) Update(pictorial *PetPictorial) error Delete(id string) error // 分页查询 FindWithPagination(query *PictorialQuery) (*PictorialPageResult, error) // 统计操作 Count() (int64, error) CountByPlayer(playerID string) (int64, error) CountUnlockedByPlayer(playerID string) (int64, error) // 图鉴相关查询 FindUnlockedByPlayer(playerID string) ([]*PetPictorial, error) FindRecentlyUnlocked(duration time.Duration) ([]*PetPictorial, error) // 批量操作 SaveBatch(pictorials []*PetPictorial) error UpdateBatch(pictorials []*PetPictorial) error } // PetStatisticsRepository 宠物统计仓储接口 type PetStatisticsRepository interface { // 保存统计数据 SaveStatistics(stats *PetStatistics) error UpdateStatistics(stats *PetStatistics) error // 查询统计数据 FindStatistics(playerID string) (*PetStatistics, error) FindStatisticsByCategory(category PetCategory) ([]*PetStatistics, error) // 全局统计 GetGlobalStatistics() (*GlobalPetStatistics, error) GetCategoryStatistics(category PetCategory) (*CategoryPetStatistics, error) // 趋势分析 GetLevelTrend(playerID string, days int) ([]*LevelTrendData, error) GetPowerTrend(playerID string, days int) ([]*PowerTrendData, error) // 排行榜数据 GetTopPlayersByPetCount(limit int) ([]*PlayerPetRanking, error) GetTopPlayersByTotalPower(limit int) ([]*PlayerPetRanking, error) } // 查询条件结构体 // PetQuery 宠物查询条件 type PetQuery struct { PlayerID string Name string Category *PetCategory State *PetState MinLevel *uint32 MaxLevel *uint32 MinStar *uint32 MaxStar *uint32 MinPower *int64 MaxPower *int64 CreatedAfter *time.Time CreatedBefore *time.Time UpdatedAfter *time.Time UpdatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // FragmentQuery 碎片查询条件 type FragmentQuery struct { PlayerID string FragmentID *uint32 RelatedPetID *uint32 MinQuantity *uint64 MaxQuantity *uint64 CreatedAfter *time.Time CreatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // SkinQuery 皮肤查询条件 type SkinQuery struct { SkinID string Name string Rarity *PetRarity Unlocked *bool Equipped *bool MinPowerBonus *int64 MaxPowerBonus *int64 OrderBy string OrderDesc bool Offset int Limit int } // SkillQuery 技能查询条件 type SkillQuery struct { SkillID string Name string SkillType *SkillType MinLevel *uint32 MaxLevel *uint32 MinDamage *int64 MaxDamage *int64 Ready *bool OrderBy string OrderDesc bool Offset int Limit int } // PictorialQuery 图鉴查询条件 type PictorialQuery struct { PlayerID string PetConfigID *uint32 Unlocked *bool MinLevel *uint32 MaxLevel *uint32 MinStar *uint32 MaxStar *uint32 CreatedAfter *time.Time CreatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // 分页结果结构体 // PetPageResult 宠物分页结果 type PetPageResult struct { Items []*PetAggregate Total int64 Offset int Limit int HasMore bool } // FragmentPageResult 碎片分页结果 type FragmentPageResult struct { Items []*PetFragment Total int64 Offset int Limit int HasMore bool } // SkinPageResult 皮肤分页结果 type SkinPageResult struct { Items []*PetSkin Total int64 Offset int Limit int HasMore bool } // SkillPageResult 技能分页结果 type SkillPageResult struct { Items []*PetSkill Total int64 Offset int Limit int HasMore bool } // PictorialPageResult 图鉴分页结果 type PictorialPageResult struct { Items []*PetPictorial Total int64 Offset int Limit int HasMore bool } // 统计数据结构体 // PetStatistics 宠物统计 type PetStatistics struct { PlayerID string TotalPets int64 AlivePets int64 DeadPets int64 MaxLevel uint32 MaxStar uint32 TotalPower int64 AveragePower float64 CategoryStats map[PetCategory]*CategoryStats FavoritePet string MostUsedSkill string TotalTrainingTime time.Duration TotalFeedingCount int64 LastActivityTime time.Time CreatedAt time.Time UpdatedAt time.Time } // CategoryStats 类别统计 type CategoryStats struct { Category PetCategory Count int64 TotalPower int64 AveragePower float64 MaxLevel uint32 MaxStar uint32 } // GlobalPetStatistics 全局宠物统计 type GlobalPetStatistics struct { TotalPets int64 TotalPlayers int64 AveragePetsPerPlayer float64 CategoryDistribution map[PetCategory]int64 RarityDistribution map[PetRarity]int64 MostPopularCategory PetCategory MostPopularRarity PetRarity TotalPower int64 AveragePower float64 TopPet string UpdatedAt time.Time } // CategoryPetStatistics 类别宠物统计 type CategoryPetStatistics struct { Category PetCategory TotalCount int64 ActiveCount int64 AverageLevel float64 AverageStar float64 TotalPower int64 AveragePower float64 TopPet string MostActivePlayer string UpdatedAt time.Time } // LevelTrendData 等级趋势数据 type LevelTrendData struct { Date time.Time AverageLevel float64 MaxLevel uint32 LevelUps int64 } // PowerTrendData 战力趋势数据 type PowerTrendData struct { Date time.Time TotalPower int64 AveragePower float64 MaxPower int64 } // PlayerPetRanking 玩家宠物排行 type PlayerPetRanking struct { PlayerID string PlayerName string PetCount int64 TotalPower int64 AveragePower float64 TopPetName string Rank int } // 缓存接口 // PetCacheRepository 宠物缓存仓储接口 type PetCacheRepository interface { // 宠物缓存 SetPet(id string, pet *PetAggregate, ttl time.Duration) error GetPet(id string) (*PetAggregate, error) DeletePet(id string) error // 碎片缓存 SetFragment(id string, fragment *PetFragment, ttl time.Duration) error GetFragment(id string) (*PetFragment, error) DeleteFragment(id string) error // 皮肤缓存 SetSkin(id string, skin *PetSkin, ttl time.Duration) error GetSkin(id string) (*PetSkin, error) DeleteSkin(id string) error // 技能缓存 SetSkill(id string, skill *PetSkill, ttl time.Duration) error GetSkill(id string) (*PetSkill, error) DeleteSkill(id string) error // 图鉴缓存 SetPictorial(id string, pictorial *PetPictorial, ttl time.Duration) error GetPictorial(id string) (*PetPictorial, error) DeletePictorial(id string) error // 统计缓存 SetStatistics(key string, stats interface{}, ttl time.Duration) error GetStatistics(key string, result interface{}) error DeleteStatistics(key string) error // 玩家宠物列表缓存 SetPlayerPets(playerID string, pets []*PetAggregate, ttl time.Duration) error GetPlayerPets(playerID string) ([]*PetAggregate, error) DeletePlayerPets(playerID string) error // 批量操作 SetBatch(items map[string]interface{}, ttl time.Duration) error GetBatch(keys []string) (map[string]interface{}, error) DeleteBatch(keys []string) error // 缓存管理 Clear() error Exists(key string) (bool, error) SetTTL(key string, ttl time.Duration) error GetTTL(key string) (time.Duration, error) } // 事务接口 // PetTransactionRepository 宠物事务仓储接口 type PetTransactionRepository interface { // 事务管理 BeginTransaction() (PetTransaction, error) CommitTransaction(tx PetTransaction) error RollbackTransaction(tx PetTransaction) error // 在事务中执行操作 ExecuteInTransaction(fn func(tx PetTransaction) error) error } // PetTransaction 宠物事务接口 type PetTransaction interface { // 宠物操作 SavePet(pet *PetAggregate) error UpdatePet(pet *PetAggregate) error DeletePet(id string) error // 碎片操作 SaveFragment(fragment *PetFragment) error UpdateFragment(fragment *PetFragment) error DeleteFragment(id string) error // 皮肤操作 SaveSkin(skin *PetSkin) error UpdateSkin(skin *PetSkin) error DeleteSkin(id string) error // 技能操作 SaveSkill(skill *PetSkill) error UpdateSkill(skill *PetSkill) error DeleteSkill(id string) error // 羁绊操作 SaveBonds(bonds *PetBonds) error UpdateBonds(bonds *PetBonds) error DeleteBonds(id string) error // 图鉴操作 SavePictorial(pictorial *PetPictorial) error UpdatePictorial(pictorial *PetPictorial) error DeletePictorial(id string) error // 统计操作 UpdateStatistics(stats *PetStatistics) error // 事务状态 IsActive() bool GetID() string } // 仓储工厂接口 // PetRepositoryFactory 宠物仓储工厂接口 type PetRepositoryFactory interface { // 创建仓储实例 CreatePetRepository() PetRepository CreateFragmentRepository() PetFragmentRepository CreateSkinRepository() PetSkinRepository CreateSkillRepository() PetSkillRepository CreateBondsRepository() PetBondsRepository CreatePictorialRepository() PetPictorialRepository CreateStatisticsRepository() PetStatisticsRepository CreateCacheRepository() PetCacheRepository CreateTransactionRepository() PetTransactionRepository // 健康检查 HealthCheck() error // 关闭连接 Close() error } // 搜索接口 // PetSearchRepository 宠物搜索仓储接口 type PetSearchRepository interface { // 全文搜索 SearchPets(query string, filters map[string]interface{}) ([]*PetAggregate, error) SearchFragments(query string, filters map[string]interface{}) ([]*PetFragment, error) SearchSkins(query string, filters map[string]interface{}) ([]*PetSkin, error) SearchSkills(query string, filters map[string]interface{}) ([]*PetSkill, error) // 智能推荐 RecommendPets(playerID string, category PetCategory, limit int) ([]*PetAggregate, error) RecommendSkills(petID string, limit int) ([]*PetSkill, error) RecommendSkins(petID string, limit int) ([]*PetSkin, error) // 相似度搜索 FindSimilarPets(petID string, limit int) ([]*PetAggregate, error) // 索引管理 RebuildIndex() error UpdateIndex(entity *PetAggregate) error } ================================================ FILE: internal/domain/pet/service.go ================================================ package pet import ( "math/rand" "time" ) // PetService 宠物领域服务 type PetService struct { petTemplates map[uint32]*PetTemplate skillTemplates map[string]*SkillTemplate skinTemplates map[string]*SkinTemplate bondTemplates map[string]*BondTemplate fragmentTemplates map[uint32]*FragmentTemplate } // NewPetService 创建宠物领域服务 func NewPetService() *PetService { return &PetService{ petTemplates: make(map[uint32]*PetTemplate), skillTemplates: make(map[string]*SkillTemplate), skinTemplates: make(map[string]*SkinTemplate), bondTemplates: make(map[string]*BondTemplate), fragmentTemplates: make(map[uint32]*FragmentTemplate), } } // CreatePet 创建宠物 func (ps *PetService) CreatePet(playerID string, configID uint32, name string) (*PetAggregate, error) { template, exists := ps.petTemplates[configID] if !exists { return nil, ErrPetTemplateNotFound } if name == "" { name = template.DefaultName } pet := NewPetAggregate(playerID, configID, name, template.Category) // 应用模板属性 ps.applyPetTemplate(pet, template) // 添加初始技能 for _, skillID := range template.InitialSkills { if skillTemplate, exists := ps.skillTemplates[skillID]; exists { skill := ps.createSkillFromTemplate(skillTemplate) pet.AddSkill(skill) } } return pet, nil } // SummonPetFromFragments 通过碎片召唤宠物 func (ps *PetService) SummonPetFromFragments(playerID string, fragments []*PetFragment) (*PetAggregate, error) { if len(fragments) == 0 { return nil, ErrNoFragmentsProvided } // 检查所有碎片是否属于同一宠物 relatedPetID := fragments[0].GetRelatedPetID() var totalQuantity uint64 for _, fragment := range fragments { if fragment.GetRelatedPetID() != relatedPetID { return nil, ErrFragmentMismatch } totalQuantity += fragment.GetQuantity() } // 检查碎片数量是否足够 fragmentTemplate, exists := ps.fragmentTemplates[fragments[0].GetFragmentID()] if !exists { return nil, ErrFragmentTemplateNotFound } if totalQuantity < fragmentTemplate.RequiredQuantity { return nil, ErrInsufficientFragments } // 消耗碎片 remainingQuantity := fragmentTemplate.RequiredQuantity for _, fragment := range fragments { if remainingQuantity <= 0 { break } consumeAmount := fragment.GetQuantity() if consumeAmount > remainingQuantity { consumeAmount = remainingQuantity } if err := fragment.ConsumeQuantity(consumeAmount); err != nil { return nil, err } remainingQuantity -= consumeAmount } // 创建宠物 return ps.CreatePet(playerID, relatedPetID, "") } // EvolvePet 宠物进化 func (ps *PetService) EvolvePet(pet *PetAggregate, materials []string) error { if pet.GetStar() >= MaxPetStar { return ErrMaxStarReached } // 检查进化材料 if !ps.checkEvolutionMaterials(pet.GetConfigID(), pet.GetStar(), materials) { return ErrInsufficientEvolutionMaterials } // 执行进化 if err := pet.UpgradeStar(); err != nil { return err } // 进化后可能解锁新技能 ps.unlockEvolutionSkills(pet) return nil } // TrainPet 训练宠物 func (ps *PetService) TrainPet(pet *PetAggregate, trainingType TrainingType, duration time.Duration) error { if !trainingType.IsValid() { return ErrInvalidTrainingType } // 检查宠物状态 if err := pet.Train(trainingType, duration); err != nil { return err } return nil } // FinishPetTraining 完成宠物训练 func (ps *PetService) FinishPetTraining(pet *PetAggregate, trainingType TrainingType) (*TrainingResult, error) { if err := pet.FinishTraining(trainingType); err != nil { return nil, err } // 计算训练结果 result := ps.calculateTrainingResult(pet, trainingType) return result, nil } // FeedPet 喂食宠物 func (ps *PetService) FeedPet(pet *PetAggregate, foodType FoodType, amount int) (*FeedingResult, error) { if !foodType.IsValid() { return nil, ErrInvalidFoodType } if amount <= 0 { return nil, ErrInvalidAmount } // 记录喂食前的状态 beforeExp := pet.GetExperience() beforeLevel := pet.GetLevel() beforeAttributes := pet.GetAttributes().Clone() // 执行喂食 if err := pet.Feed(foodType, amount); err != nil { return nil, err } // 计算喂食结果 result := &FeedingResult{ FoodType: foodType, Amount: amount, ExperienceGain: pet.GetExperience() - beforeExp, LevelUp: pet.GetLevel() > beforeLevel, AttributeChanges: ps.calculateAttributeChanges(beforeAttributes, pet.GetAttributes()), Timestamp: time.Now(), } return result, nil } // BreedPets 宠物繁殖 func (ps *PetService) BreedPets(parent1, parent2 *PetAggregate) (*PetAggregate, error) { // 检查繁殖条件 if err := ps.validateBreedingConditions(parent1, parent2); err != nil { return nil, err } // 生成后代 offspring := ps.generateOffspring(parent1, parent2) return offspring, nil } // CalculateBattlePower 计算战斗力 func (ps *PetService) CalculateBattlePower(pet *PetAggregate) int64 { basePower := pet.GetTotalPower() // 技能加成 skillBonus := ps.calculateSkillPowerBonus(pet.GetSkills()) // 等级加成 levelBonus := int64(pet.GetLevel()) * 10 // 星级加成 starBonus := int64(pet.GetStar()) * 100 return basePower + skillBonus + levelBonus + starBonus } // GenerateRandomPet 生成随机宠物 func (ps *PetService) GenerateRandomPet(playerID string, rarity PetRarity) (*PetAggregate, error) { // 根据稀有度筛选可用模板 availableTemplates := ps.getTemplatesByRarity(rarity) if len(availableTemplates) == 0 { return nil, ErrNoAvailableTemplates } // 随机选择模板 template := availableTemplates[rand.Intn(len(availableTemplates))] // 创建宠物 pet, err := ps.CreatePet(playerID, template.ConfigID, "") if err != nil { return nil, err } // 应用随机属性加成 ps.applyRandomBonus(pet, rarity) return pet, nil } // 私有方法 // applyPetTemplate 应用宠物模板 func (ps *PetService) applyPetTemplate(pet *PetAggregate, template *PetTemplate) { // 应用基础属性 attributes := pet.GetAttributes() attributes.AddHealth(template.BaseHealth) attributes.AddAttack(template.BaseAttack) attributes.AddDefense(template.BaseDefense) attributes.AddSpeed(template.BaseSpeed) attributes.AddCritical(template.BaseCritical) attributes.AddHit(template.BaseHit) attributes.AddDodge(template.BaseDodge) } // createSkillFromTemplate 从模板创建技能 func (ps *PetService) createSkillFromTemplate(template *SkillTemplate) *PetSkill { skill := NewPetSkill(template.SkillID, template.Name, template.Type, template.Cooldown, template.BaseDamage, "") // 添加技能效果 for _, effectTemplate := range template.Effects { effect := SkillEffect{ EffectType: effectTemplate.Type, Value: effectTemplate.Value, Duration: effectTemplate.Duration, } skill.AddEffect(effect) } return skill } // checkEvolutionMaterials 检查进化材料 func (ps *PetService) checkEvolutionMaterials(configID uint32, currentStar uint32, materials []string) bool { // 简化实现,实际应该根据配置检查 requiredMaterials := ps.getRequiredEvolutionMaterials(configID, currentStar) // 检查材料是否足够 materialCount := make(map[string]int) for _, material := range materials { materialCount[material]++ } for material, required := range requiredMaterials { if materialCount[material] < required { return false } } return true } // getRequiredEvolutionMaterials 获取进化所需材料 func (ps *PetService) getRequiredEvolutionMaterials(configID uint32, currentStar uint32) map[string]int { // 简化实现 return map[string]int{ "evolution_stone": int(currentStar), "gold": int(currentStar) * 1000, } } // unlockEvolutionSkills 解锁进化技能 func (ps *PetService) unlockEvolutionSkills(pet *PetAggregate) { // 根据星级解锁新技能 star := pet.GetStar() if star >= 3 { // 3星解锁特殊技能 if skillTemplate, exists := ps.skillTemplates["special_skill_1"]; exists { skill := ps.createSkillFromTemplate(skillTemplate) pet.AddSkill(skill) } } if star >= 5 { // 5星解锁终极技能 if skillTemplate, exists := ps.skillTemplates["ultimate_skill_1"]; exists { skill := ps.createSkillFromTemplate(skillTemplate) pet.AddSkill(skill) } } } // calculateTrainingResult 计算训练结果 func (ps *PetService) calculateTrainingResult(pet *PetAggregate, trainingType TrainingType) *TrainingResult { result := &TrainingResult{ TrainingType: trainingType, Timestamp: time.Now(), } switch trainingType { case TrainingTypeExperience: result.ExperienceGain = TrainingExperienceGain case TrainingTypeAttribute: result.AttributeGain = TrainingAttributeGain case TrainingTypeSkill: result.SkillExpGain = TrainingSkillExpGain } return result } // calculateAttributeChanges 计算属性变化 func (ps *PetService) calculateAttributeChanges(before, after *PetAttributes) map[string]int64 { changes := make(map[string]int64) changes["health"] = after.GetHealth() - before.GetHealth() changes["attack"] = after.GetAttack() - before.GetAttack() changes["defense"] = after.GetDefense() - before.GetDefense() changes["speed"] = after.GetSpeed() - before.GetSpeed() changes["critical"] = after.GetCritical() - before.GetCritical() changes["hit"] = after.GetHit() - before.GetHit() changes["dodge"] = after.GetDodge() - before.GetDodge() return changes } // validateBreedingConditions 验证繁殖条件 func (ps *PetService) validateBreedingConditions(parent1, parent2 *PetAggregate) error { if parent1.GetPlayerID() != parent2.GetPlayerID() { return ErrDifferentOwners } if parent1.GetLevel() < MinBreedingLevel || parent2.GetLevel() < MinBreedingLevel { return ErrInsufficientBreedingLevel } if !parent1.IsAlive() || !parent2.IsAlive() { return ErrPetIsDead } if !parent1.IsIdle() || !parent2.IsIdle() { return ErrPetNotIdle } return nil } // generateOffspring 生成后代 func (ps *PetService) generateOffspring(parent1, parent2 *PetAggregate) *PetAggregate { // 简化实现:随机选择一个父母的配置 var configID uint32 if rand.Float32() < 0.5 { configID = parent1.GetConfigID() } else { configID = parent2.GetConfigID() } // 创建后代 offspring, _ := ps.CreatePet(parent1.GetPlayerID(), configID, "") // 继承部分属性 ps.inheritAttributes(offspring, parent1, parent2) return offspring } // inheritAttributes 继承属性 func (ps *PetService) inheritAttributes(offspring, parent1, parent2 *PetAggregate) { offspringAttrs := offspring.GetAttributes() parent1Attrs := parent1.GetAttributes() parent2Attrs := parent2.GetAttributes() // 取父母属性的平均值作为基础 avgHealth := (parent1Attrs.GetHealth() + parent2Attrs.GetHealth()) / 2 avgAttack := (parent1Attrs.GetAttack() + parent2Attrs.GetAttack()) / 2 avgDefense := (parent1Attrs.GetDefense() + parent2Attrs.GetDefense()) / 2 avgSpeed := (parent1Attrs.GetSpeed() + parent2Attrs.GetSpeed()) / 2 // 应用继承属性(有一定随机性) offspringAttrs.AddHealth(int64(float64(avgHealth) * (0.8 + rand.Float64()*0.4))) offspringAttrs.AddAttack(int64(float64(avgAttack) * (0.8 + rand.Float64()*0.4))) offspringAttrs.AddDefense(int64(float64(avgDefense) * (0.8 + rand.Float64()*0.4))) offspringAttrs.AddSpeed(int64(float64(avgSpeed) * (0.8 + rand.Float64()*0.4))) } // calculateSkillPowerBonus 计算技能战力加成 func (ps *PetService) calculateSkillPowerBonus(skills []*PetSkill) int64 { var bonus int64 for _, skill := range skills { bonus += skill.GetDamage() * int64(skill.GetLevel()) } return bonus } // getTemplatesByRarity 根据稀有度获取模板 func (ps *PetService) getTemplatesByRarity(rarity PetRarity) []*PetTemplate { var templates []*PetTemplate for _, template := range ps.petTemplates { if template.Rarity == rarity { templates = append(templates, template) } } return templates } // applyRandomBonus 应用随机加成 func (ps *PetService) applyRandomBonus(pet *PetAggregate, rarity PetRarity) { multiplier := rarity.GetAttributeMultiplier() attributes := pet.GetAttributes() // 随机加成范围:0.8-1.2倍 randomFactor := 0.8 + rand.Float64()*0.4 finalMultiplier := multiplier * randomFactor bonusHealth := int64(float64(attributes.GetHealth()) * (finalMultiplier - 1.0)) bonusAttack := int64(float64(attributes.GetAttack()) * (finalMultiplier - 1.0)) bonusDefense := int64(float64(attributes.GetDefense()) * (finalMultiplier - 1.0)) bonusSpeed := int64(float64(attributes.GetSpeed()) * (finalMultiplier - 1.0)) attributes.AddHealth(bonusHealth) attributes.AddAttack(bonusAttack) attributes.AddDefense(bonusDefense) attributes.AddSpeed(bonusSpeed) } // 模板结构体定义 // PetTemplate 宠物模板 type PetTemplate struct { ConfigID uint32 Name string DefaultName string Category PetCategory Rarity PetRarity Size PetSize Gender PetGender Personality PetPersonality BaseHealth int64 BaseAttack int64 BaseDefense int64 BaseSpeed int64 BaseCritical int64 BaseHit int64 BaseDodge int64 InitialSkills []string Description string } // SkillTemplate 技能模板 type SkillTemplate struct { SkillID string Name string Type SkillType BaseDamage int64 Cooldown time.Duration Effects []SkillEffectTemplate Description string } // SkillEffectTemplate 技能效果模板 type SkillEffectTemplate struct { Type string Value float64 Duration time.Duration } // SkinTemplate 皮肤模板 type SkinTemplate struct { SkinID string Name string Rarity PetRarity PowerBonus int64 AttributeBonus map[string]float64 UnlockCondition string Description string } // BondTemplate 羁绊模板 type BondTemplate struct { BondID string Name string Description string PowerBonus int64 Attributes map[string]float64 RequiredPets []uint32 } // FragmentTemplate 碎片模板 type FragmentTemplate struct { FragmentID uint32 Name string RelatedPetID uint32 RequiredQuantity uint64 Description string } // 结果结构体定义 // TrainingResult 训练结果 type TrainingResult struct { TrainingType TrainingType ExperienceGain uint64 AttributeGain int64 SkillExpGain uint64 Timestamp time.Time } // FeedingResult 喂食结果 type FeedingResult struct { FoodType FoodType Amount int ExperienceGain uint64 LevelUp bool AttributeChanges map[string]int64 Timestamp time.Time } // 常量定义 const ( MinBreedingLevel = 10 ) ================================================ FILE: internal/domain/pet/value_object.go ================================================ package pet import ( "math/rand" "time" ) // PetCategory 宠物类别 type PetCategory int const ( PetCategoryFire PetCategory = 1 // 火系 PetCategoryWater PetCategory = 2 // 水系 PetCategoryEarth PetCategory = 3 // 土系 PetCategoryAir PetCategory = 4 // 风系 PetCategoryLight PetCategory = 5 // 光系 PetCategoryDark PetCategory = 6 // 暗系 PetCategoryNormal PetCategory = 7 // 普通系 ) // String 返回宠物类别字符串 func (pc PetCategory) String() string { switch pc { case PetCategoryFire: return "Fire" case PetCategoryWater: return "Water" case PetCategoryEarth: return "Earth" case PetCategoryAir: return "Air" case PetCategoryLight: return "Light" case PetCategoryDark: return "Dark" case PetCategoryNormal: return "Normal" default: return "Unknown" } } // IsValid 检查宠物类别是否有效 func (pc PetCategory) IsValid() bool { return pc >= PetCategoryFire && pc <= PetCategoryNormal } // PetState 宠物状态 type PetState int const ( PetStateIdle PetState = 1 // 空闲 PetStateBattle PetState = 2 // 战斗中 PetStateTraining PetState = 3 // 训练中 PetStateDead PetState = 4 // 死亡 ) // String 返回宠物状态字符串 func (ps PetState) String() string { switch ps { case PetStateIdle: return "Idle" case PetStateBattle: return "Battle" case PetStateTraining: return "Training" case PetStateDead: return "Dead" default: return "Unknown" } } // IsValid 检查宠物状态是否有效 func (ps PetState) IsValid() bool { return ps >= PetStateIdle && ps <= PetStateDead } // PetAttributes 宠物属性 type PetAttributes struct { health int64 attack int64 defense int64 speed int64 critical int64 hit int64 dodge int64 } // NewPetAttributes 创建新的宠物属性 func NewPetAttributes() *PetAttributes { return &PetAttributes{ health: 100, attack: 50, defense: 30, speed: 40, critical: 10, hit: 80, dodge: 20, } } // GetHealth 获取生命值 func (pa *PetAttributes) GetHealth() int64 { return pa.health } // GetAttack 获取攻击力 func (pa *PetAttributes) GetAttack() int64 { return pa.attack } // GetDefense 获取防御力 func (pa *PetAttributes) GetDefense() int64 { return pa.defense } // GetSpeed 获取速度 func (pa *PetAttributes) GetSpeed() int64 { return pa.speed } // GetCritical 获取暴击 func (pa *PetAttributes) GetCritical() int64 { return pa.critical } // GetHit 获取命中 func (pa *PetAttributes) GetHit() int64 { return pa.hit } // GetDodge 获取闪避 func (pa *PetAttributes) GetDodge() int64 { return pa.dodge } // AddHealth 增加生命值 func (pa *PetAttributes) AddHealth(value int64) { pa.health += value if pa.health < 0 { pa.health = 0 } } // AddAttack 增加攻击力 func (pa *PetAttributes) AddAttack(value int64) { pa.attack += value if pa.attack < 0 { pa.attack = 0 } } // AddDefense 增加防御力 func (pa *PetAttributes) AddDefense(value int64) { pa.defense += value if pa.defense < 0 { pa.defense = 0 } } // AddSpeed 增加速度 func (pa *PetAttributes) AddSpeed(value int64) { pa.speed += value if pa.speed < 0 { pa.speed = 0 } } // AddCritical 增加暴击 func (pa *PetAttributes) AddCritical(value int64) { pa.critical += value if pa.critical < 0 { pa.critical = 0 } } // AddHit 增加命中 func (pa *PetAttributes) AddHit(value int64) { pa.hit += value if pa.hit < 0 { pa.hit = 0 } } // AddDodge 增加闪避 func (pa *PetAttributes) AddDodge(value int64) { pa.dodge += value if pa.dodge < 0 { pa.dodge = 0 } } // UpgradeOnLevelUp 升级时属性提升 func (pa *PetAttributes) UpgradeOnLevelUp(level uint32) { // 每级提升基础属性 levelBonus := int64(level) pa.health += levelBonus * 10 pa.attack += levelBonus * 5 pa.defense += levelBonus * 3 pa.speed += levelBonus * 2 pa.critical += levelBonus * 1 pa.hit += levelBonus * 1 pa.dodge += levelBonus * 1 } // UpgradeOnStarUp 升星时属性提升 func (pa *PetAttributes) UpgradeOnStarUp(star uint32) { // 每星提升大量属性 starBonus := int64(star) pa.health += starBonus * 50 pa.attack += starBonus * 25 pa.defense += starBonus * 15 pa.speed += starBonus * 10 pa.critical += starBonus * 5 pa.hit += starBonus * 5 pa.dodge += starBonus * 5 } // AddRandomAttribute 随机增加属性 func (pa *PetAttributes) AddRandomAttribute(value int64) { attributeType := rand.Intn(7) switch attributeType { case 0: pa.AddHealth(value) case 1: pa.AddAttack(value) case 2: pa.AddDefense(value) case 3: pa.AddSpeed(value) case 4: pa.AddCritical(value) case 5: pa.AddHit(value) case 6: pa.AddDodge(value) } } // CalculatePower 计算战力 func (pa *PetAttributes) CalculatePower() int64 { return pa.health*2 + pa.attack*3 + pa.defense*2 + pa.speed*1 + pa.critical*2 + pa.hit*1 + pa.dodge*1 } // Clone 克隆属性 func (pa *PetAttributes) Clone() *PetAttributes { return &PetAttributes{ health: pa.health, attack: pa.attack, defense: pa.defense, speed: pa.speed, critical: pa.critical, hit: pa.hit, dodge: pa.dodge, } } // FoodType 食物类型 type FoodType int const ( FoodTypeExperience FoodType = 1 // 经验食物 FoodTypeHealth FoodType = 2 // 生命食物 FoodTypeAttack FoodType = 3 // 攻击食物 FoodTypeDefense FoodType = 4 // 防御食物 ) // String 返回食物类型字符串 func (ft FoodType) String() string { switch ft { case FoodTypeExperience: return "Experience" case FoodTypeHealth: return "Health" case FoodTypeAttack: return "Attack" case FoodTypeDefense: return "Defense" default: return "Unknown" } } // IsValid 检查食物类型是否有效 func (ft FoodType) IsValid() bool { return ft >= FoodTypeExperience && ft <= FoodTypeDefense } // TrainingType 训练类型 type TrainingType int const ( TrainingTypeExperience TrainingType = 1 // 经验训练 TrainingTypeAttribute TrainingType = 2 // 属性训练 TrainingTypeSkill TrainingType = 3 // 技能训练 ) // String 返回训练类型字符串 func (tt TrainingType) String() string { switch tt { case TrainingTypeExperience: return "Experience" case TrainingTypeAttribute: return "Attribute" case TrainingTypeSkill: return "Skill" default: return "Unknown" } } // IsValid 检查训练类型是否有效 func (tt TrainingType) IsValid() bool { return tt >= TrainingTypeExperience && tt <= TrainingTypeSkill } // GetTrainingDuration 获取训练持续时间 func (tt TrainingType) GetTrainingDuration() time.Duration { switch tt { case TrainingTypeExperience: return 30 * time.Minute case TrainingTypeAttribute: return 60 * time.Minute case TrainingTypeSkill: return 120 * time.Minute default: return 30 * time.Minute } } // GetTrainingCost 获取训练消耗 func (tt TrainingType) GetTrainingCost() int64 { switch tt { case TrainingTypeExperience: return 100 case TrainingTypeAttribute: return 200 case TrainingTypeSkill: return 500 default: return 100 } } // PetRarity 宠物稀有度 type PetRarity int const ( PetRarityCommon PetRarity = 1 // 普通 PetRarityUncommon PetRarity = 2 // 不常见 PetRarityRare PetRarity = 3 // 稀有 PetRarityEpic PetRarity = 4 // 史诗 PetRarityLegendary PetRarity = 5 // 传说 PetRarityMythic PetRarity = 6 // 神话 ) // String 返回稀有度字符串 func (pr PetRarity) String() string { switch pr { case PetRarityCommon: return "Common" case PetRarityUncommon: return "Uncommon" case PetRarityRare: return "Rare" case PetRarityEpic: return "Epic" case PetRarityLegendary: return "Legendary" case PetRarityMythic: return "Mythic" default: return "Unknown" } } // IsValid 检查稀有度是否有效 func (pr PetRarity) IsValid() bool { return pr >= PetRarityCommon && pr <= PetRarityMythic } // GetAttributeMultiplier 获取属性倍数 func (pr PetRarity) GetAttributeMultiplier() float64 { switch pr { case PetRarityCommon: return 1.0 case PetRarityUncommon: return 1.2 case PetRarityRare: return 1.5 case PetRarityEpic: return 2.0 case PetRarityLegendary: return 3.0 case PetRarityMythic: return 5.0 default: return 1.0 } } // PetGender 宠物性别 type PetGender int const ( PetGenderMale PetGender = 1 // 雄性 PetGenderFemale PetGender = 2 // 雌性 PetGenderNone PetGender = 3 // 无性别 ) // String 返回性别字符串 func (pg PetGender) String() string { switch pg { case PetGenderMale: return "Male" case PetGenderFemale: return "Female" case PetGenderNone: return "None" default: return "Unknown" } } // IsValid 检查性别是否有效 func (pg PetGender) IsValid() bool { return pg >= PetGenderMale && pg <= PetGenderNone } // PetSize 宠物体型 type PetSize int const ( PetSizeTiny PetSize = 1 // 微型 PetSizeSmall PetSize = 2 // 小型 PetSizeMedium PetSize = 3 // 中型 PetSizeLarge PetSize = 4 // 大型 PetSizeHuge PetSize = 5 // 巨型 ) // String 返回体型字符串 func (ps PetSize) String() string { switch ps { case PetSizeTiny: return "Tiny" case PetSizeSmall: return "Small" case PetSizeMedium: return "Medium" case PetSizeLarge: return "Large" case PetSizeHuge: return "Huge" default: return "Unknown" } } // IsValid 检查体型是否有效 func (ps PetSize) IsValid() bool { return ps >= PetSizeTiny && ps <= PetSizeHuge } // GetSpeedModifier 获取速度修正 func (ps PetSize) GetSpeedModifier() float64 { switch ps { case PetSizeTiny: return 1.3 case PetSizeSmall: return 1.1 case PetSizeMedium: return 1.0 case PetSizeLarge: return 0.9 case PetSizeHuge: return 0.7 default: return 1.0 } } // GetHealthModifier 获取生命值修正 func (ps PetSize) GetHealthModifier() float64 { switch ps { case PetSizeTiny: return 0.7 case PetSizeSmall: return 0.9 case PetSizeMedium: return 1.0 case PetSizeLarge: return 1.2 case PetSizeHuge: return 1.5 default: return 1.0 } } // PetPersonality 宠物性格 type PetPersonality int const ( PetPersonalityBrave PetPersonality = 1 // 勇敢 PetPersonalityTimid PetPersonality = 2 // 胆小 PetPersonalityAggressive PetPersonality = 3 // 好斗 PetPersonalityGentle PetPersonality = 4 // 温和 PetPersonalityPlayful PetPersonality = 5 // 顽皮 PetPersonalityLazy PetPersonality = 6 // 懒惰 PetPersonalityLoyal PetPersonality = 7 // 忠诚 PetPersonalityStubborn PetPersonality = 8 // 固执 PetPersonalityCurious PetPersonality = 9 // 好奇 PetPersonalityCalm PetPersonality = 10 // 冷静 ) // String 返回性格字符串 func (pp PetPersonality) String() string { switch pp { case PetPersonalityBrave: return "Brave" case PetPersonalityTimid: return "Timid" case PetPersonalityAggressive: return "Aggressive" case PetPersonalityGentle: return "Gentle" case PetPersonalityPlayful: return "Playful" case PetPersonalityLazy: return "Lazy" case PetPersonalityLoyal: return "Loyal" case PetPersonalityStubborn: return "Stubborn" case PetPersonalityCurious: return "Curious" case PetPersonalityCalm: return "Calm" default: return "Unknown" } } // IsValid 检查性格是否有效 func (pp PetPersonality) IsValid() bool { return pp >= PetPersonalityBrave && pp <= PetPersonalityCalm } // GetAttributeBonus 获取性格属性加成 func (pp PetPersonality) GetAttributeBonus() map[string]float64 { switch pp { case PetPersonalityBrave: return map[string]float64{"attack": 1.1, "defense": 0.9} case PetPersonalityTimid: return map[string]float64{"dodge": 1.2, "attack": 0.8} case PetPersonalityAggressive: return map[string]float64{"attack": 1.2, "critical": 1.1, "defense": 0.8} case PetPersonalityGentle: return map[string]float64{"health": 1.1, "attack": 0.9} case PetPersonalityPlayful: return map[string]float64{"speed": 1.2, "dodge": 1.1} case PetPersonalityLazy: return map[string]float64{"health": 1.2, "speed": 0.7} case PetPersonalityLoyal: return map[string]float64{"defense": 1.2, "health": 1.1} case PetPersonalityStubborn: return map[string]float64{"defense": 1.3, "speed": 0.8} case PetPersonalityCurious: return map[string]float64{"hit": 1.2, "critical": 1.1} case PetPersonalityCalm: return map[string]float64{"hit": 1.1, "dodge": 1.1} default: return map[string]float64{} } } // PetMood 宠物心情 type PetMood int const ( PetMoodHappy PetMood = 1 // 开心 PetMoodNormal PetMood = 2 // 普通 PetMoodSad PetMood = 3 // 伤心 PetMoodAngry PetMood = 4 // 愤怒 PetMoodExcited PetMood = 5 // 兴奋 PetMoodTired PetMood = 6 // 疲惫 ) // String 返回心情字符串 func (pm PetMood) String() string { switch pm { case PetMoodHappy: return "Happy" case PetMoodNormal: return "Normal" case PetMoodSad: return "Sad" case PetMoodAngry: return "Angry" case PetMoodExcited: return "Excited" case PetMoodTired: return "Tired" default: return "Unknown" } } // IsValid 检查心情是否有效 func (pm PetMood) IsValid() bool { return pm >= PetMoodHappy && pm <= PetMoodTired } // GetEfficiencyModifier 获取效率修正 func (pm PetMood) GetEfficiencyModifier() float64 { switch pm { case PetMoodHappy: return 1.2 case PetMoodNormal: return 1.0 case PetMoodSad: return 0.8 case PetMoodAngry: return 0.9 case PetMoodExcited: return 1.3 case PetMoodTired: return 0.7 default: return 1.0 } } // GetExperienceModifier 获取经验修正 func (pm PetMood) GetExperienceModifier() float64 { switch pm { case PetMoodHappy: return 1.1 case PetMoodNormal: return 1.0 case PetMoodSad: return 0.9 case PetMoodAngry: return 0.8 case PetMoodExcited: return 1.2 case PetMoodTired: return 0.8 default: return 1.0 } } ================================================ FILE: internal/domain/player/beginner/aggregate.go ================================================ package beginner import ( "fmt" "strings" "time" ) // BeginnerAggregate 新手聚合根 type BeginnerAggregate struct { playerID string guideSteps map[string]*GuideStep tutorials map[string]*Tutorial currentGuide string currentStep int completedGuides []string rewards []*BeginnerReward isCompleted bool startedAt time.Time completedAt *time.Time updatedAt time.Time version int } // NewBeginnerAggregate 创建新手聚合根 func NewBeginnerAggregate(playerID string) *BeginnerAggregate { return &BeginnerAggregate{ playerID: playerID, guideSteps: make(map[string]*GuideStep), tutorials: make(map[string]*Tutorial), currentGuide: "main_guide", // 默认主引导 currentStep: 1, completedGuides: make([]string, 0), rewards: make([]*BeginnerReward, 0), isCompleted: false, startedAt: time.Now(), updatedAt: time.Now(), version: 1, } } // GetPlayerID 获取玩家ID func (b *BeginnerAggregate) GetPlayerID() string { return b.playerID } // StartGuide 开始引导 func (b *BeginnerAggregate) StartGuide(guideID string) error { if b.isCompleted { return ErrBeginnerAlreadyCompleted } // 检查是否已完成该引导 for _, completed := range b.completedGuides { if completed == guideID { return ErrGuideAlreadyCompleted } } b.currentGuide = guideID b.currentStep = 1 b.updateVersion() return nil } // CompleteStep 完成步骤 func (b *BeginnerAggregate) CompleteStep(guideID string, stepID int) error { if b.isCompleted { return ErrBeginnerAlreadyCompleted } if b.currentGuide != guideID { return ErrInvalidGuide } if b.currentStep != stepID { return ErrInvalidStep } stepKey := b.getStepKey(guideID, stepID) step, exists := b.guideSteps[stepKey] if !exists { return ErrStepNotFound } // 标记步骤完成 step.Complete() // 给予奖励 if step.HasReward() { reward := step.GetReward() b.rewards = append(b.rewards, reward) } // 检查是否完成整个引导 if b.isGuideCompleted(guideID) { b.completeGuide(guideID) } else { b.currentStep++ } b.updateVersion() return nil } // AddGuideStep 添加引导步骤 func (b *BeginnerAggregate) AddGuideStep(guideID string, step *GuideStep) error { if step == nil { return ErrInvalidStep } stepKey := b.getStepKey(guideID, step.GetStepID()) b.guideSteps[stepKey] = step b.updateVersion() return nil } // GetCurrentStep 获取当前步骤 func (b *BeginnerAggregate) GetCurrentStep() *GuideStep { stepKey := b.getStepKey(b.currentGuide, b.currentStep) return b.guideSteps[stepKey] } // GetGuideProgress 获取引导进度 func (b *BeginnerAggregate) GetGuideProgress(guideID string) *GuideProgress { totalSteps := b.countGuideSteps(guideID) completedSteps := b.countCompletedSteps(guideID) return &GuideProgress{ GuideID: guideID, TotalSteps: totalSteps, CompletedSteps: completedSteps, IsCompleted: b.isGuideCompleted(guideID), Progress: float64(completedSteps) / float64(totalSteps), } } // AddTutorial 添加教程 func (b *BeginnerAggregate) AddTutorial(tutorial *Tutorial) error { if tutorial == nil { return ErrInvalidTutorial } b.tutorials[tutorial.GetID()] = tutorial b.updateVersion() return nil } // CompleteTutorial 完成教程 func (b *BeginnerAggregate) CompleteTutorial(tutorialID string) error { tutorial, exists := b.tutorials[tutorialID] if !exists { return ErrTutorialNotFound } tutorial.Complete() // 给予教程奖励 if tutorial.HasReward() { reward := tutorial.GetReward() b.rewards = append(b.rewards, reward) } b.updateVersion() return nil } // GetTutorial 获取教程 func (b *BeginnerAggregate) GetTutorial(tutorialID string) *Tutorial { return b.tutorials[tutorialID] } // GetAllTutorials 获取所有教程 func (b *BeginnerAggregate) GetAllTutorials() map[string]*Tutorial { return b.tutorials } // GetUnclaimedRewards 获取未领取的奖励 func (b *BeginnerAggregate) GetUnclaimedRewards() []*BeginnerReward { var unclaimed []*BeginnerReward for _, reward := range b.rewards { if !reward.IsClaimed() { unclaimed = append(unclaimed, reward) } } return unclaimed } // ClaimReward 领取奖励 func (b *BeginnerAggregate) ClaimReward(rewardID string) error { for _, reward := range b.rewards { if reward.GetID() == rewardID { if reward.IsClaimed() { return ErrRewardAlreadyClaimed } reward.Claim() b.updateVersion() return nil } } return ErrRewardNotFound } // IsCompleted 检查是否完成新手引导 func (b *BeginnerAggregate) IsCompleted() bool { return b.isCompleted } // GetCurrentGuide 获取当前引导 func (b *BeginnerAggregate) GetCurrentGuide() string { return b.currentGuide } // GetCurrentStepID 获取当前步骤ID func (b *BeginnerAggregate) GetCurrentStepID() int { return b.currentStep } // GetCompletedGuides 获取已完成的引导 func (b *BeginnerAggregate) GetCompletedGuides() []string { return b.completedGuides } // GetStartedAt 获取开始时间 func (b *BeginnerAggregate) GetStartedAt() time.Time { return b.startedAt } // GetCompletedAt 获取完成时间 func (b *BeginnerAggregate) GetCompletedAt() *time.Time { return b.completedAt } // GetVersion 获取版本 func (b *BeginnerAggregate) GetVersion() int { return b.version } // GetUpdatedAt 获取更新时间 func (b *BeginnerAggregate) GetUpdatedAt() time.Time { return b.updatedAt } // 私有方法 // getStepKey 获取步骤键 func (b *BeginnerAggregate) getStepKey(guideID string, stepID int) string { return fmt.Sprintf("%s_%d", guideID, stepID) } // isGuideCompleted 检查引导是否完成 func (b *BeginnerAggregate) isGuideCompleted(guideID string) bool { totalSteps := b.countGuideSteps(guideID) completedSteps := b.countCompletedSteps(guideID) return completedSteps >= totalSteps } // countGuideSteps 统计引导步骤数 func (b *BeginnerAggregate) countGuideSteps(guideID string) int { count := 0 for stepKey := range b.guideSteps { if strings.HasPrefix(stepKey, guideID+"_") { count++ } } return count } // countCompletedSteps 统计已完成步骤数 func (b *BeginnerAggregate) countCompletedSteps(guideID string) int { count := 0 for stepKey, step := range b.guideSteps { if strings.HasPrefix(stepKey, guideID+"_") && step.IsCompleted() { count++ } } return count } // completeGuide 完成引导 func (b *BeginnerAggregate) completeGuide(guideID string) { b.completedGuides = append(b.completedGuides, guideID) // 检查是否完成所有必要的引导 if b.isAllRequiredGuidesCompleted() { b.isCompleted = true now := time.Now() b.completedAt = &now // 给予完成新手引导的最终奖励 finalReward := NewBeginnerReward("final_reward", RewardTypeMultiple, map[string]interface{}{ "gold": 1000, "experience": 500, "items": []string{"beginner_sword", "beginner_armor"}, }, "完成新手引导奖励") b.rewards = append(b.rewards, finalReward) } } // isAllRequiredGuidesCompleted 检查是否完成所有必要引导 func (b *BeginnerAggregate) isAllRequiredGuidesCompleted() bool { requiredGuides := []string{"main_guide", "combat_guide", "inventory_guide"} for _, required := range requiredGuides { found := false for _, completed := range b.completedGuides { if completed == required { found = true break } } if !found { return false } } return true } // updateVersion 更新版本 func (b *BeginnerAggregate) updateVersion() { b.version++ b.updatedAt = time.Now() } ================================================ FILE: internal/domain/player/beginner/entity.go ================================================ package beginner import ( // "fmt" // 未使用 // "strings" // 未使用 "time" "github.com/google/uuid" ) // GuideStep 引导步骤实体 type GuideStep struct { id string stepID int guideID string title string description string stepType StepType conditions []*StepCondition reward *BeginnerReward isCompleted bool completedAt *time.Time createdAt time.Time } // NewGuideStep 创建引导步骤 func NewGuideStep(stepID int, guideID, title, description string, stepType StepType) *GuideStep { return &GuideStep{ id: uuid.New().String(), stepID: stepID, guideID: guideID, title: title, description: description, stepType: stepType, conditions: make([]*StepCondition, 0), isCompleted: false, createdAt: time.Now(), } } // GetID 获取步骤ID func (gs *GuideStep) GetID() string { return gs.id } // GetStepID 获取步骤序号 func (gs *GuideStep) GetStepID() int { return gs.stepID } // GetGuideID 获取引导ID func (gs *GuideStep) GetGuideID() string { return gs.guideID } // GetTitle 获取标题 func (gs *GuideStep) GetTitle() string { return gs.title } // GetDescription 获取描述 func (gs *GuideStep) GetDescription() string { return gs.description } // GetStepType 获取步骤类型 func (gs *GuideStep) GetStepType() StepType { return gs.stepType } // AddCondition 添加完成条件 func (gs *GuideStep) AddCondition(condition *StepCondition) { gs.conditions = append(gs.conditions, condition) } // GetConditions 获取完成条件 func (gs *GuideStep) GetConditions() []*StepCondition { return gs.conditions } // SetReward 设置奖励 func (gs *GuideStep) SetReward(reward *BeginnerReward) { gs.reward = reward } // GetReward 获取奖励 func (gs *GuideStep) GetReward() *BeginnerReward { return gs.reward } // HasReward 是否有奖励 func (gs *GuideStep) HasReward() bool { return gs.reward != nil } // Complete 完成步骤 func (gs *GuideStep) Complete() { gs.isCompleted = true now := time.Now() gs.completedAt = &now } // IsCompleted 是否已完成 func (gs *GuideStep) IsCompleted() bool { return gs.isCompleted } // GetCompletedAt 获取完成时间 func (gs *GuideStep) GetCompletedAt() *time.Time { return gs.completedAt } // GetCreatedAt 获取创建时间 func (gs *GuideStep) GetCreatedAt() time.Time { return gs.createdAt } // CheckConditions 检查完成条件 func (gs *GuideStep) CheckConditions(playerData map[string]interface{}) bool { for _, condition := range gs.conditions { if !condition.IsMet(playerData) { return false } } return true } // Tutorial 教程实体 type Tutorial struct { id string name string category TutorialCategory content string mediaURL string duration time.Duration reward *BeginnerReward isCompleted bool completedAt *time.Time createdAt time.Time } // NewTutorial 创建教程 func NewTutorial(name string, category TutorialCategory, content string) *Tutorial { return &Tutorial{ id: uuid.New().String(), name: name, category: category, content: content, duration: time.Minute * 5, // 默认5分钟 isCompleted: false, createdAt: time.Now(), } } // GetID 获取教程ID func (t *Tutorial) GetID() string { return t.id } // GetName 获取教程名称 func (t *Tutorial) GetName() string { return t.name } // GetCategory 获取教程分类 func (t *Tutorial) GetCategory() TutorialCategory { return t.category } // GetContent 获取教程内容 func (t *Tutorial) GetContent() string { return t.content } // SetContent 设置教程内容 func (t *Tutorial) SetContent(content string) { t.content = content } // GetMediaURL 获取媒体URL func (t *Tutorial) GetMediaURL() string { return t.mediaURL } // SetMediaURL 设置媒体URL func (t *Tutorial) SetMediaURL(url string) { t.mediaURL = url } // GetDuration 获取时长 func (t *Tutorial) GetDuration() time.Duration { return t.duration } // SetDuration 设置时长 func (t *Tutorial) SetDuration(duration time.Duration) { t.duration = duration } // SetReward 设置奖励 func (t *Tutorial) SetReward(reward *BeginnerReward) { t.reward = reward } // GetReward 获取奖励 func (t *Tutorial) GetReward() *BeginnerReward { return t.reward } // HasReward 是否有奖励 func (t *Tutorial) HasReward() bool { return t.reward != nil } // Complete 完成教程 func (t *Tutorial) Complete() { t.isCompleted = true now := time.Now() t.completedAt = &now } // IsCompleted 是否已完成 func (t *Tutorial) IsCompleted() bool { return t.isCompleted } // GetCompletedAt 获取完成时间 func (t *Tutorial) GetCompletedAt() *time.Time { return t.completedAt } // GetCreatedAt 获取创建时间 func (t *Tutorial) GetCreatedAt() time.Time { return t.createdAt } // BeginnerReward 新手奖励实体 type BeginnerReward struct { id string rewardType RewardType rewardData map[string]interface{} description string isClaimed bool claimedAt *time.Time createdAt time.Time } // NewBeginnerReward 创建新手奖励 func NewBeginnerReward(id string, rewardType RewardType, rewardData map[string]interface{}, description string) *BeginnerReward { return &BeginnerReward{ id: id, rewardType: rewardType, rewardData: rewardData, description: description, isClaimed: false, createdAt: time.Now(), } } // GetID 获取奖励ID func (br *BeginnerReward) GetID() string { return br.id } // GetRewardType 获取奖励类型 func (br *BeginnerReward) GetRewardType() RewardType { return br.rewardType } // GetRewardData 获取奖励数据 func (br *BeginnerReward) GetRewardData() map[string]interface{} { return br.rewardData } // GetDescription 获取描述 func (br *BeginnerReward) GetDescription() string { return br.description } // Claim 领取奖励 func (br *BeginnerReward) Claim() { br.isClaimed = true now := time.Now() br.claimedAt = &now } // IsClaimed 是否已领取 func (br *BeginnerReward) IsClaimed() bool { return br.isClaimed } // GetClaimedAt 获取领取时间 func (br *BeginnerReward) GetClaimedAt() *time.Time { return br.claimedAt } // GetCreatedAt 获取创建时间 func (br *BeginnerReward) GetCreatedAt() time.Time { return br.createdAt } // GetGoldAmount 获取金币数量 func (br *BeginnerReward) GetGoldAmount() int { if gold, exists := br.rewardData["gold"]; exists { if amount, ok := gold.(int); ok { return amount } } return 0 } // GetExperienceAmount 获取经验数量 func (br *BeginnerReward) GetExperienceAmount() int { if exp, exists := br.rewardData["experience"]; exists { if amount, ok := exp.(int); ok { return amount } } return 0 } // GetItems 获取物品列表 func (br *BeginnerReward) GetItems() []string { if items, exists := br.rewardData["items"]; exists { if itemList, ok := items.([]string); ok { return itemList } } return []string{} } // GuideProgress 引导进度实体 type GuideProgress struct { GuideID string `json:"guide_id"` TotalSteps int `json:"total_steps"` CompletedSteps int `json:"completed_steps"` IsCompleted bool `json:"is_completed"` Progress float64 `json:"progress"` } // GetProgressPercentage 获取进度百分比 func (gp *GuideProgress) GetProgressPercentage() int { return int(gp.Progress * 100) } // GetRemainingSteps 获取剩余步骤数 func (gp *GuideProgress) GetRemainingSteps() int { return gp.TotalSteps - gp.CompletedSteps } ================================================ FILE: internal/domain/player/beginner/errors.go ================================================ package beginner import "errors" // 新手系统相关错误 var ( ErrInvalidGuide = errors.New("invalid guide") ErrGuideNotFound = errors.New("guide not found") ErrGuideAlreadyCompleted = errors.New("guide already completed") ErrInvalidStep = errors.New("invalid step") ErrStepNotFound = errors.New("step not found") ErrStepAlreadyCompleted = errors.New("step already completed") ErrConditionNotMet = errors.New("step condition not met") ErrInvalidTutorial = errors.New("invalid tutorial") ErrTutorialNotFound = errors.New("tutorial not found") ErrTutorialAlreadyCompleted = errors.New("tutorial already completed") ErrInvalidReward = errors.New("invalid reward") ErrRewardNotFound = errors.New("reward not found") ErrRewardAlreadyClaimed = errors.New("reward already claimed") ErrBeginnerAlreadyCompleted = errors.New("beginner guide already completed") ErrInvalidCondition = errors.New("invalid step condition") ErrInvalidRewardType = errors.New("invalid reward type") ErrInsufficientLevel = errors.New("insufficient level for guide") ErrPrerequisiteNotMet = errors.New("prerequisite guide not completed") ) ================================================ FILE: internal/domain/player/beginner/events.go ================================================ package beginner import ( "github.com/google/uuid" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetEventData() interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` OccurredAt time.Time `json:"occurred_at"` } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // BeginnerGuideStartedEvent 新手引导开始事件 type BeginnerGuideStartedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` GuideID string `json:"guide_id"` } // NewBeginnerGuideStartedEvent 创建新手引导开始事件 func NewBeginnerGuideStartedEvent(playerID, guideID string) *BeginnerGuideStartedEvent { return &BeginnerGuideStartedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "BeginnerGuideStarted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, GuideID: guideID, } } // GetEventData 获取事件数据 func (e *BeginnerGuideStartedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "guide_id": e.GuideID, } } // GuideStepCompletedEvent 引导步骤完成事件 type GuideStepCompletedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` GuideID string `json:"guide_id"` StepID int `json:"step_id"` Step *GuideStep `json:"step"` Reward *BeginnerReward `json:"reward"` } // NewGuideStepCompletedEvent 创建引导步骤完成事件 func NewGuideStepCompletedEvent(playerID, guideID string, stepID int, step *GuideStep, reward *BeginnerReward) *GuideStepCompletedEvent { return &GuideStepCompletedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "GuideStepCompleted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, GuideID: guideID, StepID: stepID, Step: step, Reward: reward, } } // GetEventData 获取事件数据 func (e *GuideStepCompletedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "guide_id": e.GuideID, "step_id": e.StepID, "step": e.Step, "reward": e.Reward, } } // BeginnerGuideCompletedEvent 新手引导完成事件 type BeginnerGuideCompletedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` GuideID string `json:"guide_id"` Progress *GuideProgress `json:"progress"` Reward *BeginnerReward `json:"reward"` } // NewBeginnerGuideCompletedEvent 创建新手引导完成事件 func NewBeginnerGuideCompletedEvent(playerID, guideID string, progress *GuideProgress, reward *BeginnerReward) *BeginnerGuideCompletedEvent { return &BeginnerGuideCompletedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "BeginnerGuideCompleted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, GuideID: guideID, Progress: progress, Reward: reward, } } // GetEventData 获取事件数据 func (e *BeginnerGuideCompletedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "guide_id": e.GuideID, "progress": e.Progress, "reward": e.Reward, } } // TutorialStartedEvent 教程开始事件 type TutorialStartedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` TutorialID string `json:"tutorial_id"` Tutorial *Tutorial `json:"tutorial"` } // NewTutorialStartedEvent 创建教程开始事件 func NewTutorialStartedEvent(playerID, tutorialID string, tutorial *Tutorial) *TutorialStartedEvent { return &TutorialStartedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "TutorialStarted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, TutorialID: tutorialID, Tutorial: tutorial, } } // GetEventData 获取事件数据 func (e *TutorialStartedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "tutorial_id": e.TutorialID, "tutorial": e.Tutorial, } } // TutorialCompletedEvent 教程完成事件 type TutorialCompletedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` TutorialID string `json:"tutorial_id"` Tutorial *Tutorial `json:"tutorial"` Reward *BeginnerReward `json:"reward"` } // NewTutorialCompletedEvent 创建教程完成事件 func NewTutorialCompletedEvent(playerID, tutorialID string, tutorial *Tutorial, reward *BeginnerReward) *TutorialCompletedEvent { return &TutorialCompletedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "TutorialCompleted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, TutorialID: tutorialID, Tutorial: tutorial, Reward: reward, } } // GetEventData 获取事件数据 func (e *TutorialCompletedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "tutorial_id": e.TutorialID, "tutorial": e.Tutorial, "reward": e.Reward, } } // BeginnerRewardClaimedEvent 新手奖励领取事件 type BeginnerRewardClaimedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` RewardID string `json:"reward_id"` Reward *BeginnerReward `json:"reward"` Source string `json:"source"` // guide, tutorial, final } // NewBeginnerRewardClaimedEvent 创建新手奖励领取事件 func NewBeginnerRewardClaimedEvent(playerID, rewardID string, reward *BeginnerReward, source string) *BeginnerRewardClaimedEvent { return &BeginnerRewardClaimedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "BeginnerRewardClaimed", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, RewardID: rewardID, Reward: reward, Source: source, } } // GetEventData 获取事件数据 func (e *BeginnerRewardClaimedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "reward_id": e.RewardID, "reward": e.Reward, "source": e.Source, } } // AllBeginnerGuidesCompletedEvent 所有新手引导完成事件 type AllBeginnerGuidesCompletedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` CompletedGuides []string `json:"completed_guides"` FinalReward *BeginnerReward `json:"final_reward"` CompletionTime time.Duration `json:"completion_time"` } // NewAllBeginnerGuidesCompletedEvent 创建所有新手引导完成事件 func NewAllBeginnerGuidesCompletedEvent(playerID string, completedGuides []string, finalReward *BeginnerReward, completionTime time.Duration) *AllBeginnerGuidesCompletedEvent { return &AllBeginnerGuidesCompletedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "AllBeginnerGuidesCompleted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, CompletedGuides: completedGuides, FinalReward: finalReward, CompletionTime: completionTime, } } // GetEventData 获取事件数据 func (e *AllBeginnerGuidesCompletedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "completed_guides": e.CompletedGuides, "final_reward": e.FinalReward, "completion_time": e.CompletionTime, } } // BeginnerProgressUpdatedEvent 新手进度更新事件 type BeginnerProgressUpdatedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` GuideID string `json:"guide_id"` CurrentStep int `json:"current_step"` Progress *GuideProgress `json:"progress"` NextStepHint string `json:"next_step_hint"` } // NewBeginnerProgressUpdatedEvent 创建新手进度更新事件 func NewBeginnerProgressUpdatedEvent(playerID, guideID string, currentStep int, progress *GuideProgress, nextStepHint string) *BeginnerProgressUpdatedEvent { return &BeginnerProgressUpdatedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "BeginnerProgressUpdated", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, GuideID: guideID, CurrentStep: currentStep, Progress: progress, NextStepHint: nextStepHint, } } // GetEventData 获取事件数据 func (e *BeginnerProgressUpdatedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "guide_id": e.GuideID, "current_step": e.CurrentStep, "progress": e.Progress, "next_step_hint": e.NextStepHint, } } // BeginnerSkippedEvent 新手引导跳过事件 type BeginnerSkippedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` GuideID string `json:"guide_id"` StepID int `json:"step_id"` Reason string `json:"reason"` } // NewBeginnerSkippedEvent 创建新手引导跳过事件 func NewBeginnerSkippedEvent(playerID, guideID string, stepID int, reason string) *BeginnerSkippedEvent { return &BeginnerSkippedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "BeginnerSkipped", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, GuideID: guideID, StepID: stepID, Reason: reason, } } // GetEventData 获取事件数据 func (e *BeginnerSkippedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "guide_id": e.GuideID, "step_id": e.StepID, "reason": e.Reason, } } // BeginnerHelpRequestedEvent 新手帮助请求事件 type BeginnerHelpRequestedEvent struct { BaseDomainEvent PlayerID string `json:"player_id"` GuideID string `json:"guide_id"` StepID int `json:"step_id"` HelpType string `json:"help_type"` // hint, tutorial, support Description string `json:"description"` } // NewBeginnerHelpRequestedEvent 创建新手帮助请求事件 func NewBeginnerHelpRequestedEvent(playerID, guideID string, stepID int, helpType, description string) *BeginnerHelpRequestedEvent { return &BeginnerHelpRequestedEvent{ BaseDomainEvent: BaseDomainEvent{ EventID: uuid.New().String(), EventType: "BeginnerHelpRequested", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, GuideID: guideID, StepID: stepID, HelpType: helpType, Description: description, } } // GetEventData 获取事件数据 func (e *BeginnerHelpRequestedEvent) GetEventData() interface{} { return map[string]interface{}{ "player_id": e.PlayerID, "guide_id": e.GuideID, "step_id": e.StepID, "help_type": e.HelpType, "description": e.Description, } } ================================================ FILE: internal/domain/player/beginner/repository.go ================================================ package beginner import ( "context" "time" ) // BeginnerRepository 新手仓储接口 type BeginnerRepository interface { // SaveBeginnerAggregate 保存新手聚合根 SaveBeginnerAggregate(ctx context.Context, aggregate *BeginnerAggregate) error // GetBeginnerAggregate 获取新手聚合根 GetBeginnerAggregate(ctx context.Context, playerID string) (*BeginnerAggregate, error) // DeleteBeginnerAggregate 删除新手聚合根 DeleteBeginnerAggregate(ctx context.Context, playerID string) error // SaveGuideStep 保存引导步骤 SaveGuideStep(ctx context.Context, playerID string, step *GuideStep) error // GetGuideStep 获取引导步骤 GetGuideStep(ctx context.Context, playerID, guideID string, stepID int) (*GuideStep, error) // GetGuideSteps 获取引导的所有步骤 GetGuideSteps(ctx context.Context, playerID, guideID string) ([]*GuideStep, error) // UpdateGuideStep 更新引导步骤 UpdateGuideStep(ctx context.Context, playerID string, step *GuideStep) error // SaveTutorial 保存教程 SaveTutorial(ctx context.Context, playerID string, tutorial *Tutorial) error // GetTutorial 获取教程 GetTutorial(ctx context.Context, playerID, tutorialID string) (*Tutorial, error) // GetPlayerTutorials 获取玩家所有教程 GetPlayerTutorials(ctx context.Context, playerID string) ([]*Tutorial, error) // UpdateTutorial 更新教程 UpdateTutorial(ctx context.Context, playerID string, tutorial *Tutorial) error // SaveBeginnerReward 保存新手奖励 SaveBeginnerReward(ctx context.Context, playerID string, reward *BeginnerReward) error // GetBeginnerReward 获取新手奖励 GetBeginnerReward(ctx context.Context, playerID, rewardID string) (*BeginnerReward, error) // GetPlayerRewards 获取玩家所有奖励 GetPlayerRewards(ctx context.Context, playerID string) ([]*BeginnerReward, error) // GetUnclaimedRewards 获取未领取的奖励 GetUnclaimedRewards(ctx context.Context, playerID string) ([]*BeginnerReward, error) // UpdateRewardStatus 更新奖励状态 UpdateRewardStatus(ctx context.Context, playerID, rewardID string, claimed bool) error // GetGuideProgress 获取引导进度 GetGuideProgress(ctx context.Context, playerID, guideID string) (*GuideProgress, error) // GetCompletedGuides 获取已完成的引导 GetCompletedGuides(ctx context.Context, playerID string) ([]string, error) // IsGuideCompleted 检查引导是否完成 IsGuideCompleted(ctx context.Context, playerID, guideID string) (bool, error) // IsTutorialCompleted 检查教程是否完成 IsTutorialCompleted(ctx context.Context, playerID, tutorialID string) (bool, error) // GetTutorialsByCategory 根据分类获取教程 GetTutorialsByCategory(ctx context.Context, playerID string, category TutorialCategory) ([]*Tutorial, error) // GetCompletedTutorials 获取已完成的教程 GetCompletedTutorials(ctx context.Context, playerID string) ([]*Tutorial, error) } // GuideTemplateRepository 引导模板仓储接口 type GuideTemplateRepository interface { // GetGuideTemplate 获取引导模板 GetGuideTemplate(ctx context.Context, templateID string) (*GuideTemplate, error) // GetGuideTemplatesByCategory 根据分类获取引导模板 GetGuideTemplatesByCategory(ctx context.Context, category string) ([]*GuideTemplate, error) // SaveGuideTemplate 保存引导模板 SaveGuideTemplate(ctx context.Context, template *GuideTemplate) error // DeleteGuideTemplate 删除引导模板 DeleteGuideTemplate(ctx context.Context, templateID string) error // GetAllGuideTemplates 获取所有引导模板 GetAllGuideTemplates(ctx context.Context) ([]*GuideTemplate, error) } // TutorialTemplateRepository 教程模板仓储接口 type TutorialTemplateRepository interface { // GetTutorialTemplate 获取教程模板 GetTutorialTemplate(ctx context.Context, templateID string) (*TutorialTemplate, error) // GetTutorialTemplatesByCategory 根据分类获取教程模板 GetTutorialTemplatesByCategory(ctx context.Context, category TutorialCategory) ([]*TutorialTemplate, error) // SaveTutorialTemplate 保存教程模板 SaveTutorialTemplate(ctx context.Context, template *TutorialTemplate) error // DeleteTutorialTemplate 删除教程模板 DeleteTutorialTemplate(ctx context.Context, templateID string) error // GetAllTutorialTemplates 获取所有教程模板 GetAllTutorialTemplates(ctx context.Context) ([]*TutorialTemplate, error) } // GuideTemplate 引导模板 type GuideTemplate struct { ID string `json:"id"` Name string `json:"name"` Category string `json:"category"` Description string `json:"description"` Steps []*GuideStepTemplate `json:"steps"` Prerequisites []string `json:"prerequisites"` RequireLevel int `json:"require_level"` Reward *RewardTemplate `json:"reward"` IsActive bool `json:"is_active"` } // GuideStepTemplate 引导步骤模板 type GuideStepTemplate struct { StepID int `json:"step_id"` Title string `json:"title"` Description string `json:"description"` StepType StepType `json:"step_type"` Conditions []*StepConditionTemplate `json:"conditions"` Reward *RewardTemplate `json:"reward"` } // StepConditionTemplate 步骤条件模板 type StepConditionTemplate struct { ConditionType ConditionType `json:"condition_type"` Target string `json:"target"` Value interface{} `json:"value"` Operator ComparisonOperator `json:"operator"` Description string `json:"description"` } // TutorialTemplate 教程模板 type TutorialTemplate struct { ID string `json:"id"` Name string `json:"name"` Category TutorialCategory `json:"category"` Content string `json:"content"` MediaURL string `json:"media_url"` Duration int64 `json:"duration"` // 毫秒 Reward *RewardTemplate `json:"reward"` IsActive bool `json:"is_active"` } // RewardTemplate 奖励模板 type RewardTemplate struct { RewardType RewardType `json:"reward_type"` RewardData map[string]interface{} `json:"reward_data"` Description string `json:"description"` } // CreateGuideFromTemplate 从模板创建引导步骤 func (gt *GuideTemplate) CreateGuideFromTemplate() []*GuideStep { steps := make([]*GuideStep, len(gt.Steps)) for i, stepTemplate := range gt.Steps { step := NewGuideStep( stepTemplate.StepID, gt.ID, stepTemplate.Title, stepTemplate.Description, stepTemplate.StepType, ) // 添加条件 for _, condTemplate := range stepTemplate.Conditions { condition := NewStepCondition( condTemplate.ConditionType, condTemplate.Target, condTemplate.Value, condTemplate.Operator, condTemplate.Description, ) step.AddCondition(condition) } // 添加奖励 if stepTemplate.Reward != nil { reward := NewBeginnerReward( gt.ID+"_step_"+string(rune(stepTemplate.StepID))+"_reward", stepTemplate.Reward.RewardType, stepTemplate.Reward.RewardData, stepTemplate.Reward.Description, ) step.SetReward(reward) } steps[i] = step } return steps } // CreateTutorialFromTemplate 从模板创建教程 func (tt *TutorialTemplate) CreateTutorialFromTemplate() *Tutorial { tutorial := NewTutorial(tt.Name, tt.Category, tt.Content) tutorial.SetMediaURL(tt.MediaURL) tutorial.SetDuration(time.Duration(tt.Duration) * time.Millisecond) // 添加奖励 if tt.Reward != nil { reward := NewBeginnerReward( tt.ID+"_reward", tt.Reward.RewardType, tt.Reward.RewardData, tt.Reward.Description, ) tutorial.SetReward(reward) } return tutorial } ================================================ FILE: internal/domain/player/beginner/service.go ================================================ package beginner import ( "time" ) // BeginnerService 新手领域服务 type BeginnerService struct { guideFactory *GuideFactory tutorialFactory *TutorialFactory rewardCalculator *RewardCalculator } // NewBeginnerService 创建新手服务 func NewBeginnerService() *BeginnerService { return &BeginnerService{ guideFactory: NewGuideFactory(), tutorialFactory: NewTutorialFactory(), rewardCalculator: NewRewardCalculator(), } } // ValidateStepCompletion 验证步骤完成 func (bs *BeginnerService) ValidateStepCompletion(step *GuideStep, playerData map[string]interface{}) error { if step == nil { return ErrInvalidStep } if step.IsCompleted() { return ErrStepAlreadyCompleted } // 检查完成条件 if !step.CheckConditions(playerData) { return ErrConditionNotMet } return nil } // CalculateGuideReward 计算引导奖励 func (bs *BeginnerService) CalculateGuideReward(guideID string, playerLevel int) *BeginnerReward { return bs.rewardCalculator.CalculateGuideReward(guideID, playerLevel) } // CalculateTutorialReward 计算教程奖励 func (bs *BeginnerService) CalculateTutorialReward(category TutorialCategory, playerLevel int) *BeginnerReward { return bs.rewardCalculator.CalculateTutorialReward(category, playerLevel) } // CreateGuideFromTemplate 从模板创建引导 func (bs *BeginnerService) CreateGuideFromTemplate(templateID string, playerLevel int) ([]*GuideStep, error) { return bs.guideFactory.CreateFromTemplate(templateID, playerLevel) } // CreateTutorialFromTemplate 从模板创建教程 func (bs *BeginnerService) CreateTutorialFromTemplate(templateID string) (*Tutorial, error) { return bs.tutorialFactory.CreateFromTemplate(templateID) } // CheckPrerequisites 检查前置条件 func (bs *BeginnerService) CheckPrerequisites(guideID string, completedGuides []string) bool { prerequisites := bs.getGuidePrerequisites(guideID) for _, prerequisite := range prerequisites { found := false for _, completed := range completedGuides { if completed == prerequisite { found = true break } } if !found { return false } } return true } // GetRecommendedNextGuide 获取推荐的下一个引导 func (bs *BeginnerService) GetRecommendedNextGuide(completedGuides []string, playerLevel int) string { allGuides := []string{"main_guide", "combat_guide", "inventory_guide", "social_guide", "economy_guide"} for _, guide := range allGuides { // 检查是否已完成 alreadyCompleted := false for _, completed := range completedGuides { if completed == guide { alreadyCompleted = true break } } if !alreadyCompleted && bs.CheckPrerequisites(guide, completedGuides) { return guide } } return "" } // getGuidePrerequisites 获取引导前置条件 func (bs *BeginnerService) getGuidePrerequisites(guideID string) []string { prerequisites := map[string][]string{ "main_guide": {}, "combat_guide": {"main_guide"}, "inventory_guide": {"main_guide"}, "social_guide": {"main_guide", "combat_guide"}, "economy_guide": {"main_guide", "inventory_guide"}, } return prerequisites[guideID] } // GuideFactory 引导工厂 type GuideFactory struct{} // NewGuideFactory 创建引导工厂 func NewGuideFactory() *GuideFactory { return &GuideFactory{} } // CreateFromTemplate 从模板创建引导 func (gf *GuideFactory) CreateFromTemplate(templateID string, playerLevel int) ([]*GuideStep, error) { switch templateID { case "main_guide": return gf.createMainGuide(), nil case "combat_guide": return gf.createCombatGuide(), nil case "inventory_guide": return gf.createInventoryGuide(), nil case "social_guide": return gf.createSocialGuide(), nil case "economy_guide": return gf.createEconomyGuide(), nil default: return nil, ErrInvalidGuide } } // createMainGuide 创建主引导 func (gf *GuideFactory) createMainGuide() []*GuideStep { steps := []*GuideStep{ NewGuideStep(1, "main_guide", "欢迎来到游戏", "欢迎来到这个奇幻世界!让我们开始你的冒险之旅。", StepTypeDialog), NewGuideStep(2, "main_guide", "移动角色", "使用WASD键或方向键移动你的角色。", StepTypeNavigation), NewGuideStep(3, "main_guide", "查看角色信息", "按C键打开角色面板,查看你的属性。", StepTypeAction), NewGuideStep(4, "main_guide", "完成第一个任务", "前往村长处接受你的第一个任务。", StepTypeQuest), } // 添加条件和奖励 steps[1].AddCondition(NewStepCondition(ConditionTypeLocation, "starting_area", "moved", OperatorEqual, "移动到指定位置")) steps[2].AddCondition(NewStepCondition(ConditionTypeInteract, "character_panel", "opened", OperatorEqual, "打开角色面板")) steps[3].AddCondition(NewStepCondition(ConditionTypeQuest, "first_quest", "accepted", OperatorEqual, "接受第一个任务")) // 设置奖励 steps[0].SetReward(NewBeginnerReward("welcome_reward", RewardTypeGold, map[string]interface{}{"gold": 100}, "欢迎奖励")) steps[3].SetReward(NewBeginnerReward("quest_reward", RewardTypeMultiple, map[string]interface{}{ "gold": 200, "experience": 100, "items": []string{"beginner_sword"}, }, "任务完成奖励")) return steps } // createCombatGuide 创建战斗引导 func (gf *GuideFactory) createCombatGuide() []*GuideStep { steps := []*GuideStep{ NewGuideStep(1, "combat_guide", "学习攻击", "点击鼠标左键或按空格键攻击敌人。", StepTypeCombat), NewGuideStep(2, "combat_guide", "使用技能", "按1-4数字键使用技能攻击敌人。", StepTypeCombat), NewGuideStep(3, "combat_guide", "击败怪物", "击败3只史莱姆来完成战斗训练。", StepTypeCombat), } // 添加条件 steps[0].AddCondition(NewStepCondition(ConditionTypeInteract, "attack", "used", OperatorEqual, "使用攻击")) steps[1].AddCondition(NewStepCondition(ConditionTypeSkill, "basic_skill", "used", OperatorEqual, "使用技能")) steps[2].AddCondition(NewStepCondition(ConditionTypeKill, "slime", 3, OperatorGreaterEqual, "击败3只史莱姆")) // 设置奖励 steps[2].SetReward(NewBeginnerReward("combat_reward", RewardTypeMultiple, map[string]interface{}{ "experience": 200, "items": []string{"health_potion", "mana_potion"}, }, "战斗训练奖励")) return steps } // createInventoryGuide 创建背包引导 func (gf *GuideFactory) createInventoryGuide() []*GuideStep { steps := []*GuideStep{ NewGuideStep(1, "inventory_guide", "打开背包", "按I键打开背包界面。", StepTypeInventory), NewGuideStep(2, "inventory_guide", "装备物品", "将获得的武器装备到武器槽。", StepTypeInventory), NewGuideStep(3, "inventory_guide", "使用消耗品", "右键点击药水来恢复生命值。", StepTypeInventory), } // 添加条件 steps[0].AddCondition(NewStepCondition(ConditionTypeInteract, "inventory", "opened", OperatorEqual, "打开背包")) steps[1].AddCondition(NewStepCondition(ConditionTypeEquip, "weapon", "beginner_sword", OperatorEqual, "装备新手剑")) steps[2].AddCondition(NewStepCondition(ConditionTypeInteract, "use_item", "health_potion", OperatorEqual, "使用生命药水")) return steps } // createSocialGuide 创建社交引导 func (gf *GuideFactory) createSocialGuide() []*GuideStep { steps := []*GuideStep{ NewGuideStep(1, "social_guide", "添加好友", "学习如何添加其他玩家为好友。", StepTypeSocial), NewGuideStep(2, "social_guide", "发送消息", "向好友发送一条消息。", StepTypeSocial), NewGuideStep(3, "social_guide", "加入公会", "申请加入一个公会。", StepTypeSocial), } return steps } // createEconomyGuide 创建经济引导 func (gf *GuideFactory) createEconomyGuide() []*GuideStep { steps := []*GuideStep{ NewGuideStep(1, "economy_guide", "访问商店", "前往商店购买物品。", StepTypeShop), NewGuideStep(2, "economy_guide", "出售物品", "将不需要的物品出售给商人。", StepTypeShop), NewGuideStep(3, "economy_guide", "交易系统", "学习如何与其他玩家交易。", StepTypeShop), } return steps } // TutorialFactory 教程工厂 type TutorialFactory struct{} // NewTutorialFactory 创建教程工厂 func NewTutorialFactory() *TutorialFactory { return &TutorialFactory{} } // CreateFromTemplate 从模板创建教程 func (tf *TutorialFactory) CreateFromTemplate(templateID string) (*Tutorial, error) { switch templateID { case "basic_controls": return tf.createBasicControlsTutorial(), nil case "combat_basics": return tf.createCombatBasicsTutorial(), nil case "inventory_management": return tf.createInventoryManagementTutorial(), nil default: return nil, ErrInvalidTutorial } } // createBasicControlsTutorial 创建基础操作教程 func (tf *TutorialFactory) createBasicControlsTutorial() *Tutorial { tutorial := NewTutorial("基础操作", TutorialCategoryBasic, "学习游戏的基础操作方法") tutorial.SetDuration(time.Minute * 3) tutorial.SetMediaURL("https://example.com/tutorials/basic_controls.mp4") tutorial.SetReward(NewBeginnerReward("basic_tutorial_reward", RewardTypeExperience, map[string]interface{}{"experience": 50}, "基础教程奖励")) return tutorial } // createCombatBasicsTutorial 创建战斗基础教程 func (tf *TutorialFactory) createCombatBasicsTutorial() *Tutorial { tutorial := NewTutorial("战斗基础", TutorialCategoryCombat, "学习战斗系统的基本操作") tutorial.SetDuration(time.Minute * 5) tutorial.SetMediaURL("https://example.com/tutorials/combat_basics.mp4") tutorial.SetReward(NewBeginnerReward("combat_tutorial_reward", RewardTypeSkillPoint, map[string]interface{}{"skill_points": 1}, "战斗教程奖励")) return tutorial } // createInventoryManagementTutorial 创建背包管理教程 func (tf *TutorialFactory) createInventoryManagementTutorial() *Tutorial { tutorial := NewTutorial("背包管理", TutorialCategoryInventory, "学习如何有效管理背包空间") tutorial.SetDuration(time.Minute * 4) tutorial.SetMediaURL("https://example.com/tutorials/inventory_management.mp4") tutorial.SetReward(NewBeginnerReward("inventory_tutorial_reward", RewardTypeItem, map[string]interface{}{"items": []string{"bag_expansion"}}, "背包教程奖励")) return tutorial } // RewardCalculator 奖励计算器 type RewardCalculator struct{} // NewRewardCalculator 创建奖励计算器 func NewRewardCalculator() *RewardCalculator { return &RewardCalculator{} } // CalculateGuideReward 计算引导奖励 func (rc *RewardCalculator) CalculateGuideReward(guideID string, playerLevel int) *BeginnerReward { baseReward := map[string]interface{}{ "gold": 100, "experience": 50, } // 根据引导类型调整奖励 switch guideID { case "main_guide": baseReward["gold"] = 200 baseReward["experience"] = 100 baseReward["items"] = []string{"beginner_weapon"} case "combat_guide": baseReward["experience"] = 150 baseReward["items"] = []string{"health_potion", "mana_potion"} case "inventory_guide": baseReward["gold"] = 150 baseReward["items"] = []string{"bag_expansion"} } // 根据玩家等级调整奖励 levelMultiplier := 1.0 + float64(playerLevel)*0.1 if gold, ok := baseReward["gold"].(int); ok { baseReward["gold"] = int(float64(gold) * levelMultiplier) } if exp, ok := baseReward["experience"].(int); ok { baseReward["experience"] = int(float64(exp) * levelMultiplier) } return NewBeginnerReward(guideID+"_reward", RewardTypeMultiple, baseReward, "引导完成奖励") } // CalculateTutorialReward 计算教程奖励 func (rc *RewardCalculator) CalculateTutorialReward(category TutorialCategory, playerLevel int) *BeginnerReward { baseReward := map[string]interface{}{ "experience": 25, } // 根据教程分类调整奖励 switch category { case TutorialCategoryBasic: baseReward["experience"] = 50 case TutorialCategoryCombat: baseReward["experience"] = 75 baseReward["skill_points"] = 1 case TutorialCategoryInventory: baseReward["gold"] = 100 case TutorialCategorySkills: baseReward["skill_points"] = 2 } return NewBeginnerReward("tutorial_"+category.String()+"_reward", RewardTypeMultiple, baseReward, "教程完成奖励") } ================================================ FILE: internal/domain/player/beginner/value_object.go ================================================ package beginner import ( "strconv" "strings" ) // StepType 步骤类型 type StepType int const ( StepTypeDialog StepType = iota + 1 StepTypeAction StepTypeNavigation StepTypeCombat StepTypeInventory StepTypeShop StepTypeQuest StepTypeSkill StepTypeSocial StepTypeCustom ) // String 返回步骤类型字符串 func (st StepType) String() string { switch st { case StepTypeDialog: return "dialog" case StepTypeAction: return "action" case StepTypeNavigation: return "navigation" case StepTypeCombat: return "combat" case StepTypeInventory: return "inventory" case StepTypeShop: return "shop" case StepTypeQuest: return "quest" case StepTypeSkill: return "skill" case StepTypeSocial: return "social" case StepTypeCustom: return "custom" default: return "unknown" } } // TutorialCategory 教程分类 type TutorialCategory int const ( TutorialCategoryBasic TutorialCategory = iota + 1 TutorialCategoryCombat TutorialCategoryInventory TutorialCategorySkills TutorialCategorySocial TutorialCategoryEconomy TutorialCategoryAdvanced TutorialCategoryPvP TutorialCategoryGuild TutorialCategoryEndGame ) // String 返回教程分类字符串 func (tc TutorialCategory) String() string { switch tc { case TutorialCategoryBasic: return "basic" case TutorialCategoryCombat: return "combat" case TutorialCategoryInventory: return "inventory" case TutorialCategorySkills: return "skills" case TutorialCategorySocial: return "social" case TutorialCategoryEconomy: return "economy" case TutorialCategoryAdvanced: return "advanced" case TutorialCategoryPvP: return "pvp" case TutorialCategoryGuild: return "guild" case TutorialCategoryEndGame: return "endgame" default: return "unknown" } } // RewardType 奖励类型 type RewardType int const ( RewardTypeGold RewardType = iota + 1 RewardTypeExperience RewardTypeItem RewardTypeSkillPoint RewardTypeTitle RewardTypeMultiple RewardTypeCustom ) // String 返回奖励类型字符串 func (rt RewardType) String() string { switch rt { case RewardTypeGold: return "gold" case RewardTypeExperience: return "experience" case RewardTypeItem: return "item" case RewardTypeSkillPoint: return "skill_point" case RewardTypeTitle: return "title" case RewardTypeMultiple: return "multiple" case RewardTypeCustom: return "custom" default: return "unknown" } } // StepCondition 步骤条件值对象 type StepCondition struct { conditionType ConditionType target string value interface{} operator ComparisonOperator description string } // ConditionType 条件类型 type ConditionType int const ( ConditionTypeLevel ConditionType = iota + 1 ConditionTypeLocation ConditionTypeItem ConditionTypeQuest ConditionTypeKill ConditionTypeInteract ConditionTypeEquip ConditionTypeSkill ConditionTypeGold ConditionTypeCustom ) // String 返回条件类型字符串 func (ct ConditionType) String() string { switch ct { case ConditionTypeLevel: return "level" case ConditionTypeLocation: return "location" case ConditionTypeItem: return "item" case ConditionTypeQuest: return "quest" case ConditionTypeKill: return "kill" case ConditionTypeInteract: return "interact" case ConditionTypeEquip: return "equip" case ConditionTypeSkill: return "skill" case ConditionTypeGold: return "gold" case ConditionTypeCustom: return "custom" default: return "unknown" } } // ComparisonOperator 比较操作符 type ComparisonOperator int const ( OperatorEqual ComparisonOperator = iota + 1 OperatorGreaterThan OperatorLessThan OperatorGreaterEqual OperatorLessEqual OperatorNotEqual OperatorContains OperatorExists ) // String 返回操作符字符串 func (co ComparisonOperator) String() string { switch co { case OperatorEqual: return "==" case OperatorGreaterThan: return ">" case OperatorLessThan: return "<" case OperatorGreaterEqual: return ">=" case OperatorLessEqual: return "<=" case OperatorNotEqual: return "!=" case OperatorContains: return "contains" case OperatorExists: return "exists" default: return "unknown" } } // NewStepCondition 创建步骤条件 func NewStepCondition(conditionType ConditionType, target string, value interface{}, operator ComparisonOperator, description string) *StepCondition { return &StepCondition{ conditionType: conditionType, target: target, value: value, operator: operator, description: description, } } // GetConditionType 获取条件类型 func (sc *StepCondition) GetConditionType() ConditionType { return sc.conditionType } // GetTarget 获取目标 func (sc *StepCondition) GetTarget() string { return sc.target } // GetValue 获取值 func (sc *StepCondition) GetValue() interface{} { return sc.value } // GetOperator 获取操作符 func (sc *StepCondition) GetOperator() ComparisonOperator { return sc.operator } // GetDescription 获取描述 func (sc *StepCondition) GetDescription() string { return sc.description } // IsMet 检查条件是否满足 func (sc *StepCondition) IsMet(playerData map[string]interface{}) bool { switch sc.conditionType { case ConditionTypeLevel: return sc.checkLevelCondition(playerData) case ConditionTypeLocation: return sc.checkLocationCondition(playerData) case ConditionTypeItem: return sc.checkItemCondition(playerData) case ConditionTypeQuest: return sc.checkQuestCondition(playerData) case ConditionTypeKill: return sc.checkKillCondition(playerData) case ConditionTypeInteract: return sc.checkInteractCondition(playerData) case ConditionTypeEquip: return sc.checkEquipCondition(playerData) case ConditionTypeSkill: return sc.checkSkillCondition(playerData) case ConditionTypeGold: return sc.checkGoldCondition(playerData) case ConditionTypeCustom: return sc.checkCustomCondition(playerData) default: return false } } // checkLevelCondition 检查等级条件 func (sc *StepCondition) checkLevelCondition(playerData map[string]interface{}) bool { if level, exists := playerData["level"]; exists { if playerLevel, ok := level.(int); ok { if targetLevel, ok := sc.value.(int); ok { return sc.compareValues(playerLevel, targetLevel) } } } return false } // checkLocationCondition 检查位置条件 func (sc *StepCondition) checkLocationCondition(playerData map[string]interface{}) bool { if location, exists := playerData["location"]; exists { if playerLocation, ok := location.(string); ok { if targetLocation, ok := sc.value.(string); ok { switch sc.operator { case OperatorEqual: return playerLocation == targetLocation case OperatorContains: return strings.Contains(playerLocation, targetLocation) default: return false } } } } return false } // checkItemCondition 检查物品条件 func (sc *StepCondition) checkItemCondition(playerData map[string]interface{}) bool { if inventory, exists := playerData["inventory"]; exists { if items, ok := inventory.(map[string]int); ok { if targetItem, ok := sc.value.(string); ok { if quantity, hasItem := items[targetItem]; hasItem { switch sc.operator { case OperatorExists: return quantity > 0 case OperatorGreaterEqual: if requiredQuantity, err := strconv.Atoi(sc.target); err == nil { return quantity >= requiredQuantity } default: return quantity > 0 } } } } } return false } // checkQuestCondition 检查任务条件 func (sc *StepCondition) checkQuestCondition(playerData map[string]interface{}) bool { if quests, exists := playerData["quests"]; exists { if questMap, ok := quests.(map[string]string); ok { if targetQuest, ok := sc.value.(string); ok { if status, hasQuest := questMap[targetQuest]; hasQuest { return status == sc.target } } } } return false } // checkKillCondition 检查击杀条件 func (sc *StepCondition) checkKillCondition(playerData map[string]interface{}) bool { if kills, exists := playerData["kills"]; exists { if killMap, ok := kills.(map[string]int); ok { if targetMonster, ok := sc.value.(string); ok { if killCount, hasKill := killMap[targetMonster]; hasKill { if requiredCount, err := strconv.Atoi(sc.target); err == nil { return sc.compareValues(killCount, requiredCount) } } } } } return false } // checkInteractCondition 检查交互条件 func (sc *StepCondition) checkInteractCondition(playerData map[string]interface{}) bool { if interactions, exists := playerData["interactions"]; exists { if interactionList, ok := interactions.([]string); ok { if targetInteraction, ok := sc.value.(string); ok { for _, interaction := range interactionList { if interaction == targetInteraction { return true } } } } } return false } // checkEquipCondition 检查装备条件 func (sc *StepCondition) checkEquipCondition(playerData map[string]interface{}) bool { if equipment, exists := playerData["equipment"]; exists { if equipMap, ok := equipment.(map[string]string); ok { if targetItem, ok := sc.value.(string); ok { if equippedItem, hasSlot := equipMap[sc.target]; hasSlot { return equippedItem == targetItem } } } } return false } // checkSkillCondition 检查技能条件 func (sc *StepCondition) checkSkillCondition(playerData map[string]interface{}) bool { if skills, exists := playerData["skills"]; exists { if skillMap, ok := skills.(map[string]int); ok { if targetSkill, ok := sc.value.(string); ok { if skillLevel, hasSkill := skillMap[targetSkill]; hasSkill { if requiredLevel, err := strconv.Atoi(sc.target); err == nil { return sc.compareValues(skillLevel, requiredLevel) } } } } } return false } // checkGoldCondition 检查金币条件 func (sc *StepCondition) checkGoldCondition(playerData map[string]interface{}) bool { if gold, exists := playerData["gold"]; exists { if playerGold, ok := gold.(int); ok { if requiredGold, ok := sc.value.(int); ok { return sc.compareValues(playerGold, requiredGold) } } } return false } // checkCustomCondition 检查自定义条件 func (sc *StepCondition) checkCustomCondition(playerData map[string]interface{}) bool { // 自定义条件的实现可以根据具体需求来定制 if customData, exists := playerData[sc.target]; exists { switch sc.operator { case OperatorExists: return customData != nil case OperatorEqual: return customData == sc.value default: return false } } return false } // compareValues 比较数值 func (sc *StepCondition) compareValues(actual, expected int) bool { switch sc.operator { case OperatorEqual: return actual == expected case OperatorGreaterThan: return actual > expected case OperatorLessThan: return actual < expected case OperatorGreaterEqual: return actual >= expected case OperatorLessEqual: return actual <= expected case OperatorNotEqual: return actual != expected default: return false } } ================================================ FILE: internal/domain/player/errors.go ================================================ package player import "greatestworks/internal/errors" // 玩家领域错误定义 - 使用统一的错误处理机制 var ( ErrPlayerNotFound = errors.ErrPlayerNotFound ErrPlayerOffline = errors.ErrPlayerOffline ErrPlayerAlreadyExists = errors.ErrPlayerAlreadyExists ErrInvalidPlayerName = errors.ErrInvalidPlayerName ErrInvalidPosition = errors.ErrInvalidPosition ErrVersionMismatch = errors.ErrVersionMismatch ErrPlayerDead = errors.NewDomainError("PLAYER_DEAD", "玩家已死亡") ErrInvalidLevel = errors.NewDomainError("INVALID_LEVEL", "无效的等级") ErrInsufficientExp = errors.NewDomainError("INSUFFICIENT_EXP", "经验值不足") ) ================================================ FILE: internal/domain/player/events.go ================================================ package player import ( "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { EventID() string EventType() string AggregateID() string OccurredAt() time.Time Version() int } // BaseEvent 基础事件 type BaseEvent struct { eventID string eventType string aggregateID string occurredAt time.Time version int } // EventID 获取事件ID func (e BaseEvent) EventID() string { return e.eventID } // EventType 获取事件类型 func (e BaseEvent) EventType() string { return e.eventType } // AggregateID 获取聚合根ID func (e BaseEvent) AggregateID() string { return e.aggregateID } // OccurredAt 获取发生时间 func (e BaseEvent) OccurredAt() time.Time { return e.occurredAt } // Version 获取版本 func (e BaseEvent) Version() int { return e.version } // PlayerCreatedEvent 玩家创建事件 type PlayerCreatedEvent struct { BaseEvent PlayerID PlayerID `json:"player_id"` Name string `json:"name"` } // NewPlayerCreatedEvent 创建玩家创建事件 func NewPlayerCreatedEvent(playerID PlayerID, name string) *PlayerCreatedEvent { return &PlayerCreatedEvent{ BaseEvent: BaseEvent{ eventID: generateEventID(), eventType: "PlayerCreated", aggregateID: playerID.String(), occurredAt: time.Now(), version: 1, }, PlayerID: playerID, Name: name, } } // PlayerLevelUpEvent 玩家升级事件 type PlayerLevelUpEvent struct { BaseEvent PlayerID PlayerID `json:"player_id"` OldLevel int `json:"old_level"` NewLevel int `json:"new_level"` } // NewPlayerLevelUpEvent 创建玩家升级事件 func NewPlayerLevelUpEvent(playerID PlayerID, oldLevel, newLevel int) *PlayerLevelUpEvent { return &PlayerLevelUpEvent{ BaseEvent: BaseEvent{ eventID: generateEventID(), eventType: "PlayerLevelUp", aggregateID: playerID.String(), occurredAt: time.Now(), version: 1, }, PlayerID: playerID, OldLevel: oldLevel, NewLevel: newLevel, } } // PlayerOnlineEvent 玩家上线事件 type PlayerOnlineEvent struct { BaseEvent PlayerID PlayerID `json:"player_id"` Position Position `json:"position"` } // NewPlayerOnlineEvent 创建玩家上线事件 func NewPlayerOnlineEvent(playerID PlayerID, position Position) *PlayerOnlineEvent { return &PlayerOnlineEvent{ BaseEvent: BaseEvent{ eventID: generateEventID(), eventType: "PlayerOnline", aggregateID: playerID.String(), occurredAt: time.Now(), version: 1, }, PlayerID: playerID, Position: position, } } // PlayerOfflineEvent 玩家下线事件 type PlayerOfflineEvent struct { BaseEvent PlayerID PlayerID `json:"player_id"` Position Position `json:"position"` } // NewPlayerOfflineEvent 创建玩家下线事件 func NewPlayerOfflineEvent(playerID PlayerID, position Position) *PlayerOfflineEvent { return &PlayerOfflineEvent{ BaseEvent: BaseEvent{ eventID: generateEventID(), eventType: "PlayerOffline", aggregateID: playerID.String(), occurredAt: time.Now(), version: 1, }, PlayerID: playerID, Position: position, } } // PlayerMovedEvent 玩家移动事件 type PlayerMovedEvent struct { BaseEvent PlayerID PlayerID `json:"player_id"` OldPosition Position `json:"old_position"` NewPosition Position `json:"new_position"` } // NewPlayerMovedEvent 创建玩家移动事件 func NewPlayerMovedEvent(playerID PlayerID, oldPos, newPos Position) *PlayerMovedEvent { return &PlayerMovedEvent{ BaseEvent: BaseEvent{ eventID: generateEventID(), eventType: "PlayerMoved", aggregateID: playerID.String(), occurredAt: time.Now(), version: 1, }, PlayerID: playerID, OldPosition: oldPos, NewPosition: newPos, } } // PlayerDiedEvent 玩家死亡事件 type PlayerDiedEvent struct { BaseEvent PlayerID PlayerID `json:"player_id"` Position Position `json:"position"` KillerID *PlayerID `json:"killer_id,omitempty"` } // NewPlayerDiedEvent 创建玩家死亡事件 func NewPlayerDiedEvent(playerID PlayerID, position Position, killerID *PlayerID) *PlayerDiedEvent { return &PlayerDiedEvent{ BaseEvent: BaseEvent{ eventID: generateEventID(), eventType: "PlayerDied", aggregateID: playerID.String(), occurredAt: time.Now(), version: 1, }, PlayerID: playerID, Position: position, KillerID: killerID, } } // generateEventID 生成事件ID func generateEventID() string { return NewPlayerID().String() } ================================================ FILE: internal/domain/player/hangup/aggregate.go ================================================ package hangup import ( "time" ) // HangupAggregate 挂机聚合根 type HangupAggregate struct { playerID string currentLocation *HangupLocation offlineReward *OfflineReward hangupStatus HangupStatus efficiencyBonus *EfficiencyBonus lastOnlineTime time.Time lastOfflineTime time.Time totalHangupTime time.Duration dailyHangupTime time.Duration lastResetDate time.Time updatedAt time.Time version int } // NewHangupAggregate 创建挂机聚合根 func NewHangupAggregate(playerID string) *HangupAggregate { now := time.Now() return &HangupAggregate{ playerID: playerID, currentLocation: nil, offlineReward: NewOfflineReward(), hangupStatus: HangupStatusOffline, efficiencyBonus: NewEfficiencyBonus(), lastOnlineTime: now, lastOfflineTime: now, totalHangupTime: 0, dailyHangupTime: 0, lastResetDate: now.Truncate(24 * time.Hour), updatedAt: now, version: 1, } } // GetPlayerID 获取玩家ID func (h *HangupAggregate) GetPlayerID() string { return h.playerID } // SetHangupLocation 设置挂机地点 func (h *HangupAggregate) SetHangupLocation(location *HangupLocation) error { if location == nil { return ErrInvalidHangupLocation } // 检查地点解锁条件 if !location.IsUnlocked() { return ErrHangupLocationNotUnlocked } // 检查玩家等级要求 if !h.checkLocationRequirements(location) { return ErrHangupLocationRequirementNotMet } h.currentLocation = location h.updateVersion() return nil } // GetCurrentLocation 获取当前挂机地点 func (h *HangupAggregate) GetCurrentLocation() *HangupLocation { return h.currentLocation } // StartHangup 开始挂机 func (h *HangupAggregate) StartHangup() error { if h.currentLocation == nil { return ErrNoHangupLocationSet } if h.hangupStatus == HangupStatusOnline { return ErrAlreadyHangingUp } h.hangupStatus = HangupStatusOnline h.lastOnlineTime = time.Now() h.updateVersion() return nil } // StopHangup 停止挂机 func (h *HangupAggregate) StopHangup() error { if h.hangupStatus == HangupStatusOffline { return ErrNotHangingUp } h.hangupStatus = HangupStatusOffline h.lastOfflineTime = time.Now() // 计算挂机时间 hangupDuration := h.lastOfflineTime.Sub(h.lastOnlineTime) h.totalHangupTime += hangupDuration h.dailyHangupTime += hangupDuration h.updateVersion() return nil } // CalculateOfflineReward 计算离线奖励 func (h *HangupAggregate) CalculateOfflineReward(offlineDuration time.Duration) (*OfflineReward, error) { if h.currentLocation == nil { return nil, ErrNoHangupLocationSet } // 限制最大离线时间(例如24小时) maxOfflineTime := 24 * time.Hour if offlineDuration > maxOfflineTime { offlineDuration = maxOfflineTime } // 计算基础奖励 baseReward := h.currentLocation.CalculateBaseReward(offlineDuration) // 应用效率加成 finalReward := h.efficiencyBonus.ApplyBonus(baseReward) // 创建离线奖励 offlineReward := &OfflineReward{ Experience: finalReward.Experience, Gold: finalReward.Gold, Items: finalReward.Items, OfflineDuration: offlineDuration, LocationID: h.currentLocation.GetID(), CalculatedAt: time.Now(), } h.offlineReward = offlineReward h.updateVersion() return offlineReward, nil } // ClaimOfflineReward 领取离线奖励 func (h *HangupAggregate) ClaimOfflineReward() (*OfflineReward, error) { if h.offlineReward == nil { return nil, ErrNoOfflineRewardAvailable } if h.offlineReward.IsClaimed { return nil, ErrOfflineRewardAlreadyClaimed } // 标记为已领取 h.offlineReward.IsClaimed = true h.offlineReward.ClaimedAt = time.Now() reward := h.offlineReward h.offlineReward = nil // 清空已领取的奖励 h.updateVersion() return reward, nil } // GetOfflineReward 获取离线奖励 func (h *HangupAggregate) GetOfflineReward() *OfflineReward { return h.offlineReward } // UpdateEfficiencyBonus 更新效率加成 func (h *HangupAggregate) UpdateEfficiencyBonus(bonus *EfficiencyBonus) { h.efficiencyBonus = bonus h.updateVersion() } // GetEfficiencyBonus 获取效率加成 func (h *HangupAggregate) GetEfficiencyBonus() *EfficiencyBonus { return h.efficiencyBonus } // GetHangupStatus 获取挂机状态 func (h *HangupAggregate) GetHangupStatus() HangupStatus { return h.hangupStatus } // GetTotalHangupTime 获取总挂机时间 func (h *HangupAggregate) GetTotalHangupTime() time.Duration { return h.totalHangupTime } // GetDailyHangupTime 获取每日挂机时间 func (h *HangupAggregate) GetDailyHangupTime() time.Duration { // 检查是否需要重置每日时间 h.checkDailyReset() return h.dailyHangupTime } // GetLastOnlineTime 获取最后在线时间 func (h *HangupAggregate) GetLastOnlineTime() time.Time { return h.lastOnlineTime } // GetLastOfflineTime 获取最后离线时间 func (h *HangupAggregate) GetLastOfflineTime() time.Time { return h.lastOfflineTime } // IsOnline 是否在线挂机 func (h *HangupAggregate) IsOnline() bool { return h.hangupStatus == HangupStatusOnline } // IsOffline 是否离线 func (h *HangupAggregate) IsOffline() bool { return h.hangupStatus == HangupStatusOffline } // GetCurrentOfflineDuration 获取当前离线时长 func (h *HangupAggregate) GetCurrentOfflineDuration() time.Duration { if h.IsOnline() { return 0 } return time.Since(h.lastOfflineTime) } // GetVersion 获取版本 func (h *HangupAggregate) GetVersion() int { return h.version } // GetUpdatedAt 获取更新时间 func (h *HangupAggregate) GetUpdatedAt() time.Time { return h.updatedAt } // GetID 获取挂机ID (暂时使用playerID作为ID) func (h *HangupAggregate) GetID() string { return h.playerID } // GetLocationID 获取地点ID func (h *HangupAggregate) GetLocationID() string { if h.currentLocation == nil { return "" } return h.currentLocation.GetID() } // GetStartTime 获取开始时间 func (h *HangupAggregate) GetStartTime() time.Time { return h.lastOnlineTime } // GetEndTime 获取结束时间 func (h *HangupAggregate) GetEndTime() time.Time { return h.lastOfflineTime } // GetDuration 获取持续时间 func (h *HangupAggregate) GetDuration() time.Duration { return h.totalHangupTime } // GetEfficiency 获取效率 func (h *HangupAggregate) GetEfficiency() float64 { if h.efficiencyBonus == nil { return 1.0 } return h.efficiencyBonus.GetTotalBonus() } // GetBaseRate 获取基础速率 (经验速率) func (h *HangupAggregate) GetBaseRate() float64 { if h.currentLocation == nil { return 0.0 } return h.currentLocation.GetBaseExpRate() } // GetStatus 获取状态 func (h *HangupAggregate) GetStatus() HangupStatus { return h.hangupStatus } // GetRewards 获取奖励列表 func (h *HangupAggregate) GetRewards() []RewardItem { if h.offlineReward == nil { return []RewardItem{} } return h.offlineReward.Items } // GetCreatedAt 获取创建时间 func (h *HangupAggregate) GetCreatedAt() time.Time { return h.lastOnlineTime // 使用第一次在线时间作为创建时间 } // ReconstructHangupAggregate 重构挂机聚合根(用于从持久化数据恢复) func ReconstructHangupAggregate( hangupID string, playerID string, locationID string, startTime time.Time, endTime time.Time, duration time.Duration, efficiency float64, baseRate float64, status HangupStatus, rewards []RewardItem, createdAt time.Time, updatedAt time.Time, ) *HangupAggregate { // 创建基础聚合根 aggregate := &HangupAggregate{ playerID: playerID, currentLocation: nil, // 需要根据locationID重新加载 offlineReward: nil, hangupStatus: status, efficiencyBonus: NewEfficiencyBonus(), lastOnlineTime: startTime, lastOfflineTime: endTime, totalHangupTime: duration, dailyHangupTime: duration, lastResetDate: createdAt.Truncate(24 * time.Hour), updatedAt: updatedAt, version: 1, } // 如果有奖励,创建离线奖励对象 if len(rewards) > 0 { aggregate.offlineReward = &OfflineReward{ Items: rewards, OfflineDuration: duration, LocationID: locationID, CalculatedAt: updatedAt, IsClaimed: false, } } return aggregate } // 私有方法 // checkLocationRequirements 检查地点要求 func (h *HangupAggregate) checkLocationRequirements(location *HangupLocation) bool { // 这里需要外部提供玩家等级信息 // 暂时返回true,实际实现中需要检查玩家等级、任务完成情况等 return true } // checkDailyReset 检查每日重置 func (h *HangupAggregate) checkDailyReset() { now := time.Now() currentDate := now.Truncate(24 * time.Hour) if currentDate.After(h.lastResetDate) { h.dailyHangupTime = 0 h.lastResetDate = currentDate h.updateVersion() } } // updateVersion 更新版本 func (h *HangupAggregate) updateVersion() { h.version++ h.updatedAt = time.Now() } // HangupStatus 挂机状态 type HangupStatus int const ( HangupStatusOffline HangupStatus = iota // 离线 HangupStatusOnline // 在线挂机 HangupStatusPaused // 暂停 ) // String 返回挂机状态的字符串表示 func (hs HangupStatus) String() string { switch hs { case HangupStatusOffline: return "offline" case HangupStatusOnline: return "online" case HangupStatusPaused: return "paused" default: return "unknown" } } ================================================ FILE: internal/domain/player/hangup/entity.go ================================================ package hangup import ( "time" ) // HangupLocation 挂机地点实体 type HangupLocation struct { id string name string description string locationType LocationType requiredLevel int requiredQuests []string baseExpRate float64 // 基础经验倍率 baseGoldRate float64 // 基础金币倍率 specialItems []ItemDrop maxOfflineHours int // 最大离线小时数 isUnlocked bool isActive bool createdAt time.Time updatedAt time.Time } // NewHangupLocation 创建挂机地点 func NewHangupLocation(id, name, description string, locationType LocationType) *HangupLocation { now := time.Now() return &HangupLocation{ id: id, name: name, description: description, locationType: locationType, requiredLevel: 1, requiredQuests: make([]string, 0), baseExpRate: 1.0, baseGoldRate: 1.0, specialItems: make([]ItemDrop, 0), maxOfflineHours: 24, isUnlocked: false, isActive: true, createdAt: now, updatedAt: now, } } // GetID 获取地点ID func (hl *HangupLocation) GetID() string { return hl.id } // GetName 获取地点名称 func (hl *HangupLocation) GetName() string { return hl.name } // GetDescription 获取地点描述 func (hl *HangupLocation) GetDescription() string { return hl.description } // GetLocationType 获取地点类型 func (hl *HangupLocation) GetLocationType() LocationType { return hl.locationType } // GetRequiredLevel 获取所需等级 func (hl *HangupLocation) GetRequiredLevel() int { return hl.requiredLevel } // SetRequiredLevel 设置所需等级 func (hl *HangupLocation) SetRequiredLevel(level int) { hl.requiredLevel = level hl.updatedAt = time.Now() } // GetRequiredQuests 获取所需任务 func (hl *HangupLocation) GetRequiredQuests() []string { return hl.requiredQuests } // AddRequiredQuest 添加所需任务 func (hl *HangupLocation) AddRequiredQuest(questID string) { hl.requiredQuests = append(hl.requiredQuests, questID) hl.updatedAt = time.Now() } // GetBaseExpRate 获取基础经验倍率 func (hl *HangupLocation) GetBaseExpRate() float64 { return hl.baseExpRate } // SetBaseExpRate 设置基础经验倍率 func (hl *HangupLocation) SetBaseExpRate(rate float64) { hl.baseExpRate = rate hl.updatedAt = time.Now() } // GetBaseGoldRate 获取基础金币倍率 func (hl *HangupLocation) GetBaseGoldRate() float64 { return hl.baseGoldRate } // SetBaseGoldRate 设置基础金币倍率 func (hl *HangupLocation) SetBaseGoldRate(rate float64) { hl.baseGoldRate = rate hl.updatedAt = time.Now() } // GetSpecialItems 获取特殊物品掉落 func (hl *HangupLocation) GetSpecialItems() []ItemDrop { return hl.specialItems } // AddSpecialItem 添加特殊物品掉落 func (hl *HangupLocation) AddSpecialItem(item ItemDrop) { hl.specialItems = append(hl.specialItems, item) hl.updatedAt = time.Now() } // GetMaxOfflineHours 获取最大离线小时数 func (hl *HangupLocation) GetMaxOfflineHours() int { return hl.maxOfflineHours } // SetMaxOfflineHours 设置最大离线小时数 func (hl *HangupLocation) SetMaxOfflineHours(hours int) { hl.maxOfflineHours = hours hl.updatedAt = time.Now() } // IsUnlocked 是否已解锁 func (hl *HangupLocation) IsUnlocked() bool { return hl.isUnlocked } // Unlock 解锁地点 func (hl *HangupLocation) Unlock() { hl.isUnlocked = true hl.updatedAt = time.Now() } // Lock 锁定地点 func (hl *HangupLocation) Lock() { hl.isUnlocked = false hl.updatedAt = time.Now() } // IsActive 是否激活 func (hl *HangupLocation) IsActive() bool { return hl.isActive } // Activate 激活地点 func (hl *HangupLocation) Activate() { hl.isActive = true hl.updatedAt = time.Now() } // Deactivate 停用地点 func (hl *HangupLocation) Deactivate() { hl.isActive = false hl.updatedAt = time.Now() } // CalculateBaseReward 计算基础奖励 func (hl *HangupLocation) CalculateBaseReward(duration time.Duration) *BaseReward { hours := duration.Hours() // 限制最大离线时间 if hours > float64(hl.maxOfflineHours) { hours = float64(hl.maxOfflineHours) } // 计算基础奖励(这里使用简单的线性计算) baseExp := int64(hours * 100 * hl.baseExpRate) // 每小时100经验 baseGold := int64(hours * 50 * hl.baseGoldRate) // 每小时50金币 // 计算物品掉落 items := make([]RewardItem, 0) for _, itemDrop := range hl.specialItems { if itemDrop.ShouldDrop(hours) { items = append(items, RewardItem{ ItemID: itemDrop.ItemID, Quantity: int64(itemDrop.CalculateQuantity(hours)), }) } } return &BaseReward{ Experience: baseExp, Gold: baseGold, Items: items, } } // GetCreatedAt 获取创建时间 func (hl *HangupLocation) GetCreatedAt() time.Time { return hl.createdAt } // GetUpdatedAt 获取更新时间 func (hl *HangupLocation) GetUpdatedAt() time.Time { return hl.updatedAt } // OfflineReward 离线奖励实体 type OfflineReward struct { Experience int64 `json:"experience"` Gold int64 `json:"gold"` Items []RewardItem `json:"items"` OfflineDuration time.Duration `json:"offline_duration"` LocationID string `json:"location_id"` CalculatedAt time.Time `json:"calculated_at"` IsClaimed bool `json:"is_claimed"` ClaimedAt time.Time `json:"claimed_at,omitempty"` } // NewOfflineReward 创建离线奖励 func NewOfflineReward() *OfflineReward { return &OfflineReward{ Experience: 0, Gold: 0, Items: make([]RewardItem, 0), CalculatedAt: time.Now(), IsClaimed: false, } } // IsEmpty 是否为空奖励 func (or *OfflineReward) IsEmpty() bool { return or.Experience == 0 && or.Gold == 0 && len(or.Items) == 0 } // GetTotalValue 获取总价值(用于显示) func (or *OfflineReward) GetTotalValue() int64 { // 简单的价值计算:经验 + 金币 + 物品价值 totalValue := or.Experience + or.Gold for _, item := range or.Items { // 假设每个物品价值10金币 totalValue += int64(item.Quantity) * 10 } return totalValue } // EfficiencyBonus 效率加成实体 type EfficiencyBonus struct { vipBonus float64 `json:"vip_bonus"` // VIP加成 equipmentBonus float64 `json:"equipment_bonus"` // 装备加成 skillBonus float64 `json:"skill_bonus"` // 技能加成 guildBonus float64 `json:"guild_bonus"` // 公会加成 eventBonus float64 `json:"event_bonus"` // 活动加成 specialBonus map[string]float64 `json:"special_bonus"` // 特殊加成 updatedAt time.Time `json:"updated_at"` } // NewEfficiencyBonus 创建效率加成 func NewEfficiencyBonus() *EfficiencyBonus { return &EfficiencyBonus{ vipBonus: 0.0, equipmentBonus: 0.0, skillBonus: 0.0, guildBonus: 0.0, eventBonus: 0.0, specialBonus: make(map[string]float64), updatedAt: time.Now(), } } // GetVipBonus 获取VIP加成 func (eb *EfficiencyBonus) GetVipBonus() float64 { return eb.vipBonus } // SetVipBonus 设置VIP加成 func (eb *EfficiencyBonus) SetVipBonus(bonus float64) { eb.vipBonus = bonus eb.updatedAt = time.Now() } // GetEquipmentBonus 获取装备加成 func (eb *EfficiencyBonus) GetEquipmentBonus() float64 { return eb.equipmentBonus } // SetEquipmentBonus 设置装备加成 func (eb *EfficiencyBonus) SetEquipmentBonus(bonus float64) { eb.equipmentBonus = bonus eb.updatedAt = time.Now() } // GetSkillBonus 获取技能加成 func (eb *EfficiencyBonus) GetSkillBonus() float64 { return eb.skillBonus } // SetSkillBonus 设置技能加成 func (eb *EfficiencyBonus) SetSkillBonus(bonus float64) { eb.skillBonus = bonus eb.updatedAt = time.Now() } // GetGuildBonus 获取公会加成 func (eb *EfficiencyBonus) GetGuildBonus() float64 { return eb.guildBonus } // SetGuildBonus 设置公会加成 func (eb *EfficiencyBonus) SetGuildBonus(bonus float64) { eb.guildBonus = bonus eb.updatedAt = time.Now() } // GetEventBonus 获取活动加成 func (eb *EfficiencyBonus) GetEventBonus() float64 { return eb.eventBonus } // SetEventBonus 设置活动加成 func (eb *EfficiencyBonus) SetEventBonus(bonus float64) { eb.eventBonus = bonus eb.updatedAt = time.Now() } // GetSpecialBonus 获取特殊加成 func (eb *EfficiencyBonus) GetSpecialBonus(key string) float64 { return eb.specialBonus[key] } // SetSpecialBonus 设置特殊加成 func (eb *EfficiencyBonus) SetSpecialBonus(key string, bonus float64) { eb.specialBonus[key] = bonus eb.updatedAt = time.Now() } // GetTotalBonus 获取总加成 func (eb *EfficiencyBonus) GetTotalBonus() float64 { total := 1.0 + eb.vipBonus + eb.equipmentBonus + eb.skillBonus + eb.guildBonus + eb.eventBonus for _, bonus := range eb.specialBonus { total += bonus } return total } // ApplyBonus 应用加成到基础奖励 func (eb *EfficiencyBonus) ApplyBonus(baseReward *BaseReward) *BaseReward { totalBonus := eb.GetTotalBonus() return &BaseReward{ Experience: int64(float64(baseReward.Experience) * totalBonus), Gold: int64(float64(baseReward.Gold) * totalBonus), Items: baseReward.Items, // 物品不受加成影响 } } // GetUpdatedAt 获取更新时间 func (eb *EfficiencyBonus) GetUpdatedAt() time.Time { return eb.updatedAt } // HangupStatistics 挂机统计实体 type HangupStatistics struct { playerID string `json:"player_id"` totalHangupTime time.Duration `json:"total_hangup_time"` totalExperience int64 `json:"total_experience"` totalGold int64 `json:"total_gold"` totalItemsObtained int `json:"total_items_obtained"` favoriteLocation string `json:"favorite_location"` longestSession time.Duration `json:"longest_session"` lastHangupDate time.Time `json:"last_hangup_date"` updatedAt time.Time `json:"updated_at"` } // NewHangupStatistics 创建挂机统计 func NewHangupStatistics(playerID string) *HangupStatistics { return &HangupStatistics{ playerID: playerID, totalHangupTime: 0, totalExperience: 0, totalGold: 0, totalItemsObtained: 0, favoriteLocation: "", longestSession: 0, lastHangupDate: time.Time{}, updatedAt: time.Now(), } } // UpdateStatistics 更新统计数据 func (hs *HangupStatistics) UpdateStatistics(sessionDuration time.Duration, reward *OfflineReward, locationID string) { hs.totalHangupTime += sessionDuration hs.totalExperience += reward.Experience hs.totalGold += reward.Gold hs.totalItemsObtained += len(reward.Items) if sessionDuration > hs.longestSession { hs.longestSession = sessionDuration } hs.favoriteLocation = locationID // 简化实现,实际应该统计最常用的地点 hs.lastHangupDate = time.Now() hs.updatedAt = time.Now() } // GetPlayerID 获取玩家ID func (hs *HangupStatistics) GetPlayerID() string { return hs.playerID } // GetTotalHangupTime 获取总挂机时间 func (hs *HangupStatistics) GetTotalHangupTime() time.Duration { return hs.totalHangupTime } // GetTotalExperience 获取总经验 func (hs *HangupStatistics) GetTotalExperience() int64 { return hs.totalExperience } // GetTotalGold 获取总金币 func (hs *HangupStatistics) GetTotalGold() int64 { return hs.totalGold } // GetTotalItemsObtained 获取总物品数量 func (hs *HangupStatistics) GetTotalItemsObtained() int { return hs.totalItemsObtained } // GetFavoriteLocation 获取最喜欢的地点 func (hs *HangupStatistics) GetFavoriteLocation() string { return hs.favoriteLocation } // GetLongestSession 获取最长会话时间 func (hs *HangupStatistics) GetLongestSession() time.Duration { return hs.longestSession } // GetLastHangupDate 获取最后挂机日期 func (hs *HangupStatistics) GetLastHangupDate() time.Time { return hs.lastHangupDate } // GetUpdatedAt 获取更新时间 func (hs *HangupStatistics) GetUpdatedAt() time.Time { return hs.updatedAt } ================================================ FILE: internal/domain/player/hangup/errors.go ================================================ package hangup import ( "errors" "fmt" ) // 挂机系统相关错误定义 // 挂机地点相关错误 var ( // ErrInvalidHangupLocation 无效的挂机地点 ErrInvalidHangupLocation = errors.New("invalid hangup location") // ErrHangupLocationNotFound 挂机地点未找到 ErrHangupLocationNotFound = errors.New("hangup location not found") // ErrHangupLocationNotUnlocked 挂机地点未解锁 ErrHangupLocationNotUnlocked = errors.New("hangup location not unlocked") // ErrHangupLocationRequirementNotMet 挂机地点要求未满足 ErrHangupLocationRequirementNotMet = errors.New("hangup location requirement not met") // ErrHangupLocationInactive 挂机地点未激活 ErrHangupLocationInactive = errors.New("hangup location inactive") // ErrHangupLocationFull 挂机地点已满 ErrHangupLocationFull = errors.New("hangup location full") // ErrNoHangupLocationSet 未设置挂机地点 ErrNoHangupLocationSet = errors.New("no hangup location set") // ErrCannotChangeLocationWhileHanging 挂机中无法更换地点 ErrCannotChangeLocationWhileHanging = errors.New("cannot change location while hanging up") // ErrHangupNotFound 挂机未找到 ErrHangupNotFound = errors.New("hangup not found") ) // 挂机状态相关错误 var ( // ErrAlreadyHangingUp 已经在挂机 ErrAlreadyHangingUp = errors.New("already hanging up") // ErrNotHangingUp 未在挂机 ErrNotHangingUp = errors.New("not hanging up") // ErrHangupPaused 挂机已暂停 ErrHangupPaused = errors.New("hangup paused") // ErrCannotStartHangup 无法开始挂机 ErrCannotStartHangup = errors.New("cannot start hangup") // ErrCannotStopHangup 无法停止挂机 ErrCannotStopHangup = errors.New("cannot stop hangup") // ErrHangupCooldown 挂机冷却中 ErrHangupCooldown = errors.New("hangup cooldown") // ErrHangupLimitExceeded 挂机限制超出 ErrHangupLimitExceeded = errors.New("hangup limit exceeded") ) // 离线奖励相关错误 var ( // ErrNoOfflineRewardAvailable 没有可用的离线奖励 ErrNoOfflineRewardAvailable = errors.New("no offline reward available") // ErrOfflineRewardAlreadyClaimed 离线奖励已领取 ErrOfflineRewardAlreadyClaimed = errors.New("offline reward already claimed") // ErrOfflineRewardExpired 离线奖励已过期 ErrOfflineRewardExpired = errors.New("offline reward expired") // ErrInvalidOfflineReward 无效的离线奖励 ErrInvalidOfflineReward = errors.New("invalid offline reward") // ErrOfflineRewardCalculationFailed 离线奖励计算失败 ErrOfflineRewardCalculationFailed = errors.New("offline reward calculation failed") // ErrOfflineTimeTooShort 离线时间太短 ErrOfflineTimeTooShort = errors.New("offline time too short") // ErrOfflineTimeTooLong 离线时间太长 ErrOfflineTimeTooLong = errors.New("offline time too long") ) // 效率加成相关错误 var ( // ErrInvalidEfficiencyBonus 无效的效率加成 ErrInvalidEfficiencyBonus = errors.New("invalid efficiency bonus") // ErrEfficiencyBonusNotFound 效率加成未找到 ErrEfficiencyBonusNotFound = errors.New("efficiency bonus not found") // ErrCannotUpdateEfficiencyBonus 无法更新效率加成 ErrCannotUpdateEfficiencyBonus = errors.New("cannot update efficiency bonus") // ErrEfficiencyBonusExpired 效率加成已过期 ErrEfficiencyBonusExpired = errors.New("efficiency bonus expired") // ErrMaxEfficiencyBonusReached 已达到最大效率加成 ErrMaxEfficiencyBonusReached = errors.New("max efficiency bonus reached") ) // 玩家相关错误 var ( // ErrInvalidPlayerID 无效的玩家ID ErrInvalidPlayerID = errors.New("invalid player id") // ErrPlayerNotFound 玩家未找到 ErrPlayerNotFound = errors.New("player not found") // ErrPlayerLevelTooLow 玩家等级太低 ErrPlayerLevelTooLow = errors.New("player level too low") // ErrPlayerNotOnline 玩家不在线 ErrPlayerNotOnline = errors.New("player not online") // ErrPlayerBanned 玩家被封禁 ErrPlayerBanned = errors.New("player banned") // ErrPlayerInCombat 玩家在战斗中 ErrPlayerInCombat = errors.New("player in combat") ) // 配置相关错误 var ( // ErrInvalidHangupConfig 无效的挂机配置 ErrInvalidHangupConfig = errors.New("invalid hangup config") // ErrHangupConfigNotFound 挂机配置未找到 ErrHangupConfigNotFound = errors.New("hangup config not found") // ErrCannotUpdateHangupConfig 无法更新挂机配置 ErrCannotUpdateHangupConfig = errors.New("cannot update hangup config") // ErrHangupConfigValidationFailed 挂机配置验证失败 ErrHangupConfigValidationFailed = errors.New("hangup config validation failed") ) // 会话相关错误 var ( // ErrInvalidHangupSession 无效的挂机会话 ErrInvalidHangupSession = errors.New("invalid hangup session") // ErrHangupSessionNotFound 挂机会话未找到 ErrHangupSessionNotFound = errors.New("hangup session not found") // ErrHangupSessionAlreadyEnded 挂机会话已结束 ErrHangupSessionAlreadyEnded = errors.New("hangup session already ended") // ErrCannotEndHangupSession 无法结束挂机会话 ErrCannotEndHangupSession = errors.New("cannot end hangup session") // ErrHangupSessionExpired 挂机会话已过期 ErrHangupSessionExpired = errors.New("hangup session expired") ) // 统计相关错误 var ( // ErrInvalidHangupStatistics 无效的挂机统计 ErrInvalidHangupStatistics = errors.New("invalid hangup statistics") // ErrHangupStatisticsNotFound 挂机统计未找到 ErrHangupStatisticsNotFound = errors.New("hangup statistics not found") // ErrCannotUpdateHangupStatistics 无法更新挂机统计 ErrCannotUpdateHangupStatistics = errors.New("cannot update hangup statistics") // ErrStatisticsCalculationFailed 统计计算失败 ErrStatisticsCalculationFailed = errors.New("statistics calculation failed") ) // 数据持久化相关错误 var ( // ErrDatabaseConnection 数据库连接错误 ErrDatabaseConnection = errors.New("database connection error") // ErrDataNotFound 数据未找到 ErrDataNotFound = errors.New("data not found") // ErrDataCorrupted 数据损坏 ErrDataCorrupted = errors.New("data corrupted") // ErrSaveFailure 保存失败 ErrSaveFailure = errors.New("save failure") // ErrLoadFailure 加载失败 ErrLoadFailure = errors.New("load failure") // ErrDeleteFailure 删除失败 ErrDeleteFailure = errors.New("delete failure") // ErrVersionConflict 版本冲突 ErrVersionConflict = errors.New("version conflict") // ErrConcurrentModification 并发修改冲突 ErrConcurrentModification = errors.New("concurrent modification") // ErrTransactionFailed 事务失败 ErrTransactionFailed = errors.New("transaction failed") ) // 业务逻辑相关错误 var ( // ErrOperationNotAllowed 操作不被允许 ErrOperationNotAllowed = errors.New("operation not allowed") // ErrInvalidOperation 无效操作 ErrInvalidOperation = errors.New("invalid operation") // ErrPermissionDenied 权限被拒绝 ErrPermissionDenied = errors.New("permission denied") // ErrResourceLocked 资源被锁定 ErrResourceLocked = errors.New("resource locked") // ErrRateLimitExceeded 速率限制超出 ErrRateLimitExceeded = errors.New("rate limit exceeded") // ErrMaintenanceMode 维护模式 ErrMaintenanceMode = errors.New("system in maintenance mode") // ErrServiceUnavailable 服务不可用 ErrServiceUnavailable = errors.New("service unavailable") ) // 验证相关错误 var ( // ErrInvalidInput 无效输入 ErrInvalidInput = errors.New("invalid input") // ErrValidationFailed 验证失败 ErrValidationFailed = errors.New("validation failed") // ErrInvalidTimeRange 无效时间范围 ErrInvalidTimeRange = errors.New("invalid time range") // ErrInvalidDuration 无效持续时间 ErrInvalidDuration = errors.New("invalid duration") // ErrInvalidRewardAmount 无效奖励数量 ErrInvalidRewardAmount = errors.New("invalid reward amount") ) // HangupError 挂机系统错误类型 type HangupError struct { Code string `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` Cause error `json:"-"` } // Error 实现error接口 func (e *HangupError) Error() string { if e.Details != "" { return fmt.Sprintf("[%s] %s: %s", e.Code, e.Message, e.Details) } return fmt.Sprintf("[%s] %s", e.Code, e.Message) } // Unwrap 返回原始错误 func (e *HangupError) Unwrap() error { return e.Cause } // NewHangupError 创建挂机系统错误 func NewHangupError(code, message string) *HangupError { return &HangupError{ Code: code, Message: message, } } // NewHangupErrorWithDetails 创建带详情的挂机系统错误 func NewHangupErrorWithDetails(code, message, details string) *HangupError { return &HangupError{ Code: code, Message: message, Details: details, } } // NewHangupErrorWithCause 创建带原因的挂机系统错误 func NewHangupErrorWithCause(code, message string, cause error) *HangupError { return &HangupError{ Code: code, Message: message, Cause: cause, } } // 预定义的错误代码常量 const ( // 地点相关错误代码 ErrCodeLocationNotFound = "LOCATION_NOT_FOUND" ErrCodeLocationNotUnlocked = "LOCATION_NOT_UNLOCKED" ErrCodeLocationRequirementNotMet = "LOCATION_REQUIREMENT_NOT_MET" ErrCodeLocationInactive = "LOCATION_INACTIVE" ErrCodeLocationFull = "LOCATION_FULL" ErrCodeNoLocationSet = "NO_LOCATION_SET" // 挂机状态错误代码 ErrCodeAlreadyHangingUp = "ALREADY_HANGING_UP" ErrCodeNotHangingUp = "NOT_HANGING_UP" ErrCodeHangupPaused = "HANGUP_PAUSED" ErrCodeCannotStartHangup = "CANNOT_START_HANGUP" ErrCodeCannotStopHangup = "CANNOT_STOP_HANGUP" ErrCodeHangupCooldown = "HANGUP_COOLDOWN" ErrCodeHangupLimitExceeded = "HANGUP_LIMIT_EXCEEDED" // 奖励相关错误代码 ErrCodeNoOfflineReward = "NO_OFFLINE_REWARD" ErrCodeOfflineRewardClaimed = "OFFLINE_REWARD_CLAIMED" ErrCodeOfflineRewardExpired = "OFFLINE_REWARD_EXPIRED" ErrCodeRewardCalculationFailed = "REWARD_CALCULATION_FAILED" ErrCodeOfflineTimeTooShort = "OFFLINE_TIME_TOO_SHORT" ErrCodeOfflineTimeTooLong = "OFFLINE_TIME_TOO_LONG" // 效率加成错误代码 ErrCodeInvalidEfficiencyBonus = "INVALID_EFFICIENCY_BONUS" ErrCodeEfficiencyBonusNotFound = "EFFICIENCY_BONUS_NOT_FOUND" ErrCodeEfficiencyBonusExpired = "EFFICIENCY_BONUS_EXPIRED" ErrCodeMaxEfficiencyBonusReached = "MAX_EFFICIENCY_BONUS_REACHED" // 玩家相关错误代码 ErrCodePlayerNotFound = "PLAYER_NOT_FOUND" ErrCodePlayerLevelTooLow = "PLAYER_LEVEL_TOO_LOW" ErrCodePlayerNotOnline = "PLAYER_NOT_ONLINE" ErrCodePlayerBanned = "PLAYER_BANNED" ErrCodePlayerInCombat = "PLAYER_IN_COMBAT" // 配置相关错误代码 ErrCodeInvalidConfig = "INVALID_CONFIG" ErrCodeConfigNotFound = "CONFIG_NOT_FOUND" ErrCodeConfigValidationFailed = "CONFIG_VALIDATION_FAILED" // 会话相关错误代码 ErrCodeSessionNotFound = "SESSION_NOT_FOUND" ErrCodeSessionAlreadyEnded = "SESSION_ALREADY_ENDED" ErrCodeCannotEndSession = "CANNOT_END_SESSION" ErrCodeSessionExpired = "SESSION_EXPIRED" // 统计相关错误代码 ErrCodeStatisticsNotFound = "STATISTICS_NOT_FOUND" ErrCodeStatisticsCalculationFailed = "STATISTICS_CALCULATION_FAILED" // 数据相关错误代码 ErrCodeDataNotFound = "DATA_NOT_FOUND" ErrCodeDataCorrupted = "DATA_CORRUPTED" ErrCodeVersionConflict = "VERSION_CONFLICT" ErrCodeSaveFailure = "SAVE_FAILURE" ErrCodeLoadFailure = "LOAD_FAILURE" ErrCodeDeleteFailure = "DELETE_FAILURE" ErrCodeConcurrentModification = "CONCURRENT_MODIFICATION" ErrCodeTransactionFailed = "TRANSACTION_FAILED" // 业务逻辑错误代码 ErrCodeOperationNotAllowed = "OPERATION_NOT_ALLOWED" ErrCodePermissionDenied = "PERMISSION_DENIED" ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" ErrCodeMaintenanceMode = "MAINTENANCE_MODE" ErrCodeServiceUnavailable = "SERVICE_UNAVAILABLE" // 验证相关错误代码 ErrCodeInvalidInput = "INVALID_INPUT" ErrCodeValidationFailed = "VALIDATION_FAILED" ErrCodeInvalidTimeRange = "INVALID_TIME_RANGE" ErrCodeInvalidDuration = "INVALID_DURATION" ErrCodeInvalidRewardAmount = "INVALID_REWARD_AMOUNT" ) // IsHangupError 检查是否为挂机系统错误 func IsHangupError(err error) bool { _, ok := err.(*HangupError) return ok } // GetHangupErrorCode 获取挂机系统错误代码 func GetHangupErrorCode(err error) string { if hangupErr, ok := err.(*HangupError); ok { return hangupErr.Code } return "" } // WrapError 包装错误为挂机系统错误 func WrapError(err error, code, message string) *HangupError { return &HangupError{ Code: code, Message: message, Cause: err, } } // ValidationError 验证错误 type ValidationError struct { Field string `json:"field"` Value interface{} `json:"value"` Message string `json:"message"` } // Error 实现error接口 func (ve *ValidationError) Error() string { return fmt.Sprintf("validation failed for field '%s': %s", ve.Field, ve.Message) } // NewValidationError 创建验证错误 func NewValidationError(field string, value interface{}, message string) *ValidationError { return &ValidationError{ Field: field, Value: value, Message: message, } } // BusinessRuleError 业务规则错误 type BusinessRuleError struct { Rule string `json:"rule"` Description string `json:"description"` Suggestion string `json:"suggestion,omitempty"` } // Error 实现error接口 func (bre *BusinessRuleError) Error() string { if bre.Suggestion != "" { return fmt.Sprintf("business rule violation '%s': %s. Suggestion: %s", bre.Rule, bre.Description, bre.Suggestion) } return fmt.Sprintf("business rule violation '%s': %s", bre.Rule, bre.Description) } // NewBusinessRuleError 创建业务规则错误 func NewBusinessRuleError(rule, description, suggestion string) *BusinessRuleError { return &BusinessRuleError{ Rule: rule, Description: description, Suggestion: suggestion, } } // ConcurrencyError 并发错误 type ConcurrencyError struct { Resource string `json:"resource"` Operation string `json:"operation"` ConflictType string `json:"conflict_type"` Message string `json:"message"` } // Error 实现error接口 func (ce *ConcurrencyError) Error() string { return fmt.Sprintf("concurrency error on %s during %s (%s): %s", ce.Resource, ce.Operation, ce.ConflictType, ce.Message) } // NewConcurrencyError 创建并发错误 func NewConcurrencyError(resource, operation, conflictType, message string) *ConcurrencyError { return &ConcurrencyError{ Resource: resource, Operation: operation, ConflictType: conflictType, Message: message, } } // ErrorCollection 错误集合 type ErrorCollection struct { Errors []error `json:"errors"` } // Error 实现error接口 func (ec *ErrorCollection) Error() string { if len(ec.Errors) == 0 { return "no errors" } if len(ec.Errors) == 1 { return ec.Errors[0].Error() } return fmt.Sprintf("multiple errors occurred: %d errors", len(ec.Errors)) } // Add 添加错误 func (ec *ErrorCollection) Add(err error) { if err != nil { ec.Errors = append(ec.Errors, err) } } // HasErrors 是否有错误 func (ec *ErrorCollection) HasErrors() bool { return len(ec.Errors) > 0 } // Count 错误数量 func (ec *ErrorCollection) Count() int { return len(ec.Errors) } // First 获取第一个错误 func (ec *ErrorCollection) First() error { if len(ec.Errors) > 0 { return ec.Errors[0] } return nil } // NewErrorCollection 创建错误集合 func NewErrorCollection() *ErrorCollection { return &ErrorCollection{ Errors: make([]error, 0), } } ================================================ FILE: internal/domain/player/hangup/events.go ================================================ package hangup import ( "fmt" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetEventData() interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` OccurredAt time.Time `json:"occurred_at"` EventData interface{} `json:"event_data"` } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetEventData 获取事件数据 func (e *BaseDomainEvent) GetEventData() interface{} { return e.EventData } // HangupStartedEvent 开始挂机事件 type HangupStartedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` StartTime time.Time `json:"start_time"` IsOnline bool `json:"is_online"` } // NewHangupStartedEvent 创建开始挂机事件 func NewHangupStartedEvent(playerID, locationID, locationName string, isOnline bool) *HangupStartedEvent { return &HangupStartedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupStarted", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, LocationID: locationID, LocationName: locationName, StartTime: time.Now(), IsOnline: isOnline, } } // HangupStoppedEvent 停止挂机事件 type HangupStoppedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` IsOnline bool `json:"is_online"` } // NewHangupStoppedEvent 创建停止挂机事件 func NewHangupStoppedEvent(playerID, locationID, locationName string, startTime time.Time, isOnline bool) *HangupStoppedEvent { endTime := time.Now() return &HangupStoppedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupStopped", AggregateID: playerID, OccurredAt: endTime, }, PlayerID: playerID, LocationID: locationID, LocationName: locationName, StartTime: startTime, EndTime: endTime, Duration: endTime.Sub(startTime), IsOnline: isOnline, } } // HangupLocationChangedEvent 挂机地点变更事件 type HangupLocationChangedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PreviousLocationID string `json:"previous_location_id,omitempty"` PreviousLocationName string `json:"previous_location_name,omitempty"` NewLocationID string `json:"new_location_id"` NewLocationName string `json:"new_location_name"` Reason string `json:"reason,omitempty"` } // NewHangupLocationChangedEvent 创建挂机地点变更事件 func NewHangupLocationChangedEvent(playerID, prevLocationID, prevLocationName, newLocationID, newLocationName, reason string) *HangupLocationChangedEvent { return &HangupLocationChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupLocationChanged", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PreviousLocationID: prevLocationID, PreviousLocationName: prevLocationName, NewLocationID: newLocationID, NewLocationName: newLocationName, Reason: reason, } } // OfflineRewardCalculatedEvent 离线奖励计算事件 type OfflineRewardCalculatedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` OfflineDuration time.Duration `json:"offline_duration"` Experience int64 `json:"experience"` Gold int64 `json:"gold"` Items []RewardItem `json:"items"` EfficiencyBonus float64 `json:"efficiency_bonus"` CalculatedAt time.Time `json:"calculated_at"` } // NewOfflineRewardCalculatedEvent 创建离线奖励计算事件 func NewOfflineRewardCalculatedEvent(playerID, locationID, locationName string, reward *OfflineReward, efficiencyBonus float64) *OfflineRewardCalculatedEvent { return &OfflineRewardCalculatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "OfflineRewardCalculated", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, LocationID: locationID, LocationName: locationName, OfflineDuration: reward.OfflineDuration, Experience: reward.Experience, Gold: reward.Gold, Items: reward.Items, EfficiencyBonus: efficiencyBonus, CalculatedAt: reward.CalculatedAt, } } // OfflineRewardClaimedEvent 离线奖励领取事件 type OfflineRewardClaimedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` OfflineDuration time.Duration `json:"offline_duration"` Experience int64 `json:"experience"` Gold int64 `json:"gold"` Items []RewardItem `json:"items"` ClaimedAt time.Time `json:"claimed_at"` } // NewOfflineRewardClaimedEvent 创建离线奖励领取事件 func NewOfflineRewardClaimedEvent(playerID, locationID, locationName string, reward *OfflineReward) *OfflineRewardClaimedEvent { return &OfflineRewardClaimedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "OfflineRewardClaimed", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, LocationID: locationID, LocationName: locationName, OfflineDuration: reward.OfflineDuration, Experience: reward.Experience, Gold: reward.Gold, Items: reward.Items, ClaimedAt: reward.ClaimedAt, } } // HangupEfficiencyUpdatedEvent 挂机效率更新事件 type HangupEfficiencyUpdatedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PreviousBonus float64 `json:"previous_bonus"` NewBonus float64 `json:"new_bonus"` VipBonus float64 `json:"vip_bonus"` EquipmentBonus float64 `json:"equipment_bonus"` SkillBonus float64 `json:"skill_bonus"` GuildBonus float64 `json:"guild_bonus"` EventBonus float64 `json:"event_bonus"` UpdateReason string `json:"update_reason"` } // NewHangupEfficiencyUpdatedEvent 创建挂机效率更新事件 func NewHangupEfficiencyUpdatedEvent(playerID string, previousBonus, newBonus float64, efficiency *EfficiencyBonus, reason string) *HangupEfficiencyUpdatedEvent { return &HangupEfficiencyUpdatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupEfficiencyUpdated", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PreviousBonus: previousBonus, NewBonus: newBonus, VipBonus: efficiency.GetVipBonus(), EquipmentBonus: efficiency.GetEquipmentBonus(), SkillBonus: efficiency.GetSkillBonus(), GuildBonus: efficiency.GetGuildBonus(), EventBonus: efficiency.GetEventBonus(), UpdateReason: reason, } } // HangupLocationUnlockedEvent 挂机地点解锁事件 type HangupLocationUnlockedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` LocationType LocationType `json:"location_type"` RequiredLevel int `json:"required_level"` PlayerLevel int `json:"player_level"` UnlockMethod string `json:"unlock_method"` } // NewHangupLocationUnlockedEvent 创建挂机地点解锁事件 func NewHangupLocationUnlockedEvent(playerID string, location *HangupLocation, playerLevel int, unlockMethod string) *HangupLocationUnlockedEvent { return &HangupLocationUnlockedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupLocationUnlocked", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, LocationID: location.GetID(), LocationName: location.GetName(), LocationType: location.GetLocationType(), RequiredLevel: location.GetRequiredLevel(), PlayerLevel: playerLevel, UnlockMethod: unlockMethod, } } // HangupMilestoneReachedEvent 挂机里程碑达成事件 type HangupMilestoneReachedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` MilestoneType string `json:"milestone_type"` MilestoneName string `json:"milestone_name"` CurrentValue int64 `json:"current_value"` TargetValue int64 `json:"target_value"` Reward *BaseReward `json:"reward,omitempty"` TotalHangupTime time.Duration `json:"total_hangup_time"` } // NewHangupMilestoneReachedEvent 创建挂机里程碑达成事件 func NewHangupMilestoneReachedEvent(playerID, milestoneType, milestoneName string, currentValue, targetValue int64, reward *BaseReward, totalTime time.Duration) *HangupMilestoneReachedEvent { return &HangupMilestoneReachedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupMilestoneReached", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, MilestoneType: milestoneType, MilestoneName: milestoneName, CurrentValue: currentValue, TargetValue: targetValue, Reward: reward, TotalHangupTime: totalTime, } } // HangupRankingChangedEvent 挂机排名变化事件 type HangupRankingChangedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` RankType string `json:"rank_type"` PreviousRank int `json:"previous_rank"` NewRank int `json:"new_rank"` TotalTime time.Duration `json:"total_time"` TotalRewards int64 `json:"total_rewards"` Efficiency float64 `json:"efficiency"` } // NewHangupRankingChangedEvent 创建挂机排名变化事件 func NewHangupRankingChangedEvent(playerID, playerName, rankType string, prevRank, newRank int, totalTime time.Duration, totalRewards int64, efficiency float64) *HangupRankingChangedEvent { return &HangupRankingChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupRankingChanged", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PlayerName: playerName, RankType: rankType, PreviousRank: prevRank, NewRank: newRank, TotalTime: totalTime, TotalRewards: totalRewards, Efficiency: efficiency, } } // HangupSystemInitializedEvent 挂机系统初始化事件 type HangupSystemInitializedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` AvailableLocations int `json:"available_locations"` UnlockedLocations int `json:"unlocked_locations"` InitialEfficiency float64 `json:"initial_efficiency"` PlayerLevel int `json:"player_level"` } // NewHangupSystemInitializedEvent 创建挂机系统初始化事件 func NewHangupSystemInitializedEvent(playerID string, availableLocations, unlockedLocations int, initialEfficiency float64, playerLevel int) *HangupSystemInitializedEvent { return &HangupSystemInitializedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupSystemInitialized", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, AvailableLocations: availableLocations, UnlockedLocations: unlockedLocations, InitialEfficiency: initialEfficiency, PlayerLevel: playerLevel, } } // HangupAnomalyDetectedEvent 挂机异常检测事件 type HangupAnomalyDetectedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` AnomalyType string `json:"anomaly_type"` Description string `json:"description"` Severity string `json:"severity"` Metrics map[string]interface{} `json:"metrics"` Suggestions []string `json:"suggestions"` AutoResolved bool `json:"auto_resolved"` } // NewHangupAnomalyDetectedEvent 创建挂机异常检测事件 func NewHangupAnomalyDetectedEvent(playerID, anomalyType, description, severity string, metrics map[string]interface{}, suggestions []string, autoResolved bool) *HangupAnomalyDetectedEvent { return &HangupAnomalyDetectedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupAnomalyDetected", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, AnomalyType: anomalyType, Description: description, Severity: severity, Metrics: metrics, Suggestions: suggestions, AutoResolved: autoResolved, } } // HangupConfigChangedEvent 挂机配置变更事件 type HangupConfigChangedEvent struct { *BaseDomainEvent ConfigType string `json:"config_type"` PreviousConfig map[string]interface{} `json:"previous_config"` NewConfig map[string]interface{} `json:"new_config"` ChangedBy string `json:"changed_by"` ChangeReason string `json:"change_reason"` AffectedPlayers []string `json:"affected_players,omitempty"` } // NewHangupConfigChangedEvent 创建挂机配置变更事件 func NewHangupConfigChangedEvent(configType string, prevConfig, newConfig map[string]interface{}, changedBy, reason string, affectedPlayers []string) *HangupConfigChangedEvent { return &HangupConfigChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupConfigChanged", AggregateID: "system", OccurredAt: time.Now(), }, ConfigType: configType, PreviousConfig: prevConfig, NewConfig: newConfig, ChangedBy: changedBy, ChangeReason: reason, AffectedPlayers: affectedPlayers, } } // HangupSessionCompletedEvent 挂机会话完成事件 type HangupSessionCompletedEvent struct { *BaseDomainEvent SessionID string `json:"session_id"` PlayerID string `json:"player_id"` LocationID string `json:"location_id"` LocationName string `json:"location_name"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` IsOnline bool `json:"is_online"` Reward *BaseReward `json:"reward"` Efficiency float64 `json:"efficiency"` Quality string `json:"quality"` // "excellent", "good", "normal", "poor" } // NewHangupSessionCompletedEvent 创建挂机会话完成事件 func NewHangupSessionCompletedEvent(session *HangupSession, locationName string, efficiency float64, quality string) *HangupSessionCompletedEvent { return &HangupSessionCompletedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HangupSessionCompleted", AggregateID: session.GetPlayerID(), OccurredAt: time.Now(), }, SessionID: session.GetSessionID(), PlayerID: session.GetPlayerID(), LocationID: session.GetLocationID(), LocationName: locationName, StartTime: session.GetStartTime(), EndTime: session.GetEndTime(), Duration: session.GetDuration(), IsOnline: session.IsOnlineSession(), Reward: session.GetReward(), Efficiency: efficiency, Quality: quality, } } // generateEventID 生成事件ID func generateEventID() string { // 这里可以使用UUID或其他唯一ID生成方式 // 为了简化,使用时间戳 return fmt.Sprintf("hangup_%d", time.Now().UnixNano()) } ================================================ FILE: internal/domain/player/hangup/repository.go ================================================ package hangup import ( "context" "time" ) // HangupRepository 挂机仓储接口 type HangupRepository interface { // Save 保存挂机聚合根 Save(ctx context.Context, hangup *HangupAggregate) error // FindByPlayerID 根据玩家ID查找挂机信息 FindByPlayerID(ctx context.Context, playerID string) (*HangupAggregate, error) // Delete 删除挂机信息 Delete(ctx context.Context, playerID string) error // FindActiveHangups 查找活跃的挂机玩家 FindActiveHangups(ctx context.Context, limit, offset int) ([]*HangupAggregate, error) // FindByLocation 根据地点查找挂机玩家 FindByLocation(ctx context.Context, locationID string, limit, offset int) ([]*HangupAggregate, error) // UpdateLastOnlineTime 更新最后在线时间 UpdateLastOnlineTime(ctx context.Context, playerID string, timestamp time.Time) error // UpdateLastOfflineTime 更新最后离线时间 UpdateLastOfflineTime(ctx context.Context, playerID string, timestamp time.Time) error // GetHangupStatistics 获取挂机统计信息 GetHangupStatistics(ctx context.Context, playerID string) (*HangupStatistics, error) // UpdateHangupStatistics 更新挂机统计信息 UpdateHangupStatistics(ctx context.Context, stats *HangupStatistics) error // FindTopHangupPlayers 查找挂机排行榜 FindTopHangupPlayers(ctx context.Context, rankType string, limit int) ([]*HangupRank, error) // GetPlayerHangupRank 获取玩家挂机排名 GetPlayerHangupRank(ctx context.Context, playerID string, rankType string) (int, error) } // HangupLocationRepository 挂机地点仓储接口 type HangupLocationRepository interface { // SaveLocation 保存挂机地点 SaveLocation(ctx context.Context, location *HangupLocation) error // FindLocationByID 根据ID查找地点 FindLocationByID(ctx context.Context, locationID string) (*HangupLocation, error) // FindAllLocations 查找所有地点 FindAllLocations(ctx context.Context) ([]*HangupLocation, error) // FindLocationsByType 根据类型查找地点 FindLocationsByType(ctx context.Context, locationType LocationType) ([]*HangupLocation, error) // FindUnlockedLocations 查找已解锁的地点 FindUnlockedLocations(ctx context.Context, playerID string) ([]*HangupLocation, error) // FindLocationsByLevel 根据等级要求查找地点 FindLocationsByLevel(ctx context.Context, minLevel, maxLevel int) ([]*HangupLocation, error) // UpdateLocationStatus 更新地点状态 UpdateLocationStatus(ctx context.Context, locationID string, isActive bool) error // DeleteLocation 删除地点 DeleteLocation(ctx context.Context, locationID string) error // GetLocationStatistics 获取地点统计信息 GetLocationStatistics(ctx context.Context, locationID string) (*LocationStatistics, error) } // HangupSessionRepository 挂机会话仓储接口 type HangupSessionRepository interface { // SaveSession 保存挂机会话 SaveSession(ctx context.Context, session *HangupSession) error // FindSessionByID 根据ID查找会话 FindSessionByID(ctx context.Context, sessionID string) (*HangupSession, error) // FindSessionsByPlayer 根据玩家查找会话 FindSessionsByPlayer(ctx context.Context, playerID string, limit, offset int) ([]*HangupSession, error) // FindActiveSessions 查找活跃会话 FindActiveSessions(ctx context.Context, limit, offset int) ([]*HangupSession, error) // FindSessionsByLocation 根据地点查找会话 FindSessionsByLocation(ctx context.Context, locationID string, limit, offset int) ([]*HangupSession, error) // FindSessionsByTimeRange 根据时间范围查找会话 FindSessionsByTimeRange(ctx context.Context, startTime, endTime time.Time, limit, offset int) ([]*HangupSession, error) // EndSession 结束会话 EndSession(ctx context.Context, sessionID string, reward *BaseReward) error // DeleteSession 删除会话 DeleteSession(ctx context.Context, sessionID string) error // GetSessionStatistics 获取会话统计信息 GetSessionStatistics(ctx context.Context, playerID string, timeRange string) (*SessionStatistics, error) } // HangupRewardRepository 挂机奖励仓储接口 type HangupRewardRepository interface { // SaveOfflineReward 保存离线奖励 SaveOfflineReward(ctx context.Context, playerID string, reward *OfflineReward) error // FindOfflineReward 查找离线奖励 FindOfflineReward(ctx context.Context, playerID string) (*OfflineReward, error) // ClaimOfflineReward 领取离线奖励 ClaimOfflineReward(ctx context.Context, playerID string) (*OfflineReward, error) // DeleteOfflineReward 删除离线奖励 DeleteOfflineReward(ctx context.Context, playerID string) error // FindUnclaimedRewards 查找未领取的奖励 FindUnclaimedRewards(ctx context.Context, limit, offset int) ([]*OfflineReward, error) // GetRewardHistory 获取奖励历史 GetRewardHistory(ctx context.Context, playerID string, limit, offset int) ([]*OfflineReward, error) // GetTotalRewards 获取总奖励统计 GetTotalRewards(ctx context.Context, playerID string) (*RewardSummary, error) } // HangupConfigRepository 挂机配置仓储接口 type HangupConfigRepository interface { // SaveConfig 保存配置 SaveConfig(ctx context.Context, config *HangupConfig) error // GetConfig 获取配置 GetConfig(ctx context.Context) (*HangupConfig, error) // UpdateConfig 更新配置 UpdateConfig(ctx context.Context, config *HangupConfig) error // GetConfigHistory 获取配置历史 GetConfigHistory(ctx context.Context, limit, offset int) ([]*HangupConfig, error) } // 统计信息结构体 // LocationStatistics 地点统计信息 type LocationStatistics struct { LocationID string `json:"location_id"` LocationName string `json:"location_name"` TotalPlayers int64 `json:"total_players"` ActiveUsers int64 `json:"active_users"` AverageSession time.Duration `json:"average_session"` TotalExperience int64 `json:"total_experience"` TotalGold int64 `json:"total_gold"` PopularityRank int `json:"popularity_rank"` LastUpdated time.Time `json:"last_updated"` } // SessionStatistics 会话统计信息 type SessionStatistics struct { PlayerID string `json:"player_id"` TotalSessions int64 `json:"total_sessions"` TotalDuration time.Duration `json:"total_duration"` AverageSession time.Duration `json:"average_session"` LongestSession time.Duration `json:"longest_session"` ShortestSession time.Duration `json:"shortest_session"` OnlineSessions int64 `json:"online_sessions"` OfflineSessions int64 `json:"offline_sessions"` FavoriteLocation string `json:"favorite_location"` LastSession time.Time `json:"last_session"` } // RewardSummary 奖励汇总 type RewardSummary struct { PlayerID string `json:"player_id"` TotalExperience int64 `json:"total_experience"` TotalGold int64 `json:"total_gold"` TotalItems int `json:"total_items"` TotalRewards int64 `json:"total_rewards"` ClaimedRewards int64 `json:"claimed_rewards"` UnclaimedRewards int64 `json:"unclaimed_rewards"` LastReward time.Time `json:"last_reward"` LastClaimed time.Time `json:"last_claimed"` } // HangupQuery 挂机查询条件 type HangupQuery struct { PlayerIDs []string `json:"player_ids,omitempty"` LocationIDs []string `json:"location_ids,omitempty"` Status []string `json:"status,omitempty"` MinLevel int `json:"min_level,omitempty"` MaxLevel int `json:"max_level,omitempty"` StartTime time.Time `json:"start_time,omitempty"` EndTime time.Time `json:"end_time,omitempty"` MinDuration time.Duration `json:"min_duration,omitempty"` MaxDuration time.Duration `json:"max_duration,omitempty"` IsOnline *bool `json:"is_online,omitempty"` HasReward *bool `json:"has_reward,omitempty"` Limit int `json:"limit,omitempty"` Offset int `json:"offset,omitempty"` SortBy string `json:"sort_by,omitempty"` SortOrder string `json:"sort_order,omitempty"` } // HangupQueryRepository 挂机查询仓储接口 type HangupQueryRepository interface { // FindByQuery 根据查询条件查找挂机信息 FindByQuery(ctx context.Context, query *HangupQuery) ([]*HangupAggregate, error) // CountByQuery 根据查询条件统计数量 CountByQuery(ctx context.Context, query *HangupQuery) (int64, error) // FindPlayersInLocation 查找在指定地点挂机的玩家 FindPlayersInLocation(ctx context.Context, locationID string, isOnline bool) ([]*HangupAggregate, error) // FindLongTermHangups 查找长期挂机的玩家 FindLongTermHangups(ctx context.Context, minDuration time.Duration) ([]*HangupAggregate, error) // FindInactiveHangups 查找不活跃的挂机 FindInactiveHangups(ctx context.Context, inactiveDuration time.Duration) ([]*HangupAggregate, error) // GetHangupTrends 获取挂机趋势数据 GetHangupTrends(ctx context.Context, timeRange string, groupBy string) ([]TrendData, error) // GetLocationPopularity 获取地点热度数据 GetLocationPopularity(ctx context.Context, timeRange string) ([]LocationPopularity, error) // GetPlayerHangupSummary 获取玩家挂机汇总 GetPlayerHangupSummary(ctx context.Context, playerID string, timeRange string) (*PlayerHangupSummary, error) } // 趋势和分析数据结构体 // TrendData 趋势数据 type TrendData struct { Timestamp time.Time `json:"timestamp"` ActiveUsers int64 `json:"active_users"` TotalSessions int64 `json:"total_sessions"` AverageDuration time.Duration `json:"average_duration"` TotalRewards int64 `json:"total_rewards"` } // LocationPopularity 地点热度 type LocationPopularity struct { LocationID string `json:"location_id"` LocationName string `json:"location_name"` UserCount int64 `json:"user_count"` SessionCount int64 `json:"session_count"` PopularityScore float64 `json:"popularity_score"` Rank int `json:"rank"` } // PlayerHangupSummary 玩家挂机汇总 type PlayerHangupSummary struct { PlayerID string `json:"player_id"` TotalHangupTime time.Duration `json:"total_hangup_time"` TotalSessions int64 `json:"total_sessions"` TotalExperience int64 `json:"total_experience"` TotalGold int64 `json:"total_gold"` TotalItems int `json:"total_items"` FavoriteLocations []LocationUsage `json:"favorite_locations"` HangupEfficiency float64 `json:"hangup_efficiency"` Rank int `json:"rank"` Achievements []string `json:"achievements"` LastActive time.Time `json:"last_active"` WeeklyStats map[string]interface{} `json:"weekly_stats"` MonthlyStats map[string]interface{} `json:"monthly_stats"` } // LocationUsage 地点使用情况 type LocationUsage struct { LocationID string `json:"location_id"` LocationName string `json:"location_name"` UsageCount int64 `json:"usage_count"` TotalTime time.Duration `json:"total_time"` Percentage float64 `json:"percentage"` } // HangupAnalyticsRepository 挂机分析仓储接口 type HangupAnalyticsRepository interface { // GetDailyStats 获取每日统计 GetDailyStats(ctx context.Context, date time.Time) (*DailyHangupStats, error) // GetWeeklyStats 获取周统计 GetWeeklyStats(ctx context.Context, year, week int) (*WeeklyHangupStats, error) // GetMonthlyStats 获取月统计 GetMonthlyStats(ctx context.Context, year, month int) (*MonthlyHangupStats, error) // GetPlayerAnalytics 获取玩家分析数据 GetPlayerAnalytics(ctx context.Context, playerID string, timeRange string) (*PlayerAnalytics, error) // GetLocationAnalytics 获取地点分析数据 GetLocationAnalytics(ctx context.Context, locationID string, timeRange string) (*LocationAnalytics, error) // GetSystemAnalytics 获取系统分析数据 GetSystemAnalytics(ctx context.Context, timeRange string) (*SystemAnalytics, error) // GenerateReport 生成报告 GenerateReport(ctx context.Context, reportType string, params map[string]interface{}) ([]byte, error) } // 分析数据结构体 // DailyHangupStats 每日挂机统计 type DailyHangupStats struct { Date time.Time `json:"date"` ActiveUsers int64 `json:"active_users"` NewUsers int64 `json:"new_users"` TotalSessions int64 `json:"total_sessions"` AverageDuration time.Duration `json:"average_duration"` TotalRewards int64 `json:"total_rewards"` TopLocations []string `json:"top_locations"` } // WeeklyHangupStats 周挂机统计 type WeeklyHangupStats struct { Year int `json:"year"` Week int `json:"week"` StartDate time.Time `json:"start_date"` EndDate time.Time `json:"end_date"` ActiveUsers int64 `json:"active_users"` NewUsers int64 `json:"new_users"` TotalSessions int64 `json:"total_sessions"` AverageDuration time.Duration `json:"average_duration"` TotalRewards int64 `json:"total_rewards"` GrowthRate float64 `json:"growth_rate"` } // MonthlyHangupStats 月挂机统计 type MonthlyHangupStats struct { Year int `json:"year"` Month int `json:"month"` ActiveUsers int64 `json:"active_users"` NewUsers int64 `json:"new_users"` TotalSessions int64 `json:"total_sessions"` AverageDuration time.Duration `json:"average_duration"` TotalRewards int64 `json:"total_rewards"` RetentionRate float64 `json:"retention_rate"` ChurnRate float64 `json:"churn_rate"` } // PlayerAnalytics 玩家分析数据 type PlayerAnalytics struct { PlayerID string `json:"player_id"` HangupPattern map[string]interface{} `json:"hangup_pattern"` EfficiencyTrend []float64 `json:"efficiency_trend"` LocationPreference []LocationUsage `json:"location_preference"` RewardTrend []int64 `json:"reward_trend"` BehaviorScore float64 `json:"behavior_score"` Predictions map[string]interface{} `json:"predictions"` } // LocationAnalytics 地点分析数据 type LocationAnalytics struct { LocationID string `json:"location_id"` UsagePattern map[string]interface{} `json:"usage_pattern"` UserDistribution map[string]int64 `json:"user_distribution"` EfficiencyStats map[string]float64 `json:"efficiency_stats"` PopularityTrend []float64 `json:"popularity_trend"` OptimizationSuggestions []string `json:"optimization_suggestions"` } // SystemAnalytics 系统分析数据 type SystemAnalytics struct { OverallHealth float64 `json:"overall_health"` PerformanceMetrics map[string]float64 `json:"performance_metrics"` UsageDistribution map[string]int64 `json:"usage_distribution"` TrendAnalysis map[string]interface{} `json:"trend_analysis"` Anomalies []string `json:"anomalies"` Recommendations []string `json:"recommendations"` } ================================================ FILE: internal/domain/player/hangup/service.go ================================================ package hangup import ( "fmt" "math" "time" ) // HangupService 挂机领域服务 type HangupService struct { config *HangupConfig locationTemplates map[string]*LocationTemplate efficiencyRules []EfficiencyRule rewardCalculators map[LocationType]RewardCalculator } // NewHangupService 创建挂机服务 func NewHangupService(config *HangupConfig) *HangupService { return &HangupService{ config: config, locationTemplates: make(map[string]*LocationTemplate), efficiencyRules: make([]EfficiencyRule, 0), rewardCalculators: make(map[LocationType]RewardCalculator), } } // LocationTemplate 地点模板 type LocationTemplate struct { ID string Name string Description string LocationType LocationType RequiredLevel int RequiredQuests []string BaseExpRate float64 BaseGoldRate float64 SpecialItems []ItemDrop MaxOfflineHours int UnlockConditions []UnlockCondition } // CreateLocation 根据模板创建地点 func (lt *LocationTemplate) CreateLocation() *HangupLocation { location := NewHangupLocation(lt.ID, lt.Name, lt.Description, lt.LocationType) location.SetRequiredLevel(lt.RequiredLevel) location.SetBaseExpRate(lt.BaseExpRate) location.SetBaseGoldRate(lt.BaseGoldRate) location.SetMaxOfflineHours(lt.MaxOfflineHours) // 添加所需任务 for _, questID := range lt.RequiredQuests { location.AddRequiredQuest(questID) } // 添加特殊物品 for _, item := range lt.SpecialItems { location.AddSpecialItem(item) } return location } // UnlockCondition 解锁条件 type UnlockCondition struct { ConditionType string RequiredValue interface{} Description string } // EfficiencyRule 效率规则 type EfficiencyRule struct { Name string Condition func(*HangupAggregate) bool BonusType string // "vip", "equipment", "skill", "guild", "event" BonusValue float64 Description string } // RewardCalculator 奖励计算器接口 type RewardCalculator interface { CalculateReward(location *HangupLocation, duration time.Duration, bonus *EfficiencyBonus) *BaseReward } // DefaultRewardCalculator 默认奖励计算器 type DefaultRewardCalculator struct{} // CalculateReward 计算奖励 func (drc *DefaultRewardCalculator) CalculateReward(location *HangupLocation, duration time.Duration, bonus *EfficiencyBonus) *BaseReward { hours := duration.Hours() // 基础奖励计算 baseExp := int64(hours * 100 * location.GetBaseExpRate()) baseGold := int64(hours * 50 * location.GetBaseGoldRate()) // 应用地点类型倍率 locationMultiplier := location.GetLocationType().GetExpMultiplier() baseExp = int64(float64(baseExp) * locationMultiplier) baseGold = int64(float64(baseGold) * location.GetLocationType().GetGoldMultiplier()) baseReward := NewBaseReward(baseExp, baseGold) // 计算物品掉落 for _, itemDrop := range location.GetSpecialItems() { if itemDrop.ShouldDrop(hours) { baseReward.AddItem(NewRewardItem("item", itemDrop.ItemID, int64(itemDrop.CalculateQuantity(hours)), "normal")) } } return baseReward } // RegisterLocationTemplate 注册地点模板 func (hs *HangupService) RegisterLocationTemplate(template *LocationTemplate) { hs.locationTemplates[template.ID] = template } // GetLocationTemplate 获取地点模板 func (hs *HangupService) GetLocationTemplate(id string) *LocationTemplate { return hs.locationTemplates[id] } // GetAllLocationTemplates 获取所有地点模板 func (hs *HangupService) GetAllLocationTemplates() map[string]*LocationTemplate { return hs.locationTemplates } // AddEfficiencyRule 添加效率规则 func (hs *HangupService) AddEfficiencyRule(rule EfficiencyRule) { hs.efficiencyRules = append(hs.efficiencyRules, rule) } // RegisterRewardCalculator 注册奖励计算器 func (hs *HangupService) RegisterRewardCalculator(locationType LocationType, calculator RewardCalculator) { hs.rewardCalculators[locationType] = calculator } // CreatePlayerHangup 为玩家创建挂机系统 func (hs *HangupService) CreatePlayerHangup(playerID string) *HangupAggregate { return NewHangupAggregate(playerID) } // CalculateOfflineReward 计算离线奖励 func (hs *HangupService) CalculateOfflineReward(hangup *HangupAggregate, offlineDuration time.Duration) (*OfflineReward, error) { if hangup.GetCurrentLocation() == nil { return nil, ErrNoHangupLocationSet } // 限制最大离线时间 maxOfflineTime := time.Duration(hs.config.GetMaxOfflineHours()) * time.Hour if offlineDuration > maxOfflineTime { offlineDuration = maxOfflineTime } // 应用离线衰减 offlineMultiplier := hs.config.GetOfflineDecayRate() effectiveDuration := time.Duration(float64(offlineDuration) * offlineMultiplier) // 获取奖励计算器 location := hangup.GetCurrentLocation() calculator := hs.getRewardCalculator(location.GetLocationType()) // 计算基础奖励 baseReward := calculator.CalculateReward(location, effectiveDuration, hangup.GetEfficiencyBonus()) // 应用效率加成 finalReward := hangup.GetEfficiencyBonus().ApplyBonus(baseReward) // 创建离线奖励 offlineReward := &OfflineReward{ Experience: finalReward.Experience, Gold: finalReward.Gold, Items: finalReward.Items, OfflineDuration: offlineDuration, LocationID: location.GetID(), CalculatedAt: time.Now(), IsClaimed: false, } return offlineReward, nil } // UpdateEfficiencyBonus 更新效率加成 func (hs *HangupService) UpdateEfficiencyBonus(hangup *HangupAggregate, playerLevel int, vipLevel int, equipmentBonus float64) { bonus := NewEfficiencyBonus() // 应用所有效率规则 for _, rule := range hs.efficiencyRules { if rule.Condition(hangup) { switch rule.BonusType { case "vip": bonus.SetVipBonus(bonus.GetVipBonus() + rule.BonusValue) case "equipment": bonus.SetEquipmentBonus(bonus.GetEquipmentBonus() + rule.BonusValue) case "skill": bonus.SetSkillBonus(bonus.GetSkillBonus() + rule.BonusValue) case "guild": bonus.SetGuildBonus(bonus.GetGuildBonus() + rule.BonusValue) case "event": bonus.SetEventBonus(bonus.GetEventBonus() + rule.BonusValue) default: bonus.SetSpecialBonus(rule.BonusType, rule.BonusValue) } } } // 设置VIP加成 vipBonus := hs.calculateVipBonus(vipLevel) bonus.SetVipBonus(vipBonus) // 设置装备加成 bonus.SetEquipmentBonus(equipmentBonus) hangup.UpdateEfficiencyBonus(bonus) } // ValidateLocationUnlock 验证地点解锁 func (hs *HangupService) ValidateLocationUnlock(playerID string, locationID string, playerLevel int, completedQuests []string) error { template := hs.GetLocationTemplate(locationID) if template == nil { return fmt.Errorf("location template not found: %s", locationID) } // 检查等级要求 if playerLevel < template.RequiredLevel { return fmt.Errorf("player level %d is below required level %d", playerLevel, template.RequiredLevel) } // 检查任务要求 completedQuestMap := make(map[string]bool) for _, questID := range completedQuests { completedQuestMap[questID] = true } for _, requiredQuest := range template.RequiredQuests { if !completedQuestMap[requiredQuest] { return fmt.Errorf("required quest not completed: %s", requiredQuest) } } return nil } // CalculateOptimalLocation 计算最优挂机地点 func (hs *HangupService) CalculateOptimalLocation(hangup *HangupAggregate, availableLocations []*HangupLocation, targetType string) *HangupLocation { if len(availableLocations) == 0 { return nil } var bestLocation *HangupLocation var bestScore float64 for _, location := range availableLocations { if !location.IsUnlocked() || !location.IsActive() { continue } score := hs.calculateLocationScore(location, targetType) if bestLocation == nil || score > bestScore { bestLocation = location bestScore = score } } return bestLocation } // CalculateHangupEfficiency 计算挂机效率 func (hs *HangupService) CalculateHangupEfficiency(hangup *HangupAggregate) float64 { if hangup.GetCurrentLocation() == nil { return 0 } location := hangup.GetCurrentLocation() bonus := hangup.GetEfficiencyBonus() // 基础效率 baseEfficiency := location.GetBaseExpRate() + location.GetBaseGoldRate() // 应用加成 totalBonus := bonus.GetTotalBonus() // 地点类型加成 locationBonus := location.GetLocationType().GetExpMultiplier() + location.GetLocationType().GetGoldMultiplier() return baseEfficiency * totalBonus * locationBonus } // GetHangupRecommendations 获取挂机建议 func (hs *HangupService) GetHangupRecommendations(hangup *HangupAggregate, playerLevel int) []HangupRecommendation { recommendations := make([]HangupRecommendation, 0) // 检查当前地点是否最优 currentLocation := hangup.GetCurrentLocation() if currentLocation != nil { efficiency := hs.CalculateHangupEfficiency(hangup) if efficiency < 2.0 { // 效率较低 recommendations = append(recommendations, HangupRecommendation{ Type: "location_change", Title: "建议更换挂机地点", Description: "当前地点效率较低,建议选择更高效的地点", Priority: "medium", }) } } // 检查效率加成 bonus := hangup.GetEfficiencyBonus() if bonus.GetTotalBonus() < 1.5 { recommendations = append(recommendations, HangupRecommendation{ Type: "efficiency_boost", Title: "提升挂机效率", Description: "通过提升VIP等级、装备或技能来增加挂机效率", Priority: "high", }) } // 检查每日挂机时间 dailyTime := hangup.GetDailyHangupTime() maxDailyTime := time.Duration(hs.config.GetMaxDailyHangupHours()) * time.Hour if dailyTime < time.Duration(float64(maxDailyTime)*0.8) { //todo need check it correct recommendations = append(recommendations, HangupRecommendation{ Type: "time_optimization", Title: "增加挂机时间", Description: "今日挂机时间较少,建议增加挂机时间以获得更多收益", Priority: "low", }) } return recommendations } // 私有方法 // getRewardCalculator 获取奖励计算器 func (hs *HangupService) getRewardCalculator(locationType LocationType) RewardCalculator { if calculator, exists := hs.rewardCalculators[locationType]; exists { return calculator } return &DefaultRewardCalculator{} } // calculateVipBonus 计算VIP加成 func (hs *HangupService) calculateVipBonus(vipLevel int) float64 { if vipLevel <= 0 { return 0 } // VIP等级越高,加成越大,但有递减效应 bonus := float64(vipLevel) * 0.1 // 每级10%加成 if vipLevel > 10 { bonus = 1.0 + math.Log(float64(vipLevel-10))*0.1 // 超过10级后递减 } return bonus } // calculateLocationScore 计算地点评分 func (hs *HangupService) calculateLocationScore(location *HangupLocation, targetType string) float64 { baseScore := location.GetBaseExpRate() + location.GetBaseGoldRate() // 根据目标类型调整评分 switch targetType { case "experience": baseScore = location.GetBaseExpRate()*2 + location.GetBaseGoldRate()*0.5 case "gold": baseScore = location.GetBaseGoldRate()*2 + location.GetBaseExpRate()*0.5 case "items": baseScore += float64(len(location.GetSpecialItems())) * 0.5 default: // 平衡型 baseScore = location.GetBaseExpRate() + location.GetBaseGoldRate() } // 地点类型加成 locationMultiplier := location.GetLocationType().GetExpMultiplier() + location.GetLocationType().GetGoldMultiplier() baseScore *= locationMultiplier return baseScore } // InitializeDefaultLocations 初始化默认地点 func (hs *HangupService) InitializeDefaultLocations() { // 新手森林 beginnerForest := &LocationTemplate{ ID: "beginner_forest", Name: "新手森林", Description: "适合新手的安全森林区域", LocationType: LocationTypeForest, RequiredLevel: 1, RequiredQuests: []string{}, BaseExpRate: 1.0, BaseGoldRate: 1.0, MaxOfflineHours: 12, SpecialItems: []ItemDrop{ NewItemDrop("wood", 0.3, 1, 3), NewItemDrop("herb", 0.2, 1, 2), }, } hs.RegisterLocationTemplate(beginnerForest) // 魔法洞穴 magicCave := &LocationTemplate{ ID: "magic_cave", Name: "魔法洞穴", Description: "充满魔法能量的神秘洞穴", LocationType: LocationTypeCave, RequiredLevel: 10, RequiredQuests: []string{"explore_forest"}, BaseExpRate: 1.4, BaseGoldRate: 1.2, MaxOfflineHours: 18, SpecialItems: []ItemDrop{ NewItemDrop("magic_crystal", 0.1, 1, 1), NewItemDrop("rare_ore", 0.15, 1, 2), }, } hs.RegisterLocationTemplate(magicCave) // 古代遗迹 ancientRuins := &LocationTemplate{ ID: "ancient_ruins", Name: "古代遗迹", Description: "蕴含古老力量的神秘遗迹", LocationType: LocationTypeSpecial, RequiredLevel: 25, RequiredQuests: []string{"cave_exploration", "ancient_key"}, BaseExpRate: 2.0, BaseGoldRate: 1.8, MaxOfflineHours: 24, SpecialItems: []ItemDrop{ NewItemDrop("ancient_artifact", 0.05, 1, 1), NewItemDrop("legendary_gem", 0.02, 1, 1), }, } hs.RegisterLocationTemplate(ancientRuins) } // InitializeDefaultRules 初始化默认规则 func (hs *HangupService) InitializeDefaultRules() { // VIP加成规则 hs.AddEfficiencyRule(EfficiencyRule{ Name: "VIP Bonus", Condition: func(hangup *HangupAggregate) bool { return true // 所有玩家都适用 }, BonusType: "vip", BonusValue: 0.0, // 将在UpdateEfficiencyBonus中计算 Description: "VIP等级加成", }) // 长时间挂机加成 hs.AddEfficiencyRule(EfficiencyRule{ Name: "Long Session Bonus", Condition: func(hangup *HangupAggregate) bool { return hangup.GetDailyHangupTime() > 6*time.Hour }, BonusType: "special", BonusValue: 0.1, // 10%加成 Description: "长时间挂机加成", }) // 连续挂机加成 hs.AddEfficiencyRule(EfficiencyRule{ Name: "Consecutive Days Bonus", Condition: func(hangup *HangupAggregate) bool { // 这里需要外部提供连续挂机天数信息 return true // 简化实现 }, BonusType: "special", BonusValue: 0.05, // 5%加成 Description: "连续挂机加成", }) } // HangupRecommendation 挂机建议 type HangupRecommendation struct { Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` Priority string `json:"priority"` // "high", "medium", "low" ActionURL string `json:"action_url,omitempty"` } ================================================ FILE: internal/domain/player/hangup/value_object.go ================================================ package hangup import ( "math/rand" "time" ) // LocationType 挂机地点类型 type LocationType int const ( LocationTypeUnknown LocationType = iota LocationTypeForest // 森林 LocationTypeMountain // 山脉 LocationTypeDesert // 沙漠 LocationTypeOcean // 海洋 LocationTypeCave // 洞穴 LocationTypeDungeon // 地牢 LocationTypeCity // 城市 LocationTypeVillage // 村庄 LocationTypeSpecial // 特殊地点 ) // String 返回地点类型的字符串表示 func (lt LocationType) String() string { switch lt { case LocationTypeForest: return "forest" case LocationTypeMountain: return "mountain" case LocationTypeDesert: return "desert" case LocationTypeOcean: return "ocean" case LocationTypeCave: return "cave" case LocationTypeDungeon: return "dungeon" case LocationTypeCity: return "city" case LocationTypeVillage: return "village" case LocationTypeSpecial: return "special" default: return "unknown" } } // GetExpMultiplier 获取经验倍率 func (lt LocationType) GetExpMultiplier() float64 { switch lt { case LocationTypeForest: return 1.0 case LocationTypeMountain: return 1.2 case LocationTypeDesert: return 1.1 case LocationTypeOcean: return 1.3 case LocationTypeCave: return 1.4 case LocationTypeDungeon: return 1.5 case LocationTypeCity: return 0.8 case LocationTypeVillage: return 0.9 case LocationTypeSpecial: return 2.0 default: return 1.0 } } // GetGoldMultiplier 获取金币倍率 func (lt LocationType) GetGoldMultiplier() float64 { switch lt { case LocationTypeForest: return 1.0 case LocationTypeMountain: return 1.1 case LocationTypeDesert: return 1.2 case LocationTypeOcean: return 1.0 case LocationTypeCave: return 1.3 case LocationTypeDungeon: return 1.4 case LocationTypeCity: return 1.5 case LocationTypeVillage: return 1.2 case LocationTypeSpecial: return 1.8 default: return 1.0 } } // RewardItem 奖励物品值对象 type RewardItem struct { Type string `json:"type"` ItemID string `json:"item_id"` Quantity int64 `json:"quantity"` Quality string `json:"quality"` } // NewRewardItem 创建奖励物品 func NewRewardItem(itemType, itemID string, quantity int64, quality string) RewardItem { return RewardItem{ Type: itemType, ItemID: itemID, Quantity: quantity, Quality: quality, } } // IsValid 检查奖励物品是否有效 func (ri RewardItem) IsValid() bool { return ri.ItemID != "" && ri.Quantity > 0 } // BaseReward 基础奖励值对象 type BaseReward struct { Experience int64 `json:"experience"` Gold int64 `json:"gold"` Items []RewardItem `json:"items"` } // NewBaseReward 创建基础奖励 func NewBaseReward(experience, gold int64) *BaseReward { return &BaseReward{ Experience: experience, Gold: gold, Items: make([]RewardItem, 0), } } // AddItem 添加物品奖励 func (br *BaseReward) AddItem(item RewardItem) { if item.IsValid() { br.Items = append(br.Items, item) } } // IsEmpty 检查奖励是否为空 func (br *BaseReward) IsEmpty() bool { return br.Experience == 0 && br.Gold == 0 && len(br.Items) == 0 } // Multiply 乘以倍率 func (br *BaseReward) Multiply(multiplier float64) *BaseReward { return &BaseReward{ Experience: int64(float64(br.Experience) * multiplier), Gold: int64(float64(br.Gold) * multiplier), Items: br.Items, // 物品数量不变 } } // Add 添加另一个奖励 func (br *BaseReward) Add(other *BaseReward) *BaseReward { result := &BaseReward{ Experience: br.Experience + other.Experience, Gold: br.Gold + other.Gold, Items: make([]RewardItem, 0), } // 合并物品 result.Items = append(result.Items, br.Items...) result.Items = append(result.Items, other.Items...) return result } // ItemDrop 物品掉落值对象 type ItemDrop struct { ItemID string `json:"item_id"` DropRate float64 `json:"drop_rate"` // 掉落率 (0.0-1.0) MinQuantity int `json:"min_quantity"` // 最小数量 MaxQuantity int `json:"max_quantity"` // 最大数量 HourlyRate float64 `json:"hourly_rate"` // 每小时掉落率 } // NewItemDrop 创建物品掉落 func NewItemDrop(itemID string, dropRate float64, minQty, maxQty int) ItemDrop { return ItemDrop{ ItemID: itemID, DropRate: dropRate, MinQuantity: minQty, MaxQuantity: maxQty, HourlyRate: dropRate, // 默认每小时掉落率等于基础掉落率 } } // SetHourlyRate 设置每小时掉落率 func (id *ItemDrop) SetHourlyRate(rate float64) { id.HourlyRate = rate } // ShouldDrop 判断是否应该掉落 func (id ItemDrop) ShouldDrop(hours float64) bool { // 计算在指定小时数内的掉落概率 totalRate := id.HourlyRate * hours if totalRate >= 1.0 { return true // 100%掉落 } // 随机判断 return rand.Float64() < totalRate } // CalculateQuantity 计算掉落数量 func (id ItemDrop) CalculateQuantity(hours float64) int { if id.MinQuantity == id.MaxQuantity { return id.MinQuantity } // 基于小时数调整数量 baseQuantity := id.MinQuantity + rand.Intn(id.MaxQuantity-id.MinQuantity+1) // 长时间挂机可能获得更多物品 if hours > 12 { bonusChance := (hours - 12) / 12 // 每12小时增加一次奖励机会 if rand.Float64() < bonusChance { baseQuantity += rand.Intn(id.MaxQuantity - id.MinQuantity + 1) } } return baseQuantity } // IsValid 检查物品掉落配置是否有效 func (id ItemDrop) IsValid() bool { return id.ItemID != "" && id.DropRate >= 0 && id.DropRate <= 1.0 && id.MinQuantity > 0 && id.MaxQuantity >= id.MinQuantity } // HangupConfig 挂机配置值对象 type HangupConfig struct { MaxOfflineHours int `json:"max_offline_hours"` // 最大离线小时数 BaseExpPerHour int64 `json:"base_exp_per_hour"` // 基础每小时经验 BaseGoldPerHour int64 `json:"base_gold_per_hour"` // 基础每小时金币 VipBonusMultiplier float64 `json:"vip_bonus_multiplier"` // VIP加成倍率 MaxDailyHangupHours int `json:"max_daily_hangup_hours"` // 每日最大挂机小时数 OfflineDecayRate float64 `json:"offline_decay_rate"` // 离线衰减率 MinimumLevel int `json:"minimum_level"` // 最低等级要求 } // NewHangupConfig 创建挂机配置 func NewHangupConfig() *HangupConfig { return &HangupConfig{ MaxOfflineHours: 24, BaseExpPerHour: 100, BaseGoldPerHour: 50, VipBonusMultiplier: 1.5, MaxDailyHangupHours: 12, OfflineDecayRate: 0.8, // 离线效率为在线的80% MinimumLevel: 1, } } // GetMaxOfflineHours 获取最大离线小时数 func (hc *HangupConfig) GetMaxOfflineHours() int { return hc.MaxOfflineHours } // GetBaseExpPerHour 获取基础每小时经验 func (hc *HangupConfig) GetBaseExpPerHour() int64 { return hc.BaseExpPerHour } // GetBaseGoldPerHour 获取基础每小时金币 func (hc *HangupConfig) GetBaseGoldPerHour() int64 { return hc.BaseGoldPerHour } // GetVipBonusMultiplier 获取VIP加成倍率 func (hc *HangupConfig) GetVipBonusMultiplier() float64 { return hc.VipBonusMultiplier } // GetMaxDailyHangupHours 获取每日最大挂机小时数 func (hc *HangupConfig) GetMaxDailyHangupHours() int { return hc.MaxDailyHangupHours } // GetOfflineDecayRate 获取离线衰减率 func (hc *HangupConfig) GetOfflineDecayRate() float64 { return hc.OfflineDecayRate } // GetMinimumLevel 获取最低等级要求 func (hc *HangupConfig) GetMinimumLevel() int { return hc.MinimumLevel } // IsValid 检查配置是否有效 func (hc *HangupConfig) IsValid() bool { return hc.MaxOfflineHours > 0 && hc.BaseExpPerHour >= 0 && hc.BaseGoldPerHour >= 0 && hc.VipBonusMultiplier >= 1.0 && hc.MaxDailyHangupHours > 0 && hc.OfflineDecayRate > 0 && hc.OfflineDecayRate <= 1.0 && hc.MinimumLevel >= 1 } // HangupSession 挂机会话值对象 type HangupSession struct { SessionID string `json:"session_id"` PlayerID string `json:"player_id"` LocationID string `json:"location_id"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Duration time.Duration `json:"duration"` IsOnline bool `json:"is_online"` Reward *BaseReward `json:"reward,omitempty"` CreatedAt time.Time `json:"created_at"` } // NewHangupSession 创建挂机会话 func NewHangupSession(sessionID, playerID, locationID string, isOnline bool) *HangupSession { now := time.Now() return &HangupSession{ SessionID: sessionID, PlayerID: playerID, LocationID: locationID, StartTime: now, IsOnline: isOnline, CreatedAt: now, } } // End 结束会话 func (hs *HangupSession) End(reward *BaseReward) { hs.EndTime = time.Now() hs.Duration = hs.EndTime.Sub(hs.StartTime) hs.Reward = reward } // GetSessionID 获取会话ID func (hs *HangupSession) GetSessionID() string { return hs.SessionID } // GetPlayerID 获取玩家ID func (hs *HangupSession) GetPlayerID() string { return hs.PlayerID } // GetLocationID 获取地点ID func (hs *HangupSession) GetLocationID() string { return hs.LocationID } // GetStartTime 获取开始时间 func (hs *HangupSession) GetStartTime() time.Time { return hs.StartTime } // GetEndTime 获取结束时间 func (hs *HangupSession) GetEndTime() time.Time { return hs.EndTime } // GetDuration 获取持续时间 func (hs *HangupSession) GetDuration() time.Duration { return hs.Duration } // IsOnlineSession 是否在线会话 func (hs *HangupSession) IsOnlineSession() bool { return hs.IsOnline } // GetReward 获取奖励 func (hs *HangupSession) GetReward() *BaseReward { return hs.Reward } // IsActive 是否活跃会话 func (hs *HangupSession) IsActive() bool { return hs.EndTime.IsZero() } // GetCreatedAt 获取创建时间 func (hs *HangupSession) GetCreatedAt() time.Time { return hs.CreatedAt } // HangupRank 挂机排行值对象 type HangupRank struct { PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` TotalHangupTime time.Duration `json:"total_hangup_time"` TotalExperience int64 `json:"total_experience"` TotalGold int64 `json:"total_gold"` Rank int `json:"rank"` LastUpdated time.Time `json:"last_updated"` } // NewHangupRank 创建挂机排行 func NewHangupRank(playerID, playerName string, totalTime time.Duration, totalExp, totalGold int64, rank int) *HangupRank { return &HangupRank{ PlayerID: playerID, PlayerName: playerName, TotalHangupTime: totalTime, TotalExperience: totalExp, TotalGold: totalGold, Rank: rank, LastUpdated: time.Now(), } } // GetPlayerID 获取玩家ID func (hr *HangupRank) GetPlayerID() string { return hr.PlayerID } // GetPlayerName 获取玩家名称 func (hr *HangupRank) GetPlayerName() string { return hr.PlayerName } // GetTotalHangupTime 获取总挂机时间 func (hr *HangupRank) GetTotalHangupTime() time.Duration { return hr.TotalHangupTime } // GetTotalExperience 获取总经验 func (hr *HangupRank) GetTotalExperience() int64 { return hr.TotalExperience } // GetTotalGold 获取总金币 func (hr *HangupRank) GetTotalGold() int64 { return hr.TotalGold } // GetRank 获取排名 func (hr *HangupRank) GetRank() int { return hr.Rank } // GetLastUpdated 获取最后更新时间 func (hr *HangupRank) GetLastUpdated() time.Time { return hr.LastUpdated } // UpdateRank 更新排名 func (hr *HangupRank) UpdateRank(newRank int) { hr.Rank = newRank hr.LastUpdated = time.Now() } // CalculateEfficiency 计算挂机效率 func (hr *HangupRank) CalculateEfficiency() float64 { if hr.TotalHangupTime == 0 { return 0 } hours := hr.TotalHangupTime.Hours() expPerHour := float64(hr.TotalExperience) / hours goldPerHour := float64(hr.TotalGold) / hours // 综合效率计算(经验和金币的加权平均) return (expPerHour + goldPerHour*0.5) / 100 // 归一化到合理范围 } ================================================ FILE: internal/domain/player/honor/aggregate.go ================================================ package honor import ( "time" // "github.com/google/uuid" // 未使用 ) // HonorAggregate 荣誉聚合根 type HonorAggregate struct { playerID string titles map[string]*Title achievements map[string]*Achievement currentTitle string honorPoints int honorLevel int reputation map[string]int // 声望系统 statistics *PlayerStatistics updatedAt time.Time version int } // NewHonorAggregate 创建荣誉聚合根 func NewHonorAggregate(playerID string) *HonorAggregate { return &HonorAggregate{ playerID: playerID, titles: make(map[string]*Title), achievements: make(map[string]*Achievement), currentTitle: "", honorPoints: 0, honorLevel: 1, reputation: make(map[string]int), statistics: NewPlayerStatistics(), updatedAt: time.Now(), version: 1, } } // GetPlayerID 获取玩家ID func (h *HonorAggregate) GetPlayerID() string { return h.playerID } // AddTitle 添加称号 func (h *HonorAggregate) AddTitle(title *Title) error { if title == nil { return ErrInvalidTitle } // 检查是否已拥有该称号 if _, exists := h.titles[title.GetID()]; exists { return ErrTitleAlreadyOwned } h.titles[title.GetID()] = title h.updateVersion() return nil } // UnlockTitle 解锁称号 func (h *HonorAggregate) UnlockTitle(titleID string) error { title, exists := h.titles[titleID] if !exists { return ErrTitleNotFound } if title.IsUnlocked() { return ErrTitleAlreadyUnlocked } // 检查解锁条件 if !h.checkTitleUnlockConditions(title) { return ErrTitleConditionNotMet } title.Unlock() h.updateVersion() return nil } // EquipTitle 装备称号 func (h *HonorAggregate) EquipTitle(titleID string) error { title, exists := h.titles[titleID] if !exists { return ErrTitleNotFound } if !title.IsUnlocked() { return ErrTitleNotUnlocked } // 卸下当前称号 if h.currentTitle != "" { if currentTitle, exists := h.titles[h.currentTitle]; exists { currentTitle.Unequip() } } // 装备新称号 title.Equip() h.currentTitle = titleID h.updateVersion() return nil } // UnequipTitle 卸下称号 func (h *HonorAggregate) UnequipTitle() error { if h.currentTitle == "" { return ErrNoTitleEquipped } if title, exists := h.titles[h.currentTitle]; exists { title.Unequip() } h.currentTitle = "" h.updateVersion() return nil } // GetCurrentTitle 获取当前称号 func (h *HonorAggregate) GetCurrentTitle() *Title { if h.currentTitle == "" { return nil } return h.titles[h.currentTitle] } // GetAllTitles 获取所有称号 func (h *HonorAggregate) GetAllTitles() map[string]*Title { return h.titles } // GetUnlockedTitles 获取已解锁的称号 func (h *HonorAggregate) GetUnlockedTitles() []*Title { var unlocked []*Title for _, title := range h.titles { if title.IsUnlocked() { unlocked = append(unlocked, title) } } return unlocked } // AddAchievement 添加成就 func (h *HonorAggregate) AddAchievement(achievement *Achievement) error { if achievement == nil { return ErrInvalidAchievement } h.achievements[achievement.GetID()] = achievement h.updateVersion() return nil } // UnlockAchievement 解锁成就 func (h *HonorAggregate) UnlockAchievement(achievementID string) error { achievement, exists := h.achievements[achievementID] if !exists { return ErrAchievementNotFound } if achievement.IsUnlocked() { return ErrAchievementAlreadyUnlocked } // 检查解锁条件 if !h.checkAchievementUnlockConditions(achievement) { return ErrAchievementConditionNotMet } achievement.Unlock() // 给予荣誉点数奖励 h.AddHonorPoints(achievement.GetHonorReward()) h.updateVersion() return nil } // GetAchievement 获取成就 func (h *HonorAggregate) GetAchievement(achievementID string) *Achievement { return h.achievements[achievementID] } // GetAllAchievements 获取所有成就 func (h *HonorAggregate) GetAllAchievements() map[string]*Achievement { return h.achievements } // GetUnlockedAchievements 获取已解锁的成就 func (h *HonorAggregate) GetUnlockedAchievements() []*Achievement { var unlocked []*Achievement for _, achievement := range h.achievements { if achievement.IsUnlocked() { unlocked = append(unlocked, achievement) } } return unlocked } // AddHonorPoints 增加荣誉点数 func (h *HonorAggregate) AddHonorPoints(points int) { h.honorPoints += points // 检查是否升级 h.checkHonorLevelUp() h.updateVersion() } // GetHonorPoints 获取荣誉点数 func (h *HonorAggregate) GetHonorPoints() int { return h.honorPoints } // GetHonorLevel 获取荣誉等级 func (h *HonorAggregate) GetHonorLevel() int { return h.honorLevel } // AddReputation 增加声望 func (h *HonorAggregate) AddReputation(faction string, points int) { h.reputation[faction] += points h.updateVersion() } // GetReputation 获取声望 func (h *HonorAggregate) GetReputation(faction string) int { return h.reputation[faction] } // GetAllReputation 获取所有声望 func (h *HonorAggregate) GetAllReputation() map[string]int { return h.reputation } // UpdateStatistics 更新统计数据 func (h *HonorAggregate) UpdateStatistics(statType StatisticType, value int) { h.statistics.UpdateStatistic(statType, value) // 检查是否触发成就或称号解锁 h.checkUnlockConditions() h.updateVersion() } // GetStatistics 获取统计数据 func (h *HonorAggregate) GetStatistics() *PlayerStatistics { return h.statistics } // GetTitlesByCategory 根据分类获取称号 func (h *HonorAggregate) GetTitlesByCategory(category TitleCategory) []*Title { var titles []*Title for _, title := range h.titles { if title.GetCategory() == category { titles = append(titles, title) } } return titles } // GetAchievementsByCategory 根据分类获取成就 func (h *HonorAggregate) GetAchievementsByCategory(category AchievementCategory) []*Achievement { var achievements []*Achievement for _, achievement := range h.achievements { if achievement.GetCategory() == category { achievements = append(achievements, achievement) } } return achievements } // GetVersion 获取版本 func (h *HonorAggregate) GetVersion() int { return h.version } // GetUpdatedAt 获取更新时间 func (h *HonorAggregate) GetUpdatedAt() time.Time { return h.updatedAt } // 私有方法 // checkTitleUnlockConditions 检查称号解锁条件 func (h *HonorAggregate) checkTitleUnlockConditions(title *Title) bool { for _, condition := range title.GetUnlockConditions() { if !h.checkCondition(condition) { return false } } return true } // checkAchievementUnlockConditions 检查成就解锁条件 func (h *HonorAggregate) checkAchievementUnlockConditions(achievement *Achievement) bool { for _, condition := range achievement.GetUnlockConditions() { if !h.checkCondition(condition) { return false } } return true } // checkCondition 检查单个条件 func (h *HonorAggregate) checkCondition(condition *UnlockCondition) bool { switch condition.GetConditionType() { case ConditionTypeLevel: // 这里需要从外部获取玩家等级,暂时返回true return true case ConditionTypeStatistic: statValue := h.statistics.GetStatistic(condition.GetStatisticType()) return statValue >= condition.GetRequiredValue() case ConditionTypeReputation: repValue := h.GetReputation(condition.GetFaction()) return repValue >= condition.GetRequiredValue() case ConditionTypeAchievement: achievement := h.GetAchievement(condition.GetRequiredAchievement()) return achievement != nil && achievement.IsUnlocked() case ConditionTypeTitle: title := h.titles[condition.GetRequiredTitle()] return title != nil && title.IsUnlocked() default: return false } } // checkHonorLevelUp 检查荣誉等级提升 func (h *HonorAggregate) checkHonorLevelUp() { requiredPoints := h.getRequiredPointsForLevel(h.honorLevel + 1) if h.honorPoints >= requiredPoints { h.honorLevel++ // 可以在这里触发等级提升事件 } } // getRequiredPointsForLevel 获取等级所需点数 func (h *HonorAggregate) getRequiredPointsForLevel(level int) int { // 简单的等级计算公式 return level * level * 100 } // checkUnlockConditions 检查解锁条件 func (h *HonorAggregate) checkUnlockConditions() { // 检查所有未解锁的称号 for _, title := range h.titles { if !title.IsUnlocked() && h.checkTitleUnlockConditions(title) { title.Unlock() } } // 检查所有未解锁的成就 for _, achievement := range h.achievements { if !achievement.IsUnlocked() && h.checkAchievementUnlockConditions(achievement) { achievement.Unlock() h.AddHonorPoints(achievement.GetHonorReward()) } } } // updateVersion 更新版本 func (h *HonorAggregate) updateVersion() { h.version++ h.updatedAt = time.Now() } ================================================ FILE: internal/domain/player/honor/entity.go ================================================ package honor import ( "time" // "github.com/google/uuid" ) // Title 称号实体 type Title struct { id string name string description string category TitleCategory rarity TitleRarity unlockConditions []*UnlockCondition attributeBonus map[string]int isUnlocked bool isEquipped bool unlockedAt *time.Time equippedAt *time.Time createdAt time.Time } // NewTitle 创建新称号 func NewTitle(id, name, description string, category TitleCategory, rarity TitleRarity) *Title { return &Title{ id: id, name: name, description: description, category: category, rarity: rarity, unlockConditions: make([]*UnlockCondition, 0), attributeBonus: make(map[string]int), isUnlocked: false, isEquipped: false, createdAt: time.Now(), } } // GetID 获取称号ID func (t *Title) GetID() string { return t.id } // GetName 获取称号名称 func (t *Title) GetName() string { return t.name } // GetDescription 获取称号描述 func (t *Title) GetDescription() string { return t.description } // GetCategory 获取称号分类 func (t *Title) GetCategory() TitleCategory { return t.category } // GetRarity 获取称号稀有度 func (t *Title) GetRarity() TitleRarity { return t.rarity } // GetUnlockConditions 获取解锁条件 func (t *Title) GetUnlockConditions() []*UnlockCondition { return t.unlockConditions } // AddUnlockCondition 添加解锁条件 func (t *Title) AddUnlockCondition(condition *UnlockCondition) { t.unlockConditions = append(t.unlockConditions, condition) } // GetAttributeBonus 获取属性加成 func (t *Title) GetAttributeBonus() map[string]int { return t.attributeBonus } // SetAttributeBonus 设置属性加成 func (t *Title) SetAttributeBonus(attribute string, bonus int) { t.attributeBonus[attribute] = bonus } // IsUnlocked 是否已解锁 func (t *Title) IsUnlocked() bool { return t.isUnlocked } // IsEquipped 是否已装备 func (t *Title) IsEquipped() bool { return t.isEquipped } // Unlock 解锁称号 func (t *Title) Unlock() { if !t.isUnlocked { t.isUnlocked = true now := time.Now() t.unlockedAt = &now } } // Equip 装备称号 func (t *Title) Equip() { if t.isUnlocked && !t.isEquipped { t.isEquipped = true now := time.Now() t.equippedAt = &now } } // Unequip 卸下称号 func (t *Title) Unequip() { if t.isEquipped { t.isEquipped = false t.equippedAt = nil } } // GetUnlockedAt 获取解锁时间 func (t *Title) GetUnlockedAt() *time.Time { return t.unlockedAt } // GetEquippedAt 获取装备时间 func (t *Title) GetEquippedAt() *time.Time { return t.equippedAt } // Achievement 成就实体 type Achievement struct { id string name string description string category AchievementCategory type_ AchievementType unlockConditions []*UnlockCondition honorReward int itemRewards []string isUnlocked bool unlockedAt *time.Time createdAt time.Time } // NewAchievement 创建新成就 func NewAchievement(id, name, description string, category AchievementCategory, achievementType AchievementType) *Achievement { return &Achievement{ id: id, name: name, description: description, category: category, type_: achievementType, unlockConditions: make([]*UnlockCondition, 0), itemRewards: make([]string, 0), isUnlocked: false, createdAt: time.Now(), } } // GetID 获取成就ID func (a *Achievement) GetID() string { return a.id } // GetName 获取成就名称 func (a *Achievement) GetName() string { return a.name } // GetDescription 获取成就描述 func (a *Achievement) GetDescription() string { return a.description } // GetCategory 获取成就分类 func (a *Achievement) GetCategory() AchievementCategory { return a.category } // GetType 获取成就类型 func (a *Achievement) GetType() AchievementType { return a.type_ } // GetUnlockConditions 获取解锁条件 func (a *Achievement) GetUnlockConditions() []*UnlockCondition { return a.unlockConditions } // AddUnlockCondition 添加解锁条件 func (a *Achievement) AddUnlockCondition(condition *UnlockCondition) { a.unlockConditions = append(a.unlockConditions, condition) } // GetHonorReward 获取荣誉点数奖励 func (a *Achievement) GetHonorReward() int { return a.honorReward } // SetHonorReward 设置荣誉点数奖励 func (a *Achievement) SetHonorReward(reward int) { a.honorReward = reward } // GetItemRewards 获取物品奖励 func (a *Achievement) GetItemRewards() []string { return a.itemRewards } // AddItemReward 添加物品奖励 func (a *Achievement) AddItemReward(itemID string) { a.itemRewards = append(a.itemRewards, itemID) } // IsUnlocked 是否已解锁 func (a *Achievement) IsUnlocked() bool { return a.isUnlocked } // Unlock 解锁成就 func (a *Achievement) Unlock() { if !a.isUnlocked { a.isUnlocked = true now := time.Now() a.unlockedAt = &now } } // GetUnlockedAt 获取解锁时间 func (a *Achievement) GetUnlockedAt() *time.Time { return a.unlockedAt } // PlayerStatistics 玩家统计数据实体 type PlayerStatistics struct { statistics map[StatisticType]int updatedAt time.Time } // NewPlayerStatistics 创建新的玩家统计数据 func NewPlayerStatistics() *PlayerStatistics { return &PlayerStatistics{ statistics: make(map[StatisticType]int), updatedAt: time.Now(), } } // UpdateStatistic 更新统计数据 func (ps *PlayerStatistics) UpdateStatistic(statType StatisticType, value int) { ps.statistics[statType] = value ps.updatedAt = time.Now() } // IncrementStatistic 增加统计数据 func (ps *PlayerStatistics) IncrementStatistic(statType StatisticType, increment int) { ps.statistics[statType] += increment ps.updatedAt = time.Now() } // GetStatistic 获取统计数据 func (ps *PlayerStatistics) GetStatistic(statType StatisticType) int { return ps.statistics[statType] } // GetAllStatistics 获取所有统计数据 func (ps *PlayerStatistics) GetAllStatistics() map[StatisticType]int { return ps.statistics } // GetUpdatedAt 获取更新时间 func (ps *PlayerStatistics) GetUpdatedAt() time.Time { return ps.updatedAt } ================================================ FILE: internal/domain/player/honor/errors.go ================================================ package honor import ( "errors" "fmt" ) // 荣誉系统相关错误定义 // 称号相关错误 var ( // ErrInvalidTitle 无效的称号 ErrInvalidTitle = errors.New("invalid title") // ErrTitleNotFound 称号未找到 ErrTitleNotFound = errors.New("title not found") // ErrTitleAlreadyOwned 称号已拥有 ErrTitleAlreadyOwned = errors.New("title already owned") // ErrTitleAlreadyUnlocked 称号已解锁 ErrTitleAlreadyUnlocked = errors.New("title already unlocked") // ErrTitleNotUnlocked 称号未解锁 ErrTitleNotUnlocked = errors.New("title not unlocked") // ErrTitleConditionNotMet 称号解锁条件未满足 ErrTitleConditionNotMet = errors.New("title unlock condition not met") // ErrNoTitleEquipped 没有装备称号 ErrNoTitleEquipped = errors.New("no title equipped") // ErrTitleAlreadyEquipped 称号已装备 ErrTitleAlreadyEquipped = errors.New("title already equipped") // ErrCannotEquipTitle 无法装备称号 ErrCannotEquipTitle = errors.New("cannot equip title") // ErrTitleTemplateNotFound 称号模板未找到 ErrTitleTemplateNotFound = errors.New("title template not found") // ErrTitleTemplateAlreadyExists 称号模板已存在 ErrTitleTemplateAlreadyExists = errors.New("title template already exists") ) // 成就相关错误 var ( // ErrInvalidAchievement 无效的成就 ErrInvalidAchievement = errors.New("invalid achievement") // ErrAchievementNotFound 成就未找到 ErrAchievementNotFound = errors.New("achievement not found") // ErrAchievementAlreadyOwned 成就已拥有 ErrAchievementAlreadyOwned = errors.New("achievement already owned") // ErrAchievementAlreadyUnlocked 成就已解锁 ErrAchievementAlreadyUnlocked = errors.New("achievement already unlocked") // ErrAchievementConditionNotMet 成就解锁条件未满足 ErrAchievementConditionNotMet = errors.New("achievement unlock condition not met") // ErrAchievementTemplateNotFound 成就模板未找到 ErrAchievementTemplateNotFound = errors.New("achievement template not found") // ErrAchievementTemplateAlreadyExists 成就模板已存在 ErrAchievementTemplateAlreadyExists = errors.New("achievement template already exists") ) // 荣誉系统相关错误 var ( // ErrInvalidPlayerID 无效的玩家ID ErrInvalidPlayerID = errors.New("invalid player id") // ErrHonorNotFound 荣誉信息未找到 ErrHonorNotFound = errors.New("honor not found") // ErrHonorAlreadyExists 荣誉信息已存在 ErrHonorAlreadyExists = errors.New("honor already exists") // ErrInvalidHonorPoints 无效的荣誉点数 ErrInvalidHonorPoints = errors.New("invalid honor points") // ErrInvalidHonorLevel 无效的荣誉等级 ErrInvalidHonorLevel = errors.New("invalid honor level") // ErrHonorLevelNotReached 荣誉等级未达到 ErrHonorLevelNotReached = errors.New("honor level not reached") // ErrInsufficientHonorPoints 荣誉点数不足 ErrInsufficientHonorPoints = errors.New("insufficient honor points") ) // 声望相关错误 var ( // ErrInvalidFaction 无效的阵营 ErrInvalidFaction = errors.New("invalid faction") // ErrFactionNotFound 阵营未找到 ErrFactionNotFound = errors.New("faction not found") // ErrInvalidReputation 无效的声望值 ErrInvalidReputation = errors.New("invalid reputation") // ErrReputationTooLow 声望过低 ErrReputationTooLow = errors.New("reputation too low") // ErrReputationTooHigh 声望过高 ErrReputationTooHigh = errors.New("reputation too high") // ErrCannotChangeReputation 无法改变声望 ErrCannotChangeReputation = errors.New("cannot change reputation") ) // 统计数据相关错误 var ( // ErrInvalidStatisticType 无效的统计数据类型 ErrInvalidStatisticType = errors.New("invalid statistic type") // ErrInvalidStatisticValue 无效的统计数据值 ErrInvalidStatisticValue = errors.New("invalid statistic value") // ErrStatisticNotFound 统计数据未找到 ErrStatisticNotFound = errors.New("statistic not found") // ErrCannotUpdateStatistic 无法更新统计数据 ErrCannotUpdateStatistic = errors.New("cannot update statistic") ) // 解锁条件相关错误 var ( // ErrInvalidUnlockCondition 无效的解锁条件 ErrInvalidUnlockCondition = errors.New("invalid unlock condition") // ErrUnlockConditionNotMet 解锁条件未满足 ErrUnlockConditionNotMet = errors.New("unlock condition not met") // ErrInvalidConditionType 无效的条件类型 ErrInvalidConditionType = errors.New("invalid condition type") // ErrInvalidConditionValue 无效的条件值 ErrInvalidConditionValue = errors.New("invalid condition value") // ErrCircularDependency 循环依赖 ErrCircularDependency = errors.New("circular dependency in unlock conditions") ) // 数据持久化相关错误 var ( // ErrDatabaseConnection 数据库连接错误 ErrDatabaseConnection = errors.New("database connection error") // ErrDataNotFound 数据未找到 ErrDataNotFound = errors.New("data not found") // ErrDataCorrupted 数据损坏 ErrDataCorrupted = errors.New("data corrupted") // ErrSaveFailure 保存失败 ErrSaveFailure = errors.New("save failure") // ErrLoadFailure 加载失败 ErrLoadFailure = errors.New("load failure") // ErrDeleteFailure 删除失败 ErrDeleteFailure = errors.New("delete failure") // ErrVersionConflict 版本冲突 ErrVersionConflict = errors.New("version conflict") // ErrConcurrentModification 并发修改冲突 ErrConcurrentModification = errors.New("concurrent modification") ) // 业务逻辑相关错误 var ( // ErrOperationNotAllowed 操作不被允许 ErrOperationNotAllowed = errors.New("operation not allowed") // ErrInvalidOperation 无效操作 ErrInvalidOperation = errors.New("invalid operation") // ErrPermissionDenied 权限被拒绝 ErrPermissionDenied = errors.New("permission denied") // ErrResourceLocked 资源被锁定 ErrResourceLocked = errors.New("resource locked") // ErrRateLimitExceeded 速率限制超出 ErrRateLimitExceeded = errors.New("rate limit exceeded") // ErrMaintenanceMode 维护模式 ErrMaintenanceMode = errors.New("system in maintenance mode") ) // 配置相关错误 var ( // ErrInvalidConfiguration 无效配置 ErrInvalidConfiguration = errors.New("invalid configuration") // ErrConfigurationNotFound 配置未找到 ErrConfigurationNotFound = errors.New("configuration not found") // ErrConfigurationLoadFailure 配置加载失败 ErrConfigurationLoadFailure = errors.New("configuration load failure") ) // 事件相关错误 var ( // ErrInvalidEvent 无效事件 ErrInvalidEvent = errors.New("invalid event") // ErrEventPublishFailure 事件发布失败 ErrEventPublishFailure = errors.New("event publish failure") // ErrEventHandlerNotFound 事件处理器未找到 ErrEventHandlerNotFound = errors.New("event handler not found") // ErrEventProcessingFailure 事件处理失败 ErrEventProcessingFailure = errors.New("event processing failure") ) // HonorError 荣誉系统错误类型 type HonorError struct { Code string `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` Cause error `json:"-"` } // Error 实现error接口 func (e *HonorError) Error() string { if e.Details != "" { return fmt.Sprintf("[%s] %s: %s", e.Code, e.Message, e.Details) } return fmt.Sprintf("[%s] %s", e.Code, e.Message) } // Unwrap 返回原始错误 func (e *HonorError) Unwrap() error { return e.Cause } // NewHonorError 创建荣誉系统错误 func NewHonorError(code, message string) *HonorError { return &HonorError{ Code: code, Message: message, } } // NewHonorErrorWithDetails 创建带详情的荣誉系统错误 func NewHonorErrorWithDetails(code, message, details string) *HonorError { return &HonorError{ Code: code, Message: message, Details: details, } } // NewHonorErrorWithCause 创建带原因的荣誉系统错误 func NewHonorErrorWithCause(code, message string, cause error) *HonorError { return &HonorError{ Code: code, Message: message, Cause: cause, } } // 预定义的错误代码常量 const ( // 称号相关错误代码 ErrCodeTitleNotFound = "TITLE_NOT_FOUND" ErrCodeTitleAlreadyUnlocked = "TITLE_ALREADY_UNLOCKED" ErrCodeTitleConditionNotMet = "TITLE_CONDITION_NOT_MET" ErrCodeTitleCannotEquip = "TITLE_CANNOT_EQUIP" // 成就相关错误代码 ErrCodeAchievementNotFound = "ACHIEVEMENT_NOT_FOUND" ErrCodeAchievementAlreadyUnlocked = "ACHIEVEMENT_ALREADY_UNLOCKED" ErrCodeAchievementConditionNotMet = "ACHIEVEMENT_CONDITION_NOT_MET" // 荣誉系统错误代码 ErrCodeHonorNotFound = "HONOR_NOT_FOUND" ErrCodeInsufficientHonorPoints = "INSUFFICIENT_HONOR_POINTS" ErrCodeInvalidHonorLevel = "INVALID_HONOR_LEVEL" // 声望相关错误代码 ErrCodeFactionNotFound = "FACTION_NOT_FOUND" ErrCodeInvalidReputation = "INVALID_REPUTATION" ErrCodeReputationTooLow = "REPUTATION_TOO_LOW" // 数据相关错误代码 ErrCodeDataNotFound = "DATA_NOT_FOUND" ErrCodeDataCorrupted = "DATA_CORRUPTED" ErrCodeVersionConflict = "VERSION_CONFLICT" ErrCodeSaveFailure = "SAVE_FAILURE" // 业务逻辑错误代码 ErrCodeOperationNotAllowed = "OPERATION_NOT_ALLOWED" ErrCodePermissionDenied = "PERMISSION_DENIED" ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" ErrCodeMaintenanceMode = "MAINTENANCE_MODE" ) // IsHonorError 检查是否为荣誉系统错误 func IsHonorError(err error) bool { _, ok := err.(*HonorError) return ok } // GetHonorErrorCode 获取荣誉系统错误代码 func GetHonorErrorCode(err error) string { if honorErr, ok := err.(*HonorError); ok { return honorErr.Code } return "" } // WrapError 包装错误为荣誉系统错误 func WrapError(err error, code, message string) *HonorError { return &HonorError{ Code: code, Message: message, Cause: err, } } ================================================ FILE: internal/domain/player/honor/events.go ================================================ package honor import ( "fmt" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetEventData() interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` OccurredAt time.Time `json:"occurred_at"` EventData interface{} `json:"event_data"` } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetEventData 获取事件数据 func (e *BaseDomainEvent) GetEventData() interface{} { return e.EventData } // TitleUnlockedEvent 称号解锁事件 type TitleUnlockedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` TitleID string `json:"title_id"` TitleName string `json:"title_name"` TitleRarity TitleRarity `json:"title_rarity"` } // NewTitleUnlockedEvent 创建称号解锁事件 func NewTitleUnlockedEvent(playerID, titleID, titleName string, rarity TitleRarity) *TitleUnlockedEvent { return &TitleUnlockedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "TitleUnlocked", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, TitleID: titleID, TitleName: titleName, TitleRarity: rarity, } } // TitleEquippedEvent 称号装备事件 type TitleEquippedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` TitleID string `json:"title_id"` TitleName string `json:"title_name"` PreviousTitle string `json:"previous_title,omitempty"` } // NewTitleEquippedEvent 创建称号装备事件 func NewTitleEquippedEvent(playerID, titleID, titleName, previousTitle string) *TitleEquippedEvent { return &TitleEquippedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "TitleEquipped", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, TitleID: titleID, TitleName: titleName, PreviousTitle: previousTitle, } } // TitleUnequippedEvent 称号卸下事件 type TitleUnequippedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` TitleID string `json:"title_id"` TitleName string `json:"title_name"` } // NewTitleUnequippedEvent 创建称号卸下事件 func NewTitleUnequippedEvent(playerID, titleID, titleName string) *TitleUnequippedEvent { return &TitleUnequippedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "TitleUnequipped", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, TitleID: titleID, TitleName: titleName, } } // AchievementUnlockedEvent 成就解锁事件 type AchievementUnlockedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` AchievementID string `json:"achievement_id"` AchievementName string `json:"achievement_name"` Category AchievementCategory `json:"category"` HonorReward int `json:"honor_reward"` ItemRewards []string `json:"item_rewards"` } // NewAchievementUnlockedEvent 创建成就解锁事件 func NewAchievementUnlockedEvent(playerID, achievementID, achievementName string, category AchievementCategory, honorReward int, itemRewards []string) *AchievementUnlockedEvent { return &AchievementUnlockedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "AchievementUnlocked", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, AchievementID: achievementID, AchievementName: achievementName, Category: category, HonorReward: honorReward, ItemRewards: itemRewards, } } // HonorLevelUpEvent 荣誉等级提升事件 type HonorLevelUpEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PreviousLevel int `json:"previous_level"` NewLevel int `json:"new_level"` HonorPoints int `json:"honor_points"` LevelTitle string `json:"level_title"` } // NewHonorLevelUpEvent 创建荣誉等级提升事件 func NewHonorLevelUpEvent(playerID string, previousLevel, newLevel, honorPoints int, levelTitle string) *HonorLevelUpEvent { return &HonorLevelUpEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HonorLevelUp", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PreviousLevel: previousLevel, NewLevel: newLevel, HonorPoints: honorPoints, LevelTitle: levelTitle, } } // HonorPointsEarnedEvent 荣誉点数获得事件 type HonorPointsEarnedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PointsEarned int `json:"points_earned"` TotalPoints int `json:"total_points"` Source string `json:"source"` // 来源:achievement, quest, event等 SourceID string `json:"source_id,omitempty"` } // NewHonorPointsEarnedEvent 创建荣誉点数获得事件 func NewHonorPointsEarnedEvent(playerID string, pointsEarned, totalPoints int, source, sourceID string) *HonorPointsEarnedEvent { return &HonorPointsEarnedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HonorPointsEarned", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PointsEarned: pointsEarned, TotalPoints: totalPoints, Source: source, SourceID: sourceID, } } // ReputationChangedEvent 声望变化事件 type ReputationChangedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` Faction string `json:"faction"` PreviousReputation int `json:"previous_reputation"` NewReputation int `json:"new_reputation"` Change int `json:"change"` Reason string `json:"reason"` } // NewReputationChangedEvent 创建声望变化事件 func NewReputationChangedEvent(playerID, faction string, previousRep, newRep int, reason string) *ReputationChangedEvent { return &ReputationChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "ReputationChanged", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, Faction: faction, PreviousReputation: previousRep, NewReputation: newRep, Change: newRep - previousRep, Reason: reason, } } // StatisticUpdatedEvent 统计数据更新事件 type StatisticUpdatedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` StatisticType StatisticType `json:"statistic_type"` PreviousValue int `json:"previous_value"` NewValue int `json:"new_value"` Change int `json:"change"` } // NewStatisticUpdatedEvent 创建统计数据更新事件 func NewStatisticUpdatedEvent(playerID string, statType StatisticType, previousValue, newValue int) *StatisticUpdatedEvent { return &StatisticUpdatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "StatisticUpdated", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, StatisticType: statType, PreviousValue: previousValue, NewValue: newValue, Change: newValue - previousValue, } } // HonorSystemInitializedEvent 荣誉系统初始化事件 type HonorSystemInitializedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` InitialLevel int `json:"initial_level"` InitialPoints int `json:"initial_points"` TitlesCount int `json:"titles_count"` AchievementsCount int `json:"achievements_count"` } // NewHonorSystemInitializedEvent 创建荣誉系统初始化事件 func NewHonorSystemInitializedEvent(playerID string, initialLevel, initialPoints, titlesCount, achievementsCount int) *HonorSystemInitializedEvent { return &HonorSystemInitializedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HonorSystemInitialized", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, InitialLevel: initialLevel, InitialPoints: initialPoints, TitlesCount: titlesCount, AchievementsCount: achievementsCount, } } // RareAchievementUnlockedEvent 稀有成就解锁事件(特殊事件) type RareAchievementUnlockedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` AchievementID string `json:"achievement_id"` AchievementName string `json:"achievement_name"` Category AchievementCategory `json:"category"` Rarity string `json:"rarity"` UnlockRate float64 `json:"unlock_rate"` // 解锁率,用于判断稀有度 } // NewRareAchievementUnlockedEvent 创建稀有成就解锁事件 func NewRareAchievementUnlockedEvent(playerID, playerName, achievementID, achievementName string, category AchievementCategory, rarity string, unlockRate float64) *RareAchievementUnlockedEvent { return &RareAchievementUnlockedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "RareAchievementUnlocked", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PlayerName: playerName, AchievementID: achievementID, AchievementName: achievementName, Category: category, Rarity: rarity, UnlockRate: unlockRate, } } // HonorRankingChangedEvent 荣誉排名变化事件 type HonorRankingChangedEvent struct { *BaseDomainEvent PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` PreviousRank int `json:"previous_rank"` NewRank int `json:"new_rank"` HonorPoints int `json:"honor_points"` HonorLevel int `json:"honor_level"` } // NewHonorRankingChangedEvent 创建荣誉排名变化事件 func NewHonorRankingChangedEvent(playerID, playerName string, previousRank, newRank, honorPoints, honorLevel int) *HonorRankingChangedEvent { return &HonorRankingChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: generateEventID(), EventType: "HonorRankingChanged", AggregateID: playerID, OccurredAt: time.Now(), }, PlayerID: playerID, PlayerName: playerName, PreviousRank: previousRank, NewRank: newRank, HonorPoints: honorPoints, HonorLevel: honorLevel, } } // generateEventID 生成事件ID func generateEventID() string { // 这里可以使用UUID或其他唯一ID生成方式 // 为了简化,使用时间戳 return fmt.Sprintf("honor_%d", time.Now().UnixNano()) } ================================================ FILE: internal/domain/player/honor/repository.go ================================================ package honor import "context" // HonorRepository 荣誉仓储接口 type HonorRepository interface { // Save 保存荣誉聚合根 Save(ctx context.Context, honor *HonorAggregate) error // FindByPlayerID 根据玩家ID查找荣誉信息 FindByPlayerID(ctx context.Context, playerID string) (*HonorAggregate, error) // Delete 删除荣誉信息 Delete(ctx context.Context, playerID string) error // FindByHonorLevel 根据荣誉等级查找玩家 FindByHonorLevel(ctx context.Context, level int, limit, offset int) ([]*HonorAggregate, error) // FindTopHonorPlayers 查找荣誉排行榜 FindTopHonorPlayers(ctx context.Context, limit int) ([]*HonorAggregate, error) // CountByHonorLevel 统计指定荣誉等级的玩家数量 CountByHonorLevel(ctx context.Context, level int) (int64, error) // FindByTitleEquipped 查找装备指定称号的玩家 FindByTitleEquipped(ctx context.Context, titleID string, limit, offset int) ([]*HonorAggregate, error) // FindByAchievementUnlocked 查找解锁指定成就的玩家 FindByAchievementUnlocked(ctx context.Context, achievementID string, limit, offset int) ([]*HonorAggregate, error) // UpdateStatistics 批量更新玩家统计数据 UpdateStatistics(ctx context.Context, playerID string, statistics map[StatisticType]int) error // FindByReputation 根据声望查找玩家 FindByReputation(ctx context.Context, faction string, minReputation int, limit, offset int) ([]*HonorAggregate, error) // GetPlayerRank 获取玩家荣誉排名 GetPlayerRank(ctx context.Context, playerID string) (int, error) // FindRecentlyUnlockedTitles 查找最近解锁的称号 FindRecentlyUnlockedTitles(ctx context.Context, playerID string, limit int) ([]*Title, error) // FindRecentlyUnlockedAchievements 查找最近解锁的成就 FindRecentlyUnlockedAchievements(ctx context.Context, playerID string, limit int) ([]*Achievement, error) // GetHonorStatistics 获取荣誉系统统计信息 GetHonorStatistics(ctx context.Context) (*HonorStatistics, error) } // TitleRepository 称号仓储接口 type TitleRepository interface { // SaveTemplate 保存称号模板 SaveTemplate(ctx context.Context, template *TitleTemplate) error // FindTemplateByID 根据ID查找称号模板 FindTemplateByID(ctx context.Context, id string) (*TitleTemplate, error) // FindAllTemplates 查找所有称号模板 FindAllTemplates(ctx context.Context) ([]*TitleTemplate, error) // FindTemplatesByCategory 根据分类查找称号模板 FindTemplatesByCategory(ctx context.Context, category TitleCategory) ([]*TitleTemplate, error) // FindTemplatesByRarity 根据稀有度查找称号模板 FindTemplatesByRarity(ctx context.Context, rarity TitleRarity) ([]*TitleTemplate, error) // DeleteTemplate 删除称号模板 DeleteTemplate(ctx context.Context, id string) error // GetTitleStatistics 获取称号统计信息 GetTitleStatistics(ctx context.Context, titleID string) (*TitleStatistics, error) } // AchievementRepository 成就仓储接口 type AchievementRepository interface { // SaveTemplate 保存成就模板 SaveTemplate(ctx context.Context, template *AchievementTemplate) error // FindTemplateByID 根据ID查找成就模板 FindTemplateByID(ctx context.Context, id string) (*AchievementTemplate, error) // FindAllTemplates 查找所有成就模板 FindAllTemplates(ctx context.Context) ([]*AchievementTemplate, error) // FindTemplatesByCategory 根据分类查找成就模板 FindTemplatesByCategory(ctx context.Context, category AchievementCategory) ([]*AchievementTemplate, error) // FindTemplatesByType 根据类型查找成就模板 FindTemplatesByType(ctx context.Context, achievementType AchievementType) ([]*AchievementTemplate, error) // DeleteTemplate 删除成就模板 DeleteTemplate(ctx context.Context, id string) error // GetAchievementStatistics 获取成就统计信息 GetAchievementStatistics(ctx context.Context, achievementID string) (*AchievementStatistics, error) } // HonorStatistics 荣誉系统统计信息 type HonorStatistics struct { TotalPlayers int64 `json:"total_players"` AverageHonorPoints float64 `json:"average_honor_points"` HonorLevelDistribution map[int]int64 `json:"honor_level_distribution"` MostPopularTitles []TitleStats `json:"most_popular_titles"` MostUnlockedAchievements []AchievementStats `json:"most_unlocked_achievements"` TopReputationFactions []ReputationStats `json:"top_reputation_factions"` } // TitleStatistics 称号统计信息 type TitleStatistics struct { TitleID string `json:"title_id"` TitleName string `json:"title_name"` UnlockCount int64 `json:"unlock_count"` EquipCount int64 `json:"equip_count"` UnlockRate float64 `json:"unlock_rate"` PopularityRank int `json:"popularity_rank"` } // AchievementStatistics 成就统计信息 type AchievementStatistics struct { AchievementID string `json:"achievement_id"` AchievementName string `json:"achievement_name"` UnlockCount int64 `json:"unlock_count"` UnlockRate float64 `json:"unlock_rate"` AverageUnlockTime float64 `json:"average_unlock_time"` // 平均解锁时间(小时) DifficultyRank int `json:"difficulty_rank"` } // TitleStats 称号统计 type TitleStats struct { TitleID string `json:"title_id"` TitleName string `json:"title_name"` UnlockCount int64 `json:"unlock_count"` EquipCount int64 `json:"equip_count"` } // AchievementStats 成就统计 type AchievementStats struct { AchievementID string `json:"achievement_id"` AchievementName string `json:"achievement_name"` UnlockCount int64 `json:"unlock_count"` } // ReputationStats 声望统计 type ReputationStats struct { Faction string `json:"faction"` AverageReputation float64 `json:"average_reputation"` MaxReputation int `json:"max_reputation"` PlayerCount int64 `json:"player_count"` } // HonorQuery 荣誉查询条件 type HonorQuery struct { PlayerIDs []string `json:"player_ids,omitempty"` MinHonorLevel int `json:"min_honor_level,omitempty"` MaxHonorLevel int `json:"max_honor_level,omitempty"` MinHonorPoints int `json:"min_honor_points,omitempty"` MaxHonorPoints int `json:"max_honor_points,omitempty"` TitleEquipped string `json:"title_equipped,omitempty"` Achievements []string `json:"achievements,omitempty"` Reputation map[string]int `json:"reputation,omitempty"` Statistics map[StatisticType]int `json:"statistics,omitempty"` Limit int `json:"limit,omitempty"` Offset int `json:"offset,omitempty"` SortBy string `json:"sort_by,omitempty"` SortOrder string `json:"sort_order,omitempty"` } // HonorQueryRepository 荣誉查询仓储接口 type HonorQueryRepository interface { // FindByQuery 根据查询条件查找荣誉信息 FindByQuery(ctx context.Context, query *HonorQuery) ([]*HonorAggregate, error) // CountByQuery 根据查询条件统计数量 CountByQuery(ctx context.Context, query *HonorQuery) (int64, error) // FindPlayersWithTitle 查找拥有指定称号的玩家 FindPlayersWithTitle(ctx context.Context, titleID string, unlocked bool, equipped bool) ([]*HonorAggregate, error) // FindPlayersWithAchievement 查找拥有指定成就的玩家 FindPlayersWithAchievement(ctx context.Context, achievementID string, unlocked bool) ([]*HonorAggregate, error) // FindTopPlayersByStatistic 根据统计数据查找排行榜 FindTopPlayersByStatistic(ctx context.Context, statType StatisticType, limit int) ([]*HonorAggregate, error) // FindPlayersByReputationRange 根据声望范围查找玩家 FindPlayersByReputationRange(ctx context.Context, faction string, minRep, maxRep int) ([]*HonorAggregate, error) } ================================================ FILE: internal/domain/player/honor/service.go ================================================ package honor import ( "fmt" // "time" // 未使用 ) // HonorService 荣誉领域服务 type HonorService struct { titleTemplates map[string]*TitleTemplate achievementTemplates map[string]*AchievementTemplate honorLevels map[int]*HonorLevel } // NewHonorService 创建荣誉服务 func NewHonorService() *HonorService { return &HonorService{ titleTemplates: make(map[string]*TitleTemplate), achievementTemplates: make(map[string]*AchievementTemplate), honorLevels: make(map[int]*HonorLevel), } } // TitleTemplate 称号模板 type TitleTemplate struct { id string name string description string category TitleCategory rarity TitleRarity unlockConditions []*UnlockCondition attributeBonus map[string]int } // NewTitleTemplate 创建称号模板 func NewTitleTemplate(id, name, description string, category TitleCategory, rarity TitleRarity) *TitleTemplate { return &TitleTemplate{ id: id, name: name, description: description, category: category, rarity: rarity, unlockConditions: make([]*UnlockCondition, 0), attributeBonus: make(map[string]int), } } // CreateTitle 根据模板创建称号 func (tt *TitleTemplate) CreateTitle() *Title { title := NewTitle(tt.id, tt.name, tt.description, tt.category, tt.rarity) // 复制解锁条件 for _, condition := range tt.unlockConditions { title.AddUnlockCondition(condition) } // 复制属性加成 for attr, bonus := range tt.attributeBonus { title.SetAttributeBonus(attr, bonus) } return title } // AchievementTemplate 成就模板 type AchievementTemplate struct { id string name string description string category AchievementCategory type_ AchievementType unlockConditions []*UnlockCondition honorReward int itemRewards []string } // NewAchievementTemplate 创建成就模板 func NewAchievementTemplate(id, name, description string, category AchievementCategory, achievementType AchievementType) *AchievementTemplate { return &AchievementTemplate{ id: id, name: name, description: description, category: category, type_: achievementType, unlockConditions: make([]*UnlockCondition, 0), itemRewards: make([]string, 0), } } // CreateAchievement 根据模板创建成就 func (at *AchievementTemplate) CreateAchievement() *Achievement { achievement := NewAchievement(at.id, at.name, at.description, at.category, at.type_) // 复制解锁条件 for _, condition := range at.unlockConditions { achievement.AddUnlockCondition(condition) } // 设置奖励 achievement.SetHonorReward(at.honorReward) for _, itemID := range at.itemRewards { achievement.AddItemReward(itemID) } return achievement } // RegisterTitleTemplate 注册称号模板 func (hs *HonorService) RegisterTitleTemplate(template *TitleTemplate) { hs.titleTemplates[template.id] = template } // RegisterAchievementTemplate 注册成就模板 func (hs *HonorService) RegisterAchievementTemplate(template *AchievementTemplate) { hs.achievementTemplates[template.id] = template } // RegisterHonorLevel 注册荣誉等级 func (hs *HonorService) RegisterHonorLevel(level *HonorLevel) { hs.honorLevels[level.GetLevel()] = level } // CreatePlayerHonor 为玩家创建荣誉系统 func (hs *HonorService) CreatePlayerHonor(playerID string) *HonorAggregate { honor := NewHonorAggregate(playerID) // 添加所有称号模板 for _, template := range hs.titleTemplates { title := template.CreateTitle() honor.AddTitle(title) } // 添加所有成就模板 for _, template := range hs.achievementTemplates { achievement := template.CreateAchievement() honor.AddAchievement(achievement) } return honor } // CalculateHonorLevel 计算荣誉等级 func (hs *HonorService) CalculateHonorLevel(honorPoints int) int { level := 1 for lvl, honorLevel := range hs.honorLevels { if honorPoints >= honorLevel.GetRequiredXP() && lvl > level { level = lvl } } return level } // GetHonorLevel 获取荣誉等级信息 func (hs *HonorService) GetHonorLevel(level int) *HonorLevel { return hs.honorLevels[level] } // GetNextHonorLevel 获取下一个荣誉等级 func (hs *HonorService) GetNextHonorLevel(currentLevel int) *HonorLevel { return hs.honorLevels[currentLevel+1] } // ValidateTitleUnlock 验证称号解锁 func (hs *HonorService) ValidateTitleUnlock(honor *HonorAggregate, titleID string) error { title := honor.titles[titleID] if title == nil { return ErrTitleNotFound } if title.IsUnlocked() { return ErrTitleAlreadyUnlocked } // 检查所有解锁条件 for _, condition := range title.GetUnlockConditions() { if !hs.checkUnlockCondition(honor, condition) { return fmt.Errorf("条件未满足: %s", condition.GetDescription()) } } return nil } // ValidateAchievementUnlock 验证成就解锁 func (hs *HonorService) ValidateAchievementUnlock(honor *HonorAggregate, achievementID string) error { achievement := honor.achievements[achievementID] if achievement == nil { return ErrAchievementNotFound } if achievement.IsUnlocked() { return ErrAchievementAlreadyUnlocked } // 检查所有解锁条件 for _, condition := range achievement.GetUnlockConditions() { if !hs.checkUnlockCondition(honor, condition) { return fmt.Errorf("条件未满足: %s", condition.GetDescription()) } } return nil } // checkUnlockCondition 检查解锁条件 func (hs *HonorService) checkUnlockCondition(honor *HonorAggregate, condition *UnlockCondition) bool { switch condition.GetConditionType() { case ConditionTypeLevel: // 这里需要从外部获取玩家等级,暂时返回true return true case ConditionTypeStatistic: statValue := honor.statistics.GetStatistic(condition.GetStatisticType()) return statValue >= condition.GetRequiredValue() case ConditionTypeReputation: repValue := honor.GetReputation(condition.GetFaction()) return repValue >= condition.GetRequiredValue() case ConditionTypeAchievement: achievement := honor.GetAchievement(condition.GetRequiredAchievement()) return achievement != nil && achievement.IsUnlocked() case ConditionTypeTitle: title := honor.titles[condition.GetRequiredTitle()] return title != nil && title.IsUnlocked() default: return false } } // CalculateTitleAttributeBonus 计算称号属性加成 func (hs *HonorService) CalculateTitleAttributeBonus(honor *HonorAggregate) map[string]int { bonuses := make(map[string]int) // 只计算当前装备的称号 currentTitle := honor.GetCurrentTitle() if currentTitle != nil { for attr, bonus := range currentTitle.GetAttributeBonus() { bonuses[attr] += bonus } } return bonuses } // GetTitlesByRarity 根据稀有度获取称号 func (hs *HonorService) GetTitlesByRarity(honor *HonorAggregate, rarity TitleRarity) []*Title { var titles []*Title for _, title := range honor.GetAllTitles() { if title.GetRarity() == rarity { titles = append(titles, title) } } return titles } // GetAchievementsByType 根据类型获取成就 func (hs *HonorService) GetAchievementsByType(honor *HonorAggregate, achievementType AchievementType) []*Achievement { var achievements []*Achievement for _, achievement := range honor.GetAllAchievements() { if achievement.GetType() == achievementType { achievements = append(achievements, achievement) } } return achievements } // CalculateHonorRank 计算荣誉排名(需要外部数据支持) func (hs *HonorService) CalculateHonorRank(honor *HonorAggregate, allPlayers []*HonorAggregate) int { rank := 1 currentPoints := honor.GetHonorPoints() for _, otherHonor := range allPlayers { if otherHonor.GetPlayerID() != honor.GetPlayerID() && otherHonor.GetHonorPoints() > currentPoints { rank++ } } return rank } // GetRecommendedTitles 获取推荐称号(接近解锁的称号) func (hs *HonorService) GetRecommendedTitles(honor *HonorAggregate) []*Title { var recommended []*Title for _, title := range honor.GetAllTitles() { if !title.IsUnlocked() { // 检查是否接近解锁 meetsConditions := 0 totalConditions := len(title.GetUnlockConditions()) for _, condition := range title.GetUnlockConditions() { if hs.checkUnlockCondition(honor, condition) { meetsConditions++ } } // 如果满足80%以上的条件,则推荐 if totalConditions > 0 && float64(meetsConditions)/float64(totalConditions) >= 0.8 { recommended = append(recommended, title) } } } return recommended } // InitializeDefaultTemplates 初始化默认模板 func (hs *HonorService) InitializeDefaultTemplates() { // 初始化默认称号模板 hs.initializeDefaultTitles() // 初始化默认成就模板 hs.initializeDefaultAchievements() // 初始化荣誉等级 hs.initializeHonorLevels() } // initializeDefaultTitles 初始化默认称号 func (hs *HonorService) initializeDefaultTitles() { // 新手称号 newbieTitle := NewTitleTemplate("newbie", "新手冒险者", "刚踏上冒险之路的勇士", TitleCategorySpecial, TitleRarityCommon) newbieTitle.unlockConditions = append(newbieTitle.unlockConditions, NewLevelCondition(1)) hs.RegisterTitleTemplate(newbieTitle) // 战斗称号 warriorTitle := NewTitleTemplate("warrior", "勇敢战士", "在战斗中展现勇气的战士", TitleCategoryCombat, TitleRarityUncommon) warriorTitle.unlockConditions = append(warriorTitle.unlockConditions, NewStatisticCondition(StatisticTypeKillCount, 100)) warriorTitle.attributeBonus["attack"] = 10 hs.RegisterTitleTemplate(warriorTitle) // 探索称号 explorerTitle := NewTitleTemplate("explorer", "大陆探索者", "足迹遍布大陆的探索者", TitleCategoryExploration, TitleRarityRare) explorerTitle.unlockConditions = append(explorerTitle.unlockConditions, NewStatisticCondition(StatisticTypeDistanceTraveled, 10000)) explorerTitle.attributeBonus["speed"] = 15 hs.RegisterTitleTemplate(explorerTitle) } // initializeDefaultAchievements 初始化默认成就 func (hs *HonorService) initializeDefaultAchievements() { // 首次击杀成就 firstKill := NewAchievementTemplate("first_kill", "初次胜利", "获得第一次击杀", AchievementCategoryCombat, AchievementTypeNormal) firstKill.unlockConditions = append(firstKill.unlockConditions, NewStatisticCondition(StatisticTypeKillCount, 1)) firstKill.honorReward = 10 hs.RegisterAchievementTemplate(firstKill) // 连续登录成就 loginStreak := NewAchievementTemplate("login_streak_7", "坚持不懈", "连续登录7天", AchievementCategoryProgression, AchievementTypeNormal) loginStreak.unlockConditions = append(loginStreak.unlockConditions, NewStatisticCondition(StatisticTypeLoginDays, 7)) loginStreak.honorReward = 50 hs.RegisterAchievementTemplate(loginStreak) // 收集成就 collector := NewAchievementTemplate("collector", "收集家", "收集100个不同的物品", AchievementCategoryCollection, AchievementTypeNormal) collector.unlockConditions = append(collector.unlockConditions, NewStatisticCondition(StatisticTypeItemsCollected, 100)) collector.honorReward = 100 hs.RegisterAchievementTemplate(collector) } // initializeHonorLevels 初始化荣誉等级 func (hs *HonorService) initializeHonorLevels() { // 创建荣誉等级 levels := []struct { level int requiredXP int title string description string }{ {1, 0, "平民", "普通的冒险者"}, {2, 100, "见习者", "初入江湖的新人"}, {3, 300, "冒险者", "有一定经验的冒险者"}, {4, 600, "勇士", "勇敢的战士"}, {5, 1000, "英雄", "受人尊敬的英雄"}, {6, 1500, "传奇", "传说中的人物"}, {7, 2100, "神话", "神话般的存在"}, {8, 2800, "不朽", "不朽的传说"}, {9, 3600, "至尊", "至高无上的存在"}, {10, 4500, "神明", "如神明般的力量"}, } for _, lvl := range levels { honorLevel := NewHonorLevel(lvl.level, lvl.requiredXP, lvl.title, lvl.description) hs.RegisterHonorLevel(honorLevel) } } ================================================ FILE: internal/domain/player/honor/value_object.go ================================================ package honor import ( "fmt" "time" ) // TitleCategory 称号分类 type TitleCategory int const ( TitleCategoryUnknown TitleCategory = iota TitleCategoryCombat // 战斗类 TitleCategoryExploration // 探索类 TitleCategorySocial // 社交类 TitleCategoryLifestyle // 生活类 TitleCategorySpecial // 特殊类 TitleCategoryEvent // 活动类 ) // String 返回称号分类的字符串表示 func (tc TitleCategory) String() string { switch tc { case TitleCategoryCombat: return "combat" case TitleCategoryExploration: return "exploration" case TitleCategorySocial: return "social" case TitleCategoryLifestyle: return "lifestyle" case TitleCategorySpecial: return "special" case TitleCategoryEvent: return "event" default: return "unknown" } } // TitleRarity 称号稀有度 type TitleRarity int const ( TitleRarityCommon TitleRarity = iota TitleRarityUncommon TitleRarityRare TitleRarityEpic TitleRarityLegendary TitleRarityMythic ) // String 返回称号稀有度的字符串表示 func (tr TitleRarity) String() string { switch tr { case TitleRarityCommon: return "common" case TitleRarityUncommon: return "uncommon" case TitleRarityRare: return "rare" case TitleRarityEpic: return "epic" case TitleRarityLegendary: return "legendary" case TitleRarityMythic: return "mythic" default: return "common" } } // AchievementCategory 成就分类 type AchievementCategory int const ( AchievementCategoryUnknown AchievementCategory = iota AchievementCategoryCombat // 战斗成就 AchievementCategoryExploration // 探索成就 AchievementCategorySocial // 社交成就 AchievementCategoryCollection // 收集成就 AchievementCategoryProgression // 进度成就 AchievementCategorySpecial // 特殊成就 ) // String 返回成就分类的字符串表示 func (ac AchievementCategory) String() string { switch ac { case AchievementCategoryCombat: return "combat" case AchievementCategoryExploration: return "exploration" case AchievementCategorySocial: return "social" case AchievementCategoryCollection: return "collection" case AchievementCategoryProgression: return "progression" case AchievementCategorySpecial: return "special" default: return "unknown" } } // AchievementType 成就类型 type AchievementType int const ( AchievementTypeNormal AchievementType = iota AchievementTypeHidden // 隐藏成就 AchievementTypeDaily // 日常成就 AchievementTypeWeekly // 周常成就 AchievementTypeMonthly // 月常成就 AchievementTypeEvent // 活动成就 ) // String 返回成就类型的字符串表示 func (at AchievementType) String() string { switch at { case AchievementTypeNormal: return "normal" case AchievementTypeHidden: return "hidden" case AchievementTypeDaily: return "daily" case AchievementTypeWeekly: return "weekly" case AchievementTypeMonthly: return "monthly" case AchievementTypeEvent: return "event" default: return "normal" } } // StatisticType 统计数据类型 type StatisticType int const ( StatisticTypeUnknown StatisticType = iota StatisticTypeKillCount // 击杀数量 StatisticTypeDeathCount // 死亡数量 StatisticTypeDamageDealt // 造成伤害 StatisticTypeDamageTaken // 承受伤害 StatisticTypeHealingDone // 治疗量 StatisticTypeDistanceTraveled // 旅行距离 StatisticTypeQuestsCompleted // 完成任务数 StatisticTypeItemsCrafted // 制作物品数 StatisticTypeItemsCollected // 收集物品数 StatisticTypeGoldEarned // 获得金币 StatisticTypeGoldSpent // 花费金币 StatisticTypePlayTime // 游戏时间 StatisticTypeLoginDays // 登录天数 StatisticTypeFriendsCount // 好友数量 StatisticTypeGuildContribution // 公会贡献 ) // String 返回统计数据类型的字符串表示 func (st StatisticType) String() string { switch st { case StatisticTypeKillCount: return "kill_count" case StatisticTypeDeathCount: return "death_count" case StatisticTypeDamageDealt: return "damage_dealt" case StatisticTypeDamageTaken: return "damage_taken" case StatisticTypeHealingDone: return "healing_done" case StatisticTypeDistanceTraveled: return "distance_traveled" case StatisticTypeQuestsCompleted: return "quests_completed" case StatisticTypeItemsCrafted: return "items_crafted" case StatisticTypeItemsCollected: return "items_collected" case StatisticTypeGoldEarned: return "gold_earned" case StatisticTypeGoldSpent: return "gold_spent" case StatisticTypePlayTime: return "play_time" case StatisticTypeLoginDays: return "login_days" case StatisticTypeFriendsCount: return "friends_count" case StatisticTypeGuildContribution: return "guild_contribution" default: return "unknown" } } // ConditionType 条件类型 type ConditionType int const ( ConditionTypeUnknown ConditionType = iota ConditionTypeLevel // 等级条件 ConditionTypeStatistic // 统计数据条件 ConditionTypeReputation // 声望条件 ConditionTypeAchievement // 成就条件 ConditionTypeTitle // 称号条件 ConditionTypeItem // 物品条件 ConditionTypeQuest // 任务条件 ConditionTypeTime // 时间条件 ) // String 返回条件类型的字符串表示 func (ct ConditionType) String() string { switch ct { case ConditionTypeLevel: return "level" case ConditionTypeStatistic: return "statistic" case ConditionTypeReputation: return "reputation" case ConditionTypeAchievement: return "achievement" case ConditionTypeTitle: return "title" case ConditionTypeItem: return "item" case ConditionTypeQuest: return "quest" case ConditionTypeTime: return "time" default: return "unknown" } } // UnlockCondition 解锁条件值对象 type UnlockCondition struct { conditionType ConditionType requiredValue int statisticType StatisticType faction string requiredAchievement string requiredTitle string requiredItem string requiredQuest string timeRequirement time.Duration description string } // NewUnlockCondition 创建解锁条件 func NewUnlockCondition(conditionType ConditionType, description string) *UnlockCondition { return &UnlockCondition{ conditionType: conditionType, description: description, } } // NewLevelCondition 创建等级条件 func NewLevelCondition(requiredLevel int) *UnlockCondition { return &UnlockCondition{ conditionType: ConditionTypeLevel, requiredValue: requiredLevel, description: fmt.Sprintf("达到等级 %d", requiredLevel), } } // NewStatisticCondition 创建统计数据条件 func NewStatisticCondition(statType StatisticType, requiredValue int) *UnlockCondition { return &UnlockCondition{ conditionType: ConditionTypeStatistic, statisticType: statType, requiredValue: requiredValue, description: fmt.Sprintf("%s 达到 %d", statType.String(), requiredValue), } } // NewReputationCondition 创建声望条件 func NewReputationCondition(faction string, requiredValue int) *UnlockCondition { return &UnlockCondition{ conditionType: ConditionTypeReputation, faction: faction, requiredValue: requiredValue, description: fmt.Sprintf("%s 声望达到 %d", faction, requiredValue), } } // NewAchievementCondition 创建成就条件 func NewAchievementCondition(achievementID string) *UnlockCondition { return &UnlockCondition{ conditionType: ConditionTypeAchievement, requiredAchievement: achievementID, description: fmt.Sprintf("完成成就: %s", achievementID), } } // NewTitleCondition 创建称号条件 func NewTitleCondition(titleID string) *UnlockCondition { return &UnlockCondition{ conditionType: ConditionTypeTitle, requiredTitle: titleID, description: fmt.Sprintf("获得称号: %s", titleID), } } // GetConditionType 获取条件类型 func (uc *UnlockCondition) GetConditionType() ConditionType { return uc.conditionType } // GetRequiredValue 获取所需值 func (uc *UnlockCondition) GetRequiredValue() int { return uc.requiredValue } // GetStatisticType 获取统计数据类型 func (uc *UnlockCondition) GetStatisticType() StatisticType { return uc.statisticType } // GetFaction 获取阵营 func (uc *UnlockCondition) GetFaction() string { return uc.faction } // GetRequiredAchievement 获取所需成就 func (uc *UnlockCondition) GetRequiredAchievement() string { return uc.requiredAchievement } // GetRequiredTitle 获取所需称号 func (uc *UnlockCondition) GetRequiredTitle() string { return uc.requiredTitle } // GetRequiredItem 获取所需物品 func (uc *UnlockCondition) GetRequiredItem() string { return uc.requiredItem } // GetRequiredQuest 获取所需任务 func (uc *UnlockCondition) GetRequiredQuest() string { return uc.requiredQuest } // GetTimeRequirement 获取时间要求 func (uc *UnlockCondition) GetTimeRequirement() time.Duration { return uc.timeRequirement } // GetDescription 获取描述 func (uc *UnlockCondition) GetDescription() string { return uc.description } // HonorLevel 荣誉等级值对象 type HonorLevel struct { level int requiredXP int title string description string rewards []string } // NewHonorLevel 创建荣誉等级 func NewHonorLevel(level, requiredXP int, title, description string) *HonorLevel { return &HonorLevel{ level: level, requiredXP: requiredXP, title: title, description: description, rewards: make([]string, 0), } } // GetLevel 获取等级 func (hl *HonorLevel) GetLevel() int { return hl.level } // GetRequiredXP 获取所需经验 func (hl *HonorLevel) GetRequiredXP() int { return hl.requiredXP } // GetTitle 获取等级称号 func (hl *HonorLevel) GetTitle() string { return hl.title } // GetDescription 获取描述 func (hl *HonorLevel) GetDescription() string { return hl.description } // GetRewards 获取奖励 func (hl *HonorLevel) GetRewards() []string { return hl.rewards } // AddReward 添加奖励 func (hl *HonorLevel) AddReward(reward string) { hl.rewards = append(hl.rewards, reward) } ================================================ FILE: internal/domain/player/player.go ================================================ // Package player 玩家领域 package player import ( "time" "github.com/google/uuid" ) // PlayerID 玩家ID值对象 type PlayerID struct { value string } // NewPlayerID 创建新的玩家ID func NewPlayerID() PlayerID { return PlayerID{value: uuid.New().String()} } // String 返回字符串表示 func (id PlayerID) String() string { return id.value } // PlayerIDFromString 从字符串创建PlayerID func PlayerIDFromString(value string) PlayerID { return PlayerID{value: value} } // PlayerStatus 玩家状态枚举 type PlayerStatus int const ( PlayerStatusOffline PlayerStatus = iota PlayerStatusOnline PlayerStatusInBattle PlayerStatusInScene ) // Player 玩家聚合根 type Player struct { id PlayerID name string level int exp int64 status PlayerStatus position Position lastMapID int32 // 上次所在地图ID stats PlayerStats createdAt time.Time updatedAt time.Time version int64 // 乐观锁版本号 } // Position 位置值对象 type Position struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // PlayerStats 玩家属性值对象 type PlayerStats struct { HP int `json:"hp"` MaxHP int `json:"max_hp"` MP int `json:"mp"` MaxMP int `json:"max_mp"` Attack int `json:"attack"` Defense int `json:"defense"` Speed int `json:"speed"` } // NewPlayer 创建新玩家 func NewPlayer(name string) *Player { now := time.Now() return &Player{ id: NewPlayerID(), name: name, level: 1, exp: 0, status: PlayerStatusOffline, position: Position{X: 0, Y: 0, Z: 0}, lastMapID: 1001, // 默认新手地图 stats: PlayerStats{HP: 100, MaxHP: 100, MP: 50, MaxMP: 50, Attack: 10, Defense: 5, Speed: 10}, createdAt: now, updatedAt: now, version: 1, } } // ID 获取玩家ID func (p *Player) ID() PlayerID { return p.id } // Name 获取玩家名称 func (p *Player) Name() string { return p.name } // Level 获取玩家等级 func (p *Player) Level() int { return p.level } // Status 获取玩家状态 func (p *Player) Status() PlayerStatus { return p.status } // Position 获取玩家位置 func (p *Player) GetPosition() Position { return p.position } // LastMapID 获取上次所在地图ID func (p *Player) LastMapID() int32 { return p.lastMapID } // Stats 获取玩家属性 func (p *Player) Stats() PlayerStats { return p.stats } // SetOnline 设置玩家上线 func (p *Player) SetOnline() { p.status = PlayerStatusOnline p.updatedAt = time.Now() p.version++ } // SetOffline 设置玩家下线 func (p *Player) SetOffline() { p.status = PlayerStatusOffline p.updatedAt = time.Now() p.version++ } // MoveTo 移动到指定位置 func (p *Player) MoveTo(pos Position) error { if p.status == PlayerStatusOffline { return ErrPlayerOffline } p.position = pos p.updatedAt = time.Now() p.version++ return nil } // SetLastLocation 设置上次位置(用于登出保存) func (p *Player) SetLastLocation(mapID int32, pos Position) { p.lastMapID = mapID p.position = pos p.updatedAt = time.Now() p.version++ } // GainExp 获得经验值 func (p *Player) GainExp(exp int64) { p.exp += exp // 检查是否升级 for p.exp >= p.getExpForNextLevel() { p.levelUp() } p.updatedAt = time.Now() p.version++ } // levelUp 升级 func (p *Player) levelUp() { p.level++ // 升级时增加属性 p.stats.MaxHP += 20 p.stats.HP = p.stats.MaxHP p.stats.MaxMP += 10 p.stats.MP = p.stats.MaxMP p.stats.Attack += 2 p.stats.Defense += 1 } // getExpForNextLevel 获取下一级所需经验 func (p *Player) getExpForNextLevel() int64 { return int64(p.level * 100) } // TakeDamage 受到伤害 func (p *Player) TakeDamage(damage int) bool { if damage <= 0 { return false } actualDamage := damage - p.stats.Defense if actualDamage < 1 { actualDamage = 1 } p.stats.HP -= actualDamage if p.stats.HP < 0 { p.stats.HP = 0 } p.updatedAt = time.Now() p.version++ return p.stats.HP == 0 // 返回是否死亡 } // Heal 治疗 func (p *Player) Heal(amount int) { p.stats.HP += amount if p.stats.HP > p.stats.MaxHP { p.stats.HP = p.stats.MaxHP } p.updatedAt = time.Now() p.version++ } // IsAlive 是否存活 func (p *Player) IsAlive() bool { return p.stats.HP > 0 } // CreatedAt 获取创建时间 func (p *Player) CreatedAt() time.Time { return p.createdAt } // UpdatedAt 获取更新时间 func (p *Player) UpdatedAt() time.Time { return p.updatedAt } // Version 获取版本号 func (p *Player) Version() int64 { return p.version } // Exp 获取经验值 func (p *Player) Exp() int64 { return p.exp } // ReconstructPlayer 从持久化数据重建玩家聚合根 func ReconstructPlayer(id PlayerID, name string, level int, exp int64, status PlayerStatus, position Position, lastMapID int32, stats PlayerStats, createdAt, updatedAt time.Time, version int64) *Player { return &Player{ id: id, name: name, level: level, exp: exp, status: status, position: position, lastMapID: lastMapID, stats: stats, createdAt: createdAt, updatedAt: updatedAt, version: version, } } ================================================ FILE: internal/domain/player/query.go ================================================ package player import "time" // PlayerQuery 玩家查询条件 type PlayerQuery struct { // 基础查询条件 ID *PlayerID `json:"id,omitempty"` Username string `json:"username,omitempty"` Nickname string `json:"nickname,omitempty"` Status *PlayerStatus `json:"status,omitempty"` // 等级范围 MinLevel int `json:"min_level,omitempty"` MaxLevel int `json:"max_level,omitempty"` // 时间范围 CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` LastLoginAfter *time.Time `json:"last_login_after,omitempty"` LastLoginBefore *time.Time `json:"last_login_before,omitempty"` // 分页 Limit int `json:"limit,omitempty"` Offset int `json:"offset,omitempty"` // 排序 OrderBy string `json:"order_by,omitempty"` // "created_at", "level", "vip_level" Order string `json:"order,omitempty"` // "asc", "desc" } // NewPlayerQuery 创建新的玩家查询 func NewPlayerQuery() *PlayerQuery { return &PlayerQuery{} } // WithID 设置ID查询条件 func (q *PlayerQuery) WithID(id PlayerID) *PlayerQuery { q.ID = &id return q } // WithUsername 设置用户名查询条件 func (q *PlayerQuery) WithUsername(username string) *PlayerQuery { q.Username = username return q } // WithNickname 设置昵称查询条件 func (q *PlayerQuery) WithNickname(nickname string) *PlayerQuery { q.Nickname = nickname return q } // WithStatus 设置状态查询条件 func (q *PlayerQuery) WithStatus(status PlayerStatus) *PlayerQuery { q.Status = &status return q } // WithLevelRange 设置等级范围查询条件 func (q *PlayerQuery) WithLevelRange(minLevel, maxLevel int) *PlayerQuery { q.MinLevel = minLevel q.MaxLevel = maxLevel return q } // WithCreatedTimeRange 设置创建时间范围查询条件 func (q *PlayerQuery) WithCreatedTimeRange(after, before time.Time) *PlayerQuery { q.CreatedAfter = &after q.CreatedBefore = &before return q } // WithLastLoginTimeRange 设置最后登录时间范围查询条件 func (q *PlayerQuery) WithLastLoginTimeRange(after, before time.Time) *PlayerQuery { q.LastLoginAfter = &after q.LastLoginBefore = &before return q } // WithPagination 设置分页查询条件 func (q *PlayerQuery) WithPagination(limit, offset int) *PlayerQuery { q.Limit = limit q.Offset = offset return q } // WithOrder 设置排序查询条件 func (q *PlayerQuery) WithOrder(orderBy, order string) *PlayerQuery { q.OrderBy = orderBy q.Order = order return q } ================================================ FILE: internal/domain/player/repository.go ================================================ package player import "context" // Repository 玩家仓储接口 type Repository interface { // Save 保存玩家 Save(ctx context.Context, player *Player) error // FindByID 根据ID查找玩家 FindByID(ctx context.Context, id PlayerID) (*Player, error) // FindByName 根据名称查找玩家 FindByName(ctx context.Context, name string) (*Player, error) // Update 更新玩家 Update(ctx context.Context, player *Player) error // Delete 删除玩家 Delete(ctx context.Context, id PlayerID) error // FindOnlinePlayers 查找在线玩家 FindOnlinePlayers(ctx context.Context, limit int) ([]*Player, error) // FindPlayersByLevel 根据等级范围查找玩家 FindPlayersByLevel(ctx context.Context, minLevel, maxLevel int) ([]*Player, error) // ExistsByName 检查名称是否存在 ExistsByName(ctx context.Context, name string) bool } ================================================ FILE: internal/domain/player/service.go ================================================ package player import ( "context" "fmt" "math" ) // Service 玩家领域服务 type Service struct { repository Repository } // NewService 创建玩家领域服务 func NewService(repository Repository) *Service { return &Service{ repository: repository, } } // CreatePlayer 创建玩家 func (s *Service) CreatePlayer(ctx context.Context, name string) (*Player, error) { if name == "" { return nil, ErrInvalidPlayerName } // 检查名称是否已存在 exists := s.repository.ExistsByName(ctx, name) if exists { return nil, ErrPlayerAlreadyExists } // 创建新玩家 player := NewPlayer(name) // 保存到仓储 if err := s.repository.Save(ctx, player); err != nil { return nil, fmt.Errorf("save player: %w", err) } return player, nil } // AuthenticatePlayer 玩家认证 func (s *Service) AuthenticatePlayer(ctx context.Context, playerID PlayerID) (*Player, error) { player, err := s.repository.FindByID(ctx, playerID) if err != nil { return nil, fmt.Errorf("find player: %w", err) } // 设置玩家上线 player.SetOnline() // 更新玩家状态 if err := s.repository.Update(ctx, player); err != nil { return nil, fmt.Errorf("update player: %w", err) } return player, nil } // LogoutPlayer 玩家登出 func (s *Service) LogoutPlayer(ctx context.Context, playerID PlayerID) error { player, err := s.repository.FindByID(ctx, playerID) if err != nil { return fmt.Errorf("find player: %w", err) } // 设置玩家下线 player.SetOffline() // 更新玩家状态 if err := s.repository.Update(ctx, player); err != nil { return fmt.Errorf("update player: %w", err) } return nil } // MovePlayer 移动玩家 func (s *Service) MovePlayer(ctx context.Context, playerID PlayerID, position Position) error { player, err := s.repository.FindByID(ctx, playerID) if err != nil { return fmt.Errorf("find player: %w", err) } // 验证位置有效性 if err := s.validatePosition(position); err != nil { return err } // 移动玩家 if err := player.MoveTo(position); err != nil { return err } // 更新玩家 if err := s.repository.Update(ctx, player); err != nil { return fmt.Errorf("update player: %w", err) } return nil } // GainExperience 玩家获得经验 func (s *Service) GainExperience(ctx context.Context, playerID PlayerID, exp int64) error { player, err := s.repository.FindByID(ctx, playerID) if err != nil { return fmt.Errorf("find player: %w", err) } oldLevel := player.Level() player.GainExp(exp) newLevel := player.Level() // 更新玩家 if err := s.repository.Update(ctx, player); err != nil { return fmt.Errorf("update player: %w", err) } // 如果升级了,发布升级事件 if newLevel > oldLevel { // TODO: 发布玩家升级事件 } return nil } // HealPlayer 治疗玩家 func (s *Service) HealPlayer(ctx context.Context, playerID PlayerID, amount int) error { player, err := s.repository.FindByID(ctx, playerID) if err != nil { return fmt.Errorf("find player: %w", err) } player.Heal(amount) // 更新玩家 if err := s.repository.Update(ctx, player); err != nil { return fmt.Errorf("update player: %w", err) } return nil } // GetOnlinePlayers 获取在线玩家列表 func (s *Service) GetOnlinePlayers(ctx context.Context, limit int) ([]*Player, error) { players, err := s.repository.FindOnlinePlayers(ctx, limit) if err != nil { return nil, fmt.Errorf("find online players: %w", err) } return players, nil } // validatePosition 验证位置有效性 func (s *Service) validatePosition(pos Position) error { // 简单的位置验证逻辑 if pos.X < -1000 || pos.X > 1000 { return ErrInvalidPosition } if pos.Y < -1000 || pos.Y > 1000 { return ErrInvalidPosition } if pos.Z < -100 || pos.Z > 100 { return ErrInvalidPosition } return nil } // CalculateDistance 计算两个位置之间的距离 func (s *Service) CalculateDistance(pos1, pos2 Position) float64 { dx := pos1.X - pos2.X dy := pos1.Y - pos2.Y dz := pos1.Z - pos2.Z return math.Sqrt(dx*dx + dy*dy + dz*dz) } // IsPlayerNearby 检查玩家是否在附近 func (s *Service) IsPlayerNearby(ctx context.Context, playerID1, playerID2 PlayerID, maxDistance float64) (bool, error) { player1, err := s.repository.FindByID(ctx, playerID1) if err != nil { return false, fmt.Errorf("find player1: %w", err) } player2, err := s.repository.FindByID(ctx, playerID2) if err != nil { return false, fmt.Errorf("find player2: %w", err) } distance := s.CalculateDistance(player1.GetPosition(), player2.GetPosition()) return distance <= maxDistance, nil } ================================================ FILE: internal/domain/player/value_objects.go ================================================ package player import ( "errors" "fmt" "regexp" "strings" ) // PlayerName 玩家名称值对象 type PlayerName struct { value string } // NewPlayerName 创建玩家名称 func NewPlayerName(name string) (PlayerName, error) { if err := validatePlayerName(name); err != nil { return PlayerName{}, err } return PlayerName{value: strings.TrimSpace(name)}, nil } // String 返回字符串表示 func (n PlayerName) String() string { return n.value } // Equals 比较是否相等 func (n PlayerName) Equals(other PlayerName) bool { return n.value == other.value } // validatePlayerName 验证玩家名称 func validatePlayerName(name string) error { name = strings.TrimSpace(name) if name == "" { return errors.New("player name cannot be empty") } if len(name) < 2 { return errors.New("player name must be at least 2 characters") } if len(name) > 20 { return errors.New("player name cannot exceed 20 characters") } // 只允许字母、数字和下划线 matched, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", name) if !matched { return errors.New("player name can only contain letters, numbers and underscores") } return nil } // Level 等级值对象 type Level struct { value int } // NewLevel 创建等级 func NewLevel(level int) (Level, error) { if level < 1 { return Level{}, errors.New("level must be at least 1") } if level > 100 { return Level{}, errors.New("level cannot exceed 100") } return Level{value: level}, nil } // Value 获取等级值 func (l Level) Value() int { return l.value } // String 返回字符串表示 func (l Level) String() string { return fmt.Sprintf("Level %d", l.value) } // Equals 比较是否相等 func (l Level) Equals(other Level) bool { return l.value == other.value } // CanLevelUp 是否可以升级 func (l Level) CanLevelUp() bool { return l.value < 100 } // NextLevel 获取下一级 func (l Level) NextLevel() (Level, error) { if !l.CanLevelUp() { return Level{}, errors.New("already at max level") } return NewLevel(l.value + 1) } // Experience 经验值对象 type Experience struct { value int64 } // NewExperience 创建经验 func NewExperience(exp int64) (Experience, error) { if exp < 0 { return Experience{}, errors.New("experience cannot be negative") } return Experience{value: exp}, nil } // Value 获取经验值 func (e Experience) Value() int64 { return e.value } // String 返回字符串表示 func (e Experience) String() string { return fmt.Sprintf("%d EXP", e.value) } // Add 增加经验 func (e Experience) Add(amount int64) (Experience, error) { if amount < 0 { return Experience{}, errors.New("cannot add negative experience") } return NewExperience(e.value + amount) } // Equals 比较是否相等 func (e Experience) Equals(other Experience) bool { return e.value == other.value } // HealthPoints 生命值对象 type HealthPoints struct { current int max int } // NewHealthPoints 创建生命值 func NewHealthPoints(current, max int) (HealthPoints, error) { if max <= 0 { return HealthPoints{}, errors.New("max health must be positive") } if current < 0 { return HealthPoints{}, errors.New("current health cannot be negative") } if current > max { current = max } return HealthPoints{current: current, max: max}, nil } // Current 获取当前生命值 func (hp HealthPoints) Current() int { return hp.current } // Max 获取最大生命值 func (hp HealthPoints) Max() int { return hp.max } // Percentage 获取生命值百分比 func (hp HealthPoints) Percentage() float64 { if hp.max == 0 { return 0 } return float64(hp.current) / float64(hp.max) * 100 } // IsAlive 是否存活 func (hp HealthPoints) IsAlive() bool { return hp.current > 0 } // IsFull 是否满血 func (hp HealthPoints) IsFull() bool { return hp.current == hp.max } // TakeDamage 受到伤害 func (hp HealthPoints) TakeDamage(damage int) HealthPoints { if damage < 0 { damage = 0 } newCurrent := hp.current - damage if newCurrent < 0 { newCurrent = 0 } return HealthPoints{current: newCurrent, max: hp.max} } // Heal 治疗 func (hp HealthPoints) Heal(amount int) HealthPoints { if amount < 0 { amount = 0 } newCurrent := hp.current + amount if newCurrent > hp.max { newCurrent = hp.max } return HealthPoints{current: newCurrent, max: hp.max} } // String 返回字符串表示 func (hp HealthPoints) String() string { return fmt.Sprintf("%d/%d HP (%.1f%%)", hp.current, hp.max, hp.Percentage()) } // ManaPoints 魔法值对象 type ManaPoints struct { current int max int } // NewManaPoints 创建魔法值 func NewManaPoints(current, max int) (ManaPoints, error) { if max <= 0 { return ManaPoints{}, errors.New("max mana must be positive") } if current < 0 { return ManaPoints{}, errors.New("current mana cannot be negative") } if current > max { current = max } return ManaPoints{current: current, max: max}, nil } // Current 获取当前魔法值 func (mp ManaPoints) Current() int { return mp.current } // Max 获取最大魔法值 func (mp ManaPoints) Max() int { return mp.max } // Percentage 获取魔法值百分比 func (mp ManaPoints) Percentage() float64 { if mp.max == 0 { return 0 } return float64(mp.current) / float64(mp.max) * 100 } // HasEnough 是否有足够魔法值 func (mp ManaPoints) HasEnough(required int) bool { return mp.current >= required } // Consume 消耗魔法值 func (mp ManaPoints) Consume(amount int) (ManaPoints, error) { if amount < 0 { return mp, errors.New("cannot consume negative mana") } if mp.current < amount { return mp, errors.New("insufficient mana") } return ManaPoints{current: mp.current - amount, max: mp.max}, nil } // Restore 恢复魔法值 func (mp ManaPoints) Restore(amount int) ManaPoints { if amount < 0 { amount = 0 } newCurrent := mp.current + amount if newCurrent > mp.max { newCurrent = mp.max } return ManaPoints{current: newCurrent, max: mp.max} } // String 返回字符串表示 func (mp ManaPoints) String() string { return fmt.Sprintf("%d/%d MP (%.1f%%)", mp.current, mp.max, mp.Percentage()) } ================================================ FILE: internal/domain/quest/errors.go ================================================ package quest import "errors" var ( // 任务管理器相关错误 ErrQuestManagerNotFound = errors.New("quest manager not found") ErrInvalidQuestManager = errors.New("invalid quest manager") // 任务相关错误 ErrQuestNotFound = errors.New("quest not found") ErrQuestNotAvailable = errors.New("quest is not available") ErrQuestAlreadyAccepted = errors.New("quest already accepted") ErrQuestNotActive = errors.New("quest is not active") ErrQuestNotCompleted = errors.New("quest is not completed") ErrQuestExpired = errors.New("quest has expired") ErrQuestFailed = errors.New("quest has failed") ErrCannotAbandonMainQuest = errors.New("cannot abandon main quest") ErrQuestAlreadyCompleted = errors.New("quest already completed") ErrInvalidQuestType = errors.New("invalid quest type") ErrInvalidQuestStatus = errors.New("invalid quest status") // 任务目标相关错误 ErrObjectiveNotFound = errors.New("objective not found") ErrObjectiveAlreadyCompleted = errors.New("objective already completed") ErrInvalidObjectiveType = errors.New("invalid objective type") ErrInvalidProgress = errors.New("invalid progress value") ErrProgressExceedsRequired = errors.New("progress exceeds required amount") // 前置条件相关错误 ErrPrerequisitesNotMet = errors.New("prerequisites not met") ErrLevelRequirementNotMet = errors.New("level requirement not met") ErrClassRestriction = errors.New("class restriction for quest") ErrRaceRestriction = errors.New("race restriction for quest") ErrGuildRequirement = errors.New("guild requirement not met") // 奖励相关错误 ErrInvalidReward = errors.New("invalid quest reward") ErrRewardNotFound = errors.New("quest reward not found") ErrRewardAlreadyClaimed = errors.New("reward already claimed") ErrInsufficientSpace = errors.New("insufficient inventory space for reward") // 成就相关错误 ErrAchievementNotFound = errors.New("achievement not found") ErrAchievementAlreadyUnlocked = errors.New("achievement already unlocked") ErrAchievementRequirementsNotMet = errors.New("achievement requirements not met") ErrInvalidAchievementType = errors.New("invalid achievement type") ErrAchievementLocked = errors.New("achievement is locked") // 日常/周常任务相关错误 ErrDailyQuestLimitReached = errors.New("daily quest limit reached") ErrWeeklyQuestLimitReached = errors.New("weekly quest limit reached") ErrQuestCooldownActive = errors.New("quest cooldown is active") ErrRepeatLimitReached = errors.New("quest repeat limit reached") // 任务链相关错误 ErrQuestChainBroken = errors.New("quest chain is broken") ErrInvalidQuestOrder = errors.New("invalid quest order in chain") // 配置相关错误 ErrInvalidQuestConfig = errors.New("invalid quest configuration") ErrQuestConfigNotFound = errors.New("quest configuration not found") ErrInvalidObjectiveConfig = errors.New("invalid objective configuration") ErrInvalidRewardConfig = errors.New("invalid reward configuration") ) ================================================ FILE: internal/domain/quest/quest.go ================================================ //nolint:staticcheck // Quest rich model contains fields reserved for future use; suppress U1000 unused field warnings in early stage package quest import ( // "errors" "time" ) // QuestManager 任务管理器聚合根 type QuestManager struct { playerID string activeQuests map[string]*Quest completedQuests map[string]*Quest dailyQuests map[string]*Quest weeklyQuests map[string]*Quest achievements map[string]*Achievement lastUpdate time.Time events []DomainEvent } // NewQuestManager 创建新任务管理器 func NewQuestManager(playerID string) *QuestManager { return &QuestManager{ playerID: playerID, activeQuests: make(map[string]*Quest), completedQuests: make(map[string]*Quest), dailyQuests: make(map[string]*Quest), weeklyQuests: make(map[string]*Quest), achievements: make(map[string]*Achievement), lastUpdate: time.Now(), events: make([]DomainEvent, 0), } } // Quest 任务实体 type Quest struct { ID string Name string Description string QuestType QuestType Category QuestCategory Status QuestStatus Priority QuestPriority Objectives []*QuestObjective Rewards []*QuestReward Prerequisites []string // 前置任务ID StartTime *time.Time ExpireTime *time.Time CompletedTime *time.Time TimeLimit *time.Duration RepeatType RepeatType RepeatCount int MaxRepeats int Level int MinLevel int MaxLevel int ClassRestrictions []string RaceRestrictions []string CreatedAt time.Time UpdatedAt time.Time } // NewQuest 创建新任务 func NewQuest(id, name string, questType QuestType) *Quest { return &Quest{ ID: id, Name: name, QuestType: questType, Status: QuestStatusAvailable, Priority: QuestPriorityNormal, Objectives: make([]*QuestObjective, 0), Rewards: make([]*QuestReward, 0), RepeatType: RepeatTypeNone, RepeatCount: 0, MaxRepeats: 1, CreatedAt: time.Now(), UpdatedAt: time.Now(), } } // QuestType 任务类型 type QuestType int const ( QuestTypeMain QuestType = iota + 1 QuestTypeSide QuestTypeDaily QuestTypeWeekly QuestTypeEvent QuestTypeGuild QuestTypePvP QuestTypeDungeon QuestTypeRaid ) // QuestCategory 任务分类 type QuestCategory int const ( QuestCategoryKill QuestCategory = iota + 1 QuestCategoryCollect QuestCategoryDeliver QuestCategoryEscort QuestCategoryExplore QuestCategoryCraft QuestCategoryTalk QuestCategoryUse QuestCategoryReach ) // QuestStatus 任务状态 type QuestStatus int const ( QuestStatusAvailable QuestStatus = iota + 1 QuestStatusAccepted QuestStatusInProgress QuestStatusCompleted QuestStatusFailed QuestStatusExpired QuestStatusAbandoned ) // QuestPriority 任务优先级 type QuestPriority int const ( QuestPriorityLow QuestPriority = iota + 1 QuestPriorityNormal QuestPriorityHigh QuestPriorityUrgent ) // RepeatType 重复类型 type RepeatType int const ( RepeatTypeNone RepeatType = iota RepeatTypeDaily RepeatTypeWeekly RepeatTypeMonthly RepeatTypeUnlimited ) // QuestObjective 任务目标 type QuestObjective struct { ID string Description string ObjectiveType ObjectiveType Target string // 目标ID或名称 Current int64 Required int64 Completed bool Optional bool Order int Metadata map[string]interface{} } // ObjectiveType 目标类型 type ObjectiveType int const ( ObjectiveTypeKill ObjectiveType = iota + 1 ObjectiveTypeCollect ObjectiveTypeDeliver ObjectiveTypeReach ObjectiveTypeUse ObjectiveTypeTalk ObjectiveTypeCraft ObjectiveTypeLevel ObjectiveTypeEquip ObjectiveTypeSpend ObjectiveTypeEarn ) // QuestReward 任务奖励 type QuestReward struct { RewardType RewardType RewardID string Quantity int64 Optional bool Condition *RewardCondition } // RewardType 奖励类型 type RewardType int const ( RewardTypeExperience RewardType = iota + 1 RewardTypeGold RewardTypeItem RewardTypeSkillPoint RewardTypeReputation RewardTypeTitle RewardTypeAchievement RewardTypeBuff ) // RewardCondition 奖励条件 type RewardCondition struct { ConditionType ConditionType Value interface{} } // ConditionType 条件类型 type ConditionType int const ( ConditionTypeLevel ConditionType = iota + 1 ConditionTypeClass ConditionTypeRace ConditionTypeGuild ConditionTypeTime ConditionTypeRandom ) // Achievement 成就实体 type Achievement struct { ID string Name string Description string Category AchievementCategory Points int64 Requirements []*AchievementRequirement Rewards []*QuestReward Unlocked bool Progress int64 TotalProgress int64 UnlockedAt *time.Time Hidden bool Rare bool } // AchievementCategory 成就分类 type AchievementCategory int const ( AchievementCategoryGeneral AchievementCategory = iota + 1 AchievementCategoryCombat AchievementCategoryExploration AchievementCategoryCrafting AchievementCategorySocial AchievementCategoryPvP AchievementCategoryPvE AchievementCategoryCollection ) // AchievementRequirement 成就要求 type AchievementRequirement struct { RequirementType RequirementType Target string Value int64 Current int64 Completed bool } // RequirementType 要求类型 type RequirementType int const ( RequirementTypeKill RequirementType = iota + 1 RequirementTypeCollect RequirementTypeComplete RequirementTypeReach RequirementTypeSpend RequirementTypeEarn RequirementTypeUse RequirementTypeCraft ) // DomainEvent 领域事件接口 type DomainEvent interface { EventType() string OccurredAt() time.Time PlayerID() string } // QuestAcceptedEvent 任务接受事件 type QuestAcceptedEvent struct { Player string QuestID string QuestName string OccurredAtTime time.Time } func (e QuestAcceptedEvent) EventType() string { return "quest.accepted" } func (e QuestAcceptedEvent) OccurredAt() time.Time { return e.OccurredAtTime } func (e QuestAcceptedEvent) PlayerID() string { return e.Player } // QuestCompletedEvent 任务完成事件 type QuestCompletedEvent struct { Player string QuestID string QuestName string Rewards []*QuestReward OccurredAtTime time.Time } func (e QuestCompletedEvent) EventType() string { return "quest.completed" } func (e QuestCompletedEvent) OccurredAt() time.Time { return e.OccurredAtTime } func (e QuestCompletedEvent) PlayerID() string { return e.Player } // QuestFailedEvent 任务失败事件 type QuestFailedEvent struct { Player string QuestID string QuestName string Reason string OccurredAtTime time.Time } func (e QuestFailedEvent) EventType() string { return "quest.failed" } func (e QuestFailedEvent) OccurredAt() time.Time { return e.OccurredAtTime } func (e QuestFailedEvent) PlayerID() string { return e.Player } // ObjectiveCompletedEvent 目标完成事件 type ObjectiveCompletedEvent struct { Player string QuestID string ObjectiveID string ObjectiveName string OccurredAtTime time.Time } func (e ObjectiveCompletedEvent) EventType() string { return "objective.completed" } func (e ObjectiveCompletedEvent) OccurredAt() time.Time { return e.OccurredAtTime } func (e ObjectiveCompletedEvent) PlayerID() string { return e.Player } // AchievementUnlockedEvent 成就解锁事件 type AchievementUnlockedEvent struct { Player string AchievementID string AchievementName string Points int64 OccurredAtTime time.Time } func (e AchievementUnlockedEvent) EventType() string { return "achievement.unlocked" } func (e AchievementUnlockedEvent) OccurredAt() time.Time { return e.OccurredAtTime } func (e AchievementUnlockedEvent) PlayerID() string { return e.Player } // QuestManager 业务方法 // PlayerID 获取玩家ID func (qm *QuestManager) PlayerID() string { return qm.playerID } // ActiveQuests 获取活跃任务 func (qm *QuestManager) ActiveQuests() map[string]*Quest { return qm.activeQuests } // CompletedQuests 获取已完成任务 func (qm *QuestManager) CompletedQuests() map[string]*Quest { return qm.completedQuests } // Achievements 获取成就 func (qm *QuestManager) Achievements() map[string]*Achievement { return qm.achievements } // AcceptQuest 接受任务 func (qm *QuestManager) AcceptQuest(quest *Quest) error { // 检查任务是否可接受 if quest.Status != QuestStatusAvailable { return ErrQuestNotAvailable } // 检查是否已接受 if _, exists := qm.activeQuests[quest.ID]; exists { return ErrQuestAlreadyAccepted } // 检查前置条件 if !qm.checkPrerequisites(quest.Prerequisites) { return ErrPrerequisitesNotMet } // 检查等级限制 if quest.MinLevel > 0 || quest.MaxLevel > 0 { // 这里需要获取玩家等级,暂时跳过 } // 接受任务 quest.Status = QuestStatusAccepted now := time.Now() quest.StartTime = &now quest.UpdatedAt = time.Now() // 设置过期时间 if quest.TimeLimit != nil { expireTime := now.Add(*quest.TimeLimit) quest.ExpireTime = &expireTime } qm.activeQuests[quest.ID] = quest qm.lastUpdate = time.Now() // 发布事件 qm.addEvent(QuestAcceptedEvent{ Player: qm.playerID, QuestID: quest.ID, QuestName: quest.Name, OccurredAtTime: time.Now(), }) return nil } // UpdateObjectiveProgress 更新目标进度 func (qm *QuestManager) UpdateObjectiveProgress(questID string, objectiveID string, progress int64) error { quest, exists := qm.activeQuests[questID] if !exists { return ErrQuestNotFound } // 查找目标 var objective *QuestObjective for _, obj := range quest.Objectives { if obj.ID == objectiveID { objective = obj break } } if objective == nil { return ErrObjectiveNotFound } if objective.Completed { return ErrObjectiveAlreadyCompleted } // 更新进度 objective.Current += progress if objective.Current >= objective.Required { objective.Current = objective.Required objective.Completed = true // 发布目标完成事件 qm.addEvent(ObjectiveCompletedEvent{ Player: qm.playerID, QuestID: questID, ObjectiveID: objectiveID, ObjectiveName: objective.Description, OccurredAtTime: time.Now(), }) } quest.UpdatedAt = time.Now() qm.lastUpdate = time.Now() // 检查任务是否完成 if qm.checkQuestCompletion(quest) { return qm.CompleteQuest(questID) } return nil } // CompleteQuest 完成任务 func (qm *QuestManager) CompleteQuest(questID string) error { quest, exists := qm.activeQuests[questID] if !exists { return ErrQuestNotFound } if quest.Status != QuestStatusAccepted && quest.Status != QuestStatusInProgress { return ErrQuestNotActive } // 检查所有必需目标是否完成 if !qm.checkQuestCompletion(quest) { return ErrQuestNotCompleted } // 完成任务 quest.Status = QuestStatusCompleted now := time.Now() quest.CompletedTime = &now quest.UpdatedAt = time.Now() // 移动到已完成任务 delete(qm.activeQuests, questID) qm.completedQuests[questID] = quest // 处理重复任务 if quest.RepeatType != RepeatTypeNone { quest.RepeatCount++ if quest.MaxRepeats == 0 || quest.RepeatCount < quest.MaxRepeats { // 重置任务状态以便重复 qm.resetQuestForRepeat(quest) } } qm.lastUpdate = time.Now() // 发布事件 qm.addEvent(QuestCompletedEvent{ Player: qm.playerID, QuestID: questID, QuestName: quest.Name, Rewards: quest.Rewards, OccurredAtTime: time.Now(), }) return nil } // AbandonQuest 放弃任务 func (qm *QuestManager) AbandonQuest(questID string) error { quest, exists := qm.activeQuests[questID] if !exists { return ErrQuestNotFound } // 检查是否可以放弃 if quest.QuestType == QuestTypeMain { return ErrCannotAbandonMainQuest } quest.Status = QuestStatusAbandoned quest.UpdatedAt = time.Now() delete(qm.activeQuests, questID) qm.lastUpdate = time.Now() return nil } // UnlockAchievement 解锁成就 func (qm *QuestManager) UnlockAchievement(achievementID string, achievement *Achievement) error { if _, exists := qm.achievements[achievementID]; exists { return ErrAchievementAlreadyUnlocked } // 检查成就要求 if !qm.checkAchievementRequirements(achievement) { return ErrAchievementRequirementsNotMet } achievement.Unlocked = true now := time.Now() achievement.UnlockedAt = &now qm.achievements[achievementID] = achievement qm.lastUpdate = time.Now() // 发布事件 qm.addEvent(AchievementUnlockedEvent{ Player: qm.playerID, AchievementID: achievementID, AchievementName: achievement.Name, Points: achievement.Points, OccurredAtTime: time.Now(), }) return nil } // checkPrerequisites 检查前置条件 func (qm *QuestManager) checkPrerequisites(prerequisites []string) bool { for _, prereq := range prerequisites { if _, exists := qm.completedQuests[prereq]; !exists { return false } } return true } // checkQuestCompletion 检查任务完成条件 func (qm *QuestManager) checkQuestCompletion(quest *Quest) bool { for _, objective := range quest.Objectives { if !objective.Optional && !objective.Completed { return false } } return true } // checkAchievementRequirements 检查成就要求 func (qm *QuestManager) checkAchievementRequirements(achievement *Achievement) bool { for _, req := range achievement.Requirements { if !req.Completed { return false } } return true } // resetQuestForRepeat 重置任务以便重复 func (qm *QuestManager) resetQuestForRepeat(quest *Quest) { quest.Status = QuestStatusAvailable quest.StartTime = nil quest.CompletedTime = nil quest.ExpireTime = nil // 重置所有目标 for _, objective := range quest.Objectives { objective.Current = 0 objective.Completed = false } } // addEvent 添加领域事件 func (qm *QuestManager) addEvent(event DomainEvent) { qm.events = append(qm.events, event) } // GetEvents 获取领域事件 func (qm *QuestManager) GetEvents() []DomainEvent { return qm.events } // ClearEvents 清除领域事件 func (qm *QuestManager) ClearEvents() { qm.events = make([]DomainEvent, 0) } ================================================ FILE: internal/domain/quest/repository.go ================================================ package quest import ( "context" "time" ) // Repository 任务仓储接口 type Repository interface { // 基础CRUD操作 Save(ctx context.Context, questManager *QuestManager) error FindByPlayerID(ctx context.Context, playerID string) (*QuestManager, error) Delete(ctx context.Context, playerID string) error Exists(ctx context.Context, playerID string) (bool, error) // 批量操作 SaveBatch(ctx context.Context, questManagers []*QuestManager) error FindByPlayerIDs(ctx context.Context, playerIDs []string) ([]*QuestManager, error) // 任务查询 FindQuestsByType(ctx context.Context, playerID string, questType QuestType) ([]*Quest, error) FindActiveQuests(ctx context.Context, playerID string) ([]*Quest, error) FindCompletedQuests(ctx context.Context, playerID string) ([]*Quest, error) FindAvailableQuests(ctx context.Context, playerID string) ([]*Quest, error) FindExpiredQuests(ctx context.Context, playerID string) ([]*Quest, error) // 成就查询 FindAchievements(ctx context.Context, playerID string) ([]*Achievement, error) FindUnlockedAchievements(ctx context.Context, playerID string) ([]*Achievement, error) FindAchievementsByCategory(ctx context.Context, playerID string, category AchievementCategory) ([]*Achievement, error) // 统计查询 GetQuestStats(ctx context.Context, playerID string) (*QuestStats, error) GetAchievementStats(ctx context.Context, playerID string) (*AchievementStats, error) GetQuestHistory(ctx context.Context, playerID string, limit int) ([]*QuestHistoryRecord, error) // 配置管理 GetQuestConfig(ctx context.Context, questID string) (*QuestConfig, error) GetAllQuestConfigs(ctx context.Context) ([]*QuestConfig, error) SaveQuestConfig(ctx context.Context, config *QuestConfig) error GetAchievementConfig(ctx context.Context, achievementID string) (*AchievementConfig, error) GetAllAchievementConfigs(ctx context.Context) ([]*AchievementConfig, error) SaveAchievementConfig(ctx context.Context, config *AchievementConfig) error } // QuestStats 任务统计信息 type QuestStats struct { PlayerID string `json:"player_id"` TotalQuests int `json:"total_quests"` ActiveQuests int `json:"active_quests"` CompletedQuests int `json:"completed_quests"` FailedQuests int `json:"failed_quests"` AbandonedQuests int `json:"abandoned_quests"` QuestsByType map[QuestType]int `json:"quests_by_type"` QuestsByCategory map[QuestCategory]int `json:"quests_by_category"` CompletionRate float64 `json:"completion_rate"` AverageTime time.Duration `json:"average_completion_time"` LastUpdate time.Time `json:"last_update"` } // AchievementStats 成就统计信息 type AchievementStats struct { PlayerID string `json:"player_id"` TotalAchievements int `json:"total_achievements"` UnlockedAchievements int `json:"unlocked_achievements"` TotalPoints int64 `json:"total_points"` AchievementsByCategory map[AchievementCategory]int `json:"achievements_by_category"` CompletionRate float64 `json:"completion_rate"` RareAchievements int `json:"rare_achievements"` HiddenAchievements int `json:"hidden_achievements"` LastUnlocked *time.Time `json:"last_unlocked"` LastUpdate time.Time `json:"last_update"` } // QuestHistoryRecord 任务历史记录 type QuestHistoryRecord struct { ID string `json:"id"` PlayerID string `json:"player_id"` QuestID string `json:"quest_id"` QuestName string `json:"quest_name"` QuestType QuestType `json:"quest_type"` Action string `json:"action"` // accepted, completed, failed, abandoned StartTime *time.Time `json:"start_time"` EndTime *time.Time `json:"end_time"` Duration *time.Duration `json:"duration"` Rewards []*QuestReward `json:"rewards"` OccurredAt time.Time `json:"occurred_at"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // QuestConfig 任务配置 type QuestConfig struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` QuestType QuestType `json:"quest_type"` Category QuestCategory `json:"category"` Priority QuestPriority `json:"priority"` Objectives []*ObjectiveConfig `json:"objectives"` Rewards []*QuestReward `json:"rewards"` Prerequisites []string `json:"prerequisites"` TimeLimit *time.Duration `json:"time_limit"` RepeatType RepeatType `json:"repeat_type"` MaxRepeats int `json:"max_repeats"` Level int `json:"level"` MinLevel int `json:"min_level"` MaxLevel int `json:"max_level"` ClassRestrictions []string `json:"class_restrictions"` RaceRestrictions []string `json:"race_restrictions"` Enabled bool `json:"enabled"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ObjectiveConfig 目标配置 type ObjectiveConfig struct { ID string `json:"id"` Description string `json:"description"` ObjectiveType ObjectiveType `json:"objective_type"` Target string `json:"target"` Required int64 `json:"required"` Optional bool `json:"optional"` Order int `json:"order"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // AchievementConfig 成就配置 type AchievementConfig struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Category AchievementCategory `json:"category"` Points int64 `json:"points"` Requirements []*RequirementConfig `json:"requirements"` Rewards []*QuestReward `json:"rewards"` Hidden bool `json:"hidden"` Rare bool `json:"rare"` Enabled bool `json:"enabled"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // RequirementConfig 要求配置 type RequirementConfig struct { RequirementType RequirementType `json:"requirement_type"` Target string `json:"target"` Value int64 `json:"value"` } // QuestQueryFilter 任务查询过滤器 type QuestQueryFilter struct { PlayerID string `json:"player_id"` QuestTypes []QuestType `json:"quest_types,omitempty"` Categories []QuestCategory `json:"categories,omitempty"` Statuses []QuestStatus `json:"statuses,omitempty"` Priorities []QuestPriority `json:"priorities,omitempty"` MinLevel *int `json:"min_level,omitempty"` MaxLevel *int `json:"max_level,omitempty"` AvailableOnly bool `json:"available_only"` ActiveOnly bool `json:"active_only"` CompletedOnly bool `json:"completed_only"` IncludeExpired bool `json:"include_expired"` SortBy string `json:"sort_by"` // priority, level, created_at SortOrder string `json:"sort_order"` // asc, desc Limit int `json:"limit"` Offset int `json:"offset"` } // AchievementQueryFilter 成就查询过滤器 type AchievementQueryFilter struct { PlayerID string `json:"player_id"` Categories []AchievementCategory `json:"categories,omitempty"` UnlockedOnly bool `json:"unlocked_only"` LockedOnly bool `json:"locked_only"` HiddenOnly bool `json:"hidden_only"` RareOnly bool `json:"rare_only"` MinPoints *int64 `json:"min_points,omitempty"` MaxPoints *int64 `json:"max_points,omitempty"` SortBy string `json:"sort_by"` // points, unlocked_at, category SortOrder string `json:"sort_order"` // asc, desc Limit int `json:"limit"` Offset int `json:"offset"` } ================================================ FILE: internal/domain/ranking/aggregate.go ================================================ package ranking import ( "fmt" "sort" "sync" "time" ) // RankingAggregate 排行榜聚合根 type RankingAggregate struct { // 基础信息 ID string `json:"id" bson:"_id"` RankID uint32 `json:"rank_id" bson:"rank_id"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` RankType RankType `json:"rank_type" bson:"rank_type"` Category RankCategory `json:"category" bson:"category"` // 排序配置 SortType SortType `json:"sort_type" bson:"sort_type"` TimeBitLen uint32 `json:"time_bit_len" bson:"time_bit_len"` TimeUnit int64 `json:"time_unit" bson:"time_unit"` MaxSize int64 `json:"max_size" bson:"max_size"` // 时间配置 StartTime int64 `json:"start_time" bson:"start_time"` EndTime int64 `json:"end_time" bson:"end_time"` Period RankPeriod `json:"period" bson:"period"` ResetTime *time.Time `json:"reset_time,omitempty" bson:"reset_time,omitempty"` // 状态信息 Status RankStatus `json:"status" bson:"status"` IsActive bool `json:"is_active" bson:"is_active"` LastUpdated time.Time `json:"last_updated" bson:"last_updated"` Version int64 `json:"version" bson:"version"` // 排行数据 Entries []*RankEntry `json:"entries" bson:"entries"` Blacklist *Blacklist `json:"blacklist" bson:"blacklist"` // 统计信息 TotalPlayers int64 `json:"total_players" bson:"total_players"` ActiveEntries int64 `json:"active_entries" bson:"active_entries"` AverageScore float64 `json:"average_score" bson:"average_score"` TopScore int64 `json:"top_score" bson:"top_score"` LastScoreUpdate time.Time `json:"last_score_update" bson:"last_score_update"` // 奖励配置 RewardConfig *RankRewardConfig `json:"reward_config,omitempty" bson:"reward_config,omitempty"` // 缓存配置 CacheConfig *RankCacheConfig `json:"cache_config,omitempty" bson:"cache_config,omitempty"` // 创建和更新时间 CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` // 内部状态 mutex sync.RWMutex `json:"-" bson:"-"` dirty bool `json:"-" bson:"-"` events []RankingEvent `json:"-" bson:"-"` } // NewRankingAggregate 创建新的排行榜聚合 func NewRankingAggregate(rankID uint32, name string, rankType RankType, category RankCategory) *RankingAggregate { now := time.Now() return &RankingAggregate{ ID: generateRankingID(rankID), RankID: rankID, Name: name, RankType: rankType, Category: category, SortType: SortTypeDescending, TimeBitLen: DefaultTimeBitLen, TimeUnit: DefaultTimeUnit, MaxSize: DefaultMaxSize, StartTime: now.Unix(), EndTime: 0, // 永久排行榜 Period: RankPeriodPermanent, Status: RankStatusActive, IsActive: true, LastUpdated: now, Version: 1, Entries: make([]*RankEntry, 0), Blacklist: NewBlacklist(rankID), TotalPlayers: 0, ActiveEntries: 0, AverageScore: 0.0, TopScore: 0, LastScoreUpdate: now, CreatedAt: now, UpdatedAt: now, dirty: true, events: make([]RankingEvent, 0), } } // UpdateScore 更新玩家分数 func (r *RankingAggregate) UpdateScore(playerID uint64, score int64, metadata map[string]interface{}) error { r.mutex.Lock() defer r.mutex.Unlock() // 检查排行榜状态 if !r.IsActive || r.Status != RankStatusActive { return NewRankingInactiveError(r.RankID) } // 检查黑名单 if r.Blacklist.IsBlacklisted(playerID) { return NewPlayerBlacklistedError(playerID, r.RankID) } // 检查时间范围 if !r.isInTimeRange() { return NewRankingTimeExpiredError(r.RankID, r.StartTime, r.EndTime) } // 计算带时间因子的分数 timeScore := r.calculateTimeScore(score) // 查找现有条目 existingEntry := r.findEntry(playerID) if existingEntry != nil { // 更新现有条目 oldScore := existingEntry.Score existingEntry.UpdateScore(timeScore, score, metadata) // 发布分数更新事件 r.addEvent(NewPlayerScoreUpdatedEvent(r.ID, playerID, oldScore, score, timeScore)) } else { // 创建新条目 newEntry := NewRankEntry(playerID, timeScore, score, metadata) r.Entries = append(r.Entries, newEntry) r.TotalPlayers++ // 发布新玩家加入事件 r.addEvent(NewPlayerJoinedRankingEvent(r.ID, playerID, score, timeScore)) } // 重新排序 r.sortEntries() // 限制大小 r.limitSize() // 更新统计信息 r.updateStatistics() // 标记为脏数据 r.markDirty() return nil } // GetRanking 获取排行榜数据 func (r *RankingAggregate) GetRanking(start, end int64, excludeBlacklisted bool) ([]*RankEntry, error) { r.mutex.RLock() defer r.mutex.RUnlock() if start < 0 || end < start { return nil, NewInvalidRangeError(start, end) } entries := make([]*RankEntry, 0) count := int64(0) for i, entry := range r.Entries { // 跳过黑名单玩家 if excludeBlacklisted && r.Blacklist.IsBlacklisted(entry.PlayerID) { continue } // 检查范围 if count >= start && count <= end { // 创建副本并设置排名 entryCopy := *entry entryCopy.Rank = int64(i + 1) entries = append(entries, &entryCopy) } count++ // 达到结束位置 if count > end { break } } return entries, nil } // GetPlayerRank 获取玩家排名 func (r *RankingAggregate) GetPlayerRank(playerID uint64) (*RankEntry, int64, error) { r.mutex.RLock() defer r.mutex.RUnlock() for i, entry := range r.Entries { if entry.PlayerID == playerID { // 创建副本并设置排名 entryCopy := *entry entryCopy.Rank = int64(i + 1) return &entryCopy, int64(i + 1), nil } } return nil, -1, NewPlayerNotInRankingError(playerID, r.RankID) } // AddToBlacklist 添加到黑名单 func (r *RankingAggregate) AddToBlacklist(playerID uint64, reason string) error { r.mutex.Lock() defer r.mutex.Unlock() err := r.Blacklist.AddPlayer(playerID, reason) if err != nil { return err } // 从排行榜中移除该玩家 r.removePlayer(playerID) // 发布黑名单事件 r.addEvent(NewPlayerBlacklistedEvent(r.ID, playerID, reason)) // 标记为脏数据 r.markDirty() return nil } // RemoveFromBlacklist 从黑名单移除 func (r *RankingAggregate) RemoveFromBlacklist(playerID uint64) error { r.mutex.Lock() defer r.mutex.Unlock() err := r.Blacklist.RemovePlayer(playerID) if err != nil { return err } // 发布黑名单移除事件 r.addEvent(NewPlayerUnblacklistedEvent(r.ID, playerID)) // 标记为脏数据 r.markDirty() return nil } // Reset 重置排行榜 func (r *RankingAggregate) Reset() error { r.mutex.Lock() defer r.mutex.Unlock() // 保存重置前的数据用于事件 oldEntries := make([]*RankEntry, len(r.Entries)) copy(oldEntries, r.Entries) // 重置数据 r.Entries = make([]*RankEntry, 0) r.TotalPlayers = 0 r.ActiveEntries = 0 r.AverageScore = 0.0 r.TopScore = 0 r.LastScoreUpdate = time.Now() r.Version++ // 更新重置时间 now := time.Now() r.ResetTime = &now r.LastUpdated = now r.UpdatedAt = now // 发布重置事件 r.addEvent(NewRankingResetEvent(r.ID, len(oldEntries))) // 标记为脏数据 r.markDirty() return nil } // SetActive 设置排行榜激活状态 func (r *RankingAggregate) SetActive(active bool) { r.mutex.Lock() defer r.mutex.Unlock() oldStatus := r.IsActive r.IsActive = active if active { r.Status = RankStatusActive } else { r.Status = RankStatusInactive } r.LastUpdated = time.Now() r.UpdatedAt = time.Now() // 发布状态变更事件 if oldStatus != active { r.addEvent(NewRankingStatusChangedEvent(r.ID, oldStatus, active)) } // 标记为脏数据 r.markDirty() } // SetTimeRange 设置时间范围 func (r *RankingAggregate) SetTimeRange(startTime, endTime int64) error { r.mutex.Lock() defer r.mutex.Unlock() if startTime >= endTime && endTime != 0 { return NewInvalidTimeRangeError(startTime, endTime) } r.StartTime = startTime r.EndTime = endTime r.LastUpdated = time.Now() r.UpdatedAt = time.Now() // 标记为脏数据 r.markDirty() return nil } // SetRewardConfig 设置奖励配置 func (r *RankingAggregate) SetRewardConfig(config *RankRewardConfig) { r.mutex.Lock() defer r.mutex.Unlock() r.RewardConfig = config r.LastUpdated = time.Now() r.UpdatedAt = time.Now() // 标记为脏数据 r.markDirty() } // SetCacheConfig 设置缓存配置 func (r *RankingAggregate) SetCacheConfig(config *RankCacheConfig) { r.mutex.Lock() defer r.mutex.Unlock() r.CacheConfig = config r.LastUpdated = time.Now() r.UpdatedAt = time.Now() // 标记为脏数据 r.markDirty() } // GetTopPlayers 获取前N名玩家 func (r *RankingAggregate) GetTopPlayers(count int) []*RankEntry { r.mutex.RLock() defer r.mutex.RUnlock() if count <= 0 { return []*RankEntry{} } if count > len(r.Entries) { count = len(r.Entries) } topPlayers := make([]*RankEntry, count) for i := 0; i < count; i++ { // 创建副本并设置排名 entryCopy := *r.Entries[i] entryCopy.Rank = int64(i + 1) topPlayers[i] = &entryCopy } return topPlayers } // GetStatistics 获取统计信息 func (r *RankingAggregate) GetStatistics() *RankingStatistics { r.mutex.RLock() defer r.mutex.RUnlock() return &RankingStatistics{ RankID: r.RankID, TotalPlayers: r.TotalPlayers, ActiveEntries: r.ActiveEntries, AverageScore: r.AverageScore, TopScore: r.TopScore, BlacklistCount: int64(len(r.Blacklist.Players)), LastUpdated: r.LastUpdated, LastScoreUpdate: r.LastScoreUpdate, } } // GetEvents 获取领域事件 func (r *RankingAggregate) GetEvents() []RankingEvent { r.mutex.RLock() defer r.mutex.RUnlock() events := make([]RankingEvent, len(r.events)) copy(events, r.events) return events } // GetID 获取ID func (r *RankingAggregate) GetID() string { return r.ID } // GetName 获取名称 func (r *RankingAggregate) GetName() string { return r.Name } // GetDescription 获取描述 func (r *RankingAggregate) GetDescription() string { return r.Description } // GetRankType 获取排行榜类型 func (r *RankingAggregate) GetRankType() RankType { return r.RankType } // GetPeriodType 获取周期类型 func (r *RankingAggregate) GetPeriodType() RankPeriod { return r.Period } // GetMaxEntries 获取最大条目数 func (r *RankingAggregate) GetMaxEntries() int64 { return r.MaxSize } // GetCreatedAt 获取创建时间 func (r *RankingAggregate) GetCreatedAt() time.Time { return r.CreatedAt } // GetUpdatedAt 获取更新时间 func (r *RankingAggregate) GetUpdatedAt() time.Time { return r.UpdatedAt } // GetVersion 获取版本号 func (r *RankingAggregate) GetVersion() int64 { return r.Version } // IsRankingActive 检查排行榜是否激活 func (r *RankingAggregate) IsRankingActive() bool { return r.IsActive && r.Status == RankStatusActive } // GetBlacklist 获取黑名单 func (r *RankingAggregate) GetBlacklist() []uint64 { if r.Blacklist == nil { return []uint64{} } return r.Blacklist.GetPlayerIDs() } // GetSettings 获取设置 func (r *RankingAggregate) GetSettings() map[string]interface{} { settings := make(map[string]interface{}) settings["sort_type"] = r.SortType settings["time_bit_len"] = r.TimeBitLen settings["time_unit"] = r.TimeUnit settings["max_size"] = r.MaxSize return settings } // GetResetAt 获取重置时间 func (r *RankingAggregate) GetResetAt() time.Time { if r.ResetTime != nil { return *r.ResetTime } return time.Time{} } // SetID 设置ID func (r *RankingAggregate) SetID(id string) { r.ID = id } // SetDescription 设置描述 func (r *RankingAggregate) SetDescription(description string) { r.Description = description r.markDirty() } // SetMaxEntries 设置最大条目数 func (r *RankingAggregate) SetMaxEntries(maxEntries int64) { r.MaxSize = maxEntries r.markDirty() } // SetBlacklist 设置黑名单 func (r *RankingAggregate) SetBlacklist(playerIDs []uint64) { if r.Blacklist == nil { r.Blacklist = NewBlacklist(r.RankID) } for _, playerID := range playerIDs { err := r.Blacklist.AddPlayer(playerID, "Manual blacklist") if err != nil { // 忽略已存在的错误,继续处理其他玩家 continue } } r.markDirty() } // SetSettings 设置配置 func (r *RankingAggregate) SetSettings(settings map[string]interface{}) { if sortType, ok := settings["sort_type"]; ok { if st, ok := sortType.(SortType); ok { r.SortType = st } } if timeBitLen, ok := settings["time_bit_len"]; ok { if tbl, ok := timeBitLen.(uint32); ok { r.TimeBitLen = tbl } } if timeUnit, ok := settings["time_unit"]; ok { if tu, ok := timeUnit.(int64); ok { r.TimeUnit = tu } } if maxSize, ok := settings["max_size"]; ok { if ms, ok := maxSize.(int64); ok { r.MaxSize = ms } } r.markDirty() } // SetVersion 设置版本号 func (r *RankingAggregate) SetVersion(version int64) { r.Version = version } // Activate 激活排行榜 func (r *RankingAggregate) Activate() { r.IsActive = true r.Status = RankStatusActive r.markDirty() } // Deactivate 停用排行榜 func (r *RankingAggregate) Deactivate() { r.IsActive = false r.Status = RankStatusInactive r.markDirty() } // SetResetAt 设置重置时间 func (r *RankingAggregate) SetResetAt(resetAt time.Time) { r.ResetTime = &resetAt r.markDirty() } // ClearEvents 清除领域事件 func (r *RankingAggregate) ClearEvents() { r.mutex.Lock() defer r.mutex.Unlock() r.events = make([]RankingEvent, 0) } // IsDirty 检查是否有未保存的更改 func (r *RankingAggregate) IsDirty() bool { r.mutex.RLock() defer r.mutex.RUnlock() return r.dirty } // MarkClean 标记为已保存 func (r *RankingAggregate) MarkClean() { r.mutex.Lock() defer r.mutex.Unlock() r.dirty = false } // 私有方法 // calculateTimeScore 计算带时间因子的分数 func (r *RankingAggregate) calculateTimeScore(score int64) int64 { nowTime := time.Now().Unix() var timeFactor int64 if r.SortType == SortTypeDescending { timeFactor = (nowTime - r.StartTime) / r.TimeUnit } else { timeFactor = (r.EndTime - nowTime) / r.TimeUnit } timeScore := (score << r.TimeBitLen) | timeFactor return timeScore } // getRealScore 获取真实分数 func (r *RankingAggregate) getRealScore(timeScore int64) int64 { return timeScore >> r.TimeBitLen } // getRealScoreTime 获取分数设置时间 func (r *RankingAggregate) getRealScoreTime(timeScore int64) int64 { timeFactor := timeScore & ((1 << r.TimeBitLen) - 1) var realTime int64 if r.SortType == SortTypeDescending { realTime = (timeFactor * r.TimeUnit) + r.StartTime } else { realTime = r.EndTime - (timeFactor * r.TimeUnit) } return realTime } // findEntry 查找玩家条目 func (r *RankingAggregate) findEntry(playerID uint64) *RankEntry { for _, entry := range r.Entries { if entry.PlayerID == playerID { return entry } } return nil } // removePlayer 移除玩家 func (r *RankingAggregate) removePlayer(playerID uint64) { for i, entry := range r.Entries { if entry.PlayerID == playerID { r.Entries = append(r.Entries[:i], r.Entries[i+1:]...) r.TotalPlayers-- break } } } // sortEntries 排序条目 func (r *RankingAggregate) sortEntries() { if r.SortType == SortTypeDescending { sort.Slice(r.Entries, func(i, j int) bool { return r.Entries[i].TimeScore > r.Entries[j].TimeScore }) } else { sort.Slice(r.Entries, func(i, j int) bool { return r.Entries[i].TimeScore < r.Entries[j].TimeScore }) } } // limitSize 限制大小 func (r *RankingAggregate) limitSize() { if int64(len(r.Entries)) > r.MaxSize { r.Entries = r.Entries[:r.MaxSize] r.TotalPlayers = r.MaxSize } } // updateStatistics 更新统计信息 func (r *RankingAggregate) updateStatistics() { r.ActiveEntries = int64(len(r.Entries)) if len(r.Entries) > 0 { // 更新最高分 r.TopScore = r.getRealScore(r.Entries[0].TimeScore) // 计算平均分 totalScore := int64(0) for _, entry := range r.Entries { totalScore += r.getRealScore(entry.TimeScore) } r.AverageScore = float64(totalScore) / float64(len(r.Entries)) } else { r.TopScore = 0 r.AverageScore = 0.0 } r.LastScoreUpdate = time.Now() } // isInTimeRange 检查是否在时间范围内 func (r *RankingAggregate) isInTimeRange() bool { if r.EndTime == 0 { return true // 永久排行榜 } now := time.Now().Unix() return now >= r.StartTime && now <= r.EndTime } // addEvent 添加领域事件 func (r *RankingAggregate) addEvent(event RankingEvent) { r.events = append(r.events, event) } // markDirty 标记为脏数据 func (r *RankingAggregate) markDirty() { r.dirty = true r.LastUpdated = time.Now() r.UpdatedAt = time.Now() r.Version++ } // 辅助函数 // generateRankingID 生成排行榜ID func generateRankingID(rankID uint32) string { return fmt.Sprintf("ranking_%d", rankID) } // 常量定义 const ( // 默认配置 DefaultTimeBitLen = 24 DefaultTimeUnit = 60 DefaultMaxSize = 5000 // 排行榜限制 MaxRankingSize = 10000 MinRankingSize = 10 MaxNameLength = 100 MaxDescLength = 500 ) // 验证方法 // Validate 验证排行榜聚合 func (r *RankingAggregate) Validate() error { if r.RankID == 0 { return NewRankingValidationError("rank_id", r.RankID, "rank_id cannot be zero", "required") } if r.Name == "" { return NewRankingValidationError("name", r.Name, "name cannot be empty", "required") } if len(r.Name) > MaxNameLength { return NewRankingValidationError("name", r.Name, fmt.Sprintf("name length cannot exceed %d", MaxNameLength), "max_length") } if len(r.Description) > MaxDescLength { return NewRankingValidationError("description", r.Description, fmt.Sprintf("description length cannot exceed %d", MaxDescLength), "max_length") } if r.MaxSize < MinRankingSize || r.MaxSize > MaxRankingSize { return NewRankingValidationError("max_size", r.MaxSize, fmt.Sprintf("max_size must be between %d and %d", MinRankingSize, MaxRankingSize), "range") } if r.TimeBitLen == 0 || r.TimeBitLen > 32 { return NewRankingValidationError("time_bit_len", r.TimeBitLen, "time_bit_len must be between 1 and 32", "range") } if r.TimeUnit <= 0 { return NewRankingValidationError("time_unit", r.TimeUnit, "time_unit must be positive", "positive") } if r.EndTime != 0 && r.StartTime >= r.EndTime { return NewRankingValidationError("time_range", map[string]int64{"start": r.StartTime, "end": r.EndTime}, "start_time must be less than end_time", "time_range") } return nil } // Clone 克隆排行榜聚合 func (r *RankingAggregate) Clone() *RankingAggregate { r.mutex.RLock() defer r.mutex.RUnlock() clone := &RankingAggregate{ ID: r.ID, RankID: r.RankID, Name: r.Name, Description: r.Description, RankType: r.RankType, Category: r.Category, SortType: r.SortType, TimeBitLen: r.TimeBitLen, TimeUnit: r.TimeUnit, MaxSize: r.MaxSize, StartTime: r.StartTime, EndTime: r.EndTime, Period: r.Period, Status: r.Status, IsActive: r.IsActive, LastUpdated: r.LastUpdated, Version: r.Version, TotalPlayers: r.TotalPlayers, ActiveEntries: r.ActiveEntries, AverageScore: r.AverageScore, TopScore: r.TopScore, LastScoreUpdate: r.LastScoreUpdate, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, dirty: r.dirty, } // 深拷贝重置时间 if r.ResetTime != nil { resetTime := *r.ResetTime clone.ResetTime = &resetTime } // 深拷贝条目 clone.Entries = make([]*RankEntry, len(r.Entries)) for i, entry := range r.Entries { entryCopy := *entry clone.Entries[i] = &entryCopy } // 深拷贝黑名单 if r.Blacklist != nil { clone.Blacklist = r.Blacklist.Clone() } // 深拷贝奖励配置 if r.RewardConfig != nil { rewardConfig := *r.RewardConfig clone.RewardConfig = &rewardConfig } // 深拷贝缓存配置 if r.CacheConfig != nil { cacheConfig := *r.CacheConfig clone.CacheConfig = &cacheConfig } // 深拷贝事件 clone.events = make([]RankingEvent, len(r.events)) copy(clone.events, r.events) return clone } ================================================ FILE: internal/domain/ranking/entity.go ================================================ package ranking import ( "fmt" "sync" "time" ) // RankEntry 排行榜条目实体 type RankEntry struct { // 基础信息 ID string `json:"id" bson:"_id"` PlayerID uint64 `json:"player_id" bson:"player_id"` RankID uint32 `json:"rank_id" bson:"rank_id"` // 分数信息 Score int64 `json:"score" bson:"score"` // 真实分数 TimeScore int64 `json:"time_score" bson:"time_score"` // 带时间因子的分数 Rank int64 `json:"rank" bson:"rank"` // 当前排名 // 玩家信息 PlayerName string `json:"player_name" bson:"player_name"` PlayerLevel uint32 `json:"player_level" bson:"player_level"` PlayerAvatar string `json:"player_avatar" bson:"player_avatar"` PlayerTitle string `json:"player_title" bson:"player_title"` // 状态信息 IsActive bool `json:"is_active" bson:"is_active"` LastActive time.Time `json:"last_active" bson:"last_active"` ScoreHistory []*ScoreHistoryEntry `json:"score_history" bson:"score_history"` // 排名变化 PreviousRank *int64 `json:"previous_rank,omitempty" bson:"previous_rank,omitempty"` RankChange int64 `json:"rank_change" bson:"rank_change"` BestRank int64 `json:"best_rank" bson:"best_rank"` WorstRank int64 `json:"worst_rank" bson:"worst_rank"` // 统计信息 TotalUpdates int64 `json:"total_updates" bson:"total_updates"` ConsecutiveDays int32 `json:"consecutive_days" bson:"consecutive_days"` FirstEntryTime time.Time `json:"first_entry_time" bson:"first_entry_time"` LastUpdateTime time.Time `json:"last_update_time" bson:"last_update_time"` // 奖励信息 RewardsEarned []*RankRewardEarned `json:"rewards_earned" bson:"rewards_earned"` LastRewardTime *time.Time `json:"last_reward_time,omitempty" bson:"last_reward_time,omitempty"` TotalRewardValue int64 `json:"total_reward_value" bson:"total_reward_value"` // 元数据 Metadata map[string]interface{} `json:"metadata" bson:"metadata"` Tags []string `json:"tags" bson:"tags"` CustomData map[string]interface{} `json:"custom_data" bson:"custom_data"` // 时间戳 CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` // 内部状态 mutex sync.RWMutex `json:"-" bson:"-"` } // GetID 获取条目ID func (re *RankEntry) GetID() string { re.mutex.RLock() defer re.mutex.RUnlock() return re.ID } // GetRankingID 获取排行榜ID func (re *RankEntry) GetRankingID() uint32 { re.mutex.RLock() defer re.mutex.RUnlock() return re.RankID } // GetPlayerID 获取玩家ID func (re *RankEntry) GetPlayerID() uint64 { re.mutex.RLock() defer re.mutex.RUnlock() return re.PlayerID } // GetRank 获取排名 func (re *RankEntry) GetRank() int64 { re.mutex.RLock() defer re.mutex.RUnlock() return re.Rank } // GetScore 获取分数 func (re *RankEntry) GetScore() int64 { re.mutex.RLock() defer re.mutex.RUnlock() return re.Score } // GetPrevRank 获取前一排名 func (re *RankEntry) GetPrevRank() *int64 { re.mutex.RLock() defer re.mutex.RUnlock() return re.PreviousRank } // GetPrevScore 获取前一分数 (暂时返回0,可根据需要实现) func (re *RankEntry) GetPrevScore() int64 { re.mutex.RLock() defer re.mutex.RUnlock() // 从历史记录中获取前一分数 if len(re.ScoreHistory) > 0 { return re.ScoreHistory[len(re.ScoreHistory)-1].Score } return 0 } // GetMetadata 获取元数据 func (re *RankEntry) GetMetadata() map[string]interface{} { re.mutex.RLock() defer re.mutex.RUnlock() return re.Metadata } // GetCreatedAt 获取创建时间 func (re *RankEntry) GetCreatedAt() time.Time { re.mutex.RLock() defer re.mutex.RUnlock() return re.CreatedAt } // GetUpdatedAt 获取更新时间 func (re *RankEntry) GetUpdatedAt() time.Time { re.mutex.RLock() defer re.mutex.RUnlock() return re.UpdatedAt } // SetRank 设置排名 func (re *RankEntry) SetRank(rank int64) { re.mutex.Lock() defer re.mutex.Unlock() re.Rank = rank re.UpdatedAt = time.Now() } // SetPrevious 设置前一排名和分数 func (re *RankEntry) SetPrevious(prevRank *int64, prevScore int64) { re.mutex.Lock() defer re.mutex.Unlock() re.PreviousRank = prevRank // 可以添加前一分数的存储逻辑 re.UpdatedAt = time.Now() } // SetMetadata 设置元数据 func (re *RankEntry) SetMetadata(metadata map[string]interface{}) { re.mutex.Lock() defer re.mutex.Unlock() re.Metadata = metadata re.UpdatedAt = time.Now() } // NewRankEntry 创建新的排行榜条目 func NewRankEntry(playerID uint64, timeScore, realScore int64, metadata map[string]interface{}) *RankEntry { now := time.Now() return &RankEntry{ ID: generateRankEntryID(playerID, now), PlayerID: playerID, Score: realScore, TimeScore: timeScore, Rank: 0, // 将在排序后设置 IsActive: true, LastActive: now, ScoreHistory: make([]*ScoreHistoryEntry, 0), RankChange: 0, BestRank: 0, WorstRank: 0, TotalUpdates: 1, ConsecutiveDays: 1, FirstEntryTime: now, LastUpdateTime: now, RewardsEarned: make([]*RankRewardEarned, 0), TotalRewardValue: 0, Metadata: metadata, Tags: make([]string, 0), CustomData: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // NewRankEntryFromRepository 创建新的排行榜条目(用于repository) func NewRankEntryFromRepository(entryID string, rankingID uint32, playerID uint64, score int64) *RankEntry { now := time.Now() return &RankEntry{ ID: entryID, PlayerID: playerID, RankID: rankingID, Score: score, TimeScore: score, Rank: 0, IsActive: true, LastActive: now, ScoreHistory: make([]*ScoreHistoryEntry, 0), RankChange: 0, BestRank: 0, WorstRank: 0, TotalUpdates: 1, ConsecutiveDays: 1, FirstEntryTime: now, LastUpdateTime: now, RewardsEarned: make([]*RankRewardEarned, 0), TotalRewardValue: 0, Metadata: make(map[string]interface{}), Tags: make([]string, 0), CustomData: make(map[string]interface{}), CreatedAt: now, UpdatedAt: now, } } // UpdateScore 更新分数 func (re *RankEntry) UpdateScore(timeScore, realScore int64, metadata map[string]interface{}) { re.mutex.Lock() defer re.mutex.Unlock() // 记录历史分数 re.addScoreHistory(re.Score, re.TimeScore) // 更新分数 re.Score = realScore re.TimeScore = timeScore re.TotalUpdates++ re.LastUpdateTime = time.Now() re.UpdatedAt = time.Now() re.LastActive = time.Now() // 更新元数据 if metadata != nil { for k, v := range metadata { re.Metadata[k] = v } } // 更新连续天数 re.updateConsecutiveDays() } // UpdateRank 更新排名 func (re *RankEntry) UpdateRank(newRank int64) { re.mutex.Lock() defer re.mutex.Unlock() // 记录排名变化 if re.Rank != 0 { re.PreviousRank = &re.Rank re.RankChange = re.Rank - newRank } // 更新排名 re.Rank = newRank // 更新最佳和最差排名 if re.BestRank == 0 || newRank < re.BestRank { re.BestRank = newRank } if re.WorstRank == 0 || newRank > re.WorstRank { re.WorstRank = newRank } re.UpdatedAt = time.Now() } // UpdatePlayerInfo 更新玩家信息 func (re *RankEntry) UpdatePlayerInfo(name string, level uint32, avatar, title string) { re.mutex.Lock() defer re.mutex.Unlock() re.PlayerName = name re.PlayerLevel = level re.PlayerAvatar = avatar re.PlayerTitle = title re.UpdatedAt = time.Now() } // AddReward 添加奖励 func (re *RankEntry) AddReward(reward *RankRewardEarned) { re.mutex.Lock() defer re.mutex.Unlock() re.RewardsEarned = append(re.RewardsEarned, reward) re.TotalRewardValue += reward.Value now := time.Now() re.LastRewardTime = &now re.UpdatedAt = now } // SetActive 设置活跃状态 func (re *RankEntry) SetActive(active bool) { re.mutex.Lock() defer re.mutex.Unlock() re.IsActive = active if active { re.LastActive = time.Now() } re.UpdatedAt = time.Now() } // AddTag 添加标签 func (re *RankEntry) AddTag(tag string) { re.mutex.Lock() defer re.mutex.Unlock() // 检查是否已存在 for _, existingTag := range re.Tags { if existingTag == tag { return } } re.Tags = append(re.Tags, tag) re.UpdatedAt = time.Now() } // RemoveTag 移除标签 func (re *RankEntry) RemoveTag(tag string) { re.mutex.Lock() defer re.mutex.Unlock() for i, existingTag := range re.Tags { if existingTag == tag { re.Tags = append(re.Tags[:i], re.Tags[i+1:]...) re.UpdatedAt = time.Now() return } } } // SetCustomData 设置自定义数据 func (re *RankEntry) SetCustomData(key string, value interface{}) { re.mutex.Lock() defer re.mutex.Unlock() if re.CustomData == nil { re.CustomData = make(map[string]interface{}) } re.CustomData[key] = value re.UpdatedAt = time.Now() } // GetCustomData 获取自定义数据 func (re *RankEntry) GetCustomData(key string) (interface{}, bool) { re.mutex.RLock() defer re.mutex.RUnlock() value, exists := re.CustomData[key] return value, exists } // GetScoreHistory 获取分数历史 func (re *RankEntry) GetScoreHistory(limit int) []*ScoreHistoryEntry { re.mutex.RLock() defer re.mutex.RUnlock() if limit <= 0 || limit > len(re.ScoreHistory) { limit = len(re.ScoreHistory) } // 返回最近的记录 start := len(re.ScoreHistory) - limit history := make([]*ScoreHistoryEntry, limit) copy(history, re.ScoreHistory[start:]) return history } // GetRecentRewards 获取最近的奖励 func (re *RankEntry) GetRecentRewards(limit int) []*RankRewardEarned { re.mutex.RLock() defer re.mutex.RUnlock() if limit <= 0 || limit > len(re.RewardsEarned) { limit = len(re.RewardsEarned) } // 返回最近的奖励 start := len(re.RewardsEarned) - limit rewards := make([]*RankRewardEarned, limit) copy(rewards, re.RewardsEarned[start:]) return rewards } // IsRankImproved 检查排名是否提升 func (re *RankEntry) IsRankImproved() bool { re.mutex.RLock() defer re.mutex.RUnlock() return re.RankChange > 0 } // GetRankChangeDirection 获取排名变化方向 func (re *RankEntry) GetRankChangeDirection() string { re.mutex.RLock() defer re.mutex.RUnlock() if re.RankChange > 0 { return "up" } else if re.RankChange < 0 { return "down" } return "unchanged" } // 私有方法 // addScoreHistory 添加分数历史 func (re *RankEntry) addScoreHistory(score, timeScore int64) { history := &ScoreHistoryEntry{ Score: score, TimeScore: timeScore, Timestamp: time.Now(), } re.ScoreHistory = append(re.ScoreHistory, history) // 限制历史记录数量 if len(re.ScoreHistory) > MaxScoreHistorySize { re.ScoreHistory = re.ScoreHistory[1:] } } // updateConsecutiveDays 更新连续天数 func (re *RankEntry) updateConsecutiveDays() { now := time.Now() lastUpdate := re.LastUpdateTime // 检查是否是连续的天 if now.Sub(lastUpdate) <= 24*time.Hour { // 同一天或连续天 if now.Day() != lastUpdate.Day() { re.ConsecutiveDays++ } } else { // 中断了连续性 re.ConsecutiveDays = 1 } } // ScoreHistoryEntry 分数历史条目 type ScoreHistoryEntry struct { Score int64 `json:"score" bson:"score"` TimeScore int64 `json:"time_score" bson:"time_score"` Timestamp time.Time `json:"timestamp" bson:"timestamp"` Reason string `json:"reason,omitempty" bson:"reason,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty" bson:"metadata,omitempty"` } // RankRewardEarned 已获得的排行榜奖励 type RankRewardEarned struct { RewardID string `json:"reward_id" bson:"reward_id"` RewardType string `json:"reward_type" bson:"reward_type"` Quantity int64 `json:"quantity" bson:"quantity"` Value int64 `json:"value" bson:"value"` Rank int64 `json:"rank" bson:"rank"` RankTier string `json:"rank_tier" bson:"rank_tier"` EarnedAt time.Time `json:"earned_at" bson:"earned_at"` ClaimedAt *time.Time `json:"claimed_at,omitempty" bson:"claimed_at,omitempty"` IsClaimed bool `json:"is_claimed" bson:"is_claimed"` Description string `json:"description" bson:"description"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` } // NewRankRewardEarned 创建新的已获得奖励 func NewRankRewardEarned(rewardID, rewardType string, quantity, value, rank int64, tier, description string) *RankRewardEarned { return &RankRewardEarned{ RewardID: rewardID, RewardType: rewardType, Quantity: quantity, Value: value, Rank: rank, RankTier: tier, EarnedAt: time.Now(), IsClaimed: false, Description: description, Metadata: make(map[string]interface{}), } } // Claim 领取奖励 func (rre *RankRewardEarned) Claim() { if !rre.IsClaimed { now := time.Now() rre.ClaimedAt = &now rre.IsClaimed = true } } // Blacklist 黑名单实体 type Blacklist struct { // 基础信息 ID string `json:"id" bson:"_id"` RankID uint32 `json:"rank_id" bson:"rank_id"` // 黑名单玩家 Players map[uint64]*BlacklistEntry `json:"players" bson:"players"` // 统计信息 TotalBlacklisted int64 `json:"total_blacklisted" bson:"total_blacklisted"` LastUpdated time.Time `json:"last_updated" bson:"last_updated"` // 时间戳 CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` // 内部状态 mutex sync.RWMutex `json:"-" bson:"-"` } // NewBlacklist 创建新的黑名单 func NewBlacklist(rankID uint32) *Blacklist { now := time.Now() return &Blacklist{ ID: generateBlacklistID(rankID), RankID: rankID, Players: make(map[uint64]*BlacklistEntry), TotalBlacklisted: 0, LastUpdated: now, CreatedAt: now, UpdatedAt: now, } } // AddPlayer 添加玩家到黑名单 func (bl *Blacklist) AddPlayer(playerID uint64, reason string) error { bl.mutex.Lock() defer bl.mutex.Unlock() // 检查是否已在黑名单中 if _, exists := bl.Players[playerID]; exists { return NewPlayerAlreadyBlacklistedError(playerID, bl.RankID) } // 添加到黑名单 entry := NewBlacklistEntry(playerID, reason) bl.Players[playerID] = entry bl.TotalBlacklisted++ bl.LastUpdated = time.Now() bl.UpdatedAt = time.Now() return nil } // RemovePlayer 从黑名单移除玩家 func (bl *Blacklist) RemovePlayer(playerID uint64) error { bl.mutex.Lock() defer bl.mutex.Unlock() // 检查是否在黑名单中 if _, exists := bl.Players[playerID]; !exists { return NewPlayerNotBlacklistedError(playerID, bl.RankID) } // 从黑名单移除 delete(bl.Players, playerID) bl.TotalBlacklisted-- bl.LastUpdated = time.Now() bl.UpdatedAt = time.Now() return nil } // IsBlacklisted 检查玩家是否在黑名单中 func (bl *Blacklist) IsBlacklisted(playerID uint64) bool { bl.mutex.RLock() defer bl.mutex.RUnlock() _, exists := bl.Players[playerID] return exists } // GetBlacklistEntry 获取黑名单条目 func (bl *Blacklist) GetBlacklistEntry(playerID uint64) (*BlacklistEntry, bool) { bl.mutex.RLock() defer bl.mutex.RUnlock() entry, exists := bl.Players[playerID] return entry, exists } // GetAllPlayers 获取所有黑名单玩家 func (bl *Blacklist) GetAllPlayers() []*BlacklistEntry { bl.mutex.RLock() defer bl.mutex.RUnlock() entries := make([]*BlacklistEntry, 0, len(bl.Players)) for _, entry := range bl.Players { entries = append(entries, entry) } return entries } // GetPlayersByReason 根据原因获取黑名单玩家 func (bl *Blacklist) GetPlayersByReason(reason string) []*BlacklistEntry { bl.mutex.RLock() defer bl.mutex.RUnlock() entries := make([]*BlacklistEntry, 0) for _, entry := range bl.Players { if entry.Reason == reason { entries = append(entries, entry) } } return entries } // GetPlayerIDs 获取所有黑名单玩家ID func (bl *Blacklist) GetPlayerIDs() []uint64 { bl.mutex.RLock() defer bl.mutex.RUnlock() ids := make([]uint64, 0, len(bl.Players)) for playerID := range bl.Players { ids = append(ids, playerID) } return ids } // Clear 清空黑名单 func (bl *Blacklist) Clear() { bl.mutex.Lock() defer bl.mutex.Unlock() bl.Players = make(map[uint64]*BlacklistEntry) bl.TotalBlacklisted = 0 bl.LastUpdated = time.Now() bl.UpdatedAt = time.Now() } // Clone 克隆黑名单 func (bl *Blacklist) Clone() *Blacklist { bl.mutex.RLock() defer bl.mutex.RUnlock() clone := &Blacklist{ ID: bl.ID, RankID: bl.RankID, Players: make(map[uint64]*BlacklistEntry), TotalBlacklisted: bl.TotalBlacklisted, LastUpdated: bl.LastUpdated, CreatedAt: bl.CreatedAt, UpdatedAt: bl.UpdatedAt, } // 深拷贝玩家条目 for playerID, entry := range bl.Players { entryCopy := *entry clone.Players[playerID] = &entryCopy } return clone } // BlacklistEntry 黑名单条目 type BlacklistEntry struct { PlayerID uint64 `json:"player_id" bson:"player_id"` Reason string `json:"reason" bson:"reason"` AddedAt time.Time `json:"added_at" bson:"added_at"` AddedBy string `json:"added_by" bson:"added_by"` ExpiresAt *time.Time `json:"expires_at,omitempty" bson:"expires_at,omitempty"` IsPermanent bool `json:"is_permanent" bson:"is_permanent"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` } // NewBlacklistEntry 创建新的黑名单条目 func NewBlacklistEntry(playerID uint64, reason string) *BlacklistEntry { return &BlacklistEntry{ PlayerID: playerID, Reason: reason, AddedAt: time.Now(), IsPermanent: true, Metadata: make(map[string]interface{}), } } // NewTemporaryBlacklistEntry 创建临时黑名单条目 func NewTemporaryBlacklistEntry(playerID uint64, reason string, duration time.Duration) *BlacklistEntry { expiresAt := time.Now().Add(duration) return &BlacklistEntry{ PlayerID: playerID, Reason: reason, AddedAt: time.Now(), ExpiresAt: &expiresAt, IsPermanent: false, Metadata: make(map[string]interface{}), } } // IsExpired 检查是否已过期 func (ble *BlacklistEntry) IsExpired() bool { if ble.IsPermanent || ble.ExpiresAt == nil { return false } return time.Now().After(*ble.ExpiresAt) } // GetRemainingTime 获取剩余时间 func (ble *BlacklistEntry) GetRemainingTime() time.Duration { if ble.IsPermanent || ble.ExpiresAt == nil { return 0 } remaining := ble.ExpiresAt.Sub(time.Now()) if remaining < 0 { return 0 } return remaining } // SetExpiration 设置过期时间 func (ble *BlacklistEntry) SetExpiration(expiresAt time.Time) { ble.ExpiresAt = &expiresAt ble.IsPermanent = false } // SetPermanent 设置为永久 func (ble *BlacklistEntry) SetPermanent() { ble.ExpiresAt = nil ble.IsPermanent = true } // SetMetadata 设置元数据 func (ble *BlacklistEntry) SetMetadata(key string, value interface{}) { if ble.Metadata == nil { ble.Metadata = make(map[string]interface{}) } ble.Metadata[key] = value } // GetMetadata 获取元数据 func (ble *BlacklistEntry) GetMetadata(key string) (interface{}, bool) { value, exists := ble.Metadata[key] return value, exists } // 常量定义 const ( // 历史记录限制 MaxScoreHistorySize = 100 MaxRewardHistorySize = 50 // 黑名单限制 MaxBlacklistSize = 10000 // 标签限制 MaxTagsPerEntry = 10 MaxTagLength = 50 // 元数据限制 MaxMetadataSize = 1024 * 1024 // 1MB ) // 辅助函数 // generateRankEntryID 生成排行榜条目ID func generateRankEntryID(playerID uint64, timestamp time.Time) string { return fmt.Sprintf("rank_entry_%d_%d", playerID, timestamp.Unix()) } // generateBlacklistID 生成黑名单ID func generateBlacklistID(rankID uint32) string { return fmt.Sprintf("blacklist_%d", rankID) } // 验证方法 // ValidateRankEntry 验证排行榜条目 func ValidateRankEntry(entry *RankEntry) error { if entry == nil { return fmt.Errorf("rank entry cannot be nil") } if entry.PlayerID == 0 { return fmt.Errorf("player ID cannot be zero") } if entry.RankID == 0 { return fmt.Errorf("rank ID cannot be zero") } if entry.Score < 0 { return fmt.Errorf("score cannot be negative") } if entry.Rank < 0 { return fmt.Errorf("rank cannot be negative") } if len(entry.Tags) > MaxTagsPerEntry { return fmt.Errorf("too many tags: max %d, got %d", MaxTagsPerEntry, len(entry.Tags)) } for _, tag := range entry.Tags { if len(tag) > MaxTagLength { return fmt.Errorf("tag too long: max %d, got %d", MaxTagLength, len(tag)) } } return nil } // ValidateBlacklistEntry 验证黑名单条目 func ValidateBlacklistEntry(entry *BlacklistEntry) error { if entry == nil { return fmt.Errorf("blacklist entry cannot be nil") } if entry.PlayerID == 0 { return fmt.Errorf("player ID cannot be zero") } if entry.Reason == "" { return fmt.Errorf("reason cannot be empty") } if len(entry.Reason) > 500 { return fmt.Errorf("reason too long: max 500, got %d", len(entry.Reason)) } if !entry.IsPermanent && entry.ExpiresAt == nil { return fmt.Errorf("temporary blacklist entry must have expiration time") } if entry.ExpiresAt != nil && entry.ExpiresAt.Before(entry.AddedAt) { return fmt.Errorf("expiration time cannot be before added time") } return nil } ================================================ FILE: internal/domain/ranking/errors.go ================================================ package ranking import ( "fmt" "time" ) // 排行榜领域错误定义 // RankingError 排行榜错误基础接口 type RankingError interface { error GetCode() string GetMessage() string GetDetails() map[string]interface{} IsRetryable() bool GetSeverity() ErrorSeverity } // ErrorSeverity 错误严重程度 type ErrorSeverity int const ( ErrorSeverityLow ErrorSeverity = iota ErrorSeverityMedium ErrorSeverityHigh ErrorSeverityCritical ) // String 返回错误严重程度的字符串表示 func (s ErrorSeverity) String() string { switch s { case ErrorSeverityLow: return "low" case ErrorSeverityMedium: return "medium" case ErrorSeverityHigh: return "high" case ErrorSeverityCritical: return "critical" default: return "unknown" } } // BaseRankingError 排行榜错误基础结构 type BaseRankingError struct { Code string `json:"code"` Message string `json:"message"` Details map[string]interface{} `json:"details"` Retryable bool `json:"retryable"` Severity ErrorSeverity `json:"severity"` } // Error 实现error接口 func (e *BaseRankingError) Error() string { return fmt.Sprintf("[%s] %s", e.Code, e.Message) } // GetCode 获取错误代码 func (e *BaseRankingError) GetCode() string { return e.Code } // GetMessage 获取错误消息 func (e *BaseRankingError) GetMessage() string { return e.Message } // GetDetails 获取错误详情 func (e *BaseRankingError) GetDetails() map[string]interface{} { return e.Details } // IsRetryable 是否可重试 func (e *BaseRankingError) IsRetryable() bool { return e.Retryable } // GetSeverity 获取错误严重程度 func (e *BaseRankingError) GetSeverity() ErrorSeverity { return e.Severity } // 排行榜相关错误 // RankingNotFoundError 排行榜未找到错误 type RankingNotFoundError struct { *BaseRankingError RankID uint32 `json:"rank_id"` } // NewRankingNotFoundError 创建排行榜未找到错误 func NewRankingNotFoundError(rankID uint32) *RankingNotFoundError { return &RankingNotFoundError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_NOT_FOUND", Message: fmt.Sprintf("Ranking with ID %d not found", rankID), Details: map[string]interface{}{"rank_id": rankID}, Retryable: false, Severity: ErrorSeverityMedium, }, RankID: rankID, } } // RankingAlreadyExistsError 排行榜已存在错误 type RankingAlreadyExistsError struct { *BaseRankingError RankID uint32 `json:"rank_id"` } // NewRankingAlreadyExistsError 创建排行榜已存在错误 func NewRankingAlreadyExistsError(rankID uint32) *RankingAlreadyExistsError { return &RankingAlreadyExistsError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_ALREADY_EXISTS", Message: fmt.Sprintf("Ranking with ID %d already exists", rankID), Details: map[string]interface{}{"rank_id": rankID}, Retryable: false, Severity: ErrorSeverityMedium, }, RankID: rankID, } } // RankingInactiveError 排行榜非活跃错误 type RankingInactiveError struct { *BaseRankingError RankID uint32 `json:"rank_id"` Status RankStatus `json:"status"` } // NewRankingInactiveError 创建排行榜非活跃错误 func NewRankingInactiveError(rankID uint32) *RankingInactiveError { return &RankingInactiveError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_INACTIVE", Message: fmt.Sprintf("Ranking %d is not active", rankID), Details: map[string]interface{}{"rank_id": rankID}, Retryable: false, Severity: ErrorSeverityMedium, }, RankID: rankID, } } // RankingTimeExpiredError 排行榜时间过期错误 type RankingTimeExpiredError struct { *BaseRankingError RankID uint32 `json:"rank_id"` StartTime int64 `json:"start_time"` EndTime int64 `json:"end_time"` } // NewRankingTimeExpiredError 创建排行榜时间过期错误 func NewRankingTimeExpiredError(rankID uint32, startTime, endTime int64) *RankingTimeExpiredError { return &RankingTimeExpiredError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_TIME_EXPIRED", Message: fmt.Sprintf("Ranking %d is outside valid time range", rankID), Details: map[string]interface{}{ "rank_id": rankID, "start_time": startTime, "end_time": endTime, }, Retryable: false, Severity: ErrorSeverityMedium, }, RankID: rankID, StartTime: startTime, EndTime: endTime, } } // RankingFullError 排行榜已满错误 type RankingFullError struct { *BaseRankingError RankID uint32 `json:"rank_id"` MaxSize int64 `json:"max_size"` CurrentSize int64 `json:"current_size"` } // NewRankingFullError 创建排行榜已满错误 func NewRankingFullError(rankID uint32, maxSize, currentSize int64) *RankingFullError { return &RankingFullError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_FULL", Message: fmt.Sprintf("Ranking %d is full (%d/%d)", rankID, currentSize, maxSize), Details: map[string]interface{}{ "rank_id": rankID, "max_size": maxSize, "current_size": currentSize, }, Retryable: false, Severity: ErrorSeverityMedium, }, RankID: rankID, MaxSize: maxSize, CurrentSize: currentSize, } } // 玩家相关错误 // PlayerNotInRankingError 玩家不在排行榜错误 type PlayerNotInRankingError struct { *BaseRankingError PlayerID uint64 `json:"player_id"` RankID uint32 `json:"rank_id"` } // NewPlayerNotInRankingError 创建玩家不在排行榜错误 func NewPlayerNotInRankingError(playerID uint64, rankID uint32) *PlayerNotInRankingError { return &PlayerNotInRankingError{ BaseRankingError: &BaseRankingError{ Code: "PLAYER_NOT_IN_RANKING", Message: fmt.Sprintf("Player %d not found in ranking %d", playerID, rankID), Details: map[string]interface{}{"player_id": playerID, "rank_id": rankID}, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, RankID: rankID, } } // PlayerAlreadyInRankingError 玩家已在排行榜错误 type PlayerAlreadyInRankingError struct { *BaseRankingError PlayerID uint64 `json:"player_id"` RankID uint32 `json:"rank_id"` CurrentRank int64 `json:"current_rank"` } // NewPlayerAlreadyInRankingError 创建玩家已在排行榜错误 func NewPlayerAlreadyInRankingError(playerID uint64, rankID uint32, currentRank int64) *PlayerAlreadyInRankingError { return &PlayerAlreadyInRankingError{ BaseRankingError: &BaseRankingError{ Code: "PLAYER_ALREADY_IN_RANKING", Message: fmt.Sprintf("Player %d already exists in ranking %d at rank %d", playerID, rankID, currentRank), Details: map[string]interface{}{ "player_id": playerID, "rank_id": rankID, "current_rank": currentRank, }, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, RankID: rankID, CurrentRank: currentRank, } } // PlayerBlacklistedError 玩家被黑名单错误 type PlayerBlacklistedError struct { *BaseRankingError PlayerID uint64 `json:"player_id"` RankID uint32 `json:"rank_id"` Reason string `json:"reason"` } // NewPlayerBlacklistedError 创建玩家被黑名单错误 func NewPlayerBlacklistedError(playerID uint64, rankID uint32) *PlayerBlacklistedError { return &PlayerBlacklistedError{ BaseRankingError: &BaseRankingError{ Code: "PLAYER_BLACKLISTED", Message: fmt.Sprintf("Player %d is blacklisted in ranking %d", playerID, rankID), Details: map[string]interface{}{"player_id": playerID, "rank_id": rankID}, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, RankID: rankID, } } // PlayerAlreadyBlacklistedError 玩家已被黑名单错误 type PlayerAlreadyBlacklistedError struct { *BaseRankingError PlayerID uint64 `json:"player_id"` RankID uint32 `json:"rank_id"` } // NewPlayerAlreadyBlacklistedError 创建玩家已被黑名单错误 func NewPlayerAlreadyBlacklistedError(playerID uint64, rankID uint32) *PlayerAlreadyBlacklistedError { return &PlayerAlreadyBlacklistedError{ BaseRankingError: &BaseRankingError{ Code: "PLAYER_ALREADY_BLACKLISTED", Message: fmt.Sprintf("Player %d is already blacklisted in ranking %d", playerID, rankID), Details: map[string]interface{}{"player_id": playerID, "rank_id": rankID}, Retryable: false, Severity: ErrorSeverityLow, }, PlayerID: playerID, RankID: rankID, } } // PlayerNotBlacklistedError 玩家未被黑名单错误 type PlayerNotBlacklistedError struct { *BaseRankingError PlayerID uint64 `json:"player_id"` RankID uint32 `json:"rank_id"` } // NewPlayerNotBlacklistedError 创建玩家未被黑名单错误 func NewPlayerNotBlacklistedError(playerID uint64, rankID uint32) *PlayerNotBlacklistedError { return &PlayerNotBlacklistedError{ BaseRankingError: &BaseRankingError{ Code: "PLAYER_NOT_BLACKLISTED", Message: fmt.Sprintf("Player %d is not blacklisted in ranking %d", playerID, rankID), Details: map[string]interface{}{"player_id": playerID, "rank_id": rankID}, Retryable: false, Severity: ErrorSeverityMedium, }, PlayerID: playerID, RankID: rankID, } } // 范围和参数相关错误 // InvalidRangeError 无效范围错误 type InvalidRangeError struct { *BaseRankingError Start int64 `json:"start"` End int64 `json:"end"` } // NewInvalidRangeError 创建无效范围错误 func NewInvalidRangeError(start, end int64) *InvalidRangeError { return &InvalidRangeError{ BaseRankingError: &BaseRankingError{ Code: "INVALID_RANGE", Message: fmt.Sprintf("Invalid range: start=%d, end=%d", start, end), Details: map[string]interface{}{"start": start, "end": end}, Retryable: false, Severity: ErrorSeverityMedium, }, Start: start, End: end, } } // InvalidTimeRangeError 无效时间范围错误 type InvalidTimeRangeError struct { *BaseRankingError StartTime int64 `json:"start_time"` EndTime int64 `json:"end_time"` } // NewInvalidTimeRangeError 创建无效时间范围错误 func NewInvalidTimeRangeError(startTime, endTime int64) *InvalidTimeRangeError { return &InvalidTimeRangeError{ BaseRankingError: &BaseRankingError{ Code: "INVALID_TIME_RANGE", Message: fmt.Sprintf("Invalid time range: start=%d, end=%d", startTime, endTime), Details: map[string]interface{}{"start_time": startTime, "end_time": endTime}, Retryable: false, Severity: ErrorSeverityMedium, }, StartTime: startTime, EndTime: endTime, } } // InvalidScoreError 无效分数错误 type InvalidScoreError struct { *BaseRankingError Score int64 `json:"score"` MinScore *int64 `json:"min_score,omitempty"` MaxScore *int64 `json:"max_score,omitempty"` } // NewInvalidScoreError 创建无效分数错误 func NewInvalidScoreError(score int64, minScore, maxScore *int64) *InvalidScoreError { return &InvalidScoreError{ BaseRankingError: &BaseRankingError{ Code: "INVALID_SCORE", Message: fmt.Sprintf("Invalid score: %d", score), Details: map[string]interface{}{"score": score, "min_score": minScore, "max_score": maxScore}, Retryable: false, Severity: ErrorSeverityMedium, }, Score: score, MinScore: minScore, MaxScore: maxScore, } } // 权限相关错误 // InsufficientPermissionError 权限不足错误 type InsufficientPermissionError struct { *BaseRankingError UserID string `json:"user_id"` RequiredPermission string `json:"required_permission"` Operation string `json:"operation"` } // NewInsufficientPermissionError 创建权限不足错误 func NewInsufficientPermissionError(userID, operation, permission string) *InsufficientPermissionError { return &InsufficientPermissionError{ BaseRankingError: &BaseRankingError{ Code: "INSUFFICIENT_PERMISSION", Message: fmt.Sprintf("User %s lacks permission %s for operation %s", userID, permission, operation), Details: map[string]interface{}{ "user_id": userID, "required_permission": permission, "operation": operation, }, Retryable: false, Severity: ErrorSeverityHigh, }, UserID: userID, RequiredPermission: permission, Operation: operation, } } // OperationNotAllowedError 操作不允许错误 type OperationNotAllowedError struct { *BaseRankingError Operation string `json:"operation"` RankID uint32 `json:"rank_id"` Reason string `json:"reason"` } // NewOperationNotAllowedError 创建操作不允许错误 func NewOperationNotAllowedError(operation string, rankID uint32, reason string) *OperationNotAllowedError { return &OperationNotAllowedError{ BaseRankingError: &BaseRankingError{ Code: "OPERATION_NOT_ALLOWED", Message: fmt.Sprintf("Operation %s not allowed for ranking %d: %s", operation, rankID, reason), Details: map[string]interface{}{"operation": operation, "rank_id": rankID, "reason": reason}, Retryable: false, Severity: ErrorSeverityMedium, }, Operation: operation, RankID: rankID, Reason: reason, } } // 系统错误 // RankingSystemError 排行榜系统错误 type RankingSystemError struct { *BaseRankingError SystemComponent string `json:"system_component"` InternalError error `json:"internal_error,omitempty"` } // NewRankingSystemError 创建排行榜系统错误 func NewRankingSystemError(component, message string, internalErr error) *RankingSystemError { return &RankingSystemError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_SYSTEM_ERROR", Message: fmt.Sprintf("System error in %s: %s", component, message), Details: map[string]interface{}{"system_component": component}, Retryable: true, Severity: ErrorSeverityCritical, }, SystemComponent: component, InternalError: internalErr, } } // RankingDatabaseError 排行榜数据库错误 type RankingDatabaseError struct { *BaseRankingError Operation string `json:"operation"` Table string `json:"table"` InternalError error `json:"internal_error,omitempty"` } // NewRankingDatabaseError 创建排行榜数据库错误 func NewRankingDatabaseError(operation, table, message string, internalErr error) *RankingDatabaseError { return &RankingDatabaseError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_DATABASE_ERROR", Message: fmt.Sprintf("Database error during %s on table %s: %s", operation, table, message), Details: map[string]interface{}{"operation": operation, "table": table}, Retryable: true, Severity: ErrorSeverityHigh, }, Operation: operation, Table: table, InternalError: internalErr, } } // RankingCacheError 排行榜缓存错误 type RankingCacheError struct { *BaseRankingError Operation string `json:"operation"` Key string `json:"key"` InternalError error `json:"internal_error,omitempty"` } // NewRankingCacheError 创建排行榜缓存错误 func NewRankingCacheError(operation, key, message string, internalErr error) *RankingCacheError { return &RankingCacheError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_CACHE_ERROR", Message: fmt.Sprintf("Cache error during %s for key %s: %s", operation, key, message), Details: map[string]interface{}{"operation": operation, "key": key}, Retryable: true, Severity: ErrorSeverityMedium, }, Operation: operation, Key: key, InternalError: internalErr, } } // 验证错误 // RankingValidationError 排行榜验证错误 type RankingValidationError struct { *BaseRankingError Field string `json:"field"` Value interface{} `json:"value"` Constraint string `json:"constraint"` ValidationRule string `json:"validation_rule"` } // NewRankingValidationError 创建排行榜验证错误 func NewRankingValidationError(field string, value interface{}, constraint, rule string) *RankingValidationError { return &RankingValidationError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_VALIDATION_ERROR", Message: fmt.Sprintf("Validation failed for field %s: %s (rule: %s)", field, constraint, rule), Details: map[string]interface{}{ "field": field, "value": value, "constraint": constraint, "validation_rule": rule, }, Retryable: false, Severity: ErrorSeverityMedium, }, Field: field, Value: value, Constraint: constraint, ValidationRule: rule, } } // 并发相关错误 // RankingConcurrencyError 排行榜并发错误 type RankingConcurrencyError struct { *BaseRankingError RankID uint32 `json:"rank_id"` ExpectedVersion int64 `json:"expected_version"` ActualVersion int64 `json:"actual_version"` } // NewRankingConcurrencyError 创建排行榜并发错误 func NewRankingConcurrencyError(rankID uint32, expectedVersion, actualVersion int64) *RankingConcurrencyError { return &RankingConcurrencyError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_CONCURRENCY_ERROR", Message: fmt.Sprintf("Concurrency conflict for ranking %d: expected version %d, actual version %d", rankID, expectedVersion, actualVersion), Details: map[string]interface{}{ "rank_id": rankID, "expected_version": expectedVersion, "actual_version": actualVersion, }, Retryable: true, Severity: ErrorSeverityMedium, }, RankID: rankID, ExpectedVersion: expectedVersion, ActualVersion: actualVersion, } } // RankingLockError 排行榜锁错误 type RankingLockError struct { *BaseRankingError RankID uint32 `json:"rank_id"` LockType string `json:"lock_type"` LockOwner string `json:"lock_owner"` } // NewRankingLockError 创建排行榜锁错误 func NewRankingLockError(rankID uint32, lockType, lockOwner string) *RankingLockError { return &RankingLockError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_LOCK_ERROR", Message: fmt.Sprintf("Cannot acquire %s lock for ranking %d, owned by %s", lockType, rankID, lockOwner), Details: map[string]interface{}{ "rank_id": rankID, "lock_type": lockType, "lock_owner": lockOwner, }, Retryable: true, Severity: ErrorSeverityMedium, }, RankID: rankID, LockType: lockType, LockOwner: lockOwner, } } // 配置相关错误 // RankingConfigError 排行榜配置错误 type RankingConfigError struct { *BaseRankingError ConfigKey string `json:"config_key"` ConfigValue interface{} `json:"config_value"` Reason string `json:"reason"` } // NewRankingConfigError 创建排行榜配置错误 func NewRankingConfigError(configKey string, configValue interface{}, reason string) *RankingConfigError { return &RankingConfigError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_CONFIG_ERROR", Message: fmt.Sprintf("Configuration error for %s: %s", configKey, reason), Details: map[string]interface{}{ "config_key": configKey, "config_value": configValue, "reason": reason, }, Retryable: false, Severity: ErrorSeverityHigh, }, ConfigKey: configKey, ConfigValue: configValue, Reason: reason, } } // 限流相关错误 // RankingRateLimitError 排行榜限流错误 type RankingRateLimitError struct { *BaseRankingError PlayerID uint64 `json:"player_id"` RankID uint32 `json:"rank_id"` Operation string `json:"operation"` CurrentRate int64 `json:"current_rate"` MaxRate int64 `json:"max_rate"` ResetTime int64 `json:"reset_time"` } // NewRankingRateLimitError 创建排行榜限流错误 func NewRankingRateLimitError(playerID uint64, rankID uint32, operation string, currentRate, maxRate, resetTime int64) *RankingRateLimitError { return &RankingRateLimitError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_RATE_LIMIT_ERROR", Message: fmt.Sprintf("Rate limit exceeded for player %d in ranking %d: %d/%d %s operations", playerID, rankID, currentRate, maxRate, operation), Details: map[string]interface{}{ "player_id": playerID, "rank_id": rankID, "operation": operation, "current_rate": currentRate, "max_rate": maxRate, "reset_time": resetTime, }, Retryable: true, Severity: ErrorSeverityMedium, }, PlayerID: playerID, RankID: rankID, Operation: operation, CurrentRate: currentRate, MaxRate: maxRate, ResetTime: resetTime, } } // 事件相关错误 // RankingEventError 排行榜事件错误 type RankingEventError struct { *BaseRankingError EventID string `json:"event_id"` EventType string `json:"event_type"` Reason string `json:"reason"` } // NewRankingEventError 创建排行榜事件错误 func NewRankingEventError(eventID, eventType, reason string) *RankingEventError { return &RankingEventError{ BaseRankingError: &BaseRankingError{ Code: "RANKING_EVENT_ERROR", Message: fmt.Sprintf("Event error for %s (%s): %s", eventID, eventType, reason), Details: map[string]interface{}{ "event_id": eventID, "event_type": eventType, "reason": reason, }, Retryable: true, Severity: ErrorSeverityMedium, }, EventID: eventID, EventType: eventType, Reason: reason, } } // 错误代码常量 const ( // 排行榜相关错误代码 ErrCodeRankingNotFound = "RANKING_NOT_FOUND" ErrCodeRankingAlreadyExists = "RANKING_ALREADY_EXISTS" ErrCodeRankingInactive = "RANKING_INACTIVE" ErrCodeRankingTimeExpired = "RANKING_TIME_EXPIRED" ErrCodeRankingFull = "RANKING_FULL" // 玩家相关错误代码 ErrCodePlayerNotInRanking = "PLAYER_NOT_IN_RANKING" ErrCodePlayerAlreadyInRanking = "PLAYER_ALREADY_IN_RANKING" ErrCodePlayerBlacklisted = "PLAYER_BLACKLISTED" ErrCodePlayerAlreadyBlacklisted = "PLAYER_ALREADY_BLACKLISTED" ErrCodePlayerNotBlacklisted = "PLAYER_NOT_BLACKLISTED" // 参数相关错误代码 ErrCodeInvalidRange = "INVALID_RANGE" ErrCodeInvalidTimeRange = "INVALID_TIME_RANGE" ErrCodeInvalidScore = "INVALID_SCORE" // 权限相关错误代码 ErrCodeInsufficientPermission = "INSUFFICIENT_PERMISSION" ErrCodeOperationNotAllowed = "OPERATION_NOT_ALLOWED" // 系统错误代码 ErrCodeRankingSystemError = "RANKING_SYSTEM_ERROR" ErrCodeRankingDatabaseError = "RANKING_DATABASE_ERROR" ErrCodeRankingCacheError = "RANKING_CACHE_ERROR" // 验证错误代码 ErrCodeRankingValidationError = "RANKING_VALIDATION_ERROR" // 并发相关错误代码 ErrCodeRankingConcurrencyError = "RANKING_CONCURRENCY_ERROR" ErrCodeRankingLockError = "RANKING_LOCK_ERROR" // 配置相关错误代码 ErrCodeRankingConfigError = "RANKING_CONFIG_ERROR" // 限流相关错误代码 ErrCodeRankingRateLimitError = "RANKING_RATE_LIMIT_ERROR" // 事件相关错误代码 ErrCodeRankingEventError = "RANKING_EVENT_ERROR" ) // 错误工具函数 // IsRankingError 检查是否为排行榜错误 func IsRankingError(err error) bool { _, ok := err.(RankingError) return ok } // GetRankingErrorCode 获取排行榜错误代码 func GetRankingErrorCode(err error) string { if rankingErr, ok := err.(RankingError); ok { return rankingErr.GetCode() } return "" } // IsRetryableRankingError 检查是否为可重试的排行榜错误 func IsRetryableRankingError(err error) bool { if rankingErr, ok := err.(RankingError); ok { return rankingErr.IsRetryable() } return false } // GetRankingErrorSeverity 获取排行榜错误严重程度 func GetRankingErrorSeverity(err error) ErrorSeverity { if rankingErr, ok := err.(RankingError); ok { return rankingErr.GetSeverity() } return ErrorSeverityLow } // WrapRankingError 包装排行榜错误 func WrapRankingError(err error, code, message string) RankingError { return &BaseRankingError{ Code: code, Message: fmt.Sprintf("%s: %v", message, err), Details: map[string]interface{}{"wrapped_error": err.Error()}, Retryable: IsRetryableRankingError(err), Severity: GetRankingErrorSeverity(err), } } // FormatRankingError 格式化排行榜错误 func FormatRankingError(err RankingError) string { return fmt.Sprintf("[%s][%s] %s", err.GetSeverity(), err.GetCode(), err.GetMessage()) } // LogRankingError 记录排行榜错误(占位符函数) func LogRankingError(err RankingError) { // 实现错误日志记录逻辑 fmt.Printf("RANKING_ERROR: %s\n", FormatRankingError(err)) } // 错误分类函数 // IsTemporaryError 检查是否为临时错误 func IsTemporaryError(err error) bool { if rankingErr, ok := err.(RankingError); ok { code := rankingErr.GetCode() return code == ErrCodeRankingSystemError || code == ErrCodeRankingDatabaseError || code == ErrCodeRankingCacheError || code == ErrCodeRankingConcurrencyError || code == ErrCodeRankingLockError || code == ErrCodeRankingRateLimitError } return false } // IsPermanentError 检查是否为永久错误 func IsPermanentError(err error) bool { if rankingErr, ok := err.(RankingError); ok { code := rankingErr.GetCode() return code == ErrCodeRankingNotFound || code == ErrCodeRankingAlreadyExists || code == ErrCodePlayerNotInRanking || code == ErrCodePlayerAlreadyInRanking || code == ErrCodeInvalidRange || code == ErrCodeInvalidTimeRange || code == ErrCodeInvalidScore || code == ErrCodeRankingValidationError } return false } // IsUserError 检查是否为用户错误 func IsUserError(err error) bool { if rankingErr, ok := err.(RankingError); ok { code := rankingErr.GetCode() return code == ErrCodeInvalidRange || code == ErrCodeInvalidTimeRange || code == ErrCodeInvalidScore || code == ErrCodeRankingValidationError || code == ErrCodeInsufficientPermission || code == ErrCodeOperationNotAllowed } return false } // IsSystemError 检查是否为系统错误 func IsSystemError(err error) bool { if rankingErr, ok := err.(RankingError); ok { code := rankingErr.GetCode() return code == ErrCodeRankingSystemError || code == ErrCodeRankingDatabaseError || code == ErrCodeRankingCacheError || code == ErrCodeRankingConfigError } return false } // 错误恢复策略 // ErrorRecoveryStrategy 错误恢复策略 type ErrorRecoveryStrategy struct { MaxRetries int `json:"max_retries"` RetryInterval time.Duration `json:"retry_interval"` BackoffFactor float64 `json:"backoff_factor"` MaxInterval time.Duration `json:"max_interval"` RecoveryActions []string `json:"recovery_actions"` } // GetRecoveryStrategy 获取错误恢复策略 func GetRecoveryStrategy(err error) *ErrorRecoveryStrategy { if !IsRankingError(err) { return nil } rankingErr := err.(RankingError) code := rankingErr.GetCode() switch code { case ErrCodeRankingSystemError, ErrCodeRankingDatabaseError: return &ErrorRecoveryStrategy{ MaxRetries: 5, RetryInterval: time.Second, BackoffFactor: 2.0, MaxInterval: 30 * time.Second, RecoveryActions: []string{"retry", "fallback", "alert"}, } case ErrCodeRankingCacheError: return &ErrorRecoveryStrategy{ MaxRetries: 3, RetryInterval: 500 * time.Millisecond, BackoffFactor: 1.5, MaxInterval: 5 * time.Second, RecoveryActions: []string{"retry", "bypass_cache"}, } case ErrCodeRankingConcurrencyError, ErrCodeRankingLockError: return &ErrorRecoveryStrategy{ MaxRetries: 10, RetryInterval: 100 * time.Millisecond, BackoffFactor: 1.2, MaxInterval: 2 * time.Second, RecoveryActions: []string{"retry", "backoff"}, } case ErrCodeRankingRateLimitError: return &ErrorRecoveryStrategy{ MaxRetries: 1, RetryInterval: time.Minute, BackoffFactor: 1.0, MaxInterval: time.Minute, RecoveryActions: []string{"wait", "throttle"}, } default: return &ErrorRecoveryStrategy{ MaxRetries: 0, RetryInterval: 0, BackoffFactor: 1.0, MaxInterval: 0, RecoveryActions: []string{"fail"}, } } } ================================================ FILE: internal/domain/ranking/events.go ================================================ package ranking import ( "fmt" "time" ) // 排行榜相关事件定义 // RankingEvent 排行榜事件基础接口 type RankingEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetRankID() uint32 GetTimestamp() time.Time GetVersion() int GetMetadata() map[string]interface{} } // BaseRankingEvent 排行榜事件基础结构 type BaseRankingEvent struct { EventID string `json:"event_id"` EventType string `json:"event_type"` AggregateID string `json:"aggregate_id"` RankID uint32 `json:"rank_id"` Timestamp time.Time `json:"timestamp"` Version int `json:"version"` Metadata map[string]interface{} `json:"metadata"` } // GetEventID 获取事件ID func (e *BaseRankingEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseRankingEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合ID func (e *BaseRankingEvent) GetAggregateID() string { return e.AggregateID } // GetRankID 获取排行榜ID func (e *BaseRankingEvent) GetRankID() uint32 { return e.RankID } // GetTimestamp 获取时间戳 func (e *BaseRankingEvent) GetTimestamp() time.Time { return e.Timestamp } // GetVersion 获取版本 func (e *BaseRankingEvent) GetVersion() int { return e.Version } // GetMetadata 获取元数据 func (e *BaseRankingEvent) GetMetadata() map[string]interface{} { return e.Metadata } // 排行榜生命周期事件 // RankingCreatedEvent 排行榜创建事件 type RankingCreatedEvent struct { BaseRankingEvent Name string `json:"name"` RankType RankType `json:"rank_type"` Category RankCategory `json:"category"` SortType SortType `json:"sort_type"` MaxSize int64 `json:"max_size"` Period RankPeriod `json:"period"` StartTime int64 `json:"start_time"` EndTime int64 `json:"end_time"` CreatedBy string `json:"created_by"` } // RankingDeletedEvent 排行榜删除事件 type RankingDeletedEvent struct { BaseRankingEvent Name string `json:"name"` FinalPlayers int64 `json:"final_players"` FinalTopScore int64 `json:"final_top_score"` DeletedBy string `json:"deleted_by"` DeleteReason string `json:"delete_reason"` } // RankingUpdatedEvent 排行榜更新事件 type RankingUpdatedEvent struct { BaseRankingEvent ChangedFields []string `json:"changed_fields"` OldValues map[string]interface{} `json:"old_values"` NewValues map[string]interface{} `json:"new_values"` UpdatedBy string `json:"updated_by"` } // 排行榜状态事件 // RankingStatusChangedEvent 排行榜状态改变事件 type RankingStatusChangedEvent struct { BaseRankingEvent OldStatus RankStatus `json:"old_status"` NewStatus RankStatus `json:"new_status"` OldActive bool `json:"old_active"` NewActive bool `json:"new_active"` Reason string `json:"reason"` ChangedBy string `json:"changed_by"` } // RankingResetEvent 排行榜重置事件 type RankingResetEvent struct { BaseRankingEvent PreviousPlayerCount int `json:"previous_player_count"` ResetReason string `json:"reset_reason"` ResetBy string `json:"reset_by"` BackupCreated bool `json:"backup_created"` BackupLocation string `json:"backup_location,omitempty"` } // RankingArchivedEvent 排行榜归档事件 type RankingArchivedEvent struct { BaseRankingEvent ArchiveReason string `json:"archive_reason"` ArchiveLocation string `json:"archive_location"` ArchivedBy string `json:"archived_by"` RetentionPeriod int64 `json:"retention_period"` } // 玩家相关事件 // PlayerJoinedRankingEvent 玩家加入排行榜事件 type PlayerJoinedRankingEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` InitialScore int64 `json:"initial_score"` TimeScore int64 `json:"time_score"` InitialRank int64 `json:"initial_rank"` JoinMethod string `json:"join_method"` // "first_score", "migration", "restore" } // PlayerLeftRankingEvent 玩家离开排行榜事件 type PlayerLeftRankingEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` FinalScore int64 `json:"final_score"` FinalRank int64 `json:"final_rank"` LeaveReason string `json:"leave_reason"` // "removed", "blacklisted", "expired", "reset" DaysActive int32 `json:"days_active"` } // PlayerScoreUpdatedEvent 玩家分数更新事件 type PlayerScoreUpdatedEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` OldScore int64 `json:"old_score"` NewScore int64 `json:"new_score"` ScoreDelta int64 `json:"score_delta"` OldTimeScore int64 `json:"old_time_score"` NewTimeScore int64 `json:"new_time_score"` UpdateSource string `json:"update_source"` // "game", "admin", "system", "correction" UpdateMetadata map[string]interface{} `json:"update_metadata"` } // PlayerRankChangedEvent 玩家排名改变事件 type PlayerRankChangedEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` OldRank int64 `json:"old_rank"` NewRank int64 `json:"new_rank"` RankDelta int64 `json:"rank_delta"` Score int64 `json:"score"` ChangeType string `json:"change_type"` // "improvement", "decline", "new_entry" TriggerEvent string `json:"trigger_event"` // "score_update", "other_player_update", "reset" } // 黑名单相关事件 // PlayerBlacklistedEvent 玩家被加入黑名单事件 type PlayerBlacklistedEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` Reason string `json:"reason"` BlacklistedBy string `json:"blacklisted_by"` IsPermanent bool `json:"is_permanent"` ExpiresAt *time.Time `json:"expires_at,omitempty"` PreviousRank int64 `json:"previous_rank"` PreviousScore int64 `json:"previous_score"` } // PlayerUnblacklistedEvent 玩家从黑名单移除事件 type PlayerUnblacklistedEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` OriginalReason string `json:"original_reason"` UnblacklistedBy string `json:"unblacklisted_by"` UnblacklistReason string `json:"unblacklist_reason"` BlacklistDuration time.Duration `json:"blacklist_duration"` CanRejoin bool `json:"can_rejoin"` } // BlacklistExpiredEvent 黑名单过期事件 type BlacklistExpiredEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` OriginalReason string `json:"original_reason"` BlacklistDuration time.Duration `json:"blacklist_duration"` AutoRemoved bool `json:"auto_removed"` CanRejoin bool `json:"can_rejoin"` } // 奖励相关事件 // RankingRewardDistributedEvent 排行榜奖励分发事件 type RankingRewardDistributedEvent struct { BaseRankingEvent RewardTier string `json:"reward_tier"` MinRank int64 `json:"min_rank"` MaxRank int64 `json:"max_rank"` RewardCount int64 `json:"reward_count"` TotalValue int64 `json:"total_value"` DistributionMethod string `json:"distribution_method"` // "immediate", "mail", "deferred" DistributedBy string `json:"distributed_by"` RewardDetails []RewardDistribution `json:"reward_details"` } // PlayerRewardEarnedEvent 玩家获得奖励事件 type PlayerRewardEarnedEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` PlayerRank int64 `json:"player_rank"` PlayerScore int64 `json:"player_score"` RewardTier string `json:"reward_tier"` RewardType string `json:"reward_type"` RewardID string `json:"reward_id"` RewardQuantity int64 `json:"reward_quantity"` RewardValue int64 `json:"reward_value"` EarnedAt time.Time `json:"earned_at"` ClaimDeadline *time.Time `json:"claim_deadline,omitempty"` } // PlayerRewardClaimedEvent 玩家领取奖励事件 type PlayerRewardClaimedEvent struct { BaseRankingEvent PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` RewardID string `json:"reward_id"` RewardType string `json:"reward_type"` RewardQuantity int64 `json:"reward_quantity"` RewardValue int64 `json:"reward_value"` EarnedAt time.Time `json:"earned_at"` ClaimedAt time.Time `json:"claimed_at"` ClaimDelay time.Duration `json:"claim_delay"` } // 统计相关事件 // RankingStatisticsUpdatedEvent 排行榜统计更新事件 type RankingStatisticsUpdatedEvent struct { BaseRankingEvent OldStatistics *RankingStatistics `json:"old_statistics"` NewStatistics *RankingStatistics `json:"new_statistics"` ChangedFields []string `json:"changed_fields"` UpdateTrigger string `json:"update_trigger"` // "score_update", "player_join", "player_leave", "scheduled" } // RankingMilestoneReachedEvent 排行榜里程碑达成事件 type RankingMilestoneReachedEvent struct { BaseRankingEvent MilestoneType string `json:"milestone_type"` // "player_count", "score_threshold", "activity_level" MilestoneValue int64 `json:"milestone_value"` CurrentValue int64 `json:"current_value"` ReachedAt time.Time `json:"reached_at"` IsFirstTime bool `json:"is_first_time"` Celebration bool `json:"celebration"` } // RankingRecordBrokenEvent 排行榜记录被打破事件 type RankingRecordBrokenEvent struct { BaseRankingEvent RecordType string `json:"record_type"` // "highest_score", "most_players", "longest_streak" PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` OldRecord int64 `json:"old_record"` NewRecord int64 `json:"new_record"` RecordImprovement int64 `json:"record_improvement"` PreviousHolder string `json:"previous_holder"` RecordDuration time.Duration `json:"record_duration"` } // 系统事件 // RankingMaintenanceEvent 排行榜维护事件 type RankingMaintenanceEvent struct { BaseRankingEvent MaintenanceType string `json:"maintenance_type"` // "scheduled", "emergency", "optimization" StartTime time.Time `json:"start_time"` EndTime *time.Time `json:"end_time,omitempty"` Duration time.Duration `json:"duration"` AffectedFeatures []string `json:"affected_features"` MaintenanceBy string `json:"maintenance_by"` Reason string `json:"reason"` Impact string `json:"impact"` // "none", "limited", "full" } // RankingDataMigrationEvent 排行榜数据迁移事件 type RankingDataMigrationEvent struct { BaseRankingEvent MigrationType string `json:"migration_type"` // "version_upgrade", "schema_change", "platform_migration" FromVersion string `json:"from_version"` ToVersion string `json:"to_version"` MigratedRecords int64 `json:"migrated_records"` FailedRecords int64 `json:"failed_records"` MigrationStatus string `json:"migration_status"` // "started", "in_progress", "completed", "failed" MigrationErrors []string `json:"migration_errors,omitempty"` RollbackPlan string `json:"rollback_plan"` } // RankingPerformanceEvent 排行榜性能事件 type RankingPerformanceEvent struct { BaseRankingEvent MetricType string `json:"metric_type"` // "response_time", "throughput", "error_rate", "memory_usage" MetricValue float64 `json:"metric_value"` Threshold float64 `json:"threshold"` Severity string `json:"severity"` // "info", "warning", "critical" Duration time.Duration `json:"duration"` AffectedOperations []string `json:"affected_operations"` ResolutionAction string `json:"resolution_action"` } // 缓存相关事件 // RankingCacheUpdatedEvent 排行榜缓存更新事件 type RankingCacheUpdatedEvent struct { BaseRankingEvent CacheType string `json:"cache_type"` // "ranking_data", "player_rank", "statistics", "top_players" CacheKey string `json:"cache_key"` UpdateReason string `json:"update_reason"` // "data_change", "expiration", "manual_refresh" CacheSize int64 `json:"cache_size"` TTL time.Duration `json:"ttl"` HitRate float64 `json:"hit_rate"` } // RankingCacheEvictedEvent 排行榜缓存驱逐事件 type RankingCacheEvictedEvent struct { BaseRankingEvent CacheType string `json:"cache_type"` CacheKey string `json:"cache_key"` EvictionReason string `json:"eviction_reason"` // "memory_pressure", "ttl_expired", "manual_clear", "size_limit" CacheAge time.Duration `json:"cache_age"` AccessCount int64 `json:"access_count"` LastAccessed time.Time `json:"last_accessed"` } // 事件相关的辅助结构体 // RewardDistribution 奖励分发详情 type RewardDistribution struct { PlayerID uint64 `json:"player_id"` PlayerName string `json:"player_name"` PlayerRank int64 `json:"player_rank"` RewardType string `json:"reward_type"` RewardQuantity int64 `json:"reward_quantity"` RewardValue int64 `json:"reward_value"` DistributionStatus string `json:"distribution_status"` // "success", "failed", "pending" ErrorMessage string `json:"error_message,omitempty"` } // 事件常量 const ( // 生命周期事件类型 EventTypeRankingCreated = "ranking.created" EventTypeRankingDeleted = "ranking.deleted" EventTypeRankingUpdated = "ranking.updated" // 状态事件类型 EventTypeRankingStatusChanged = "ranking.status_changed" EventTypeRankingReset = "ranking.reset" EventTypeRankingArchived = "ranking.archived" // 玩家事件类型 EventTypePlayerJoinedRanking = "ranking.player_joined" EventTypePlayerLeftRanking = "ranking.player_left" EventTypePlayerScoreUpdated = "ranking.player_score_updated" EventTypePlayerRankChanged = "ranking.player_rank_changed" // 黑名单事件类型 EventTypePlayerBlacklisted = "ranking.player_blacklisted" EventTypePlayerUnblacklisted = "ranking.player_unblacklisted" EventTypeBlacklistExpired = "ranking.blacklist_expired" // 奖励事件类型 EventTypeRankingRewardDistributed = "ranking.reward_distributed" EventTypePlayerRewardEarned = "ranking.player_reward_earned" EventTypePlayerRewardClaimed = "ranking.player_reward_claimed" // 统计事件类型 EventTypeRankingStatisticsUpdated = "ranking.statistics_updated" EventTypeRankingMilestoneReached = "ranking.milestone_reached" EventTypeRankingRecordBroken = "ranking.record_broken" // 系统事件类型 EventTypeRankingMaintenance = "ranking.maintenance" EventTypeRankingDataMigration = "ranking.data_migration" EventTypeRankingPerformance = "ranking.performance" // 缓存事件类型 EventTypeRankingCacheUpdated = "ranking.cache_updated" EventTypeRankingCacheEvicted = "ranking.cache_evicted" ) // 事件工厂函数 // NewRankingCreatedEvent 创建排行榜创建事件 func NewRankingCreatedEvent(aggregateID string, rankID uint32, name string, rankType RankType, category RankCategory, createdBy string) *RankingCreatedEvent { return &RankingCreatedEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypeRankingCreated, AggregateID: aggregateID, RankID: rankID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, Name: name, RankType: rankType, Category: category, CreatedBy: createdBy, } } // NewPlayerJoinedRankingEvent 创建玩家加入排行榜事件 func NewPlayerJoinedRankingEvent(aggregateID string, playerID uint64, score, timeScore int64) *PlayerJoinedRankingEvent { return &PlayerJoinedRankingEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypePlayerJoinedRanking, AggregateID: aggregateID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PlayerID: playerID, InitialScore: score, TimeScore: timeScore, JoinMethod: "first_score", } } // NewPlayerScoreUpdatedEvent 创建玩家分数更新事件 func NewPlayerScoreUpdatedEvent(aggregateID string, playerID uint64, oldScore, newScore, timeScore int64) *PlayerScoreUpdatedEvent { return &PlayerScoreUpdatedEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypePlayerScoreUpdated, AggregateID: aggregateID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PlayerID: playerID, OldScore: oldScore, NewScore: newScore, ScoreDelta: newScore - oldScore, NewTimeScore: timeScore, UpdateSource: "game", } } // NewPlayerBlacklistedEvent 创建玩家黑名单事件 func NewPlayerBlacklistedEvent(aggregateID string, playerID uint64, reason string) *PlayerBlacklistedEvent { return &PlayerBlacklistedEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypePlayerBlacklisted, AggregateID: aggregateID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PlayerID: playerID, Reason: reason, IsPermanent: true, } } // NewPlayerUnblacklistedEvent 创建玩家解除黑名单事件 func NewPlayerUnblacklistedEvent(aggregateID string, playerID uint64) *PlayerUnblacklistedEvent { return &PlayerUnblacklistedEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypePlayerUnblacklisted, AggregateID: aggregateID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PlayerID: playerID, CanRejoin: true, } } // NewRankingResetEvent 创建排行榜重置事件 func NewRankingResetEvent(aggregateID string, previousPlayerCount int) *RankingResetEvent { return &RankingResetEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypeRankingReset, AggregateID: aggregateID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, PreviousPlayerCount: previousPlayerCount, ResetReason: "manual_reset", BackupCreated: false, } } // NewRankingStatusChangedEvent 创建排行榜状态改变事件 func NewRankingStatusChangedEvent(aggregateID string, oldActive, newActive bool) *RankingStatusChangedEvent { return &RankingStatusChangedEvent{ BaseRankingEvent: BaseRankingEvent{ EventID: generateEventID(), EventType: EventTypeRankingStatusChanged, AggregateID: aggregateID, Timestamp: time.Now(), Version: 1, Metadata: make(map[string]interface{}), }, OldActive: oldActive, NewActive: newActive, Reason: "manual_change", } } // 事件处理器接口 // RankingEventHandler 排行榜事件处理器接口 type RankingEventHandler interface { Handle(event RankingEvent) error CanHandle(eventType string) bool GetHandlerName() string } // RankingEventBus 排行榜事件总线接口 type RankingEventBus interface { // 发布事件 Publish(event RankingEvent) error PublishBatch(events []RankingEvent) error // 订阅事件 Subscribe(eventType string, handler RankingEventHandler) error Unsubscribe(eventType string, handlerName string) error // 事件存储 Store(event RankingEvent) error GetEvents(aggregateID string, fromVersion int) ([]RankingEvent, error) GetEventsByType(eventType string, limit int) ([]RankingEvent, error) GetEventsByRankID(rankID uint32, limit int) ([]RankingEvent, error) // 事件重放 Replay(aggregateID string, fromVersion int, handler RankingEventHandler) error // 快照管理 CreateSnapshot(aggregateID string, version int, data interface{}) error GetSnapshot(aggregateID string) (interface{}, int, error) // 事件清理 CleanupEvents(beforeTime time.Time) error ArchiveEvents(beforeTime time.Time) error } // 事件聚合器接口 // RankingEventAggregator 排行榜事件聚合器接口 type RankingEventAggregator interface { // 聚合统计 AggregatePlayerActivity(rankID uint32, period time.Duration) (*PlayerActivityStats, error) AggregateScoreChanges(rankID uint32, period time.Duration) (*ScoreChangeStats, error) AggregateRankingHealth(rankID uint32, period time.Duration) (*RankingHealthStats, error) // 趋势分析 AnalyzePlayerTrends(rankID uint32, playerID uint64, period time.Duration) (*PlayerTrendAnalysis, error) AnalyzeRankingTrends(rankID uint32, period time.Duration) (*RankingTrendAnalysis, error) // 异常检测 DetectAnomalies(rankID uint32, period time.Duration) ([]*RankingAnomaly, error) DetectSuspiciousActivity(rankID uint32, period time.Duration) ([]*SuspiciousActivity, error) // 性能分析 AnalyzePerformance(rankID uint32, period time.Duration) (*RankingPerformanceAnalysis, error) } // 事件聚合统计结构体 // PlayerActivityStats 玩家活动统计 type PlayerActivityStats struct { RankID uint32 `json:"rank_id"` Period time.Duration `json:"period"` TotalPlayers int64 `json:"total_players"` ActivePlayers int64 `json:"active_players"` NewPlayers int64 `json:"new_players"` LeavingPlayers int64 `json:"leaving_players"` RetentionRate float64 `json:"retention_rate"` ChurnRate float64 `json:"churn_rate"` AverageSessionTime time.Duration `json:"average_session_time"` PeakActivityTime time.Time `json:"peak_activity_time"` CalculatedAt time.Time `json:"calculated_at"` } // ScoreChangeStats 分数变化统计 type ScoreChangeStats struct { RankID uint32 `json:"rank_id"` Period time.Duration `json:"period"` TotalUpdates int64 `json:"total_updates"` AverageScoreChange float64 `json:"average_score_change"` MaxScoreIncrease int64 `json:"max_score_increase"` MaxScoreDecrease int64 `json:"max_score_decrease"` ScoreVolatility float64 `json:"score_volatility"` TopScoreChanges []*ScoreChange `json:"top_score_changes"` CalculatedAt time.Time `json:"calculated_at"` } // RankingHealthStats 排行榜健康统计 type RankingHealthStats struct { RankID uint32 `json:"rank_id"` Period time.Duration `json:"period"` HealthScore float64 `json:"health_score"` Competitiveness float64 `json:"competitiveness"` EngagementLevel float64 `json:"engagement_level"` StabilityIndex float64 `json:"stability_index"` GrowthRate float64 `json:"growth_rate"` ErrorRate float64 `json:"error_rate"` PerformanceScore float64 `json:"performance_score"` Recommendations []string `json:"recommendations"` CalculatedAt time.Time `json:"calculated_at"` } // PlayerTrendAnalysis 玩家趋势分析 type PlayerTrendAnalysis struct { RankID uint32 `json:"rank_id"` PlayerID uint64 `json:"player_id"` Period time.Duration `json:"period"` ScoreTrend string `json:"score_trend"` // "increasing", "decreasing", "stable", "volatile" RankTrend string `json:"rank_trend"` ActivityLevel string `json:"activity_level"` // "high", "medium", "low" Consistency float64 `json:"consistency"` Improvement float64 `json:"improvement"` PredictedRank int64 `json:"predicted_rank"` RiskFactors []string `json:"risk_factors"` CalculatedAt time.Time `json:"calculated_at"` } // RankingTrendAnalysis 排行榜趋势分析 type RankingTrendAnalysis struct { RankID uint32 `json:"rank_id"` Period time.Duration `json:"period"` OverallTrend string `json:"overall_trend"` // "growing", "declining", "stable" PlayerGrowthRate float64 `json:"player_growth_rate"` ScoreInflation float64 `json:"score_inflation"` CompetitionLevel string `json:"competition_level"` // "high", "medium", "low" Seasonality []SeasonalPattern `json:"seasonality"` Predictions *RankingPredictions `json:"predictions"` Recommendations []string `json:"recommendations"` CalculatedAt time.Time `json:"calculated_at"` } // RankingAnomaly 排行榜异常 type RankingAnomaly struct { RankID uint32 `json:"rank_id"` AnomalyType string `json:"anomaly_type"` // "score_spike", "mass_exodus", "unusual_pattern" Severity string `json:"severity"` // "low", "medium", "high", "critical" Description string `json:"description"` AffectedPlayers []uint64 `json:"affected_players"` DetectedAt time.Time `json:"detected_at"` StartTime time.Time `json:"start_time"` EndTime *time.Time `json:"end_time,omitempty"` Metrics map[string]float64 `json:"metrics"` RecommendedActions []string `json:"recommended_actions"` } // SuspiciousActivity 可疑活动 type SuspiciousActivity struct { RankID uint32 `json:"rank_id"` PlayerID uint64 `json:"player_id"` ActivityType string `json:"activity_type"` // "rapid_score_increase", "impossible_score", "bot_behavior" RiskLevel string `json:"risk_level"` // "low", "medium", "high" Confidence float64 `json:"confidence"` Description string `json:"description"` Evidence []string `json:"evidence"` DetectedAt time.Time `json:"detected_at"` RecommendedActions []string `json:"recommended_actions"` } // RankingPerformanceAnalysis 排行榜性能分析 type RankingPerformanceAnalysis struct { RankID uint32 `json:"rank_id"` Period time.Duration `json:"period"` AverageResponseTime time.Duration `json:"average_response_time"` Throughput float64 `json:"throughput"` ErrorRate float64 `json:"error_rate"` CacheHitRate float64 `json:"cache_hit_rate"` MemoryUsage int64 `json:"memory_usage"` CPUUsage float64 `json:"cpu_usage"` Bottlenecks []string `json:"bottlenecks"` Optimizations []string `json:"optimizations"` CalculatedAt time.Time `json:"calculated_at"` } // 辅助结构体 // ScoreChange 分数变化 type ScoreChange struct { PlayerID uint64 `json:"player_id"` OldScore int64 `json:"old_score"` NewScore int64 `json:"new_score"` Change int64 `json:"change"` Timestamp time.Time `json:"timestamp"` } // SeasonalPattern 季节性模式 type SeasonalPattern struct { Period string `json:"period"` // "daily", "weekly", "monthly" Pattern string `json:"pattern"` Strength float64 `json:"strength"` PeakTimes []string `json:"peak_times"` } // RankingPredictions 排行榜预测 type RankingPredictions struct { NextWeekPlayers int64 `json:"next_week_players"` NextMonthPlayers int64 `json:"next_month_players"` ScoreGrowthRate float64 `json:"score_growth_rate"` ChurnProbability float64 `json:"churn_probability"` ConfidenceLevel float64 `json:"confidence_level"` } // 辅助函数 // generateEventID 生成事件ID func generateEventID() string { return fmt.Sprintf("ranking_event_%d", time.Now().UnixNano()) } // ValidateEvent 验证事件 func ValidateEvent(event RankingEvent) error { if event.GetEventID() == "" { return fmt.Errorf("event ID cannot be empty") } if event.GetEventType() == "" { return fmt.Errorf("event type cannot be empty") } if event.GetAggregateID() == "" { return fmt.Errorf("aggregate ID cannot be empty") } if event.GetRankID() == 0 { return fmt.Errorf("rank ID cannot be zero") } if event.GetTimestamp().IsZero() { return fmt.Errorf("timestamp cannot be zero") } return nil } // SerializeEvent 序列化事件 func SerializeEvent(event RankingEvent) ([]byte, error) { // 实现事件序列化逻辑 return nil, nil } // DeserializeEvent 反序列化事件 func DeserializeEvent(data []byte, eventType string) (RankingEvent, error) { // 实现事件反序列化逻辑 return nil, nil } ================================================ FILE: internal/domain/ranking/repository.go ================================================ package ranking import ( "fmt" "time" ) // RankingRepository 排行榜仓储接口 type RankingRepository interface { // 基础CRUD操作 Save(ranking *RankingAggregate) error FindByID(id string) (*RankingAggregate, error) FindByRankID(rankID uint32) (*RankingAggregate, error) Update(ranking *RankingAggregate) error Delete(id string) error // 查询操作 FindByType(rankType RankType) ([]*RankingAggregate, error) FindByCategory(category RankCategory) ([]*RankingAggregate, error) FindByStatus(status RankStatus) ([]*RankingAggregate, error) FindByPeriod(period RankPeriod) ([]*RankingAggregate, error) FindActive() ([]*RankingAggregate, error) FindExpired() ([]*RankingAggregate, error) // 分页查询 FindWithPagination(query *RankingQuery) (*RankingPageResult, error) FindByPlayerID(playerID uint64) ([]*RankingAggregate, error) // 统计操作 Count() (int64, error) CountByType(rankType RankType) (int64, error) CountByCategory(category RankCategory) (int64, error) CountByStatus(status RankStatus) (int64, error) // 批量操作 SaveBatch(rankings []*RankingAggregate) error UpdateBatch(rankings []*RankingAggregate) error DeleteBatch(ids []string) error // 高级查询 FindByTimeRange(startTime, endTime time.Time) ([]*RankingAggregate, error) FindByScoreRange(minScore, maxScore int64) ([]*RankingAggregate, error) FindTopRankings(limit int) ([]*RankingAggregate, error) FindRecentlyUpdated(duration time.Duration) ([]*RankingAggregate, error) // 搜索操作 Search(keyword string, filters map[string]interface{}) ([]*RankingAggregate, error) FindSimilar(ranking *RankingAggregate, limit int) ([]*RankingAggregate, error) } // RankEntryRepository 排行榜条目仓储接口 type RankEntryRepository interface { // 基础CRUD操作 Save(entry *RankEntry) error FindByID(id string) (*RankEntry, error) FindByPlayerAndRank(playerID uint64, rankID uint32) (*RankEntry, error) Update(entry *RankEntry) error Delete(id string) error // 查询操作 FindByRankID(rankID uint32) ([]*RankEntry, error) FindByPlayerID(playerID uint64) ([]*RankEntry, error) FindByRankRange(rankID uint32, startRank, endRank int64) ([]*RankEntry, error) FindByScoreRange(rankID uint32, minScore, maxScore int64) ([]*RankEntry, error) // 分页查询 FindWithPagination(query *RankEntryQuery) (*RankEntryPageResult, error) // 排序查询 FindTopEntries(rankID uint32, limit int) ([]*RankEntry, error) FindBottomEntries(rankID uint32, limit int) ([]*RankEntry, error) FindAroundPlayer(rankID uint32, playerID uint64, range_ int) ([]*RankEntry, error) // 统计操作 Count() (int64, error) CountByRankID(rankID uint32) (int64, error) CountByPlayerID(playerID uint64) (int64, error) CountActive(rankID uint32) (int64, error) // 批量操作 SaveBatch(entries []*RankEntry) error UpdateBatch(entries []*RankEntry) error DeleteBatch(ids []string) error DeleteByRankID(rankID uint32) error DeleteByPlayerID(playerID uint64) error // 高级查询 FindByLastActive(rankID uint32, since time.Time) ([]*RankEntry, error) FindByConsecutiveDays(rankID uint32, minDays int32) ([]*RankEntry, error) FindByTotalUpdates(rankID uint32, minUpdates int64) ([]*RankEntry, error) FindRecentlyImproved(rankID uint32, duration time.Duration) ([]*RankEntry, error) // 历史数据 FindScoreHistory(entryID string, limit int) ([]*ScoreHistoryEntry, error) FindRewardHistory(entryID string, limit int) ([]*RankRewardEarned, error) // 聚合查询 GetAverageScore(rankID uint32) (float64, error) GetTopScore(rankID uint32) (int64, error) GetScoreDistribution(rankID uint32, buckets int) (map[string]int64, error) } // BlacklistRepository 黑名单仓储接口 type BlacklistRepository interface { // 基础CRUD操作 Save(blacklist *Blacklist) error FindByID(id string) (*Blacklist, error) FindByRankID(rankID uint32) (*Blacklist, error) Update(blacklist *Blacklist) error Delete(id string) error // 玩家操作 AddPlayer(rankID uint32, entry *BlacklistEntry) error RemovePlayer(rankID uint32, playerID uint64) error IsPlayerBlacklisted(rankID uint32, playerID uint64) (bool, error) GetBlacklistEntry(rankID uint32, playerID uint64) (*BlacklistEntry, error) // 查询操作 FindBlacklistedPlayers(rankID uint32) ([]*BlacklistEntry, error) FindByReason(rankID uint32, reason string) ([]*BlacklistEntry, error) FindExpiredEntries(rankID uint32) ([]*BlacklistEntry, error) FindPermanentEntries(rankID uint32) ([]*BlacklistEntry, error) // 分页查询 FindWithPagination(query *BlacklistQuery) (*BlacklistPageResult, error) // 统计操作 Count() (int64, error) CountByRankID(rankID uint32) (int64, error) CountByReason(rankID uint32, reason string) (int64, error) CountExpired(rankID uint32) (int64, error) // 批量操作 AddPlayersBatch(rankID uint32, entries []*BlacklistEntry) error RemovePlayersBatch(rankID uint32, playerIDs []uint64) error CleanupExpired(rankID uint32) (int64, error) // 全局操作 FindPlayerInAllRankings(playerID uint64) ([]*BlacklistEntry, error) IsPlayerGloballyBlacklisted(playerID uint64) (bool, error) } // RankingStatisticsRepository 排行榜统计仓储接口 type RankingStatisticsRepository interface { // 基础操作 Save(stats *RankingStatistics) error FindByRankID(rankID uint32) (*RankingStatistics, error) Update(stats *RankingStatistics) error Delete(rankID uint32) error // 历史统计 SaveHistorySnapshot(rankID uint32, stats *RankingStatistics) error GetHistoryStatistics(rankID uint32, period RankPeriod, points int) ([]*RankingStatistics, error) GetStatisticsTrend(rankID uint32, startTime, endTime time.Time) ([]*RankingStatistics, error) // 聚合统计 GetGlobalStatistics() (*GlobalRankingStatistics, error) GetCategoryStatistics(category RankCategory) (*CategoryRankingStatistics, error) GetTypeStatistics(rankType RankType) (*TypeRankingStatistics, error) // 排行榜统计 GetTopRankingsByPlayers(limit int) ([]*RankingStatistics, error) GetTopRankingsByActivity(limit int) ([]*RankingStatistics, error) GetMostCompetitiveRankings(limit int) ([]*RankingStatistics, error) // 时间范围统计 GetStatisticsByTimeRange(startTime, endTime time.Time) ([]*RankingStatistics, error) GetDailyStatistics(rankID uint32, days int) ([]*DailyRankingStats, error) GetWeeklyStatistics(rankID uint32, weeks int) ([]*WeeklyRankingStats, error) GetMonthlyStatistics(rankID uint32, months int) ([]*MonthlyRankingStats, error) // 比较统计 CompareRankings(rankID1, rankID2 uint32) (*RankingComparison, error) GetRankingPerformance(rankID uint32, period RankPeriod) (*RankingPerformance, error) // 清理操作 CleanupOldStatistics(before time.Time) (int64, error) ArchiveStatistics(before time.Time) error } // RankingCacheRepository 排行榜缓存仓储接口 type RankingCacheRepository interface { // 排行榜缓存 SetRanking(rankID uint32, ranking *RankingAggregate, ttl time.Duration) error GetRanking(rankID uint32) (*RankingAggregate, error) DeleteRanking(rankID uint32) error // 排行榜数据缓存 SetRankingData(rankID uint32, start, end int64, entries []*RankEntry, ttl time.Duration) error GetRankingData(rankID uint32, start, end int64) ([]*RankEntry, error) DeleteRankingData(rankID uint32) error // 玩家排名缓存 SetPlayerRank(rankID uint32, playerID uint64, entry *RankEntry, rank int64, ttl time.Duration) error GetPlayerRank(rankID uint32, playerID uint64) (*RankEntry, int64, error) DeletePlayerRank(rankID uint32, playerID uint64) error // 前N名缓存 SetTopPlayers(rankID uint32, count int, entries []*RankEntry, ttl time.Duration) error GetTopPlayers(rankID uint32, count int) ([]*RankEntry, error) DeleteTopPlayers(rankID uint32) error // 统计缓存 SetStatistics(rankID uint32, stats *RankingStatistics, ttl time.Duration) error GetStatistics(rankID uint32) (*RankingStatistics, error) DeleteStatistics(rankID uint32) error // 黑名单缓存 SetBlacklist(rankID uint32, blacklist *Blacklist, ttl time.Duration) error GetBlacklist(rankID uint32) (*Blacklist, error) DeleteBlacklist(rankID uint32) error // 玩家黑名单状态缓存 SetPlayerBlacklistStatus(rankID uint32, playerID uint64, isBlacklisted bool, ttl time.Duration) error GetPlayerBlacklistStatus(rankID uint32, playerID uint64) (bool, error) DeletePlayerBlacklistStatus(rankID uint32, playerID uint64) error // 批量操作 SetBatch(items map[string]interface{}, ttl time.Duration) error GetBatch(keys []string) (map[string]interface{}, error) DeleteBatch(keys []string) error // 缓存管理 Clear() error ClearByPattern(pattern string) error Exists(key string) (bool, error) SetTTL(key string, ttl time.Duration) error GetTTL(key string) (time.Duration, error) GetCacheInfo() (*CacheInfo, error) // 预热和刷新 WarmupRanking(rankID uint32) error RefreshRanking(rankID uint32) error ScheduleRefresh(rankID uint32, interval time.Duration) error CancelScheduledRefresh(rankID uint32) error } // RankingEventRepository 排行榜事件仓储接口 type RankingEventRepository interface { // 事件存储 Save(event RankingEvent) error SaveBatch(events []RankingEvent) error // 事件查询 FindByID(eventID string) (RankingEvent, error) FindByAggregateID(aggregateID string) ([]RankingEvent, error) FindByEventType(eventType string) ([]RankingEvent, error) FindByPlayerID(playerID uint64) ([]RankingEvent, error) FindByRankID(rankID uint32) ([]RankingEvent, error) // 时间范围查询 FindByTimeRange(startTime, endTime time.Time) ([]RankingEvent, error) FindRecent(limit int) ([]RankingEvent, error) // 分页查询 FindWithPagination(query *RankingEventQuery) (*RankingEventPageResult, error) // 事件流 GetEventStream(aggregateID string, fromVersion int) ([]RankingEvent, error) GetEventStreamByType(eventType string, limit int) ([]RankingEvent, error) // 快照管理 SaveSnapshot(aggregateID string, version int, data interface{}) error GetSnapshot(aggregateID string) (interface{}, int, error) DeleteSnapshot(aggregateID string) error // 事件清理 DeleteByAggregateID(aggregateID string) error DeleteByTimeRange(startTime, endTime time.Time) error ArchiveEvents(before time.Time) error CleanupEvents(before time.Time) error // 统计 CountEvents() (int64, error) CountByEventType(eventType string) (int64, error) CountByAggregateID(aggregateID string) (int64, error) } // RankingSearchRepository 排行榜搜索仓储接口 type RankingSearchRepository interface { // 全文搜索 SearchRankings(query string, filters map[string]interface{}) ([]*RankingAggregate, error) SearchEntries(query string, filters map[string]interface{}) ([]*RankEntry, error) SearchPlayers(query string, filters map[string]interface{}) ([]*RankEntry, error) // 智能推荐 RecommendRankings(playerID uint64, limit int) ([]*RankingAggregate, error) RecommendCompetitors(rankID uint32, playerID uint64, limit int) ([]*RankEntry, error) RecommendSimilarPlayers(rankID uint32, playerID uint64, limit int) ([]*RankEntry, error) // 相似度搜索 FindSimilarRankings(rankID uint32, limit int) ([]*RankingAggregate, error) FindSimilarPlayers(playerID uint64, limit int) ([]*RankEntry, error) // 趋势分析 AnalyzeTrends(rankID uint32, period RankPeriod) (*RankingTrend, error) PredictRankings(rankID uint32, days int) (*RankingPrediction, error) // 索引管理 RebuildIndex() error UpdateIndex(entityType string, entityID string, data interface{}) error DeleteFromIndex(entityType string, entityID string) error OptimizeIndex() error } // 查询条件结构体 // RankEntryQuery 排行榜条目查询条件 type RankEntryQuery struct { RankID *uint32 `json:"rank_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` PlayerName string `json:"player_name,omitempty"` MinScore *int64 `json:"min_score,omitempty"` MaxScore *int64 `json:"max_score,omitempty"` MinRank *int64 `json:"min_rank,omitempty"` MaxRank *int64 `json:"max_rank,omitempty"` IsActive *bool `json:"is_active,omitempty"` MinLevel *uint32 `json:"min_level,omitempty"` MaxLevel *uint32 `json:"max_level,omitempty"` LastActiveAfter *time.Time `json:"last_active_after,omitempty"` LastActiveBefore *time.Time `json:"last_active_before,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` UpdatedAfter *time.Time `json:"updated_after,omitempty"` UpdatedBefore *time.Time `json:"updated_before,omitempty"` Tags []string `json:"tags,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` Offset int `json:"offset,omitempty"` Limit int `json:"limit,omitempty"` } // GetSort 获取排序字段 func (q *RankEntryQuery) GetSort() string { return q.OrderBy } // GetSortOrder 获取排序顺序 func (q *RankEntryQuery) GetSortOrder() bool { return q.OrderDesc } // GetLimit 获取限制数量 func (q *RankEntryQuery) GetLimit() int { return q.Limit } // GetOffset 获取偏移量 func (q *RankEntryQuery) GetOffset() int { return q.Offset } // GetMinRank 获取最小排名 func (q *RankEntryQuery) GetMinRank() *int32 { if q.MinRank != nil { minRank := int32(*q.MinRank) return &minRank } return nil } // GetMaxRank 获取最大排名 func (q *RankEntryQuery) GetMaxRank() *int32 { if q.MaxRank != nil { maxRank := int32(*q.MaxRank) return &maxRank } return nil } // GetMinScore 获取最小分数 func (q *RankEntryQuery) GetMinScore() *int64 { if q.MinScore != nil { return q.MinScore } return nil } // GetMaxScore 获取最大分数 func (q *RankEntryQuery) GetMaxScore() *int64 { if q.MaxScore != nil { return q.MaxScore } return nil } // GetRankingID 获取排行榜ID func (q *RankEntryQuery) GetRankingID() string { if q.RankID != nil { return fmt.Sprintf("%d", *q.RankID) } return "" } // GetPlayerID 获取玩家ID func (q *RankEntryQuery) GetPlayerID() uint64 { if q.PlayerID != nil { return *q.PlayerID } return 0 } // BlacklistQuery 黑名单查询条件 type BlacklistQuery struct { RankID *uint32 `json:"rank_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` Reason string `json:"reason,omitempty"` IsPermanent *bool `json:"is_permanent,omitempty"` IsExpired *bool `json:"is_expired,omitempty"` AddedBy string `json:"added_by,omitempty"` AddedAfter *time.Time `json:"added_after,omitempty"` AddedBefore *time.Time `json:"added_before,omitempty"` ExpiresAfter *time.Time `json:"expires_after,omitempty"` ExpiresBefore *time.Time `json:"expires_before,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` Offset int `json:"offset,omitempty"` Limit int `json:"limit,omitempty"` } // RankingEventQuery 排行榜事件查询条件 type RankingEventQuery struct { EventID string `json:"event_id,omitempty"` EventType string `json:"event_type,omitempty"` AggregateID string `json:"aggregate_id,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` RankID *uint32 `json:"rank_id,omitempty"` MinVersion *int `json:"min_version,omitempty"` MaxVersion *int `json:"max_version,omitempty"` TimestampAfter *time.Time `json:"timestamp_after,omitempty"` TimestampBefore *time.Time `json:"timestamp_before,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` Offset int `json:"offset,omitempty"` Limit int `json:"limit,omitempty"` } // 分页结果结构体 // RankingPageResult 排行榜分页结果 type RankingPageResult struct { Items []*RankingAggregate `json:"items"` Total int64 `json:"total"` Offset int `json:"offset"` Limit int `json:"limit"` HasMore bool `json:"has_more"` } // RankEntryPageResult 排行榜条目分页结果 type RankEntryPageResult struct { Items []*RankEntry `json:"items"` Total int64 `json:"total"` Offset int `json:"offset"` Limit int `json:"limit"` HasMore bool `json:"has_more"` } // BlacklistPageResult 黑名单分页结果 type BlacklistPageResult struct { Items []*BlacklistEntry `json:"items"` Total int64 `json:"total"` Offset int `json:"offset"` Limit int `json:"limit"` HasMore bool `json:"has_more"` } // RankingEventPageResult 排行榜事件分页结果 type RankingEventPageResult struct { Items []RankingEvent `json:"items"` Total int64 `json:"total"` Offset int `json:"offset"` Limit int `json:"limit"` HasMore bool `json:"has_more"` } // 统计数据结构体 // GlobalRankingStatistics 全局排行榜统计 type GlobalRankingStatistics struct { TotalRankings int64 `json:"total_rankings"` ActiveRankings int64 `json:"active_rankings"` TotalPlayers int64 `json:"total_players"` TotalEntries int64 `json:"total_entries"` AveragePlayersPerRanking float64 `json:"average_players_per_ranking"` CategoryDistribution map[RankCategory]int64 `json:"category_distribution"` TypeDistribution map[RankType]int64 `json:"type_distribution"` StatusDistribution map[RankStatus]int64 `json:"status_distribution"` MostPopularCategory RankCategory `json:"most_popular_category"` MostPopularType RankType `json:"most_popular_type"` TotalBlacklisted int64 `json:"total_blacklisted"` LastUpdated time.Time `json:"last_updated"` } // CategoryRankingStatistics 分类排行榜统计 type CategoryRankingStatistics struct { Category RankCategory `json:"category"` TotalRankings int64 `json:"total_rankings"` ActiveRankings int64 `json:"active_rankings"` TotalPlayers int64 `json:"total_players"` AveragePlayersPerRanking float64 `json:"average_players_per_ranking"` MostActiveRanking uint32 `json:"most_active_ranking"` HighestScore int64 `json:"highest_score"` AverageScore float64 `json:"average_score"` LastUpdated time.Time `json:"last_updated"` } // TypeRankingStatistics 类型排行榜统计 type TypeRankingStatistics struct { RankType RankType `json:"rank_type"` TotalRankings int64 `json:"total_rankings"` ActiveRankings int64 `json:"active_rankings"` TotalPlayers int64 `json:"total_players"` AveragePlayersPerRanking float64 `json:"average_players_per_ranking"` MostCompetitiveRanking uint32 `json:"most_competitive_ranking"` HighestScore int64 `json:"highest_score"` AverageScore float64 `json:"average_score"` LastUpdated time.Time `json:"last_updated"` } // DailyRankingStats 日排行榜统计 type DailyRankingStats struct { Date time.Time `json:"date"` RankID uint32 `json:"rank_id"` TotalPlayers int64 `json:"total_players"` ActiveEntries int64 `json:"active_entries"` NewPlayers int64 `json:"new_players"` TopScore int64 `json:"top_score"` AverageScore float64 `json:"average_score"` ScoreUpdates int64 `json:"score_updates"` RankChanges int64 `json:"rank_changes"` BlacklistChanges int64 `json:"blacklist_changes"` } // WeeklyRankingStats 周排行榜统计 type WeeklyRankingStats struct { WeekStart time.Time `json:"week_start"` WeekEnd time.Time `json:"week_end"` RankID uint32 `json:"rank_id"` TotalPlayers int64 `json:"total_players"` ActiveEntries int64 `json:"active_entries"` NewPlayers int64 `json:"new_players"` TopScore int64 `json:"top_score"` AverageScore float64 `json:"average_score"` ScoreUpdates int64 `json:"score_updates"` RankChanges int64 `json:"rank_changes"` Competitiveness float64 `json:"competitiveness"` GrowthRate float64 `json:"growth_rate"` } // MonthlyRankingStats 月排行榜统计 type MonthlyRankingStats struct { Month time.Time `json:"month"` RankID uint32 `json:"rank_id"` TotalPlayers int64 `json:"total_players"` ActiveEntries int64 `json:"active_entries"` NewPlayers int64 `json:"new_players"` TopScore int64 `json:"top_score"` AverageScore float64 `json:"average_score"` ScoreUpdates int64 `json:"score_updates"` RankChanges int64 `json:"rank_changes"` Competitiveness float64 `json:"competitiveness"` GrowthRate float64 `json:"growth_rate"` RetentionRate float64 `json:"retention_rate"` ChurnRate float64 `json:"churn_rate"` } // RankingComparison 排行榜比较 type RankingComparison struct { RankID1 uint32 `json:"rank_id_1"` RankID2 uint32 `json:"rank_id_2"` PlayerDiff int64 `json:"player_diff"` ScoreDiff float64 `json:"score_diff"` ActivityDiff float64 `json:"activity_diff"` CompetitivenessDiff float64 `json:"competitiveness_diff"` SimilarityScore float64 `json:"similarity_score"` ComparedAt time.Time `json:"compared_at"` } // RankingPerformance 排行榜性能 type RankingPerformance struct { RankID uint32 `json:"rank_id"` Period RankPeriod `json:"period"` PlayerGrowth float64 `json:"player_growth"` ScoreGrowth float64 `json:"score_growth"` ActivityLevel float64 `json:"activity_level"` Competitiveness float64 `json:"competitiveness"` RetentionRate float64 `json:"retention_rate"` EngagementScore float64 `json:"engagement_score"` HealthScore float64 `json:"health_score"` TrendDirection string `json:"trend_direction"` CalculatedAt time.Time `json:"calculated_at"` } // RankingPrediction 排行榜预测 type RankingPrediction struct { RankID uint32 `json:"rank_id"` PredictionDays int `json:"prediction_days"` PredictedPlayers int64 `json:"predicted_players"` PredictedTopScore int64 `json:"predicted_top_score"` PredictedAvgScore float64 `json:"predicted_avg_score"` ConfidenceLevel float64 `json:"confidence_level"` PredictionAccuracy float64 `json:"prediction_accuracy"` RiskFactors []string `json:"risk_factors"` Recommendations []string `json:"recommendations"` CreatedAt time.Time `json:"created_at"` ValidUntil time.Time `json:"valid_until"` } // CacheInfo 缓存信息 type CacheInfo struct { TotalKeys int64 `json:"total_keys"` UsedMemory int64 `json:"used_memory"` HitRate float64 `json:"hit_rate"` MissRate float64 `json:"miss_rate"` EvictionCount int64 `json:"eviction_count"` ExpiredCount int64 `json:"expired_count"` AverageTTL time.Duration `json:"average_ttl"` OldestKey string `json:"oldest_key"` NewestKey string `json:"newest_key"` LastCleanup time.Time `json:"last_cleanup"` CacheHealth string `json:"cache_health"` } // 仓储工厂接口 // RankingRepositoryFactory 排行榜仓储工厂接口 type RankingRepositoryFactory interface { // 创建仓储实例 CreateRankingRepository() RankingRepository CreateRankEntryRepository() RankEntryRepository CreateBlacklistRepository() BlacklistRepository CreateStatisticsRepository() RankingStatisticsRepository CreateCacheRepository() RankingCacheRepository CreateEventRepository() RankingEventRepository CreateSearchRepository() RankingSearchRepository // 健康检查 HealthCheck() error // 关闭连接 Close() error } // 事务接口 // RankingTransactionRepository 排行榜事务仓储接口 type RankingTransactionRepository interface { // 事务管理 BeginTransaction() (RankingTransaction, error) CommitTransaction(tx RankingTransaction) error RollbackTransaction(tx RankingTransaction) error // 在事务中执行操作 ExecuteInTransaction(fn func(tx RankingTransaction) error) error } // RankingTransaction 排行榜事务接口 type RankingTransaction interface { // 排行榜操作 SaveRanking(ranking *RankingAggregate) error UpdateRanking(ranking *RankingAggregate) error DeleteRanking(id string) error // 条目操作 SaveEntry(entry *RankEntry) error UpdateEntry(entry *RankEntry) error DeleteEntry(id string) error // 黑名单操作 SaveBlacklist(blacklist *Blacklist) error UpdateBlacklist(blacklist *Blacklist) error DeleteBlacklist(id string) error // 统计操作 SaveStatistics(stats *RankingStatistics) error UpdateStatistics(stats *RankingStatistics) error // 事件操作 SaveEvent(event RankingEvent) error SaveEvents(events []RankingEvent) error // 事务状态 IsActive() bool GetID() string } ================================================ FILE: internal/domain/ranking/service.go ================================================ package ranking import ( "fmt" "math" "sync" "time" ) // RankingService 排行榜领域服务 type RankingService struct { // 依赖的仓储 rankingRepo RankingRepository blacklistRepo BlacklistRepository cacheRepo RankingCacheRepository statisticsRepo RankingStatisticsRepository // 配置 config *RankingServiceConfig // 内部状态 mutex sync.RWMutex activeRankings map[uint32]*RankingAggregate lastCleanup time.Time } // RankingServiceConfig 排行榜服务配置 type RankingServiceConfig struct { // 缓存配置 EnableCache bool `json:"enable_cache"` CacheTTL time.Duration `json:"cache_ttl"` CacheRefreshInterval time.Duration `json:"cache_refresh_interval"` // 性能配置 MaxConcurrentUpdates int `json:"max_concurrent_updates"` BatchSize int `json:"batch_size"` UpdateTimeout time.Duration `json:"update_timeout"` // 清理配置 CleanupInterval time.Duration `json:"cleanup_interval"` ExpiredDataRetention time.Duration `json:"expired_data_retention"` // 统计配置 EnableStatistics bool `json:"enable_statistics"` StatisticsInterval time.Duration `json:"statistics_interval"` // 奖励配置 EnableRewards bool `json:"enable_rewards"` AutoDistributeRewards bool `json:"auto_distribute_rewards"` // 验证配置 EnableValidation bool `json:"enable_validation"` StrictMode bool `json:"strict_mode"` } // NewRankingService 创建排行榜服务 func NewRankingService( rankingRepo RankingRepository, blacklistRepo BlacklistRepository, cacheRepo RankingCacheRepository, statisticsRepo RankingStatisticsRepository, config *RankingServiceConfig, ) *RankingService { if config == nil { config = DefaultRankingServiceConfig() } return &RankingService{ rankingRepo: rankingRepo, blacklistRepo: blacklistRepo, cacheRepo: cacheRepo, statisticsRepo: statisticsRepo, config: config, activeRankings: make(map[uint32]*RankingAggregate), lastCleanup: time.Now(), } } // CreateRanking 创建排行榜 func (rs *RankingService) CreateRanking(rankID uint32, name string, rankType RankType, category RankCategory, config *RankingCreateConfig) (*RankingAggregate, error) { // 验证参数 if err := rs.validateCreateRankingParams(rankID, name, rankType, category, config); err != nil { return nil, err } // 检查排行榜是否已存在 existing, _ := rs.rankingRepo.FindByRankID(rankID) if existing != nil { return nil, NewRankingAlreadyExistsError(rankID) } // 创建排行榜聚合 ranking := NewRankingAggregate(rankID, name, rankType, category) // 应用配置 if config != nil { rs.applyCreateConfig(ranking, config) } // 验证排行榜 if rs.config.EnableValidation { if err := ranking.Validate(); err != nil { return nil, err } } // 保存到仓储 if err := rs.rankingRepo.Save(ranking); err != nil { return nil, NewRankingSystemError("repository", "failed to save ranking", err) } // 添加到活跃排行榜 rs.mutex.Lock() rs.activeRankings[rankID] = ranking rs.mutex.Unlock() // 初始化缓存 if rs.config.EnableCache { rs.initializeRankingCache(ranking) } // 初始化统计 if rs.config.EnableStatistics { rs.initializeRankingStatistics(ranking) } return ranking, nil } // UpdatePlayerScore 更新玩家分数 func (rs *RankingService) UpdatePlayerScore(rankID uint32, playerID uint64, score int64, metadata map[string]interface{}) (*RankingOperationResult, error) { start := time.Now() result := NewRankingOperationResult(RankingOperationUpdate, rankID, false) defer result.SetDuration(start) // 获取排行榜 ranking, err := rs.getRanking(rankID) if err != nil { result.SetError(err) return result, err } // 获取旧排名和分数 oldEntry, oldRank, _ := ranking.GetPlayerRank(playerID) var oldScore *int64 if oldEntry != nil { oldScore = &oldEntry.Score } // 更新分数 err = ranking.UpdateScore(playerID, score, metadata) if err != nil { result.SetError(err) return result, err } // 获取新排名 _, newRank, _ := ranking.GetPlayerRank(playerID) // 保存排行榜 if err := rs.rankingRepo.Update(ranking); err != nil { err = NewRankingSystemError("repository", "failed to update ranking", err) result.SetError(err) return result, err } // 更新缓存 if rs.config.EnableCache { rs.updateRankingCache(ranking) } // 更新统计 if rs.config.EnableStatistics { rs.updateRankingStatistics(ranking) } // 检查奖励 if rs.config.EnableRewards && rs.config.AutoDistributeRewards { rs.checkAndDistributeRewards(ranking, playerID, oldRank, newRank) } // 设置结果 result.Success = true result.SetPlayerInfo(playerID, &oldRank, &newRank, oldScore, &score) result.AffectedCount = 1 result.SetMessage(fmt.Sprintf("Player %d score updated from %v to %d", playerID, oldScore, score)) return result, nil } // GetRanking 获取排行榜数据 func (rs *RankingService) GetRanking(rankID uint32, start, end int64, filter *RankingFilter) ([]*RankEntry, error) { // 验证范围 if err := ValidateRankingRange(start, end); err != nil { return nil, err } // 尝试从缓存获取 if rs.config.EnableCache { if entries, err := rs.getRankingFromCache(rankID, start, end, filter); err == nil { return entries, nil } } // 从仓储获取 ranking, err := rs.getRanking(rankID) if err != nil { return nil, err } // 应用过滤器 excludeBlacklisted := filter == nil || filter.ExcludeBlacklisted entries, err := ranking.GetRanking(start, end, excludeBlacklisted) if err != nil { return nil, err } // 应用额外过滤 if filter != nil { entries = rs.applyRankingFilter(entries, filter) } // 更新缓存 if rs.config.EnableCache { rs.cacheRankingData(rankID, start, end, entries) } return entries, nil } // GetPlayerRank 获取玩家排名 func (rs *RankingService) GetPlayerRank(rankID uint32, playerID uint64) (*RankEntry, int64, error) { // 尝试从缓存获取 if rs.config.EnableCache { if entry, rank, err := rs.getPlayerRankFromCache(rankID, playerID); err == nil { return entry, rank, nil } } // 从仓储获取 ranking, err := rs.getRanking(rankID) if err != nil { return nil, -1, err } entry, rank, err := ranking.GetPlayerRank(playerID) if err != nil { return nil, -1, err } // 更新缓存 if rs.config.EnableCache { rs.cachePlayerRank(rankID, playerID, entry, rank) } return entry, rank, nil } // AddToBlacklist 添加到黑名单 func (rs *RankingService) AddToBlacklist(rankID uint32, playerID uint64, reason string, duration *time.Duration) error { // 获取排行榜 ranking, err := rs.getRanking(rankID) if err != nil { return err } // 添加到黑名单 err = ranking.AddToBlacklist(playerID, reason) if err != nil { return err } // 如果是临时黑名单,设置过期时间 if duration != nil { blacklistEntry, exists := ranking.Blacklist.GetBlacklistEntry(playerID) if exists { blacklistEntry.SetExpiration(time.Now().Add(*duration)) } } // 保存排行榜 if err := rs.rankingRepo.Update(ranking); err != nil { return NewRankingSystemError("repository", "failed to update ranking", err) } // 清除相关缓存 if rs.config.EnableCache { rs.clearPlayerCache(rankID, playerID) rs.clearRankingCache(rankID) } return nil } // RemoveFromBlacklist 从黑名单移除 func (rs *RankingService) RemoveFromBlacklist(rankID uint32, playerID uint64) error { // 获取排行榜 ranking, err := rs.getRanking(rankID) if err != nil { return err } // 从黑名单移除 err = ranking.RemoveFromBlacklist(playerID) if err != nil { return err } // 保存排行榜 if err := rs.rankingRepo.Update(ranking); err != nil { return NewRankingSystemError("repository", "failed to update ranking", err) } // 清除相关缓存 if rs.config.EnableCache { rs.clearPlayerCache(rankID, playerID) rs.clearRankingCache(rankID) } return nil } // ResetRanking 重置排行榜 func (rs *RankingService) ResetRanking(rankID uint32) (*RankingOperationResult, error) { start := time.Now() result := NewRankingOperationResult(RankingOperationReset, rankID, false) defer result.SetDuration(start) // 获取排行榜 ranking, err := rs.getRanking(rankID) if err != nil { result.SetError(err) return result, err } // 记录重置前的玩家数量 oldPlayerCount := ranking.TotalPlayers // 重置排行榜 err = ranking.Reset() if err != nil { result.SetError(err) return result, err } // 保存排行榜 if err := rs.rankingRepo.Update(ranking); err != nil { err = NewRankingSystemError("repository", "failed to update ranking", err) result.SetError(err) return result, err } // 清除缓存 if rs.config.EnableCache { rs.clearRankingCache(rankID) } // 重置统计 if rs.config.EnableStatistics { rs.resetRankingStatistics(rankID) } // 设置结果 result.Success = true result.AffectedCount = oldPlayerCount result.SetMessage(fmt.Sprintf("Ranking %d reset, %d players removed", rankID, oldPlayerCount)) return result, nil } // GetRankingStatistics 获取排行榜统计 func (rs *RankingService) GetRankingStatistics(rankID uint32) (*RankingStatistics, error) { // 尝试从缓存获取 if rs.config.EnableCache { if stats, err := rs.getStatisticsFromCache(rankID); err == nil { return stats, nil } } // 从排行榜获取 ranking, err := rs.getRanking(rankID) if err != nil { return nil, err } stats := ranking.GetStatistics() // 更新缓存 if rs.config.EnableCache { rs.cacheStatistics(rankID, stats) } return stats, nil } // BatchUpdateScores 批量更新分数 func (rs *RankingService) BatchUpdateScores(rankID uint32, updates []*ScoreUpdate) ([]*RankingOperationResult, error) { if len(updates) == 0 { return []*RankingOperationResult{}, nil } // 获取排行榜 ranking, err := rs.getRanking(rankID) if err != nil { return nil, err } results := make([]*RankingOperationResult, len(updates)) // 批量处理更新 for i, update := range updates { start := time.Now() result := NewRankingOperationResult(RankingOperationUpdate, rankID, false) // 获取旧排名和分数 oldEntry, oldRank, _ := ranking.GetPlayerRank(update.PlayerID) var oldScore *int64 if oldEntry != nil { oldScore = &oldEntry.Score } // 更新分数 err := ranking.UpdateScore(update.PlayerID, update.Score, update.Metadata) if err != nil { result.SetError(err) } else { // 获取新排名 _, newRank, _ := ranking.GetPlayerRank(update.PlayerID) result.Success = true result.SetPlayerInfo(update.PlayerID, &oldRank, &newRank, oldScore, &update.Score) result.AffectedCount = 1 } result.SetDuration(start) results[i] = result } // 保存排行榜 if err := rs.rankingRepo.Update(ranking); err != nil { return results, NewRankingSystemError("repository", "failed to update ranking", err) } // 更新缓存 if rs.config.EnableCache { rs.updateRankingCache(ranking) } // 更新统计 if rs.config.EnableStatistics { rs.updateRankingStatistics(ranking) } return results, nil } // GetTopPlayers 获取前N名玩家 func (rs *RankingService) GetTopPlayers(rankID uint32, count int) ([]*RankEntry, error) { if count <= 0 { return []*RankEntry{}, nil } // 尝试从缓存获取 if rs.config.EnableCache { if entries, err := rs.getTopPlayersFromCache(rankID, count); err == nil { return entries, nil } } // 从排行榜获取 ranking, err := rs.getRanking(rankID) if err != nil { return nil, err } entries := ranking.GetTopPlayers(count) // 更新缓存 if rs.config.EnableCache { rs.cacheTopPlayers(rankID, count, entries) } return entries, nil } // CalculateRankingTrend 计算排行榜趋势 func (rs *RankingService) CalculateRankingTrend(rankID uint32, period RankPeriod, points int) (*RankingTrend, error) { // 获取历史统计数据 historyStats, err := rs.statisticsRepo.GetHistoryStatistics(rankID, period, points) if err != nil { return nil, err } // 计算趋势数据 trendData := make([]*RankingTrendPoint, len(historyStats)) for i, stats := range historyStats { trendData[i] = &RankingTrendPoint{ Timestamp: stats.LastUpdated, PlayerCount: stats.TotalPlayers, AverageScore: stats.AverageScore, TopScore: stats.TopScore, ScoreVariance: rs.calculateScoreVariance(stats), NewPlayers: rs.calculateNewPlayers(stats), ActivePlayers: stats.ActiveEntries, } } // 计算增长率和波动性 growthRate := rs.calculateGrowthRate(trendData) volatility := rs.calculateVolatility(trendData) // 生成预测 prediction := rs.generateTrendPrediction(trendData, period) trend := &RankingTrend{ RankID: rankID, Period: period, TrendData: trendData, GrowthRate: growthRate, Volatility: volatility, Prediction: prediction, CreatedAt: time.Now(), UpdatedAt: time.Now(), } return trend, nil } // CleanupExpiredData 清理过期数据 func (rs *RankingService) CleanupExpiredData() error { now := time.Now() // 检查是否需要清理 if now.Sub(rs.lastCleanup) < rs.config.CleanupInterval { return nil } rs.mutex.Lock() defer rs.mutex.Unlock() // 清理过期的黑名单条目 for _, ranking := range rs.activeRankings { rs.cleanupExpiredBlacklistEntries(ranking) } // 清理过期的缓存 if rs.config.EnableCache { rs.cleanupExpiredCache() } // 清理过期的统计数据 if rs.config.EnableStatistics { rs.cleanupExpiredStatistics() } rs.lastCleanup = now return nil } // 私有方法 // getRanking 获取排行榜 func (rs *RankingService) getRanking(rankID uint32) (*RankingAggregate, error) { // 先从内存中获取 rs.mutex.RLock() ranking, exists := rs.activeRankings[rankID] rs.mutex.RUnlock() if exists { return ranking, nil } // 从仓储加载 ranking, err := rs.rankingRepo.FindByRankID(rankID) if err != nil { return nil, err } if ranking == nil { return nil, NewRankingNotFoundError(rankID) } // 添加到内存 rs.mutex.Lock() rs.activeRankings[rankID] = ranking rs.mutex.Unlock() return ranking, nil } // validateCreateRankingParams 验证创建排行榜参数 func (rs *RankingService) validateCreateRankingParams(rankID uint32, name string, rankType RankType, category RankCategory, config *RankingCreateConfig) error { if rankID == 0 { return NewRankingValidationError("rank_id", rankID, "rank_id cannot be zero", "required") } if name == "" { return NewRankingValidationError("name", name, "name cannot be empty", "required") } if !rankType.IsValid() { return NewRankingValidationError("rank_type", rankType, "invalid rank type", "enum") } if !category.IsValid() { return NewRankingValidationError("category", category, "invalid category", "enum") } return nil } // applyCreateConfig 应用创建配置 func (rs *RankingService) applyCreateConfig(ranking *RankingAggregate, config *RankingCreateConfig) { if config.Description != nil { ranking.Description = *config.Description } if config.SortType != nil { ranking.SortType = *config.SortType } if config.MaxSize != nil { ranking.MaxSize = *config.MaxSize } if config.Period != nil { ranking.Period = *config.Period } if config.StartTime != nil { ranking.StartTime = config.StartTime.Unix() } if config.EndTime != nil { ranking.EndTime = config.EndTime.Unix() } if config.RewardConfig != nil { ranking.SetRewardConfig(config.RewardConfig) } if config.CacheConfig != nil { ranking.SetCacheConfig(config.CacheConfig) } } // applyRankingFilter 应用排行榜过滤器 func (rs *RankingService) applyRankingFilter(entries []*RankEntry, filter *RankingFilter) []*RankEntry { if filter == nil { return entries } filteredEntries := make([]*RankEntry, 0, len(entries)) for _, entry := range entries { // 检查分数范围 if filter.MinScore != nil && entry.Score < *filter.MinScore { continue } if filter.MaxScore != nil && entry.Score > *filter.MaxScore { continue } // 检查玩家ID过滤 if len(filter.PlayerIDs) > 0 { found := false for _, playerID := range filter.PlayerIDs { if entry.PlayerID == playerID { found = true break } } if !found { continue } } // 检查排除玩家ID if len(filter.ExcludePlayerIDs) > 0 { excluded := false for _, playerID := range filter.ExcludePlayerIDs { if entry.PlayerID == playerID { excluded = true break } } if excluded { continue } } // 检查活跃状态 if filter.ExcludeInactive && !entry.IsActive { continue } // 检查时间范围 if filter.TimeRange != nil { if !filter.TimeRange.Contains(entry.LastUpdateTime) { continue } } filteredEntries = append(filteredEntries, entry) } return filteredEntries } // 缓存相关方法 func (rs *RankingService) initializeRankingCache(ranking *RankingAggregate) { // 实现缓存初始化逻辑 } func (rs *RankingService) updateRankingCache(ranking *RankingAggregate) { // 实现缓存更新逻辑 } func (rs *RankingService) getRankingFromCache(rankID uint32, start, end int64, filter *RankingFilter) ([]*RankEntry, error) { // 实现从缓存获取排行榜数据的逻辑 return nil, fmt.Errorf("cache miss") } func (rs *RankingService) cacheRankingData(rankID uint32, start, end int64, entries []*RankEntry) { // 实现缓存排行榜数据的逻辑 } func (rs *RankingService) getPlayerRankFromCache(rankID uint32, playerID uint64) (*RankEntry, int64, error) { // 实现从缓存获取玩家排名的逻辑 return nil, -1, fmt.Errorf("cache miss") } func (rs *RankingService) cachePlayerRank(rankID uint32, playerID uint64, entry *RankEntry, rank int64) { // 实现缓存玩家排名的逻辑 } func (rs *RankingService) clearPlayerCache(rankID uint32, playerID uint64) { // 实现清除玩家缓存的逻辑 } func (rs *RankingService) clearRankingCache(rankID uint32) { // 实现清除排行榜缓存的逻辑 } func (rs *RankingService) getTopPlayersFromCache(rankID uint32, count int) ([]*RankEntry, error) { // 实现从缓存获取前N名玩家的逻辑 return nil, fmt.Errorf("cache miss") } func (rs *RankingService) cacheTopPlayers(rankID uint32, count int, entries []*RankEntry) { // 实现缓存前N名玩家的逻辑 } func (rs *RankingService) cleanupExpiredCache() { // 实现清理过期缓存的逻辑 } // 统计相关方法 func (rs *RankingService) initializeRankingStatistics(ranking *RankingAggregate) { // 实现统计初始化逻辑 } func (rs *RankingService) updateRankingStatistics(ranking *RankingAggregate) { // 实现统计更新逻辑 } func (rs *RankingService) resetRankingStatistics(rankID uint32) { // 实现统计重置逻辑 } func (rs *RankingService) getStatisticsFromCache(rankID uint32) (*RankingStatistics, error) { // 实现从缓存获取统计的逻辑 return nil, fmt.Errorf("cache miss") } func (rs *RankingService) cacheStatistics(rankID uint32, stats *RankingStatistics) { // 实现缓存统计的逻辑 } func (rs *RankingService) cleanupExpiredStatistics() { // 实现清理过期统计的逻辑 } // 奖励相关方法 func (rs *RankingService) checkAndDistributeRewards(ranking *RankingAggregate, playerID uint64, oldRank, newRank int64) { // 实现检查和分发奖励的逻辑 } // 清理相关方法 func (rs *RankingService) cleanupExpiredBlacklistEntries(ranking *RankingAggregate) { // 清理过期的黑名单条目 expiredPlayers := make([]uint64, 0) for playerID, entry := range ranking.Blacklist.Players { if entry.IsExpired() { expiredPlayers = append(expiredPlayers, playerID) } } // 移除过期的玩家 for _, playerID := range expiredPlayers { ranking.RemoveFromBlacklist(playerID) } // 如果有变更,保存排行榜 if len(expiredPlayers) > 0 { rs.rankingRepo.Update(ranking) } } // 趋势计算相关方法 func (rs *RankingService) calculateScoreVariance(stats *RankingStatistics) float64 { // 实现分数方差计算逻辑 return 0.0 } func (rs *RankingService) calculateNewPlayers(stats *RankingStatistics) int64 { // 实现新玩家数量计算逻辑 return 0 } func (rs *RankingService) calculateGrowthRate(trendData []*RankingTrendPoint) float64 { if len(trendData) < 2 { return 0.0 } first := trendData[0] last := trendData[len(trendData)-1] if first.PlayerCount == 0 { return 0.0 } return float64(last.PlayerCount-first.PlayerCount) / float64(first.PlayerCount) * 100 } func (rs *RankingService) calculateVolatility(trendData []*RankingTrendPoint) float64 { if len(trendData) < 2 { return 0.0 } // 计算分数变化的标准差 scores := make([]float64, len(trendData)) for i, point := range trendData { scores[i] = point.AverageScore } // 计算平均值 sum := 0.0 for _, score := range scores { sum += score } mean := sum / float64(len(scores)) // 计算方差 variance := 0.0 for _, score := range scores { variance += math.Pow(score-mean, 2) } variance /= float64(len(scores)) // 返回标准差 return math.Sqrt(variance) } func (rs *RankingService) generateTrendPrediction(trendData []*RankingTrendPoint, period RankPeriod) *RankingTrendPrediction { if len(trendData) < 3 { return nil } // 简单的线性预测 last := trendData[len(trendData)-1] secondLast := trendData[len(trendData)-2] playerGrowth := last.PlayerCount - secondLast.PlayerCount scoreGrowth := last.AverageScore - secondLast.AverageScore topScoreGrowth := last.TopScore - secondLast.TopScore prediction := &RankingTrendPrediction{ PredictedPlayerCount: last.PlayerCount + playerGrowth, PredictedAverageScore: last.AverageScore + scoreGrowth, PredictedTopScore: last.TopScore + topScoreGrowth, ConfidenceLevel: 0.7, // 70%置信度 PredictionTime: time.Now(), ValidUntil: time.Now().Add(period.GetDuration()), } return prediction } // 辅助结构体 // RankingCreateConfig 排行榜创建配置 type RankingCreateConfig struct { Description *string `json:"description,omitempty"` SortType *SortType `json:"sort_type,omitempty"` MaxSize *int64 `json:"max_size,omitempty"` Period *RankPeriod `json:"period,omitempty"` StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` RewardConfig *RankRewardConfig `json:"reward_config,omitempty"` CacheConfig *RankCacheConfig `json:"cache_config,omitempty"` } // ScoreUpdate 分数更新 type ScoreUpdate struct { PlayerID uint64 `json:"player_id"` Score int64 `json:"score"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // DefaultRankingServiceConfig 默认排行榜服务配置 func DefaultRankingServiceConfig() *RankingServiceConfig { return &RankingServiceConfig{ EnableCache: true, CacheTTL: 30 * time.Minute, CacheRefreshInterval: 5 * time.Minute, MaxConcurrentUpdates: 100, BatchSize: 50, UpdateTimeout: 30 * time.Second, CleanupInterval: 1 * time.Hour, ExpiredDataRetention: 7 * 24 * time.Hour, EnableStatistics: true, StatisticsInterval: 10 * time.Minute, EnableRewards: true, AutoDistributeRewards: true, EnableValidation: true, StrictMode: false, } } ================================================ FILE: internal/domain/ranking/value_object.go ================================================ package ranking import ( "fmt" "time" ) // 排行榜类型相关值对象 // RankType 排行榜类型 type RankType int32 const ( RankTypeLevel RankType = iota + 1 // 等级排行榜 RankTypePower // 战力排行榜 RankTypeWealth // 财富排行榜 RankTypeAchievement // 成就排行榜 RankTypePet // 宠物排行榜 RankTypeGuild // 公会排行榜 RankTypeArena // 竞技场排行榜 RankTypeDungeon // 副本排行榜 RankTypeActivity // 活动排行榜 RankTypeCustom // 自定义排行榜 ) // String 返回排行榜类型的字符串表示 func (rt RankType) String() string { switch rt { case RankTypeLevel: return "level" case RankTypePower: return "power" case RankTypeWealth: return "wealth" case RankTypeAchievement: return "achievement" case RankTypePet: return "pet" case RankTypeGuild: return "guild" case RankTypeArena: return "arena" case RankTypeDungeon: return "dungeon" case RankTypeActivity: return "activity" case RankTypeCustom: return "custom" default: return "unknown" } } // ParseRankType 解析排行榜类型 func ParseRankType(s string) RankType { switch s { case "level": return RankTypeLevel case "power": return RankTypePower case "wealth": return RankTypeWealth case "achievement": return RankTypeAchievement case "pet": return RankTypePet case "guild": return RankTypeGuild case "arena": return RankTypeArena case "dungeon": return RankTypeDungeon case "activity": return RankTypeActivity case "custom": return RankTypeCustom default: return RankTypeLevel // 默认值 } } // IsValid 检查排行榜类型是否有效 func (rt RankType) IsValid() bool { return rt >= RankTypeLevel && rt <= RankTypeCustom } // RankCategory 排行榜分类 type RankCategory int32 const ( RankCategoryPlayer RankCategory = iota + 1 // 玩家排行榜 RankCategoryGuild // 公会排行榜 RankCategoryServer // 服务器排行榜 RankCategoryGlobal // 全球排行榜 RankCategoryEvent // 活动排行榜 RankCategorySeason // 赛季排行榜 ) // String 返回排行榜分类的字符串表示 func (rc RankCategory) String() string { switch rc { case RankCategoryPlayer: return "player" case RankCategoryGuild: return "guild" case RankCategoryServer: return "server" case RankCategoryGlobal: return "global" case RankCategoryEvent: return "event" case RankCategorySeason: return "season" default: return "unknown" } } // IsValid 检查排行榜分类是否有效 func (rc RankCategory) IsValid() bool { return rc >= RankCategoryPlayer && rc <= RankCategorySeason } // SortType 排序类型 type SortType int32 const ( SortTypeDescending SortType = iota + 1 // 降序(从高到低) SortTypeAscending // 升序(从低到高) ) // String 返回排序类型的字符串表示 func (st SortType) String() string { switch st { case SortTypeDescending: return "descending" case SortTypeAscending: return "ascending" default: return "unknown" } } // IsValid 检查排序类型是否有效 func (st SortType) IsValid() bool { return st == SortTypeDescending || st == SortTypeAscending } // RankPeriod 排行榜周期 type RankPeriod int32 const ( RankPeriodPermanent RankPeriod = iota + 1 // 永久排行榜 RankPeriodDaily // 日排行榜 RankPeriodWeekly // 周排行榜 RankPeriodMonthly // 月排行榜 RankPeriodSeasonal // 赛季排行榜 RankPeriodEvent // 活动排行榜 RankPeriodCustom // 自定义周期 ) // String 返回排行榜周期的字符串表示 func (rp RankPeriod) String() string { switch rp { case RankPeriodPermanent: return "permanent" case RankPeriodDaily: return "daily" case RankPeriodWeekly: return "weekly" case RankPeriodMonthly: return "monthly" case RankPeriodSeasonal: return "seasonal" case RankPeriodEvent: return "event" case RankPeriodCustom: return "custom" default: return "unknown" } } // ParsePeriodType 解析排行榜周期类型 func ParsePeriodType(s string) RankPeriod { switch s { case "permanent": return RankPeriodPermanent case "daily": return RankPeriodDaily case "weekly": return RankPeriodWeekly case "monthly": return RankPeriodMonthly case "seasonal": return RankPeriodSeasonal case "event": return RankPeriodEvent case "custom": return RankPeriodCustom default: return RankPeriodPermanent // 默认值 } } // IsValid 检查排行榜周期是否有效 func (rp RankPeriod) IsValid() bool { return rp >= RankPeriodPermanent && rp <= RankPeriodCustom } // GetDuration 获取周期持续时间 func (rp RankPeriod) GetDuration() time.Duration { switch rp { case RankPeriodDaily: return 24 * time.Hour case RankPeriodWeekly: return 7 * 24 * time.Hour case RankPeriodMonthly: return 30 * 24 * time.Hour case RankPeriodSeasonal: return 90 * 24 * time.Hour default: return 0 // 永久或自定义周期 } } // RankStatus 排行榜状态 type RankStatus int32 const ( RankStatusActive RankStatus = iota + 1 // 活跃状态 RankStatusInactive // 非活跃状态 RankStatusPaused // 暂停状态 RankStatusExpired // 过期状态 RankStatusMaintenance // 维护状态 RankStatusArchived // 归档状态 ) // String 返回排行榜状态的字符串表示 func (rs RankStatus) String() string { switch rs { case RankStatusActive: return "active" case RankStatusInactive: return "inactive" case RankStatusPaused: return "paused" case RankStatusExpired: return "expired" case RankStatusMaintenance: return "maintenance" case RankStatusArchived: return "archived" default: return "unknown" } } // IsValid 检查排行榜状态是否有效 func (rs RankStatus) IsValid() bool { return rs >= RankStatusActive && rs <= RankStatusArchived } // CanAcceptUpdates 检查状态是否可以接受更新 func (rs RankStatus) CanAcceptUpdates() bool { return rs == RankStatusActive } // 排行榜配置相关值对象 // RankRewardConfig 排行榜奖励配置 type RankRewardConfig struct { Enabled bool `json:"enabled" bson:"enabled"` RewardTiers []*RankRewardTier `json:"reward_tiers" bson:"reward_tiers"` RewardType RankRewardType `json:"reward_type" bson:"reward_type"` DistributeAt RankRewardDistributeAt `json:"distribute_at" bson:"distribute_at"` AutoDistribute bool `json:"auto_distribute" bson:"auto_distribute"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // RankRewardTier 排行榜奖励档次 type RankRewardTier struct { MinRank int64 `json:"min_rank" bson:"min_rank"` MaxRank int64 `json:"max_rank" bson:"max_rank"` Rewards []*RankReward `json:"rewards" bson:"rewards"` Title string `json:"title" bson:"title"` Badge string `json:"badge" bson:"badge"` Metadata map[string]interface{} `json:"metadata" bson:"metadata"` } // RankReward 排行榜奖励 type RankReward struct { RewardID string `json:"reward_id" bson:"reward_id"` RewardType string `json:"reward_type" bson:"reward_type"` Quantity int64 `json:"quantity" bson:"quantity"` Name string `json:"name" bson:"name"` Description string `json:"description" bson:"description"` } // RankRewardType 奖励类型 type RankRewardType int32 const ( RankRewardTypeImmediate RankRewardType = iota + 1 // 立即奖励 RankRewardTypeDeferred // 延迟奖励 RankRewardTypeMail // 邮件奖励 RankRewardTypeAchievement // 成就奖励 ) // String 返回奖励类型的字符串表示 func (rrt RankRewardType) String() string { switch rrt { case RankRewardTypeImmediate: return "immediate" case RankRewardTypeDeferred: return "deferred" case RankRewardTypeMail: return "mail" case RankRewardTypeAchievement: return "achievement" default: return "unknown" } } // RankRewardDistributeAt 奖励分发时机 type RankRewardDistributeAt int32 const ( RankRewardDistributeAtPeriodEnd RankRewardDistributeAt = iota + 1 // 周期结束时 RankRewardDistributeAtRankChange // 排名变化时 RankRewardDistributeAtManual // 手动分发 RankRewardDistributeAtScheduled // 定时分发 ) // String 返回奖励分发时机的字符串表示 func (rrda RankRewardDistributeAt) String() string { switch rrda { case RankRewardDistributeAtPeriodEnd: return "period_end" case RankRewardDistributeAtRankChange: return "rank_change" case RankRewardDistributeAtManual: return "manual" case RankRewardDistributeAtScheduled: return "scheduled" default: return "unknown" } } // RankCacheConfig 排行榜缓存配置 type RankCacheConfig struct { Enabled bool `json:"enabled" bson:"enabled"` CacheSize int64 `json:"cache_size" bson:"cache_size"` CacheTTL time.Duration `json:"cache_ttl" bson:"cache_ttl"` RefreshInterval time.Duration `json:"refresh_interval" bson:"refresh_interval"` PreloadTop int64 `json:"preload_top" bson:"preload_top"` LazyLoad bool `json:"lazy_load" bson:"lazy_load"` Compression bool `json:"compression" bson:"compression"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // 排行榜统计相关值对象 // RankingStatistics 排行榜统计信息 type RankingStatistics struct { RankID uint32 `json:"rank_id" bson:"rank_id"` TotalPlayers int64 `json:"total_players" bson:"total_players"` ActiveEntries int64 `json:"active_entries" bson:"active_entries"` AverageScore float64 `json:"average_score" bson:"average_score"` TopScore int64 `json:"top_score" bson:"top_score"` LowestScore int64 `json:"lowest_score" bson:"lowest_score"` ScoreRange int64 `json:"score_range" bson:"score_range"` MedianScore float64 `json:"median_score" bson:"median_score"` StandardDeviation float64 `json:"standard_deviation" bson:"standard_deviation"` BlacklistCount int64 `json:"blacklist_count" bson:"blacklist_count"` LastUpdated time.Time `json:"last_updated" bson:"last_updated"` LastScoreUpdate time.Time `json:"last_score_update" bson:"last_score_update"` UpdateFrequency float64 `json:"update_frequency" bson:"update_frequency"` PeakPlayers int64 `json:"peak_players" bson:"peak_players"` PeakTime time.Time `json:"peak_time" bson:"peak_time"` } // RankingTrend 排行榜趋势数据 type RankingTrend struct { RankID uint32 `json:"rank_id" bson:"rank_id"` Period RankPeriod `json:"period" bson:"period"` TrendData []*RankingTrendPoint `json:"trend_data" bson:"trend_data"` GrowthRate float64 `json:"growth_rate" bson:"growth_rate"` Volatility float64 `json:"volatility" bson:"volatility"` Prediction *RankingTrendPrediction `json:"prediction,omitempty" bson:"prediction,omitempty"` CreatedAt time.Time `json:"created_at" bson:"created_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } // RankingTrendPoint 排行榜趋势点 type RankingTrendPoint struct { Timestamp time.Time `json:"timestamp" bson:"timestamp"` PlayerCount int64 `json:"player_count" bson:"player_count"` AverageScore float64 `json:"average_score" bson:"average_score"` TopScore int64 `json:"top_score" bson:"top_score"` ScoreVariance float64 `json:"score_variance" bson:"score_variance"` NewPlayers int64 `json:"new_players" bson:"new_players"` ActivePlayers int64 `json:"active_players" bson:"active_players"` } // RankingTrendPrediction 排行榜趋势预测 type RankingTrendPrediction struct { PredictedPlayerCount int64 `json:"predicted_player_count" bson:"predicted_player_count"` PredictedAverageScore float64 `json:"predicted_average_score" bson:"predicted_average_score"` PredictedTopScore int64 `json:"predicted_top_score" bson:"predicted_top_score"` ConfidenceLevel float64 `json:"confidence_level" bson:"confidence_level"` PredictionTime time.Time `json:"prediction_time" bson:"prediction_time"` ValidUntil time.Time `json:"valid_until" bson:"valid_until"` } // 排行榜查询相关值对象 // RankingQuery 排行榜查询条件 type RankingQuery struct { RankID *uint32 `json:"rank_id,omitempty"` RankType *RankType `json:"rank_type,omitempty"` Category *RankCategory `json:"category,omitempty"` Status *RankStatus `json:"status,omitempty"` Period *RankPeriod `json:"period,omitempty"` IsActive *bool `json:"is_active,omitempty"` PlayerID *uint64 `json:"player_id,omitempty"` MinScore *int64 `json:"min_score,omitempty"` MaxScore *int64 `json:"max_score,omitempty"` MinRank *int64 `json:"min_rank,omitempty"` MaxRank *int64 `json:"max_rank,omitempty"` StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` CreatedAfter *time.Time `json:"created_after,omitempty"` CreatedBefore *time.Time `json:"created_before,omitempty"` UpdatedAfter *time.Time `json:"updated_after,omitempty"` UpdatedBefore *time.Time `json:"updated_before,omitempty"` Keywords []string `json:"keywords,omitempty"` Tags []string `json:"tags,omitempty"` OrderBy string `json:"order_by,omitempty"` OrderDesc bool `json:"order_desc,omitempty"` Offset int `json:"offset,omitempty"` Limit int `json:"limit,omitempty"` } // RankingRange 排行榜范围 type RankingRange struct { Start int64 `json:"start"` End int64 `json:"end"` IncludeStart bool `json:"include_start"` IncludeEnd bool `json:"include_end"` } // NewRankingRange 创建排行榜范围 func NewRankingRange(start, end int64) *RankingRange { return &RankingRange{ Start: start, End: end, IncludeStart: true, IncludeEnd: true, } } // IsValid 检查范围是否有效 func (rr *RankingRange) IsValid() bool { return rr.Start >= 0 && rr.End >= rr.Start } // Size 获取范围大小 func (rr *RankingRange) Size() int64 { if !rr.IsValid() { return 0 } return rr.End - rr.Start + 1 } // Contains 检查是否包含指定位置 func (rr *RankingRange) Contains(position int64) bool { if !rr.IsValid() { return false } startCheck := position > rr.Start || (rr.IncludeStart && position == rr.Start) endCheck := position < rr.End || (rr.IncludeEnd && position == rr.End) return startCheck && endCheck } // 排行榜过滤相关值对象 // RankingFilter 排行榜过滤器 type RankingFilter struct { ExcludeBlacklisted bool `json:"exclude_blacklisted"` ExcludeInactive bool `json:"exclude_inactive"` MinScore *int64 `json:"min_score,omitempty"` MaxScore *int64 `json:"max_score,omitempty"` PlayerIDs []uint64 `json:"player_ids,omitempty"` ExcludePlayerIDs []uint64 `json:"exclude_player_ids,omitempty"` ScoreRange *RankingRange `json:"score_range,omitempty"` TimeRange *TimeRange `json:"time_range,omitempty"` CustomFilters map[string]interface{} `json:"custom_filters,omitempty"` } // TimeRange 时间范围 type TimeRange struct { Start *time.Time `json:"start,omitempty"` End *time.Time `json:"end,omitempty"` } // NewTimeRange 创建时间范围 func NewTimeRange(start, end *time.Time) *TimeRange { return &TimeRange{ Start: start, End: end, } } // IsValid 检查时间范围是否有效 func (tr *TimeRange) IsValid() bool { if tr.Start == nil && tr.End == nil { return true // 无限制 } if tr.Start != nil && tr.End != nil { return tr.Start.Before(*tr.End) || tr.Start.Equal(*tr.End) } return true // 单边限制 } // Contains 检查是否包含指定时间 func (tr *TimeRange) Contains(t time.Time) bool { if !tr.IsValid() { return false } if tr.Start != nil && t.Before(*tr.Start) { return false } if tr.End != nil && t.After(*tr.End) { return false } return true } // 排行榜操作相关值对象 // RankingOperation 排行榜操作类型 type RankingOperation int32 const ( RankingOperationUpdate RankingOperation = iota + 1 // 更新分数 RankingOperationRemove // 移除玩家 RankingOperationReset // 重置排行榜 RankingOperationFreeze // 冻结排行榜 RankingOperationUnfreeze // 解冻排行榜 RankingOperationArchive // 归档排行榜 RankingOperationRestore // 恢复排行榜 ) // String 返回排行榜操作的字符串表示 func (ro RankingOperation) String() string { switch ro { case RankingOperationUpdate: return "update" case RankingOperationRemove: return "remove" case RankingOperationReset: return "reset" case RankingOperationFreeze: return "freeze" case RankingOperationUnfreeze: return "unfreeze" case RankingOperationArchive: return "archive" case RankingOperationRestore: return "restore" default: return "unknown" } } // IsValid 检查排行榜操作是否有效 func (ro RankingOperation) IsValid() bool { return ro >= RankingOperationUpdate && ro <= RankingOperationRestore } // RequiresPermission 检查操作是否需要权限 func (ro RankingOperation) RequiresPermission() bool { switch ro { case RankingOperationReset, RankingOperationFreeze, RankingOperationUnfreeze, RankingOperationArchive, RankingOperationRestore: return true default: return false } } // RankingOperationResult 排行榜操作结果 type RankingOperationResult struct { Success bool `json:"success"` Operation RankingOperation `json:"operation"` RankID uint32 `json:"rank_id"` PlayerID *uint64 `json:"player_id,omitempty"` OldRank *int64 `json:"old_rank,omitempty"` NewRank *int64 `json:"new_rank,omitempty"` OldScore *int64 `json:"old_score,omitempty"` NewScore *int64 `json:"new_score,omitempty"` AffectedCount int64 `json:"affected_count"` Message string `json:"message"` Error string `json:"error,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` Timestamp time.Time `json:"timestamp"` Duration time.Duration `json:"duration"` } // NewRankingOperationResult 创建排行榜操作结果 func NewRankingOperationResult(operation RankingOperation, rankID uint32, success bool) *RankingOperationResult { return &RankingOperationResult{ Success: success, Operation: operation, RankID: rankID, Timestamp: time.Now(), Metadata: make(map[string]interface{}), } } // SetPlayerInfo 设置玩家信息 func (ror *RankingOperationResult) SetPlayerInfo(playerID uint64, oldRank, newRank, oldScore, newScore *int64) { ror.PlayerID = &playerID ror.OldRank = oldRank ror.NewRank = newRank ror.OldScore = oldScore ror.NewScore = newScore } // SetError 设置错误信息 func (ror *RankingOperationResult) SetError(err error) { ror.Success = false ror.Error = err.Error() } // SetMessage 设置消息 func (ror *RankingOperationResult) SetMessage(message string) { ror.Message = message } // SetDuration 设置持续时间 func (ror *RankingOperationResult) SetDuration(start time.Time) { ror.Duration = time.Since(start) } // AddMetadata 添加元数据 func (ror *RankingOperationResult) AddMetadata(key string, value interface{}) { if ror.Metadata == nil { ror.Metadata = make(map[string]interface{}) } ror.Metadata[key] = value } // 验证函数 // ValidateRankingRange 验证排行榜范围 func ValidateRankingRange(start, end int64) error { if start < 0 { return fmt.Errorf("start position cannot be negative: %d", start) } if end < start { return fmt.Errorf("end position cannot be less than start: start=%d, end=%d", start, end) } if end-start > 1000 { return fmt.Errorf("range too large: max 1000, requested %d", end-start+1) } return nil } // ValidateRankingQuery 验证排行榜查询 func ValidateRankingQuery(query *RankingQuery) error { if query == nil { return fmt.Errorf("query cannot be nil") } if query.Limit != 0 && query.Limit <= 0 { return fmt.Errorf("limit must be positive") } if query.Limit != 0 && query.Limit > 1000 { return fmt.Errorf("limit cannot exceed 1000") } if query.Offset != 0 && query.Offset < 0 { return fmt.Errorf("offset cannot be negative") } if query.MinScore != nil && query.MaxScore != nil && *query.MinScore > *query.MaxScore { return fmt.Errorf("min_score cannot be greater than max_score") } if query.MinRank != nil && query.MaxRank != nil && *query.MinRank > *query.MaxRank { return fmt.Errorf("min_rank cannot be greater than max_rank") } if query.StartTime != nil && query.EndTime != nil && query.StartTime.After(*query.EndTime) { return fmt.Errorf("start_time cannot be after end_time") } if query.CreatedAfter != nil && query.CreatedBefore != nil && query.CreatedAfter.After(*query.CreatedBefore) { return fmt.Errorf("created_after cannot be after created_before") } if query.UpdatedAfter != nil && query.UpdatedBefore != nil && query.UpdatedAfter.After(*query.UpdatedBefore) { return fmt.Errorf("updated_after cannot be after updated_before") } return nil } // 辅助函数 // GetRankTypeByString 根据字符串获取排行榜类型 func GetRankTypeByString(s string) (RankType, error) { switch s { case "level": return RankTypeLevel, nil case "power": return RankTypePower, nil case "wealth": return RankTypeWealth, nil case "achievement": return RankTypeAchievement, nil case "pet": return RankTypePet, nil case "guild": return RankTypeGuild, nil case "arena": return RankTypeArena, nil case "dungeon": return RankTypeDungeon, nil case "activity": return RankTypeActivity, nil case "custom": return RankTypeCustom, nil default: return 0, fmt.Errorf("unknown rank type: %s", s) } } // GetRankCategoryByString 根据字符串获取排行榜分类 func GetRankCategoryByString(s string) (RankCategory, error) { switch s { case "player": return RankCategoryPlayer, nil case "guild": return RankCategoryGuild, nil case "server": return RankCategoryServer, nil case "global": return RankCategoryGlobal, nil case "event": return RankCategoryEvent, nil case "season": return RankCategorySeason, nil default: return 0, fmt.Errorf("unknown rank category: %s", s) } } // GetSortTypeByString 根据字符串获取排序类型 func GetSortTypeByString(s string) (SortType, error) { switch s { case "desc", "descending": return SortTypeDescending, nil case "asc", "ascending": return SortTypeAscending, nil default: return 0, fmt.Errorf("unknown sort type: %s", s) } } // GetRankPeriodByString 根据字符串获取排行榜周期 func GetRankPeriodByString(s string) (RankPeriod, error) { switch s { case "permanent": return RankPeriodPermanent, nil case "daily": return RankPeriodDaily, nil case "weekly": return RankPeriodWeekly, nil case "monthly": return RankPeriodMonthly, nil case "seasonal": return RankPeriodSeasonal, nil case "event": return RankPeriodEvent, nil case "custom": return RankPeriodCustom, nil default: return 0, fmt.Errorf("unknown rank period: %s", s) } } // GetRankStatusByString 根据字符串获取排行榜状态 func GetRankStatusByString(s string) (RankStatus, error) { switch s { case "active": return RankStatusActive, nil case "inactive": return RankStatusInactive, nil case "paused": return RankStatusPaused, nil case "expired": return RankStatusExpired, nil case "maintenance": return RankStatusMaintenance, nil case "archived": return RankStatusArchived, nil default: return 0, fmt.Errorf("unknown rank status: %s", s) } } ================================================ FILE: internal/domain/replication/events.go ================================================ package replication import "time" // PlayerJoinedEvent 玩家加入事件 type PlayerJoinedEvent struct { InstanceID string PlayerID string PlayerName string Timestamp time.Time } // PlayerLeftEvent 玩家离开事件 type PlayerLeftEvent struct { InstanceID string PlayerID string Timestamp time.Time } // InstanceStartedEvent 实例启动事件 type InstanceStartedEvent struct { InstanceID string PlayerCount int Timestamp time.Time } // InstanceFullEvent 实例满员事件 type InstanceFullEvent struct { InstanceID string Timestamp time.Time } // InstanceProgressUpdatedEvent 进度更新事件 type InstanceProgressUpdatedEvent struct { InstanceID string Progress int Task string Timestamp time.Time } // InstanceCompletedEvent 实例完成事件 type InstanceCompletedEvent struct { InstanceID string Duration time.Duration Timestamp time.Time } // InstanceClosingEvent 实例关闭中事件 type InstanceClosingEvent struct { InstanceID string Timestamp time.Time } // InstanceClosedEvent 实例已关闭事件 type InstanceClosedEvent struct { InstanceID string Timestamp time.Time } ================================================ FILE: internal/domain/replication/instance.go ================================================ // Package replication 副本/实例领域模型 // 负责管理游戏副本(Dungeon)和实例(Instance)的生命周期 package replication import ( "fmt" "sync" "time" ) // InstanceStatus 实例状态 type InstanceStatus int const ( InstanceStatusPending InstanceStatus = iota // 等待创建 InstanceStatusCreating // 创建中 InstanceStatusActive // 活跃中 InstanceStatusFull // 已满员 InstanceStatusClosing // 关闭中 InstanceStatusClosed // 已关闭 ) // InstanceType 实例类型 type InstanceType int const ( InstanceTypeDungeon InstanceType = iota // 副本 InstanceTypeRaid // 团队副本 InstanceTypePVP // PVP竞技场 InstanceTypeEvent // 活动副本 ) // Instance 副本实例聚合根 type Instance struct { mu sync.RWMutex // 标识 instanceID string // 实例ID templateID string // 模板ID(副本配置ID) instanceType InstanceType // 实例类型 sceneID string // 关联的场景ID // 玩家 players map[string]*PlayerInfo // 玩家列表 maxPlayers int // 最大玩家数 minPlayers int // 最小玩家数 ownerPlayerID string // 创建者/队长 // 状态 status InstanceStatus difficulty int // 难度等级 // 时间 createdAt time.Time startedAt time.Time expireAt time.Time closedAt time.Time lifetime time.Duration // 生命周期 // 进度 progress int // 进度百分比 completedTasks []string // 已完成任务 metadata map[string]string // 元数据 scoreMultiplier float64 // 分数倍率 // 领域事件 events []interface{} } // PlayerInfo 玩家信息 type PlayerInfo struct { PlayerID string PlayerName string Level int JoinedAt time.Time IsReady bool Role string // tank, healer, dps } // NewInstance 创建新实例 func NewInstance( instanceID string, templateID string, instanceType InstanceType, ownerPlayerID string, maxPlayers int, lifetime time.Duration, ) *Instance { now := time.Now() return &Instance{ instanceID: instanceID, templateID: templateID, instanceType: instanceType, ownerPlayerID: ownerPlayerID, maxPlayers: maxPlayers, minPlayers: 1, players: make(map[string]*PlayerInfo), status: InstanceStatusPending, difficulty: 1, createdAt: now, expireAt: now.Add(lifetime), lifetime: lifetime, progress: 0, completedTasks: []string{}, metadata: make(map[string]string), scoreMultiplier: 1.0, events: []interface{}{}, } } // ID 获取实例ID func (i *Instance) ID() string { i.mu.RLock() defer i.mu.RUnlock() return i.instanceID } // TemplateID 获取模板ID func (i *Instance) TemplateID() string { i.mu.RLock() defer i.mu.RUnlock() return i.templateID } // Type 获取实例类型 func (i *Instance) Type() InstanceType { i.mu.RLock() defer i.mu.RUnlock() return i.instanceType } // Status 获取状态 func (i *Instance) Status() InstanceStatus { i.mu.RLock() defer i.mu.RUnlock() return i.status } // PlayerCount 获取当前玩家数 func (i *Instance) PlayerCount() int { i.mu.RLock() defer i.mu.RUnlock() return len(i.players) } // MaxPlayers 获取最大玩家数 func (i *Instance) MaxPlayers() int { i.mu.RLock() defer i.mu.RUnlock() return i.maxPlayers } // Progress 获取进度 func (i *Instance) Progress() int { i.mu.RLock() defer i.mu.RUnlock() return i.progress } // SceneID 获取场景ID func (i *Instance) SceneID() string { i.mu.RLock() defer i.mu.RUnlock() return i.sceneID } // SetSceneID 设置场景ID func (i *Instance) SetSceneID(sceneID string) { i.mu.Lock() defer i.mu.Unlock() i.sceneID = sceneID } // AddPlayer 添加玩家 func (i *Instance) AddPlayer(playerID, playerName string, level int, role string) error { i.mu.Lock() defer i.mu.Unlock() // 检查状态 if i.status == InstanceStatusClosed || i.status == InstanceStatusClosing { return fmt.Errorf("instance is closed or closing") } // 检查是否已满 if len(i.players) >= i.maxPlayers { return fmt.Errorf("instance is full") } // 检查是否已存在 if _, exists := i.players[playerID]; exists { return fmt.Errorf("player already in instance") } // 添加玩家 i.players[playerID] = &PlayerInfo{ PlayerID: playerID, PlayerName: playerName, Level: level, JoinedAt: time.Now(), IsReady: false, Role: role, } // 发布事件 i.addEvent(&PlayerJoinedEvent{ InstanceID: i.instanceID, PlayerID: playerID, PlayerName: playerName, Timestamp: time.Now(), }) // 检查是否满员 if len(i.players) >= i.maxPlayers { i.status = InstanceStatusFull i.addEvent(&InstanceFullEvent{ InstanceID: i.instanceID, Timestamp: time.Now(), }) } return nil } // RemovePlayer 移除玩家 func (i *Instance) RemovePlayer(playerID string) error { i.mu.Lock() defer i.mu.Unlock() if _, exists := i.players[playerID]; !exists { return fmt.Errorf("player not in instance") } delete(i.players, playerID) // 发布事件 i.addEvent(&PlayerLeftEvent{ InstanceID: i.instanceID, PlayerID: playerID, Timestamp: time.Now(), }) // 更新状态 if i.status == InstanceStatusFull && len(i.players) < i.maxPlayers { i.status = InstanceStatusActive } // 如果没有玩家了,标记为关闭中 if len(i.players) == 0 { i.MarkForClosing() } return nil } // Start 启动实例 func (i *Instance) Start() error { i.mu.Lock() defer i.mu.Unlock() if i.status != InstanceStatusPending && i.status != InstanceStatusCreating { return fmt.Errorf("instance cannot be started in current status: %d", i.status) } // 检查最小玩家数 if len(i.players) < i.minPlayers { return fmt.Errorf("not enough players: %d/%d", len(i.players), i.minPlayers) } i.status = InstanceStatusActive i.startedAt = time.Now() // 发布事件 i.addEvent(&InstanceStartedEvent{ InstanceID: i.instanceID, PlayerCount: len(i.players), Timestamp: time.Now(), }) return nil } // UpdateProgress 更新进度 func (i *Instance) UpdateProgress(progress int, completedTask string) { i.mu.Lock() defer i.mu.Unlock() i.progress = progress if completedTask != "" { i.completedTasks = append(i.completedTasks, completedTask) } // 发布事件 i.addEvent(&InstanceProgressUpdatedEvent{ InstanceID: i.instanceID, Progress: progress, Task: completedTask, Timestamp: time.Now(), }) // 如果完成了 if progress >= 100 { i.status = InstanceStatusClosing i.addEvent(&InstanceCompletedEvent{ InstanceID: i.instanceID, Duration: time.Since(i.startedAt), Timestamp: time.Now(), }) } } // MarkForClosing 标记为关闭中 func (i *Instance) MarkForClosing() { if i.status != InstanceStatusClosed { i.status = InstanceStatusClosing i.addEvent(&InstanceClosingEvent{ InstanceID: i.instanceID, Timestamp: time.Now(), }) } } // Close 关闭实例 func (i *Instance) Close() { i.mu.Lock() defer i.mu.Unlock() i.status = InstanceStatusClosed i.closedAt = time.Now() // 发布事件 i.addEvent(&InstanceClosedEvent{ InstanceID: i.instanceID, Timestamp: time.Now(), }) } // IsExpired 检查是否过期 func (i *Instance) IsExpired() bool { i.mu.RLock() defer i.mu.RUnlock() return time.Now().After(i.expireAt) } // GetPlayers 获取玩家列表 func (i *Instance) GetPlayers() []*PlayerInfo { i.mu.RLock() defer i.mu.RUnlock() players := make([]*PlayerInfo, 0, len(i.players)) for _, p := range i.players { players = append(players, p) } return players } // addEvent 添加领域事件 func (i *Instance) addEvent(event interface{}) { i.events = append(i.events, event) } // GetEvents 获取并清空事件 func (i *Instance) GetEvents() []interface{} { i.mu.Lock() defer i.mu.Unlock() events := i.events i.events = []interface{}{} return events } // GetMetadata 获取元数据 func (i *Instance) GetMetadata(key string) (string, bool) { i.mu.RLock() defer i.mu.RUnlock() val, ok := i.metadata[key] return val, ok } // SetMetadata 设置元数据 func (i *Instance) SetMetadata(key, value string) { i.mu.Lock() defer i.mu.Unlock() i.metadata[key] = value } // CreatedAt 获取创建时间 func (i *Instance) CreatedAt() time.Time { i.mu.RLock() defer i.mu.RUnlock() return i.createdAt } // Difficulty 获取难度 func (i *Instance) Difficulty() int { i.mu.RLock() defer i.mu.RUnlock() return i.difficulty } ================================================ FILE: internal/domain/replication/repository.go ================================================ package replication import "context" // Repository 副本实例仓储接口 type Repository interface { // Save 保存实例 Save(ctx context.Context, instance *Instance) error // FindByID 根据ID查找实例 FindByID(ctx context.Context, instanceID string) (*Instance, error) // FindByTemplateID 根据模板ID查找实例列表 FindByTemplateID(ctx context.Context, templateID string) ([]*Instance, error) // FindActiveInstances 查找所有活跃实例 FindActiveInstances(ctx context.Context) ([]*Instance, error) // FindByPlayerID 根据玩家ID查找实例 FindByPlayerID(ctx context.Context, playerID string) (*Instance, error) // Delete 删除实例 Delete(ctx context.Context, instanceID string) error // UpdateStatus 更新实例状态 UpdateStatus(ctx context.Context, instanceID string, status InstanceStatus) error // FindExpiredInstances 查找过期实例 FindExpiredInstances(ctx context.Context) ([]*Instance, error) } ================================================ FILE: internal/domain/replication/snapshot.go ================================================ package replication import "time" // InstanceSnapshot 用于持久化/重建的快照 type InstanceSnapshot struct { InstanceID, TemplateID, SceneID, OwnerPlayerID string InstanceType InstanceType Status InstanceStatus MaxPlayers int MinPlayers int Difficulty int CreatedAt time.Time StartedAt time.Time ExpireAt time.Time ClosedAt time.Time Lifetime time.Duration Progress int Completed []string Metadata map[string]string Players []PlayerInfo } // NewInstanceFromSnapshot 通过快照重建实例 func NewInstanceFromSnapshot(s InstanceSnapshot) *Instance { inst := &Instance{ instanceID: s.InstanceID, templateID: s.TemplateID, instanceType: s.InstanceType, sceneID: s.SceneID, players: make(map[string]*PlayerInfo), maxPlayers: s.MaxPlayers, minPlayers: s.MinPlayers, ownerPlayerID: s.OwnerPlayerID, status: s.Status, difficulty: s.Difficulty, createdAt: s.CreatedAt, startedAt: s.StartedAt, expireAt: s.ExpireAt, closedAt: s.ClosedAt, lifetime: s.Lifetime, progress: s.Progress, completedTasks: append([]string(nil), s.Completed...), metadata: map[string]string{}, scoreMultiplier: 1.0, events: nil, } if s.Metadata != nil { for k, v := range s.Metadata { inst.metadata[k] = v } } for _, p := range s.Players { cp := p // copy inst.players[p.PlayerID] = &cp } return inst } ================================================ FILE: internal/domain/scene/errors.go ================================================ package scene import "errors" var ( // 场景相关错误 ErrSceneNotFound = errors.New("scene not found") ErrSceneNotActive = errors.New("scene is not active") ErrSceneFull = errors.New("scene is full") ErrSceneClosed = errors.New("scene is closed") ErrSceneMaintenance = errors.New("scene is under maintenance") ErrInvalidSceneType = errors.New("invalid scene type") ErrInvalidSceneStatus = errors.New("invalid scene status") ErrSceneAlreadyExists = errors.New("scene already exists") // 玩家相关错误 ErrPlayerNotInScene = errors.New("player not in scene") ErrPlayerAlreadyInScene = errors.New("player already in scene") ErrPlayerDead = errors.New("player is dead") ErrPlayerAFK = errors.New("player is AFK") ErrPlayerInCombat = errors.New("player is in combat") ErrPlayerTrading = errors.New("player is trading") // 实体相关错误 ErrEntityNotFound = errors.New("entity not found") ErrEntityNotActive = errors.New("entity is not active") ErrEntityAlreadyExists = errors.New("entity already exists") ErrInvalidEntityType = errors.New("invalid entity type") ErrEntityDead = errors.New("entity is dead") // 位置相关错误 ErrInvalidPosition = errors.New("invalid position") ErrPositionOccupied = errors.New("position is occupied") ErrOutOfBounds = errors.New("position is out of bounds") ErrTooFarAway = errors.New("target is too far away") ErrCannotMove = errors.New("cannot move to target position") // 怪物相关错误 ErrMonsterNotFound = errors.New("monster not found") ErrMonsterAlreadyExists = errors.New("monster already exists") ErrMonsterDead = errors.New("monster is dead") ErrMonsterRespawning = errors.New("monster is respawning") ErrInvalidMonsterType = errors.New("invalid monster type") // NPC相关错误 ErrNPCNotFound = errors.New("npc not found") ErrNPCNotAvailable = errors.New("npc is not available") ErrNPCBusy = errors.New("npc is busy") ErrNPCDead = errors.New("npc is dead") ErrInvalidNPCType = errors.New("invalid npc type") // 物品相关错误 ErrItemNotFound = errors.New("item not found") ErrItemAlreadyExists = errors.New("item already exists") ErrItemExpired = errors.New("item has expired") ErrItemNotPickable = errors.New("item is not pickable") ErrItemOwnershipError = errors.New("item ownership error") // 传送门相关错误 ErrPortalNotFound = errors.New("portal not found") ErrPortalNotActive = errors.New("portal is not active") ErrPortalLocked = errors.New("portal is locked") ErrInsufficientLevel = errors.New("insufficient level for portal") ErrMissingRequiredItems = errors.New("missing required items for portal") ErrInsufficientGold = errors.New("insufficient gold for portal") // 刷新点相关错误 ErrSpawnPointNotFound = errors.New("spawn point not found") ErrSpawnPointFull = errors.New("spawn point is full") ErrSpawnPointInactive = errors.New("spawn point is inactive") ErrSpawnCooldown = errors.New("spawn point is on cooldown") ErrInvalidSpawnType = errors.New("invalid spawn type") // AOI相关错误 ErrAOINotInitialized = errors.New("aoi manager not initialized") ErrInvalidAOIRadius = errors.New("invalid aoi radius") ErrAOIEntityNotFound = errors.New("aoi entity not found") ErrAOIGridNotFound = errors.New("aoi grid not found") // AI相关错误 ErrAINotInitialized = errors.New("ai behavior not initialized") ErrInvalidAIBehavior = errors.New("invalid ai behavior type") ErrAITargetNotFound = errors.New("ai target not found") ErrAIPathNotFound = errors.New("ai path not found") // 战斗相关错误 ErrNotInCombat = errors.New("not in combat") ErrAlreadyInCombat = errors.New("already in combat") ErrCannotAttack = errors.New("cannot attack target") ErrAttackOnCooldown = errors.New("attack is on cooldown") ErrInvalidTarget = errors.New("invalid attack target") // 权限相关错误 ErrInsufficientPermission = errors.New("insufficient permission") ErrAccessDenied = errors.New("access denied") ErrNotAuthorized = errors.New("not authorized") // 配置相关错误 ErrInvalidSceneConfig = errors.New("invalid scene configuration") ErrSceneConfigNotFound = errors.New("scene configuration not found") ErrInvalidEntityConfig = errors.New("invalid entity configuration") ErrConfigLoadFailed = errors.New("failed to load configuration") ) ================================================ FILE: internal/domain/scene/plant/aggregate.go ================================================ package plant import ( "errors" "fmt" "time" ) // FarmAggregate 农场聚合根 type FarmAggregate struct { farmID string sceneID string owner string name string description string size FarmSize soil *Soil crops map[string]*Crop plots map[string]*Plot tools []*FarmTool resources *FarmResources statistics *FarmStatistics climateZone string seasonModifier *SeasonModifier automation *AutomationSettings lastUpdateTime time.Time lastHarvestTime time.Time createdAt time.Time updatedAt time.Time version int } // NewFarmAggregate 创建农场聚合根 func NewFarmAggregate(farmID, sceneID, owner, name string, size FarmSize) *FarmAggregate { now := time.Now() return &FarmAggregate{ farmID: farmID, sceneID: sceneID, owner: owner, name: name, size: size, soil: NewSoil(SoilTypeLoam, 50.0, 7.0, 2.0), // 默认壤土 crops: make(map[string]*Crop), plots: make(map[string]*Plot), tools: make([]*FarmTool, 0), resources: NewFarmResources(), statistics: NewFarmStatistics(), climateZone: "temperate", // 默认温带 seasonModifier: NewSeasonModifier(), automation: NewAutomationSettings(), lastUpdateTime: now, lastHarvestTime: now, createdAt: now, updatedAt: now, version: 1, } } // GetFarmID 获取农场ID func (f *FarmAggregate) GetFarmID() string { return f.farmID } // GetSceneID 获取场景ID func (f *FarmAggregate) GetSceneID() string { return f.sceneID } // GetOwner 获取所有者 func (f *FarmAggregate) GetOwner() string { return f.owner } // GetName 获取农场名称 func (f *FarmAggregate) GetName() string { return f.name } // SetName 设置农场名称 func (f *FarmAggregate) SetName(name string) error { if name == "" { return ErrInvalidFarmName } f.name = name f.updateVersion() return nil } // GetDescription 获取描述 func (f *FarmAggregate) GetDescription() string { return f.description } // SetDescription 设置描述 func (f *FarmAggregate) SetDescription(description string) { f.description = description f.updateVersion() } // GetSize 获取农场大小 func (f *FarmAggregate) GetSize() FarmSize { return f.size } // ExpandFarm 扩展农场 func (f *FarmAggregate) ExpandFarm(newSize FarmSize) error { if newSize <= f.size { return ErrInvalidFarmSize } // 检查扩展条件 if !f.canExpand(newSize) { return ErrFarmExpansionNotAllowed } f.size = newSize f.updateVersion() return nil } // GetSoil 获取土壤 func (f *FarmAggregate) GetSoil() *Soil { return f.soil } // UpdateSoil 更新土壤 func (f *FarmAggregate) UpdateSoil(soil *Soil) error { if soil == nil { return ErrInvalidSoil } f.soil = soil f.updateVersion() return nil } // ImproveSoil 改良土壤 func (f *FarmAggregate) ImproveSoil(fertilizer *Fertilizer) error { if fertilizer == nil { return ErrInvalidFertilizer } // 检查资源是否足够 if !f.resources.HasEnoughFertilizer(fertilizer.GetType(), fertilizer.GetAmount()) { return ErrInsufficientResources } // 应用肥料效果 f.soil.ApplyFertilizer(fertilizer) // 消耗资源 f.resources.ConsumeFertilizer(fertilizer.GetType(), fertilizer.GetAmount()) // 更新统计 f.statistics.AddFertilizerUsage(fertilizer.GetType(), fertilizer.GetAmount()) f.updateVersion() return nil } // PlantCrop 种植作物 func (f *FarmAggregate) PlantCrop(plotID string, seedType SeedType, quantity int) error { if plotID == "" { return ErrInvalidPlotID } if !seedType.IsValid() { return ErrInvalidSeedType } if quantity <= 0 { return ErrInvalidQuantity } // 检查地块是否存在 plot, exists := f.plots[plotID] if !exists { return ErrPlotNotFound } // 检查地块是否可用 if !plot.IsAvailable { return ErrPlotNotAvailable } // 检查种子资源 if !f.resources.HasEnoughSeeds(seedType, quantity) { return ErrInsufficientSeeds } // 检查土壤适宜性 if !f.soil.IsSuitableFor(seedType) { return ErrSoilNotSuitable } // 创建作物 crop := NewCrop(generateCropID(), "", seedType, quantity, f.soil, f.climateZone) // 应用季节修正 f.seasonModifier.ApplyToCrop(crop) // 种植作物 plot.PlantCrop(crop) f.crops[crop.GetID()] = crop // 消耗种子 f.resources.ConsumeSeeds(seedType, quantity) // 更新统计 f.statistics.AddPlantingActivity(seedType, quantity) f.updateVersion() return nil } // WaterCrops 浇水 func (f *FarmAggregate) WaterCrops(plotIDs []string, waterAmount float64) error { if len(plotIDs) == 0 { return ErrInvalidPlotID } if waterAmount <= 0 { return ErrInvalidWaterAmount } // 检查水资源 totalWaterNeeded := waterAmount * float64(len(plotIDs)) if !f.resources.HasEnoughWater(totalWaterNeeded) { return ErrInsufficientWater } for _, plotID := range plotIDs { plot, exists := f.plots[plotID] if !exists { continue } // 浇水 if crop := plot.GetCrop(); crop != nil { crop.Water(waterAmount) } // 更新土壤湿度 f.soil.AddMoisture(waterAmount) } // 消耗水资源 f.resources.ConsumeWater(totalWaterNeeded) // 更新统计 f.statistics.AddWateringActivity(len(plotIDs), totalWaterNeeded) f.updateVersion() return nil } // HarvestCrop 收获作物 func (f *FarmAggregate) HarvestCrop(cropID string) (*HarvestResult, error) { if cropID == "" { return nil, ErrInvalidCropID } crop, exists := f.crops[cropID] if !exists { return nil, ErrCropNotFound } // 检查作物是否可收获 if !crop.IsHarvestable() { return nil, ErrCropNotHarvestable } // 计算收获量 yield := f.calculateYield(crop) quality := f.calculateQuality(crop) // 创建收获结果 result := &HarvestResult{ CropID: cropID, SeedType: crop.GetSeedType(), Yield: yield, Quality: quality, HarvestTime: time.Now(), Experience: f.calculateExperience(crop, yield, quality), } // 添加收获物到资源 f.resources.AddHarvest(crop.GetSeedType(), yield, quality) // 更新统计 f.statistics.AddHarvestActivity(crop.GetSeedType(), yield, quality) // 移除作物 delete(f.crops, cropID) // 释放地块 for _, plot := range f.plots { if plot.GetCrop() != nil && plot.GetCrop().GetID() == cropID { plot.ClearCrop() break } } f.lastHarvestTime = time.Now() f.updateVersion() return result, nil } // UpdateCrops 更新所有作物 func (f *FarmAggregate) UpdateCrops() error { now := time.Now() for _, crop := range f.crops { // 更新作物生长 crop.Update(now) // 应用环境影响 f.applyEnvironmentalEffects(crop) // 检查病虫害 f.checkPestsAndDiseases(crop) } f.lastUpdateTime = now f.updateVersion() return nil } // GetCrops 获取所有作物 func (f *FarmAggregate) GetCrops() map[string]*Crop { return f.crops } // GetCrop 获取指定作物 func (f *FarmAggregate) GetCrop(cropID string) *Crop { return f.crops[cropID] } // GetPlots 获取所有地块 func (f *FarmAggregate) GetPlots() map[string]*Plot { return f.plots } // AddPlot 添加地块 func (f *FarmAggregate) AddPlot(plot *Plot) error { if plot == nil { return ErrInvalidPlot } // 检查地块数量限制 if len(f.plots) >= f.size.GetMaxPlots() { return ErrMaxPlotsReached } f.plots[plot.GetID()] = plot f.updateVersion() return nil } // RemovePlot 移除地块 func (f *FarmAggregate) RemovePlot(plotID string) error { plot, exists := f.plots[plotID] if !exists { return ErrPlotNotFound } // 检查地块是否有作物 if plot.HasCrop() { return ErrPlotHasCrop } delete(f.plots, plotID) f.updateVersion() return nil } // GetTools 获取农具 func (f *FarmAggregate) GetTools() []*FarmTool { return f.tools } // AddTool 添加农具 func (f *FarmAggregate) AddTool(tool *FarmTool) error { if tool == nil { return ErrInvalidTool } f.tools = append(f.tools, tool) f.updateVersion() return nil } // UseTool 使用农具 func (f *FarmAggregate) UseTool(toolID string, targetID string) error { tool := f.findTool(toolID) if tool == nil { return ErrToolNotFound } if !tool.IsUsable() { return ErrToolNotUsable } // 使用农具 effect := tool.Use() // 应用效果 f.applyToolEffect(effect, targetID) // 更新统计 f.statistics.AddToolUsage(tool.GetType()) f.updateVersion() return nil } // GetResources 获取资源 func (f *FarmAggregate) GetResources() *FarmResources { return f.resources } // GetStatistics 获取统计信息 func (f *FarmAggregate) GetStatistics() *FarmStatistics { return f.statistics } // GetClimateZone 获取气候区域 func (f *FarmAggregate) GetClimateZone() string { return f.climateZone } // SetClimateZone 设置气候区域 func (f *FarmAggregate) SetClimateZone(zone string) { f.climateZone = zone f.updateVersion() } // GetSeasonModifier 获取季节修正 func (f *FarmAggregate) GetSeasonModifier() *SeasonModifier { return f.seasonModifier } // UpdateSeasonModifier 更新季节修正 func (f *FarmAggregate) UpdateSeasonModifier(modifier *SeasonModifier) { f.seasonModifier = modifier f.updateVersion() } // GetAutomationSettings 获取自动化设置 func (f *FarmAggregate) GetAutomationSettings() *AutomationSettings { return f.automation } // UpdateAutomationSettings 更新自动化设置 func (f *FarmAggregate) UpdateAutomationSettings(settings *AutomationSettings) { f.automation = settings f.updateVersion() } // GetLastUpdateTime 获取最后更新时间 func (f *FarmAggregate) GetLastUpdateTime() time.Time { return f.lastUpdateTime } // GetLastHarvestTime 获取最后收获时间 func (f *FarmAggregate) GetLastHarvestTime() time.Time { return f.lastHarvestTime } // GetCreatedAt 获取创建时间 func (f *FarmAggregate) GetCreatedAt() time.Time { return f.createdAt } // GetUpdatedAt 获取更新时间 func (f *FarmAggregate) GetUpdatedAt() time.Time { return f.updatedAt } // GetVersion 获取版本 func (f *FarmAggregate) GetVersion() int { return f.version } // CalculateFarmValue 计算农场价值 func (f *FarmAggregate) CalculateFarmValue() float64 { value := 0.0 // 基础价值 value += f.size.GetBaseValue() // 土壤价值 value += f.soil.GetValue() // 作物价值 for _, crop := range f.crops { value += crop.GetValue() } // 农具价值 for _, tool := range f.tools { value += tool.GetValue() } // 资源价值 value += f.resources.GetTotalValue() return value } // CalculateProductivity 计算生产力 func (f *FarmAggregate) CalculateProductivity() float64 { productivity := 1.0 // 土壤影响 productivity *= f.soil.GetProductivityMultiplier() // 季节影响 productivity *= f.seasonModifier.GetProductivityMultiplier() // 农具影响 for _, tool := range f.tools { if tool.IsActive { productivity *= tool.GetProductivityBonus() } } return productivity } // GetFarmStatus 获取农场状态 func (f *FarmAggregate) GetFarmStatus() FarmStatus { if len(f.crops) == 0 { return FarmStatusIdle } // 检查是否有成熟的作物 for _, crop := range f.crops { if crop.IsHarvestable() { return FarmStatusHarvestReady } } // 检查是否需要照料 for _, crop := range f.crops { if crop.NeedsCare() { return FarmStatusNeedsCare } } return FarmStatusGrowing } // 私有方法 // canExpand 检查是否可以扩展 func (f *FarmAggregate) canExpand(newSize FarmSize) bool { // 检查资源是否足够 expansionCost := newSize.GetExpansionCost(f.size) return f.resources.CanAfford(expansionCost) } // calculateYield 计算收获量 func (f *FarmAggregate) calculateYield(crop *Crop) int { baseYield := crop.GetBaseYield() multiplier := 1.0 // 土壤影响 multiplier *= f.soil.GetYieldMultiplier(crop.GetSeedType()) // 季节影响 multiplier *= f.seasonModifier.GetYieldMultiplier(crop.GetSeedType()) // 照料质量影响 multiplier *= crop.GetCareQualityMultiplier() return int(float64(baseYield) * multiplier) } // calculateQuality 计算品质 func (f *FarmAggregate) calculateQuality(crop *Crop) CropQuality { qualityScore := 0.0 // 土壤质量影响 qualityScore += f.soil.GetQualityScore() // 作物健康度影响 qualityScore += crop.GetHealthScore() // 照料质量影响 qualityScore += crop.GetCareQualityScore() // 转换为品质等级 if qualityScore >= 90 { return CropQualityLegendary } else if qualityScore >= 80 { return CropQualityEpic } else if qualityScore >= 70 { return CropQualityRare } else if qualityScore >= 60 { return CropQualityUncommon } else { return CropQualityCommon } } // calculateExperience 计算经验值 func (f *FarmAggregate) calculateExperience(crop *Crop, yield int, quality CropQuality) int { baseExp := crop.GetBaseExperience() multiplier := 1.0 // 产量影响 multiplier += float64(yield) * 0.01 // 品质影响 multiplier += quality.GetExperienceMultiplier() return int(float64(baseExp) * multiplier) } // applyEnvironmentalEffects 应用环境影响 func (f *FarmAggregate) applyEnvironmentalEffects(crop *Crop) { // 土壤影响 f.soil.ApplyToCrop(crop) // 季节影响 f.seasonModifier.ApplyToCrop(crop) } // checkPestsAndDiseases 检查病虫害 func (f *FarmAggregate) checkPestsAndDiseases(crop *Crop) { // 简化的病虫害检查逻辑 if crop.GetHealthScore() < 50 { // 可能有病虫害,需要处理 crop.AddProblem("pest_infestation") } } // findTool 查找农具 func (f *FarmAggregate) findTool(toolID string) *FarmTool { for _, tool := range f.tools { if tool.GetID() == toolID { return tool } } return nil } // applyToolEffect 应用农具效果 func (f *FarmAggregate) applyToolEffect(effect *ToolEffect, targetID string) { switch effect.GetType() { case "soil_improvement": f.soil.ApplyImprovement(effect.GetValue()) case "crop_growth_boost": if crop := f.crops[targetID]; crop != nil { crop.ApplyGrowthBoost(effect.GetValue()) } case "pest_control": if crop := f.crops[targetID]; crop != nil { crop.RemoveProblem("pest_infestation") } } } // updateVersion 更新版本 func (f *FarmAggregate) updateVersion() { f.version++ f.updatedAt = time.Now() } // generateCropID 生成作物ID func generateCropID() string { return fmt.Sprintf("crop_%d", time.Now().UnixNano()) } // HarvestResult 收获结果 type HarvestResult struct { CropID string SeedType SeedType Yield int Quality CropQuality HarvestTime time.Time Experience int } // FarmStatus 农场状态 type FarmStatus int const ( FarmStatusIdle FarmStatus = iota + 1 FarmStatusGrowing FarmStatusHarvestReady FarmStatusNeedsCare FarmStatusMaintenance ) // String 返回状态字符串 func (fs FarmStatus) String() string { switch fs { case FarmStatusIdle: return "idle" case FarmStatusGrowing: return "growing" case FarmStatusHarvestReady: return "harvest_ready" case FarmStatusNeedsCare: return "needs_care" case FarmStatusMaintenance: return "maintenance" default: return "unknown" } } // 农场相关错误 var ( ErrInvalidFarmName = errors.New("invalid farm name") ErrInvalidFarmSize = errors.New("invalid farm size") ErrFarmExpansionNotAllowed = errors.New("farm expansion not allowed") // 错误定义已移动到errors.go文件中 ) ================================================ FILE: internal/domain/scene/plant/entity.go ================================================ package plant import ( "fmt" "time" ) // Crop 作物实体 type Crop struct { ID string PlayerID string SeedType SeedType Quantity int GrowthStage GrowthStage HealthPoints float64 MaxHealthPoints float64 GrowthProgress float64 WaterLevel float64 NutrientLevel float64 PlantedTime time.Time LastWateredTime time.Time LastFertilizedTime time.Time ExpectedHarvestTime time.Time SoilCondition *Soil ClimateZone string CareHistory []*CareRecord Problems []string Bonuses []*GrowthBonus CreatedAt time.Time UpdatedAt time.Time } // NewCrop 创建作物 func NewCrop(id string, playerID string, seedType SeedType, quantity int, soil *Soil, climateZone string) *Crop { now := time.Now() growthDuration := seedType.GetGrowthDuration() return &Crop{ ID: id, PlayerID: playerID, SeedType: seedType, Quantity: quantity, GrowthStage: GrowthStageSeed, HealthPoints: 100.0, MaxHealthPoints: 100.0, GrowthProgress: 0.0, WaterLevel: 50.0, NutrientLevel: 50.0, PlantedTime: now, LastWateredTime: now, LastFertilizedTime: now, ExpectedHarvestTime: now.Add(growthDuration), SoilCondition: soil, ClimateZone: climateZone, CareHistory: make([]*CareRecord, 0), Problems: make([]string, 0), Bonuses: make([]*GrowthBonus, 0), CreatedAt: now, UpdatedAt: now, } } // GetID 获取ID func (c *Crop) GetID() string { return c.ID } // GetPlayerID 获取玩家ID func (c *Crop) GetPlayerID() string { return c.PlayerID } // GetSeedType 获取种子类型 func (c *Crop) GetSeedType() SeedType { return c.SeedType } // GetQuantity 获取数量 func (c *Crop) GetQuantity() int { return c.Quantity } // GetGrowthStage 获取生长阶段 func (c *Crop) GetGrowthStage() GrowthStage { return c.GrowthStage } // GetGrowthProgress 获取生长进度 func (c *Crop) GetGrowthProgress() float64 { return c.GrowthProgress } // GetHealthPoints 获取健康值 func (c *Crop) GetHealthPoints() float64 { return c.HealthPoints } // GetHealthScore 获取健康分数 func (c *Crop) GetHealthScore() float64 { return (c.HealthPoints / c.MaxHealthPoints) * 100 } // GetWaterLevel 获取水分等级 func (c *Crop) GetWaterLevel() float64 { return c.WaterLevel } // GetNutrientLevel 获取营养等级 func (c *Crop) GetNutrientLevel() float64 { return c.NutrientLevel } // GetPlotID 获取地块ID (暂时返回空字符串,需要在Crop结构体中添加PlotID字段) func (c *Crop) GetPlotID() string { return "" // TODO: Add PlotID field to Crop struct } // GetSeedID 获取种子ID (暂时返回空字符串,需要在Crop结构体中添加SeedID字段) func (c *Crop) GetSeedID() string { return "" // TODO: Add SeedID field to Crop struct } // GetCropType 获取作物类型 (返回种子类型的字符串表示) func (c *Crop) GetCropType() string { return c.SeedType.String() } // GetCurrentStage 获取当前阶段 (返回生长阶段的字符串表示) func (c *Crop) GetCurrentStage() string { return c.GrowthStage.String() } // IsHarvestable 检查是否可收获 func (c *Crop) IsHarvestable() bool { return c.GrowthStage == GrowthStageMature && c.GrowthProgress >= 100.0 } // NeedsCare 检查是否需要照料 func (c *Crop) NeedsCare() bool { return c.WaterLevel < 30.0 || c.NutrientLevel < 30.0 || c.HealthPoints < 70.0 || len(c.Problems) > 0 } // Water 浇水 func (c *Crop) Water(amount float64) { c.WaterLevel += amount if c.WaterLevel > 100.0 { c.WaterLevel = 100.0 } c.LastWateredTime = time.Now() c.addCareRecord("watering", amount) c.UpdatedAt = time.Now() } // Fertilize 施肥 func (c *Crop) Fertilize(fertilizer *Fertilizer) { c.NutrientLevel += fertilizer.GetNutrientValue() if c.NutrientLevel > 100.0 { c.NutrientLevel = 100.0 } // 应用肥料的额外效果 if bonus := fertilizer.GetGrowthBonus(); bonus != nil { c.AddBonus(bonus) } c.LastFertilizedTime = time.Now() c.addCareRecord("fertilizing", fertilizer.GetNutrientValue()) c.UpdatedAt = time.Now() } // Update 更新作物状态 func (c *Crop) Update(currentTime time.Time) { // 计算时间差 timeDiff := currentTime.Sub(c.UpdatedAt) hours := timeDiff.Hours() if hours <= 0 { return } // 更新生长进度 c.updateGrowthProgress(hours) // 更新生长阶段 c.updateGrowthStage() // 消耗水分和营养 c.consumeResources(hours) // 更新健康状态 c.updateHealth() // 处理奖励效果 c.processGrowthBonuses(hours) // 检查问题 c.checkForProblems() c.UpdatedAt = currentTime } // AddBonus 添加生长奖励 func (c *Crop) AddBonus(bonus *GrowthBonus) { c.Bonuses = append(c.Bonuses, bonus) c.UpdatedAt = time.Now() } // RemoveBonus 移除生长奖励 func (c *Crop) RemoveBonus(bonusID string) { for i, bonus := range c.Bonuses { if bonus.ID == bonusID { c.Bonuses = append(c.Bonuses[:i], c.Bonuses[i+1:]...) break } } c.UpdatedAt = time.Now() } // AddProblem 添加问题 func (c *Crop) AddProblem(problem string) { for _, p := range c.Problems { if p == problem { return // 问题已存在 } } c.Problems = append(c.Problems, problem) c.UpdatedAt = time.Now() } // RemoveProblem 移除问题 func (c *Crop) RemoveProblem(problem string) { for i, p := range c.Problems { if p == problem { c.Problems = append(c.Problems[:i], c.Problems[i+1:]...) break } } c.UpdatedAt = time.Now() } // GetProblems 获取问题列表 func (c *Crop) GetProblems() []string { return c.Problems } // ApplyGrowthBoost 应用生长加速 func (c *Crop) ApplyGrowthBoost(multiplier float64) { bonus := &GrowthBonus{ ID: fmt.Sprintf("boost_%d", time.Now().UnixNano()), Type: "growth_speed", Multiplier: multiplier, Duration: 24 * time.Hour, // 持续24小时 StartTime: time.Now(), } c.AddBonus(bonus) } // GetBaseYield 获取基础产量 func (c *Crop) GetBaseYield() int { return c.SeedType.GetBaseYield() * c.Quantity } // GetBaseExperience 获取基础经验 func (c *Crop) GetBaseExperience() int { return c.SeedType.GetBaseExperience() * c.Quantity } // GetValue 获取作物价值 func (c *Crop) GetValue() float64 { baseValue := c.SeedType.GetBaseValue() * float64(c.Quantity) // 生长进度影响 progressMultiplier := c.GrowthProgress / 100.0 // 健康状态影响 healthMultiplier := c.GetHealthScore() / 100.0 return baseValue * progressMultiplier * healthMultiplier } // GetCareQualityMultiplier 获取照料质量倍率 func (c *Crop) GetCareQualityMultiplier() float64 { if len(c.CareHistory) == 0 { return 1.0 } // 基于照料历史计算质量倍率 totalCare := 0.0 for _, record := range c.CareHistory { totalCare += record.Quality } averageQuality := totalCare / float64(len(c.CareHistory)) return 0.8 + (averageQuality/100.0)*0.4 // 0.8-1.2倍率 } // GetCareQualityScore 获取照料质量分数 func (c *Crop) GetCareQualityScore() float64 { return c.GetCareQualityMultiplier() * 100.0 } // 私有方法 // updateGrowthProgress 更新生长进度 func (c *Crop) updateGrowthProgress(hours float64) { baseGrowthRate := c.SeedType.GetGrowthRate() // 应用环境因素 environmentMultiplier := c.calculateEnvironmentMultiplier() // 应用奖励效果 bonusMultiplier := c.calculateBonusMultiplier() // 计算实际生长速度 actualGrowthRate := baseGrowthRate * environmentMultiplier * bonusMultiplier // 更新进度 c.GrowthProgress += actualGrowthRate * hours if c.GrowthProgress > 100.0 { c.GrowthProgress = 100.0 } } // updateGrowthStage 更新生长阶段 func (c *Crop) updateGrowthStage() { if c.GrowthProgress >= 100.0 { c.GrowthStage = GrowthStageMature } else if c.GrowthProgress >= 75.0 { c.GrowthStage = GrowthStageFlowering } else if c.GrowthProgress >= 50.0 { c.GrowthStage = GrowthStageGrowing } else if c.GrowthProgress >= 25.0 { c.GrowthStage = GrowthStageSeedling } else { c.GrowthStage = GrowthStageSeed } } // consumeResources 消耗资源 func (c *Crop) consumeResources(hours float64) { // 水分消耗 waterConsumption := c.SeedType.GetWaterConsumption() * hours c.WaterLevel -= waterConsumption if c.WaterLevel < 0 { c.WaterLevel = 0 } // 营养消耗 nutrientConsumption := c.SeedType.GetNutrientConsumption() * hours c.NutrientLevel -= nutrientConsumption if c.NutrientLevel < 0 { c.NutrientLevel = 0 } } // updateHealth 更新健康状态 func (c *Crop) updateHealth() { // 基于水分和营养水平调整健康值 if c.WaterLevel < 20.0 || c.NutrientLevel < 20.0 { c.HealthPoints -= 5.0 // 缺水或缺营养会降低健康值 } else if c.WaterLevel > 80.0 && c.NutrientLevel > 80.0 { c.HealthPoints += 2.0 // 充足的水分和营养会恢复健康值 } // 限制健康值范围 if c.HealthPoints < 0 { c.HealthPoints = 0 } else if c.HealthPoints > c.MaxHealthPoints { c.HealthPoints = c.MaxHealthPoints } } // processGrowthBonuses 处理生长奖励 func (c *Crop) processGrowthBonuses(hours float64) { // 移除过期的奖励 now := time.Now() for i := len(c.Bonuses) - 1; i >= 0; i-- { bonus := c.Bonuses[i] if now.After(bonus.StartTime.Add(bonus.Duration)) { c.Bonuses = append(c.Bonuses[:i], c.Bonuses[i+1:]...) } } } // checkForProblems 检查问题 func (c *Crop) checkForProblems() { // 清除旧问题 c.Problems = c.Problems[:0] // 检查缺水 if c.WaterLevel < 20.0 { c.AddProblem("drought_stress") } // 检查缺营养 if c.NutrientLevel < 20.0 { c.AddProblem("nutrient_deficiency") } // 检查健康状态 if c.HealthPoints < 30.0 { c.AddProblem("poor_health") } // 检查过度浇水 if c.WaterLevel > 95.0 { c.AddProblem("overwatering") } } // calculateEnvironmentMultiplier 计算环境倍率 func (c *Crop) calculateEnvironmentMultiplier() float64 { multiplier := 1.0 // 土壤影响 if c.SoilCondition != nil { multiplier *= c.SoilCondition.GetGrowthMultiplier(c.SeedType) } // 水分影响 if c.WaterLevel < 30.0 { multiplier *= 0.7 // 缺水减慢生长 } else if c.WaterLevel > 80.0 { multiplier *= 1.1 // 充足水分加速生长 } // 营养影响 if c.NutrientLevel < 30.0 { multiplier *= 0.8 // 缺营养减慢生长 } else if c.NutrientLevel > 80.0 { multiplier *= 1.2 // 充足营养加速生长 } return multiplier } // calculateBonusMultiplier 计算奖励倍率 func (c *Crop) calculateBonusMultiplier() float64 { multiplier := 1.0 for _, bonus := range c.Bonuses { if bonus.IsActive() { multiplier *= bonus.Multiplier } } return multiplier } // addCareRecord 添加照料记录 func (c *Crop) addCareRecord(careType string, value float64) { record := &CareRecord{ Type: careType, Value: value, Quality: c.calculateCareQuality(careType, value), Timestamp: time.Now(), } c.CareHistory = append(c.CareHistory, record) // 限制历史记录数量 if len(c.CareHistory) > 50 { c.CareHistory = c.CareHistory[1:] } } // calculateCareQuality 计算照料质量 func (c *Crop) calculateCareQuality(careType string, value float64) float64 { // 基于照料类型和数值计算质量分数 switch careType { case "watering": if value >= 20.0 && value <= 30.0 { return 100.0 // 完美浇水量 } else if value >= 10.0 && value <= 40.0 { return 80.0 // 良好浇水量 } else { return 60.0 // 一般浇水量 } case "fertilizing": if value >= 15.0 && value <= 25.0 { return 100.0 // 完美施肥量 } else if value >= 10.0 && value <= 30.0 { return 80.0 // 良好施肥量 } else { return 60.0 // 一般施肥量 } default: return 70.0 // 默认质量 } } // Plot 地块实体 type Plot struct { ID string Name string Size PlotSize SoilType SoilType Fertility float64 Moisture float64 Crop *Crop IsAvailable bool LastUsed time.Time CreatedAt time.Time UpdatedAt time.Time } // NewPlot 创建地块 func NewPlot(id, name string, size PlotSize, soilType SoilType) *Plot { now := time.Now() return &Plot{ ID: id, Name: name, Size: size, SoilType: soilType, Fertility: 50.0, // 默认肥力 Moisture: 30.0, // 默认湿度 Crop: nil, IsAvailable: true, LastUsed: now, CreatedAt: now, UpdatedAt: now, } } // GetID 获取ID func (p *Plot) GetID() string { return p.ID } // GetName 获取名称 func (p *Plot) GetName() string { return p.Name } // GetSize 获取大小 func (p *Plot) GetSize() PlotSize { return p.Size } // GetSoilType 获取土壤类型 func (p *Plot) GetSoilType() SoilType { return p.SoilType } // GetFertility 获取肥力 func (p *Plot) GetFertility() float64 { return p.Fertility } // GetMoisture 获取湿度 func (p *Plot) GetMoisture() float64 { return p.Moisture } // GetCrop 获取作物 func (p *Plot) GetCrop() *Crop { return p.Crop } // GetCropID 获取作物ID func (p *Plot) GetCropID() string { if p.Crop != nil { return p.Crop.GetID() } return "" } // HasCrop 检查是否有作物 func (p *Plot) HasCrop() bool { return p.Crop != nil } // PlantCrop 种植作物 func (p *Plot) PlantCrop(crop *Crop) error { if !p.IsAvailable { return fmt.Errorf("plot is not available") } p.Crop = crop p.IsAvailable = false p.LastUsed = time.Now() p.UpdatedAt = time.Now() return nil } // ClearCrop 清除作物 func (p *Plot) ClearCrop() { p.Crop = nil p.IsAvailable = true p.UpdatedAt = time.Now() } // FarmTool 农具实体 type FarmTool struct { ID string Name string Type ToolType Level int Durability float64 MaxDurability float64 Efficiency float64 IsActive bool LastUsed time.Time CreatedAt time.Time UpdatedAt time.Time } // NewFarmTool 创建农具 func NewFarmTool(id, name string, toolType ToolType, level int) *FarmTool { now := time.Now() maxDurability := float64(100 + level*20) // 等级越高耐久越高 return &FarmTool{ ID: id, Name: name, Type: toolType, Level: level, Durability: maxDurability, MaxDurability: maxDurability, Efficiency: 1.0 + float64(level)*0.1, // 等级越高效率越高 IsActive: true, LastUsed: now, CreatedAt: now, UpdatedAt: now, } } // GetID 获取ID func (ft *FarmTool) GetID() string { return ft.ID } // GetName 获取名称 func (ft *FarmTool) GetName() string { return ft.Name } // GetType 获取类型 func (ft *FarmTool) GetType() ToolType { return ft.Type } // GetLevel 获取等级 func (ft *FarmTool) GetLevel() int { return ft.Level } // GetDurability 获取耐久度 func (ft *FarmTool) GetDurability() float64 { return ft.Durability } // GetEfficiency 获取效率 func (ft *FarmTool) GetEfficiency() float64 { return ft.Efficiency } // IsUsable 检查是否可用 func (ft *FarmTool) IsUsable() bool { return ft.IsActive && ft.Durability >= 10.0 // 至少需要10点耐久 } // Use 使用农具 func (ft *FarmTool) Use() *ToolEffect { if !ft.IsUsable() { return nil } // 消耗耐久度 ft.Durability -= 5.0 if ft.Durability < 0 { ft.Durability = 0 } ft.LastUsed = time.Now() ft.UpdatedAt = time.Now() // 返回工具效果 return ft.Type.GetEffect(ft.Level, ft.Efficiency) } // Repair 修理农具 func (ft *FarmTool) Repair(amount float64) { ft.Durability += amount if ft.Durability > ft.MaxDurability { ft.Durability = ft.MaxDurability } ft.UpdatedAt = time.Now() } // Upgrade 升级农具 func (ft *FarmTool) Upgrade() { ft.Level++ ft.MaxDurability += 20.0 ft.Durability = ft.MaxDurability // 升级后恢复满耐久 ft.Efficiency += 0.1 ft.UpdatedAt = time.Now() } // GetValue 获取价值 func (ft *FarmTool) GetValue() float64 { baseValue := ft.Type.GetBaseValue() levelMultiplier := 1.0 + float64(ft.Level)*0.2 durabilityMultiplier := ft.Durability / ft.MaxDurability return baseValue * levelMultiplier * durabilityMultiplier } // GetProductivityBonus 获取生产力奖励 func (ft *FarmTool) GetProductivityBonus() float64 { if !ft.IsActive { return 1.0 } return ft.Efficiency } // CareRecord 照料记录 type CareRecord struct { Type string Value float64 Quality float64 Timestamp time.Time } // GrowthBonus 生长奖励 type GrowthBonus struct { ID string Type string Multiplier float64 Duration time.Duration StartTime time.Time } // IsActive 检查奖励是否激活 func (gb *GrowthBonus) IsActive() bool { return time.Now().Before(gb.StartTime.Add(gb.Duration)) } // ToolEffect 工具效果 type ToolEffect struct { Type string Value float64 } // GetType 获取类型 func (te *ToolEffect) GetType() string { return te.Type } // GetValue 获取数值 func (te *ToolEffect) GetValue() float64 { return te.Value } ================================================ FILE: internal/domain/scene/plant/errors.go ================================================ package plant import ( "fmt" "strings" "time" ) // 基础错误变量 var ( // 种子相关错误 ErrInvalidSeedType = fmt.Errorf("invalid seed type") ErrSeedNotFound = fmt.Errorf("seed not found") ErrInsufficientSeeds = fmt.Errorf("insufficient seeds") ErrSeedExpired = fmt.Errorf("seed has expired") ErrSeedAlreadyPlanted = fmt.Errorf("seed already planted") ErrSeedNotCompatible = fmt.Errorf("seed not compatible with soil") // 作物相关错误 ErrCropNotFound = fmt.Errorf("crop not found") ErrCropNotMature = fmt.Errorf("crop is not mature") ErrCropAlreadyHarvested = fmt.Errorf("crop already harvested") ErrCropDead = fmt.Errorf("crop is dead") ErrCropDiseased = fmt.Errorf("crop is diseased") ErrCropNotHealthy = fmt.Errorf("crop is not healthy") ErrCropOverwatered = fmt.Errorf("crop is overwatered") ErrCropUnderwatered = fmt.Errorf("crop is underwatered") // 地块相关错误 ErrPlotNotFound = fmt.Errorf("plot not found") ErrPlotOccupied = fmt.Errorf("plot is occupied") ErrPlotNotEmpty = fmt.Errorf("plot is not empty") ErrPlotNotReady = fmt.Errorf("plot is not ready for planting") ErrPlotTooSmall = fmt.Errorf("plot is too small") ErrPlotDamaged = fmt.Errorf("plot is damaged") ErrInvalidPlotID = fmt.Errorf("invalid plot ID") ErrPlotNotAvailable = fmt.Errorf("plot is not available") ErrInvalidQuantity = fmt.Errorf("invalid quantity") ErrSoilNotSuitable = fmt.Errorf("soil is not suitable") ErrInvalidWaterAmount = fmt.Errorf("invalid water amount") ErrInvalidCropID = fmt.Errorf("invalid crop ID") ErrCropNotHarvestable = fmt.Errorf("crop is not harvestable") ErrInvalidPlot = fmt.Errorf("invalid plot") ErrMaxPlotsReached = fmt.Errorf("max plots reached") ErrPlotHasCrop = fmt.Errorf("plot has crop") ErrInvalidTool = fmt.Errorf("invalid tool") ErrToolNotUsable = fmt.Errorf("tool not usable") // 土壤相关错误 ErrInvalidSoilType = fmt.Errorf("invalid soil type") ErrSoilTooAcidic = fmt.Errorf("soil is too acidic") ErrSoilTooAlkaline = fmt.Errorf("soil is too alkaline") ErrSoilPoorFertility = fmt.Errorf("soil has poor fertility") ErrSoilContaminated = fmt.Errorf("soil is contaminated") ErrSoilTooWet = fmt.Errorf("soil is too wet") ErrSoilTooDry = fmt.Errorf("soil is too dry") // 肥料相关错误 ErrInvalidFertilizer = fmt.Errorf("invalid fertilizer") ErrInsufficientFertilizer = fmt.Errorf("insufficient fertilizer") ErrFertilizerExpired = fmt.Errorf("fertilizer has expired") ErrOverFertilization = fmt.Errorf("over fertilization") ErrFertilizerNotCompatible = fmt.Errorf("fertilizer not compatible") // 工具相关错误 ErrToolNotFound = fmt.Errorf("tool not found") ErrToolBroken = fmt.Errorf("tool is broken") ErrToolNotSuitable = fmt.Errorf("tool is not suitable for this operation") ErrToolInUse = fmt.Errorf("tool is in use") ErrInsufficientDurability = fmt.Errorf("insufficient tool durability") // 农场相关错误 ErrFarmNotFound = fmt.Errorf("farm not found") ErrFarmFull = fmt.Errorf("farm is full") ErrFarmLocked = fmt.Errorf("farm is locked") ErrInsufficientSpace = fmt.Errorf("insufficient space") ErrFarmNotOwned = fmt.Errorf("farm is not owned by player") // 季节相关错误 ErrInvalidSeason = fmt.Errorf("invalid season") ErrSeasonNotSuitable = fmt.Errorf("season is not suitable for this crop") ErrSeasonTransition = fmt.Errorf("season transition in progress") // 天气相关错误 ErrBadWeather = fmt.Errorf("bad weather conditions") ErrWeatherNotSuitable = fmt.Errorf("weather not suitable for operation") ErrExtremeWeather = fmt.Errorf("extreme weather conditions") // 时间相关错误 ErrInvalidTime = fmt.Errorf("invalid time") ErrTooEarly = fmt.Errorf("too early for this operation") ErrTooLate = fmt.Errorf("too late for this operation") ErrTimeExpired = fmt.Errorf("time has expired") // 资源相关错误 ErrInsufficientResources = fmt.Errorf("insufficient resources") ErrInsufficientWater = fmt.Errorf("insufficient water") ErrInsufficientGold = fmt.Errorf("insufficient gold") ErrResourceNotFound = fmt.Errorf("resource not found") // 权限相关错误 ErrPermissionDenied = fmt.Errorf("permission denied") ErrUnauthorized = fmt.Errorf("unauthorized operation") ErrAccessRestricted = fmt.Errorf("access restricted") // 配置相关错误 ErrInvalidConfiguration = fmt.Errorf("invalid configuration") ErrConfigurationNotFound = fmt.Errorf("configuration not found") ErrConfigurationCorrupted = fmt.Errorf("configuration corrupted") // 数据相关错误 ErrDataCorrupted = fmt.Errorf("data corrupted") ErrDataNotFound = fmt.Errorf("data not found") ErrDataInconsistent = fmt.Errorf("data inconsistent") // 系统相关错误 ErrSystemError = fmt.Errorf("system error") ErrServiceUnavailable = fmt.Errorf("service unavailable") ErrTimeout = fmt.Errorf("operation timeout") // 并发相关错误 ErrConcurrentModification = fmt.Errorf("concurrent modification") ErrResourceLocked = fmt.Errorf("resource is locked") ErrDeadlock = fmt.Errorf("deadlock detected") ) // PlantError 种植系统错误 type PlantError struct { Code string Message string Details map[string]interface{} Cause error Timestamp time.Time Context map[string]string } // Error 实现error接口 func (e *PlantError) Error() string { if e.Cause != nil { return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // Unwrap 返回原始错误 func (e *PlantError) Unwrap() error { return e.Cause } // WithDetail 添加详细信息 func (e *PlantError) WithDetail(key string, value interface{}) *PlantError { if e.Details == nil { e.Details = make(map[string]interface{}) } e.Details[key] = value return e } // WithContext 添加上下文信息 func (e *PlantError) WithContext(key, value string) *PlantError { if e.Context == nil { e.Context = make(map[string]string) } e.Context[key] = value return e } // ValidationError 验证错误 type ValidationError struct { *PlantError Field string Value interface{} Constraint string Rule string } // NewValidationError 创建验证错误 func NewValidationError(field, constraint, rule string, value interface{}) *ValidationError { return &ValidationError{ PlantError: &PlantError{ Code: "VALIDATION_ERROR", Message: fmt.Sprintf("validation failed for field '%s': %s", field, constraint), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), }, Field: field, Value: value, Constraint: constraint, Rule: rule, } } // BusinessRuleError 业务规则错误 type BusinessRuleError struct { *PlantError Rule string Violation string Expected interface{} Actual interface{} Suggestion string } // NewBusinessRuleError 创建业务规则错误 func NewBusinessRuleError(rule, violation string, expected, actual interface{}) *BusinessRuleError { return &BusinessRuleError{ PlantError: &PlantError{ Code: "BUSINESS_RULE_ERROR", Message: fmt.Sprintf("business rule violation: %s - %s", rule, violation), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), }, Rule: rule, Violation: violation, Expected: expected, Actual: actual, } } // WithSuggestion 添加建议 func (e *BusinessRuleError) WithSuggestion(suggestion string) *BusinessRuleError { e.Suggestion = suggestion return e } // ConcurrencyError 并发错误 type ConcurrencyError struct { *PlantError Resource string Operation string ConflictID string RetryAfter time.Duration } // NewConcurrencyError 创建并发错误 func NewConcurrencyError(resource, operation, conflictID string) *ConcurrencyError { return &ConcurrencyError{ PlantError: &PlantError{ Code: "CONCURRENCY_ERROR", Message: fmt.Sprintf("concurrent access conflict on %s during %s", resource, operation), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), }, Resource: resource, Operation: operation, ConflictID: conflictID, } } // WithRetryAfter 设置重试时间 func (e *ConcurrencyError) WithRetryAfter(duration time.Duration) *ConcurrencyError { e.RetryAfter = duration return e } // ConfigurationError 配置错误 type ConfigurationError struct { *PlantError ConfigKey string ConfigValue interface{} ExpectedType string ValidValues []interface{} } // NewConfigurationError 创建配置错误 func NewConfigurationError(configKey string, configValue interface{}, expectedType string) *ConfigurationError { return &ConfigurationError{ PlantError: &PlantError{ Code: "CONFIGURATION_ERROR", Message: fmt.Sprintf("invalid configuration for key '%s': expected %s", configKey, expectedType), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), }, ConfigKey: configKey, ConfigValue: configValue, ExpectedType: expectedType, } } // WithValidValues 设置有效值 func (e *ConfigurationError) WithValidValues(values ...interface{}) *ConfigurationError { e.ValidValues = values return e } // SystemError 系统错误 type SystemError struct { *PlantError Component string Operation string ErrorCode int Recoverable bool RetryCount int MaxRetries int } // NewSystemError 创建系统错误 func NewSystemError(component, operation string, errorCode int, cause error) *SystemError { return &SystemError{ PlantError: &PlantError{ Code: "SYSTEM_ERROR", Message: fmt.Sprintf("system error in %s during %s (code: %d)", component, operation, errorCode), Cause: cause, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), }, Component: component, Operation: operation, ErrorCode: errorCode, MaxRetries: 3, } } // SetRecoverable 设置是否可恢复 func (e *SystemError) SetRecoverable(recoverable bool) *SystemError { e.Recoverable = recoverable return e } // IncrementRetry 增加重试次数 func (e *SystemError) IncrementRetry() *SystemError { e.RetryCount++ return e } // CanRetry 检查是否可以重试 func (e *SystemError) CanRetry() bool { return e.Recoverable && e.RetryCount < e.MaxRetries } // ErrorCollection 错误集合 type ErrorCollection struct { Errors []error Context string Timestamp time.Time } // NewErrorCollection 创建错误集合 func NewErrorCollection(context string) *ErrorCollection { return &ErrorCollection{ Errors: make([]error, 0), Context: context, Timestamp: time.Now(), } } // Add 添加错误 func (ec *ErrorCollection) Add(err error) { if err != nil { ec.Errors = append(ec.Errors, err) } } // HasErrors 检查是否有错误 func (ec *ErrorCollection) HasErrors() bool { return len(ec.Errors) > 0 } // Count 获取错误数量 func (ec *ErrorCollection) Count() int { return len(ec.Errors) } // Error 实现error接口 func (ec *ErrorCollection) Error() string { if len(ec.Errors) == 0 { return "no errors" } var messages []string for i, err := range ec.Errors { messages = append(messages, fmt.Sprintf("%d: %v", i+1, err)) } return fmt.Sprintf("multiple errors in %s: [%s]", ec.Context, strings.Join(messages, "; ")) } // First 获取第一个错误 func (ec *ErrorCollection) First() error { if len(ec.Errors) > 0 { return ec.Errors[0] } return nil } // Last 获取最后一个错误 func (ec *ErrorCollection) Last() error { if len(ec.Errors) > 0 { return ec.Errors[len(ec.Errors)-1] } return nil } // 错误工厂函数 // NewPlantError 创建种植错误 func NewPlantError(code, message string) *PlantError { return &PlantError{ Code: code, Message: message, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), } } // NewPlantErrorWithCause 创建带原因的种植错误 func NewPlantErrorWithCause(code, message string, cause error) *PlantError { return &PlantError{ Code: code, Message: message, Cause: cause, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), } } // WrapError 包装错误 func WrapError(err error, code, message string) *PlantError { return &PlantError{ Code: code, Message: message, Cause: err, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), } } // 错误检查函数 // IsValidationError 检查是否为验证错误 func IsValidationError(err error) bool { _, ok := err.(*ValidationError) return ok } // IsBusinessRuleError 检查是否为业务规则错误 func IsBusinessRuleError(err error) bool { _, ok := err.(*BusinessRuleError) return ok } // IsConcurrencyError 检查是否为并发错误 func IsConcurrencyError(err error) bool { _, ok := err.(*ConcurrencyError) return ok } // IsConfigurationError 检查是否为配置错误 func IsConfigurationError(err error) bool { _, ok := err.(*ConfigurationError) return ok } // IsSystemError 检查是否为系统错误 func IsSystemError(err error) bool { _, ok := err.(*SystemError) return ok } // IsPlantError 检查是否为种植错误 func IsPlantError(err error) bool { _, ok := err.(*PlantError) return ok } // 错误分类函数 // IsRetryableError 检查错误是否可重试 func IsRetryableError(err error) bool { if sysErr, ok := err.(*SystemError); ok { return sysErr.CanRetry() } if concErr, ok := err.(*ConcurrencyError); ok { return concErr.RetryAfter > 0 } return false } // IsTemporaryError 检查错误是否为临时错误 func IsTemporaryError(err error) bool { switch err { case ErrServiceUnavailable, ErrTimeout, ErrResourceLocked: return true default: return IsRetryableError(err) } } // IsPermanentError 检查错误是否为永久错误 func IsPermanentError(err error) bool { switch err { case ErrPermissionDenied, ErrUnauthorized, ErrDataCorrupted: return true default: return IsValidationError(err) || IsConfigurationError(err) } } // 辅助函数 // GetErrorCode 获取错误代码 func GetErrorCode(err error) string { if plantErr, ok := err.(*PlantError); ok { return plantErr.Code } return "UNKNOWN_ERROR" } // GetErrorDetails 获取错误详情 func GetErrorDetails(err error) map[string]interface{} { if plantErr, ok := err.(*PlantError); ok { return plantErr.Details } return nil } // GetErrorContext 获取错误上下文 func GetErrorContext(err error) map[string]string { if plantErr, ok := err.(*PlantError); ok { return plantErr.Context } return nil } // FormatError 格式化错误信息 func FormatError(err error) string { if err == nil { return "no error" } if plantErr, ok := err.(*PlantError); ok { var parts []string parts = append(parts, fmt.Sprintf("Code: %s", plantErr.Code)) parts = append(parts, fmt.Sprintf("Message: %s", plantErr.Message)) parts = append(parts, fmt.Sprintf("Time: %s", plantErr.Timestamp.Format(time.RFC3339))) if len(plantErr.Details) > 0 { parts = append(parts, fmt.Sprintf("Details: %+v", plantErr.Details)) } if len(plantErr.Context) > 0 { parts = append(parts, fmt.Sprintf("Context: %+v", plantErr.Context)) } if plantErr.Cause != nil { parts = append(parts, fmt.Sprintf("Cause: %v", plantErr.Cause)) } return strings.Join(parts, ", ") } return err.Error() } ================================================ FILE: internal/domain/scene/plant/events.go ================================================ package plant import ( "fmt" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetVersion() int GetPayload() map[string]interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string EventType string AggregateID string OccurredAt time.Time Version int Payload map[string]interface{} } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetVersion 获取版本 func (e *BaseDomainEvent) GetVersion() int { return e.Version } // GetPayload 获取载荷 func (e *BaseDomainEvent) GetPayload() map[string]interface{} { return e.Payload } // CropPlantedEvent 作物种植事件 type CropPlantedEvent struct { *BaseDomainEvent FarmID string PlotID string CropID string SeedType SeedType Quantity int Soil *Soil Season Season } // NewCropPlantedEvent 创建作物种植事件 func NewCropPlantedEvent(farmID, plotID, cropID string, seedType SeedType, quantity int, soil *Soil, season Season) *CropPlantedEvent { now := time.Now() event := &CropPlantedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("crop_planted_%d", now.UnixNano()), EventType: "plant.crop_planted", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, PlotID: plotID, CropID: cropID, SeedType: seedType, Quantity: quantity, Soil: soil, Season: season, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["plot_id"] = plotID event.Payload["crop_id"] = cropID event.Payload["seed_type"] = seedType.String() event.Payload["quantity"] = quantity event.Payload["season"] = season.String() if soil != nil { event.Payload["soil_type"] = soil.Type.String() event.Payload["soil_fertility"] = soil.Fertility } return event } // CropHarvestedEvent 作物收获事件 type CropHarvestedEvent struct { *BaseDomainEvent FarmID string CropID string SeedType SeedType Yield int Quality CropQuality Experience int HarvestResult *HarvestResult } // NewCropHarvestedEvent 创建作物收获事件 func NewCropHarvestedEvent(farmID, cropID string, harvestResult *HarvestResult) *CropHarvestedEvent { now := time.Now() event := &CropHarvestedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("crop_harvested_%d", now.UnixNano()), EventType: "plant.crop_harvested", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, CropID: cropID, SeedType: harvestResult.SeedType, Yield: harvestResult.Yield, Quality: harvestResult.Quality, Experience: harvestResult.Experience, HarvestResult: harvestResult, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["crop_id"] = cropID event.Payload["seed_type"] = harvestResult.SeedType.String() event.Payload["yield"] = harvestResult.Yield event.Payload["quality"] = harvestResult.Quality.String() event.Payload["experience"] = harvestResult.Experience event.Payload["harvest_time"] = harvestResult.HarvestTime return event } // CropWateredEvent 作物浇水事件 type CropWateredEvent struct { *BaseDomainEvent FarmID string PlotIDs []string WaterAmount float64 TotalCrops int } // NewCropWateredEvent 创建作物浇水事件 func NewCropWateredEvent(farmID string, plotIDs []string, waterAmount float64, totalCrops int) *CropWateredEvent { now := time.Now() event := &CropWateredEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("crop_watered_%d", now.UnixNano()), EventType: "plant.crop_watered", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, PlotIDs: plotIDs, WaterAmount: waterAmount, TotalCrops: totalCrops, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["plot_ids"] = plotIDs event.Payload["water_amount"] = waterAmount event.Payload["total_crops"] = totalCrops event.Payload["plots_count"] = len(plotIDs) return event } // SoilFertilizedEvent 土壤施肥事件 type SoilFertilizedEvent struct { *BaseDomainEvent FarmID string Fertilizer *Fertilizer PreviousSoil *Soil UpdatedSoil *Soil FertilityChange float64 } // NewSoilFertilizedEvent 创建土壤施肥事件 func NewSoilFertilizedEvent(farmID string, fertilizer *Fertilizer, previousSoil, updatedSoil *Soil) *SoilFertilizedEvent { now := time.Now() fertilityChange := updatedSoil.Fertility - previousSoil.Fertility event := &SoilFertilizedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("soil_fertilized_%d", now.UnixNano()), EventType: "plant.soil_fertilized", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, Fertilizer: fertilizer, PreviousSoil: previousSoil, UpdatedSoil: updatedSoil, FertilityChange: fertilityChange, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["fertilizer_type"] = fertilizer.Type.String() event.Payload["fertilizer_amount"] = fertilizer.Amount event.Payload["previous_fertility"] = previousSoil.Fertility event.Payload["updated_fertility"] = updatedSoil.Fertility event.Payload["fertility_change"] = fertilityChange return event } // CropGrowthStageChangedEvent 作物生长阶段变化事件 type CropGrowthStageChangedEvent struct { *BaseDomainEvent FarmID string CropID string SeedType SeedType PreviousStage GrowthStage CurrentStage GrowthStage GrowthProgress float64 } // NewCropGrowthStageChangedEvent 创建作物生长阶段变化事件 func NewCropGrowthStageChangedEvent(farmID, cropID string, seedType SeedType, previousStage, currentStage GrowthStage, progress float64) *CropGrowthStageChangedEvent { now := time.Now() event := &CropGrowthStageChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("crop_growth_stage_changed_%d", now.UnixNano()), EventType: "plant.crop_growth_stage_changed", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, CropID: cropID, SeedType: seedType, PreviousStage: previousStage, CurrentStage: currentStage, GrowthProgress: progress, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["crop_id"] = cropID event.Payload["seed_type"] = seedType.String() event.Payload["previous_stage"] = previousStage.String() event.Payload["current_stage"] = currentStage.String() event.Payload["growth_progress"] = progress return event } // ToolUsedEvent 工具使用事件 type ToolUsedEvent struct { *BaseDomainEvent FarmID string ToolID string ToolType ToolType Operation string TargetID string Efficiency float64 DurabilityLoss float64 } // NewToolUsedEvent 创建工具使用事件 func NewToolUsedEvent(farmID, toolID string, toolType ToolType, operation, targetID string, efficiency, durabilityLoss float64) *ToolUsedEvent { now := time.Now() event := &ToolUsedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("tool_used_%d", now.UnixNano()), EventType: "plant.tool_used", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, ToolID: toolID, ToolType: toolType, Operation: operation, TargetID: targetID, Efficiency: efficiency, DurabilityLoss: durabilityLoss, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["tool_id"] = toolID event.Payload["tool_type"] = toolType.String() event.Payload["operation"] = operation event.Payload["target_id"] = targetID event.Payload["efficiency"] = efficiency event.Payload["durability_loss"] = durabilityLoss return event } // FarmExpandedEvent 农场扩展事件 type FarmExpandedEvent struct { *BaseDomainEvent FarmID string PreviousSize FarmSize NewSize FarmSize ExpansionCost *ExpansionCost NewPlots []*Plot } // NewFarmExpandedEvent 创建农场扩展事件 func NewFarmExpandedEvent(farmID string, previousSize, newSize FarmSize, cost *ExpansionCost, newPlots []*Plot) *FarmExpandedEvent { now := time.Now() event := &FarmExpandedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("farm_expanded_%d", now.UnixNano()), EventType: "plant.farm_expanded", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, PreviousSize: previousSize, NewSize: newSize, ExpansionCost: cost, NewPlots: newPlots, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["previous_size"] = previousSize.String() event.Payload["new_size"] = newSize.String() event.Payload["new_plots_count"] = len(newPlots) if cost != nil { event.Payload["expansion_cost_gold"] = cost.Gold event.Payload["expansion_cost_materials"] = cost.Materials event.Payload["expansion_time"] = cost.Time } return event } // PestDiseaseDetectedEvent 病虫害检测事件 type PestDiseaseDetectedEvent struct { *BaseDomainEvent FarmID string CropID string SeedType SeedType PestDisease *PestDiseaseEvent Severity string AffectedArea float64 } // NewPestDiseaseDetectedEvent 创建病虫害检测事件 func NewPestDiseaseDetectedEvent(farmID, cropID string, seedType SeedType, pestDisease *PestDiseaseEvent, affectedArea float64) *PestDiseaseDetectedEvent { now := time.Now() event := &PestDiseaseDetectedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("pest_disease_detected_%d", now.UnixNano()), EventType: "plant.pest_disease_detected", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, CropID: cropID, SeedType: seedType, PestDisease: pestDisease, Severity: pestDisease.Severity, AffectedArea: affectedArea, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["crop_id"] = cropID event.Payload["seed_type"] = seedType.String() event.Payload["pest_disease_name"] = pestDisease.Name event.Payload["severity"] = pestDisease.Severity event.Payload["affected_area"] = affectedArea event.Payload["occurred_at"] = pestDisease.OccurredAt return event } // SeasonChangedEvent 季节变化事件 type SeasonChangedEvent struct { *BaseDomainEvent FarmID string PreviousSeason Season CurrentSeason Season SeasonModifier *SeasonModifier AffectedCrops []string } // NewSeasonChangedEvent 创建季节变化事件 func NewSeasonChangedEvent(farmID string, previousSeason, currentSeason Season, modifier *SeasonModifier, affectedCrops []string) *SeasonChangedEvent { now := time.Now() event := &SeasonChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("season_changed_%d", now.UnixNano()), EventType: "plant.season_changed", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, PreviousSeason: previousSeason, CurrentSeason: currentSeason, SeasonModifier: modifier, AffectedCrops: affectedCrops, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["previous_season"] = previousSeason.String() event.Payload["current_season"] = currentSeason.String() event.Payload["affected_crops_count"] = len(affectedCrops) if modifier != nil { event.Payload["growth_multiplier"] = modifier.GrowthMultiplier event.Payload["yield_multiplier"] = modifier.YieldMultiplier event.Payload["quality_multiplier"] = modifier.QualityMultiplier } return event } // CropHealthChangedEvent 作物健康变化事件 type CropHealthChangedEvent struct { *BaseDomainEvent FarmID string CropID string SeedType SeedType PreviousHealth float64 CurrentHealth float64 HealthChange float64 Cause string Problems []string } // NewCropHealthChangedEvent 创建作物健康变化事件 func NewCropHealthChangedEvent(farmID, cropID string, seedType SeedType, previousHealth, currentHealth float64, cause string, problems []string) *CropHealthChangedEvent { now := time.Now() healthChange := currentHealth - previousHealth event := &CropHealthChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("crop_health_changed_%d", now.UnixNano()), EventType: "plant.crop_health_changed", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, CropID: cropID, SeedType: seedType, PreviousHealth: previousHealth, CurrentHealth: currentHealth, HealthChange: healthChange, Cause: cause, Problems: problems, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["crop_id"] = cropID event.Payload["seed_type"] = seedType.String() event.Payload["previous_health"] = previousHealth event.Payload["current_health"] = currentHealth event.Payload["health_change"] = healthChange event.Payload["cause"] = cause event.Payload["problems"] = problems event.Payload["problems_count"] = len(problems) return event } // AutomationTriggeredEvent 自动化触发事件 type AutomationTriggeredEvent struct { *BaseDomainEvent FarmID string AutomationType string TriggerReason string TargetCrops []string ActionTaken string ResourcesUsed map[string]float64 } // NewAutomationTriggeredEvent 创建自动化触发事件 func NewAutomationTriggeredEvent(farmID, automationType, triggerReason string, targetCrops []string, actionTaken string, resourcesUsed map[string]float64) *AutomationTriggeredEvent { now := time.Now() event := &AutomationTriggeredEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("automation_triggered_%d", now.UnixNano()), EventType: "plant.automation_triggered", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, AutomationType: automationType, TriggerReason: triggerReason, TargetCrops: targetCrops, ActionTaken: actionTaken, ResourcesUsed: resourcesUsed, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["automation_type"] = automationType event.Payload["trigger_reason"] = triggerReason event.Payload["target_crops"] = targetCrops event.Payload["target_crops_count"] = len(targetCrops) event.Payload["action_taken"] = actionTaken event.Payload["resources_used"] = resourcesUsed return event } // FarmValueChangedEvent 农场价值变化事件 type FarmValueChangedEvent struct { *BaseDomainEvent FarmID string PreviousValue float64 CurrentValue float64 ValueChange float64 ChangeReason string Contributors map[string]float64 } // NewFarmValueChangedEvent 创建农场价值变化事件 func NewFarmValueChangedEvent(farmID string, previousValue, currentValue float64, changeReason string, contributors map[string]float64) *FarmValueChangedEvent { now := time.Now() valueChange := currentValue - previousValue event := &FarmValueChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("farm_value_changed_%d", now.UnixNano()), EventType: "plant.farm_value_changed", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, PreviousValue: previousValue, CurrentValue: currentValue, ValueChange: valueChange, ChangeReason: changeReason, Contributors: contributors, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["previous_value"] = previousValue event.Payload["current_value"] = currentValue event.Payload["value_change"] = valueChange event.Payload["change_reason"] = changeReason event.Payload["contributors"] = contributors return event } // PlotStatusChangedEvent 地块状态变化事件 type PlotStatusChangedEvent struct { *BaseDomainEvent FarmID string PlotID string PreviousStatus string CurrentStatus string CropID string StatusChangeTime time.Time } // NewPlotStatusChangedEvent 创建地块状态变化事件 func NewPlotStatusChangedEvent(farmID, plotID, previousStatus, currentStatus, cropID string) *PlotStatusChangedEvent { now := time.Now() event := &PlotStatusChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("plot_status_changed_%d", now.UnixNano()), EventType: "plant.plot_status_changed", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, PlotID: plotID, PreviousStatus: previousStatus, CurrentStatus: currentStatus, CropID: cropID, StatusChangeTime: now, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["plot_id"] = plotID event.Payload["previous_status"] = previousStatus event.Payload["current_status"] = currentStatus event.Payload["crop_id"] = cropID event.Payload["status_change_time"] = now return event } // ResourcesConsumedEvent 资源消耗事件 type ResourcesConsumedEvent struct { *BaseDomainEvent FarmID string ResourceType string AmountConsumed float64 RemainingAmount float64 ConsumptionReason string RelatedCropID string } // NewResourcesConsumedEvent 创建资源消耗事件 func NewResourcesConsumedEvent(farmID, resourceType string, amountConsumed, remainingAmount float64, reason, relatedCropID string) *ResourcesConsumedEvent { now := time.Now() event := &ResourcesConsumedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("resources_consumed_%d", now.UnixNano()), EventType: "plant.resources_consumed", AggregateID: farmID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, FarmID: farmID, ResourceType: resourceType, AmountConsumed: amountConsumed, RemainingAmount: remainingAmount, ConsumptionReason: reason, RelatedCropID: relatedCropID, } // 设置载荷 event.Payload["farm_id"] = farmID event.Payload["resource_type"] = resourceType event.Payload["amount_consumed"] = amountConsumed event.Payload["remaining_amount"] = remainingAmount event.Payload["consumption_reason"] = reason event.Payload["related_crop_id"] = relatedCropID return event } // 事件处理器接口 // EventHandler 事件处理器接口 type EventHandler interface { Handle(event DomainEvent) error CanHandle(eventType string) bool } // EventBus 事件总线接口 type EventBus interface { Publish(event DomainEvent) error Subscribe(eventType string, handler EventHandler) error Unsubscribe(eventType string, handler EventHandler) error } // 事件存储接口 // EventStore 事件存储接口 type EventStore interface { Save(event DomainEvent) error Load(aggregateID string) ([]DomainEvent, error) LoadFromVersion(aggregateID string, version int) ([]DomainEvent, error) LoadByEventType(eventType string, limit int) ([]DomainEvent, error) LoadByTimeRange(startTime, endTime time.Time) ([]DomainEvent, error) } ================================================ FILE: internal/domain/scene/plant/repository.go ================================================ package plant import ( "context" "time" ) // FarmRepository 农场仓储接口 type FarmRepository interface { // 基础CRUD操作 Save(ctx context.Context, farm *FarmAggregate) error FindByID(ctx context.Context, farmID string) (*FarmAggregate, error) FindByOwner(ctx context.Context, owner string) ([]*FarmAggregate, error) FindBySceneID(ctx context.Context, sceneID string) ([]*FarmAggregate, error) Update(ctx context.Context, farm *FarmAggregate) error Delete(ctx context.Context, farmID string) error // 查询操作 FindBySize(ctx context.Context, size FarmSize, limit int) ([]*FarmAggregate, error) FindByStatus(ctx context.Context, status FarmStatus, limit int) ([]*FarmAggregate, error) FindActiveByOwner(ctx context.Context, owner string) ([]*FarmAggregate, error) FindByClimateZone(ctx context.Context, climateZone string, limit int) ([]*FarmAggregate, error) // 统计操作 GetFarmStatistics(ctx context.Context, farmID string) (*FarmStatistics, error) GetOwnerStatistics(ctx context.Context, owner string) (*OwnerStatistics, error) GetFarmCount(ctx context.Context) (int64, error) GetFarmCountByOwner(ctx context.Context, owner string) (int64, error) // 批量操作 SaveBatch(ctx context.Context, farms []*FarmAggregate) error UpdateBatch(ctx context.Context, farms []*FarmAggregate) error DeleteBatch(ctx context.Context, farmIDs []string) error // 排行榜操作 GetTopFarmsByValue(ctx context.Context, limit int) ([]*FarmRanking, error) GetTopFarmsByProductivity(ctx context.Context, limit int) ([]*FarmRanking, error) GetTopFarmsByYield(ctx context.Context, period time.Duration, limit int) ([]*FarmRanking, error) } // CropRepository 作物仓储接口 type CropRepository interface { // 基础CRUD操作 Save(ctx context.Context, crop *Crop) error FindByID(ctx context.Context, cropID string) (*Crop, error) Update(ctx context.Context, crop *Crop) error Delete(ctx context.Context, cropID string) error // 查询操作 FindByFarmID(ctx context.Context, farmID string) ([]*Crop, error) FindBySeedType(ctx context.Context, seedType SeedType, limit int) ([]*Crop, error) FindByGrowthStage(ctx context.Context, stage GrowthStage, limit int) ([]*Crop, error) FindHarvestable(ctx context.Context, farmID string) ([]*Crop, error) FindNeedsCare(ctx context.Context, farmID string) ([]*Crop, error) FindByTimeRange(ctx context.Context, startTime, endTime time.Time) ([]*Crop, error) // 状态查询 FindByHealthRange(ctx context.Context, minHealth, maxHealth float64, limit int) ([]*Crop, error) FindByProgressRange(ctx context.Context, minProgress, maxProgress float64, limit int) ([]*Crop, error) FindExpiredCrops(ctx context.Context, beforeTime time.Time) ([]*Crop, error) // 统计操作 GetCropStatistics(ctx context.Context, farmID string) (*CropStatistics, error) GetCropCountByType(ctx context.Context, seedType SeedType) (int64, error) GetCropCountByStage(ctx context.Context, stage GrowthStage) (int64, error) GetAverageGrowthProgress(ctx context.Context, seedType SeedType) (float64, error) // 批量操作 SaveBatch(ctx context.Context, crops []*Crop) error UpdateBatch(ctx context.Context, crops []*Crop) error DeleteBatch(ctx context.Context, cropIDs []string) error // 清理操作 CleanupExpiredCrops(ctx context.Context, beforeTime time.Time) (int64, error) } // PlotRepository 地块仓储接口 type PlotRepository interface { // 基础CRUD操作 Save(ctx context.Context, plot *Plot) error FindByID(ctx context.Context, plotID string) (*Plot, error) Update(ctx context.Context, plot *Plot) error Delete(ctx context.Context, plotID string) error // 查询操作 FindByFarmID(ctx context.Context, farmID string) ([]*Plot, error) FindAvailable(ctx context.Context, farmID string) ([]*Plot, error) FindOccupied(ctx context.Context, farmID string) ([]*Plot, error) FindBySize(ctx context.Context, size PlotSize, limit int) ([]*Plot, error) FindBySoilType(ctx context.Context, soilType SoilType, limit int) ([]*Plot, error) // 统计操作 GetPlotStatistics(ctx context.Context, farmID string) (*PlotStatistics, error) GetAvailablePlotCount(ctx context.Context, farmID string) (int64, error) GetOccupiedPlotCount(ctx context.Context, farmID string) (int64, error) // 批量操作 SaveBatch(ctx context.Context, plots []*Plot) error UpdateBatch(ctx context.Context, plots []*Plot) error DeleteBatch(ctx context.Context, plotIDs []string) error } // FarmToolRepository 农具仓储接口 type FarmToolRepository interface { // 基础CRUD操作 Save(ctx context.Context, tool *FarmTool) error FindByID(ctx context.Context, toolID string) (*FarmTool, error) Update(ctx context.Context, tool *FarmTool) error Delete(ctx context.Context, toolID string) error // 查询操作 FindByFarmID(ctx context.Context, farmID string) ([]*FarmTool, error) FindByType(ctx context.Context, toolType ToolType, limit int) ([]*FarmTool, error) FindByLevel(ctx context.Context, minLevel, maxLevel int, limit int) ([]*FarmTool, error) FindUsable(ctx context.Context, farmID string) ([]*FarmTool, error) FindNeedsMaintenance(ctx context.Context, farmID string, durabilityThreshold float64) ([]*FarmTool, error) // 统计操作 GetToolStatistics(ctx context.Context, farmID string) (*ToolStatistics, error) GetToolCountByType(ctx context.Context, toolType ToolType) (int64, error) GetAverageToolLevel(ctx context.Context, toolType ToolType) (float64, error) // 批量操作 SaveBatch(ctx context.Context, tools []*FarmTool) error UpdateBatch(ctx context.Context, tools []*FarmTool) error DeleteBatch(ctx context.Context, toolIDs []string) error } // SoilRepository 土壤仓储接口 type SoilRepository interface { // 基础CRUD操作 Save(ctx context.Context, soil *Soil) error FindByID(ctx context.Context, soilID string) (*Soil, error) Update(ctx context.Context, soil *Soil) error Delete(ctx context.Context, soilID string) error // 查询操作 FindByFarmID(ctx context.Context, farmID string) (*Soil, error) FindByType(ctx context.Context, soilType SoilType, limit int) ([]*Soil, error) FindByFertilityRange(ctx context.Context, minFertility, maxFertility float64, limit int) ([]*Soil, error) FindByPHRange(ctx context.Context, minPH, maxPH float64, limit int) ([]*Soil, error) FindHighQuality(ctx context.Context, qualityThreshold float64, limit int) ([]*Soil, error) // 统计操作 GetSoilStatistics(ctx context.Context, farmID string) (*SoilStatistics, error) GetAverageFertility(ctx context.Context, soilType SoilType) (float64, error) GetAveragePH(ctx context.Context, soilType SoilType) (float64, error) // 历史记录 SaveSoilHistory(ctx context.Context, farmID string, soil *Soil) error GetSoilHistory(ctx context.Context, farmID string, limit int) ([]*SoilHistoryRecord, error) // 批量操作 SaveBatch(ctx context.Context, soils []*Soil) error UpdateBatch(ctx context.Context, soils []*Soil) error } // HarvestRepository 收获仓储接口 type HarvestRepository interface { // 基础CRUD操作 Save(ctx context.Context, harvest *HarvestResult) error FindByID(ctx context.Context, harvestID string) (*HarvestResult, error) Update(ctx context.Context, harvest *HarvestResult) error Delete(ctx context.Context, harvestID string) error // 查询操作 FindByFarmID(ctx context.Context, farmID string, limit int) ([]*HarvestResult, error) FindByCropID(ctx context.Context, cropID string) (*HarvestResult, error) FindBySeedType(ctx context.Context, seedType SeedType, limit int) ([]*HarvestResult, error) FindByQuality(ctx context.Context, quality CropQuality, limit int) ([]*HarvestResult, error) FindByTimeRange(ctx context.Context, startTime, endTime time.Time) ([]*HarvestResult, error) // 统计操作 GetHarvestStatistics(ctx context.Context, farmID string, period time.Duration) (*HarvestStatistics, error) GetTotalYield(ctx context.Context, farmID string, seedType SeedType, period time.Duration) (int, error) GetAverageQuality(ctx context.Context, farmID string, seedType SeedType, period time.Duration) (float64, error) GetHarvestTrend(ctx context.Context, farmID string, period time.Duration) (*HarvestTrend, error) // 排行榜操作 GetTopHarvestsByYield(ctx context.Context, period time.Duration, limit int) ([]*HarvestRanking, error) GetTopHarvestsByQuality(ctx context.Context, period time.Duration, limit int) ([]*HarvestRanking, error) // 批量操作 SaveBatch(ctx context.Context, harvests []*HarvestResult) error DeleteBatch(ctx context.Context, harvestIDs []string) error // 清理操作 CleanupOldHarvests(ctx context.Context, beforeTime time.Time) (int64, error) } // PlantEventRepository 种植事件仓储接口 type PlantEventRepository interface { // 基础CRUD操作 Save(ctx context.Context, event *PlantEvent) error FindByID(ctx context.Context, eventID string) (*PlantEvent, error) Update(ctx context.Context, event *PlantEvent) error Delete(ctx context.Context, eventID string) error // 查询操作 FindByFarmID(ctx context.Context, farmID string, limit int) ([]*PlantEvent, error) FindByCropID(ctx context.Context, cropID string, limit int) ([]*PlantEvent, error) FindByEventType(ctx context.Context, eventType string, limit int) ([]*PlantEvent, error) FindByTimeRange(ctx context.Context, startTime, endTime time.Time) ([]*PlantEvent, error) FindActiveEvents(ctx context.Context, farmID string) ([]*PlantEvent, error) // 统计操作 GetEventStatistics(ctx context.Context, farmID string, period time.Duration) (*EventStatistics, error) GetEventCountByType(ctx context.Context, eventType string, period time.Duration) (int64, error) // 批量操作 SaveBatch(ctx context.Context, events []*PlantEvent) error DeleteBatch(ctx context.Context, eventIDs []string) error // 清理操作 CleanupExpiredEvents(ctx context.Context, beforeTime time.Time) (int64, error) } // OwnerStatistics 所有者统计信息 type OwnerStatistics struct { Owner string TotalFarms int TotalPlots int TotalCrops int TotalHarvests int TotalYield int TotalExperience int TotalValue float64 AverageProductivity float64 BestFarmID string CreatedAt time.Time UpdatedAt time.Time } // CropStatistics 作物统计信息 type CropStatistics struct { FarmID string TotalCrops int CropsByType map[SeedType]int CropsByStage map[GrowthStage]int AverageGrowthProgress float64 AverageHealthScore float64 HarvestableCrops int CropsNeedingCare int CreatedAt time.Time UpdatedAt time.Time } // PlotStatistics 地块统计信息 type PlotStatistics struct { FarmID string TotalPlots int AvailablePlots int OccupiedPlots int PlotsBySize map[PlotSize]int PlotsBySoil map[SoilType]int UtilizationRate float64 CreatedAt time.Time UpdatedAt time.Time } // ToolStatistics 工具统计信息 type ToolStatistics struct { FarmID string TotalTools int ToolsByType map[ToolType]int ToolsByLevel map[int]int UsableTools int ToolsNeedingRepair int AverageEfficiency float64 AverageDurability float64 TotalValue float64 CreatedAt time.Time UpdatedAt time.Time } // SoilStatistics 土壤统计信息 type SoilStatistics struct { FarmID string SoilType SoilType Fertility float64 PH float64 Moisture float64 Organic float64 Nitrogen float64 Phosphorus float64 Potassium float64 QualityScore float64 ProductivityScore float64 LastTested time.Time CreatedAt time.Time UpdatedAt time.Time } // HarvestStatistics 收获统计信息 type HarvestStatistics struct { FarmID string Period time.Duration StartTime time.Time EndTime time.Time TotalHarvests int TotalYield int HarvestsByType map[SeedType]int YieldByType map[SeedType]int HarvestsByQuality map[CropQuality]int AverageYield float64 AverageQuality float64 BestHarvest *HarvestResult TotalExperience int TotalValue float64 CreatedAt time.Time } // EventStatistics 事件统计信息 type EventStatistics struct { FarmID string Period time.Duration StartTime time.Time EndTime time.Time TotalEvents int EventsByType map[string]int ActiveEvents int ResolvedEvents int CriticalEvents int CreatedAt time.Time } // 排行榜结构体 // FarmRanking 农场排行 type FarmRanking struct { Rank int FarmID string Owner string FarmName string Score float64 Metric string Value interface{} LastUpdated time.Time } // HarvestRanking 收获排行 type HarvestRanking struct { Rank int FarmID string Owner string HarvestID string SeedType SeedType Yield int Quality CropQuality Score float64 HarvestTime time.Time } // 趋势分析结构体 // HarvestTrend 收获趋势 type HarvestTrend struct { FarmID string Period time.Duration StartTime time.Time EndTime time.Time TrendType TrendType YieldTrend YieldTrend QualityTrend QualityTrend DataPoints []*TrendDataPoint Prediction *TrendPrediction Confidence float64 CreatedAt time.Time } // TrendType 趋势类型 type TrendType int const ( TrendTypeIncreasing TrendType = iota + 1 TrendTypeDecreasing TrendTypeStable TrendTypeVolatile TrendTypeCyclical ) // String 返回趋势类型字符串 func (tt TrendType) String() string { switch tt { case TrendTypeIncreasing: return "increasing" case TrendTypeDecreasing: return "decreasing" case TrendTypeStable: return "stable" case TrendTypeVolatile: return "volatile" case TrendTypeCyclical: return "cyclical" default: return "unknown" } } // YieldTrend 产量趋势 type YieldTrend struct { Direction TrendDirection ChangeRate float64 AverageYield float64 MinYield int MaxYield int Variance float64 } // QualityTrend 品质趋势 type QualityTrend struct { Direction TrendDirection ChangeRate float64 AverageQuality float64 BestQuality CropQuality WorstQuality CropQuality Variance float64 } // TrendDirection 趋势方向 type TrendDirection int const ( TrendDirectionUp TrendDirection = iota + 1 TrendDirectionDown TrendDirectionStable TrendDirectionVolatile ) // String 返回趋势方向字符串 func (td TrendDirection) String() string { switch td { case TrendDirectionUp: return "up" case TrendDirectionDown: return "down" case TrendDirectionStable: return "stable" case TrendDirectionVolatile: return "volatile" default: return "unknown" } } // TrendDataPoint 趋势数据点 type TrendDataPoint struct { Time time.Time Yield int Quality float64 Value float64 Metadata map[string]interface{} } // TrendPrediction 趋势预测 type TrendPrediction struct { PredictedYield int PredictedQuality float64 PredictedValue float64 Timeframe time.Duration Confidence float64 Factors []string } // 查询条件结构体 // FarmQuery 农场查询条件 type FarmQuery struct { Owners []string SceneIDs []string Sizes []FarmSize Statuses []FarmStatus ClimateZones []string MinValue *float64 MaxValue *float64 CreatedAfter *time.Time CreatedBefore *time.Time Limit int Offset int OrderBy string OrderDesc bool } // CropQuery 作物查询条件 type CropQuery struct { FarmIDs []string SeedTypes []SeedType GrowthStages []GrowthStage MinHealth *float64 MaxHealth *float64 MinProgress *float64 MaxProgress *float64 IsHarvestable *bool NeedsCare *bool PlantedAfter *time.Time PlantedBefore *time.Time Limit int Offset int OrderBy string OrderDesc bool } // HarvestQuery 收获查询条件 type HarvestQuery struct { FarmIDs []string SeedTypes []SeedType Qualities []CropQuality MinYield *int MaxYield *int HarvestedAfter *time.Time HarvestedBefore *time.Time Limit int Offset int OrderBy string OrderDesc bool } // 历史记录结构体 // SoilHistoryRecord 土壤历史记录 type SoilHistoryRecord struct { ID string FarmID string Soil *Soil ChangeType string Changes map[string]interface{} Reason string RecordedAt time.Time } // PlantEvent 种植事件 type PlantEvent struct { ID string FarmID string CropID string EventType string Title string Description string Severity string Status string Data map[string]interface{} OccurredAt time.Time ResolvedAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // 缓存接口 // PlantCacheRepository 种植缓存仓储接口 type PlantCacheRepository interface { // 缓存操作 Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error Get(ctx context.Context, key string, dest interface{}) error Delete(ctx context.Context, key string) error Exists(ctx context.Context, key string) (bool, error) // 批量操作 SetBatch(ctx context.Context, items map[string]interface{}, expiration time.Duration) error GetBatch(ctx context.Context, keys []string) (map[string]interface{}, error) DeleteBatch(ctx context.Context, keys []string) error // 模式操作 DeleteByPattern(ctx context.Context, pattern string) error GetKeysByPattern(ctx context.Context, pattern string) ([]string, error) // 缓存管理 Flush(ctx context.Context) error GetStats(ctx context.Context) (*CacheStats, error) } // CacheStats 缓存统计 type CacheStats struct { Hits int64 Misses int64 Keys int64 MemoryUsage int64 HitRate float64 CreatedAt time.Time } // 事务接口 // PlantTransaction 种植事务接口 type PlantTransaction interface { // 事务控制 Begin(ctx context.Context) error Commit(ctx context.Context) error Rollback(ctx context.Context) error // 获取仓储 FarmRepository() FarmRepository CropRepository() CropRepository PlotRepository() PlotRepository FarmToolRepository() FarmToolRepository SoilRepository() SoilRepository HarvestRepository() HarvestRepository PlantEventRepository() PlantEventRepository } // 仓储工厂接口 // PlantRepositoryFactory 种植仓储工厂接口 type PlantRepositoryFactory interface { // 创建仓储 CreateFarmRepository() FarmRepository CreateCropRepository() CropRepository CreatePlotRepository() PlotRepository CreateFarmToolRepository() FarmToolRepository CreateSoilRepository() SoilRepository CreateHarvestRepository() HarvestRepository CreatePlantEventRepository() PlantEventRepository CreatePlantCacheRepository() PlantCacheRepository // 创建事务 CreateTransaction() PlantTransaction // 健康检查 HealthCheck(ctx context.Context) error // 关闭连接 Close() error } ================================================ FILE: internal/domain/scene/plant/service.go ================================================ package plant import ( "errors" "fmt" "math" "time" ) // PlantService 种植领域服务 type PlantService struct { seedTemplates map[SeedType]*SeedTemplate soilTemplates map[SoilType]*SoilTemplate fertilizerTemplates map[FertilizerType]*FertilizerTemplate toolTemplates map[ToolType]*ToolTemplate growthRules []*GrowthRule seasonalEffects map[Season]map[SeedType]float64 climateZones map[string]*ClimateZone pestDiseaseRules []*PestDiseaseRule qualityRules []*QualityRule createdAt time.Time updatedAt time.Time } // NewPlantService 创建种植服务 func NewPlantService() *PlantService { now := time.Now() service := &PlantService{ seedTemplates: make(map[SeedType]*SeedTemplate), soilTemplates: make(map[SoilType]*SoilTemplate), fertilizerTemplates: make(map[FertilizerType]*FertilizerTemplate), toolTemplates: make(map[ToolType]*ToolTemplate), growthRules: make([]*GrowthRule, 0), seasonalEffects: make(map[Season]map[SeedType]float64), climateZones: make(map[string]*ClimateZone), pestDiseaseRules: make([]*PestDiseaseRule, 0), qualityRules: make([]*QualityRule, 0), createdAt: now, updatedAt: now, } // 初始化默认模板和规则 service.initializeDefaultTemplates() service.initializeDefaultRules() service.initializeSeasonalEffects() service.initializeClimateZones() return service } // RegisterSeedTemplate 注册种子模板 func (ps *PlantService) RegisterSeedTemplate(seedType SeedType, template *SeedTemplate) { ps.seedTemplates[seedType] = template ps.updatedAt = time.Now() } // GetSeedTemplate 获取种子模板 func (ps *PlantService) GetSeedTemplate(seedType SeedType) *SeedTemplate { return ps.seedTemplates[seedType] } // RegisterSoilTemplate 注册土壤模板 func (ps *PlantService) RegisterSoilTemplate(soilType SoilType, template *SoilTemplate) { ps.soilTemplates[soilType] = template ps.updatedAt = time.Now() } // GetSoilTemplate 获取土壤模板 func (ps *PlantService) GetSoilTemplate(soilType SoilType) *SoilTemplate { return ps.soilTemplates[soilType] } // RegisterFertilizerTemplate 注册肥料模板 func (ps *PlantService) RegisterFertilizerTemplate(fertilizerType FertilizerType, template *FertilizerTemplate) { ps.fertilizerTemplates[fertilizerType] = template ps.updatedAt = time.Now() } // GetFertilizerTemplate 获取肥料模板 func (ps *PlantService) GetFertilizerTemplate(fertilizerType FertilizerType) *FertilizerTemplate { return ps.fertilizerTemplates[fertilizerType] } // RegisterToolTemplate 注册工具模板 func (ps *PlantService) RegisterToolTemplate(toolType ToolType, template *ToolTemplate) { ps.toolTemplates[toolType] = template ps.updatedAt = time.Now() } // GetToolTemplate 获取工具模板 func (ps *PlantService) GetToolTemplate(toolType ToolType) *ToolTemplate { return ps.toolTemplates[toolType] } // AddGrowthRule 添加生长规则 func (ps *PlantService) AddGrowthRule(rule *GrowthRule) { ps.growthRules = append(ps.growthRules, rule) ps.updatedAt = time.Now() } // GetGrowthRules 获取生长规则 func (ps *PlantService) GetGrowthRules() []*GrowthRule { return ps.growthRules } // RegisterClimateZone 注册气候区域 func (ps *PlantService) RegisterClimateZone(zoneID string, zone *ClimateZone) { ps.climateZones[zoneID] = zone ps.updatedAt = time.Now() } // GetClimateZone 获取气候区域 func (ps *PlantService) GetClimateZone(zoneID string) *ClimateZone { return ps.climateZones[zoneID] } // CalculateOptimalPlantingTime 计算最佳种植时间 func (ps *PlantService) CalculateOptimalPlantingTime(seedType SeedType, climateZone string) (time.Time, error) { if !seedType.IsValid() { return time.Time{}, ErrInvalidSeedType } zone := ps.GetClimateZone(climateZone) if zone == nil { zone = ps.getDefaultClimateZone() } now := time.Now() currentSeason := getCurrentSeason(now) // 获取种子的最佳种植季节 optimalSeasons := ps.getOptimalSeasonsForSeed(seedType) // 如果当前季节是最佳季节,返回当前时间 for _, season := range optimalSeasons { if season == currentSeason { return now, nil } } // 计算下一个最佳季节的开始时间 nextOptimalTime := ps.calculateNextSeasonTime(now, optimalSeasons[0]) return nextOptimalTime, nil } // CalculateGrowthProgress 计算生长进度 func (ps *PlantService) CalculateGrowthProgress(crop *Crop, deltaTime time.Duration) (float64, error) { if crop == nil { return 0, ErrInvalidCrop } // 基础生长速度 baseGrowthRate := crop.SeedType.GetGrowthRate() // 应用生长规则 actualGrowthRate := ps.applyGrowthRules(crop, baseGrowthRate) // 计算进度增量 progressDelta := actualGrowthRate * deltaTime.Hours() return progressDelta, nil } // CalculateYield 计算产量 func (ps *PlantService) CalculateYield(crop *Crop, soil *Soil, season Season) (int, error) { if crop == nil { return 0, ErrInvalidCrop } if soil == nil { return 0, ErrInvalidSoil } // 基础产量 baseYield := crop.GetBaseYield() // 土壤影响 soilMultiplier := soil.GetYieldMultiplier(crop.SeedType) // 季节影响 seasonMultiplier := ps.getSeasonalEffect(season, crop.SeedType) // 健康状态影响 healthMultiplier := crop.GetHealthScore() / 100.0 // 照料质量影响 careMultiplier := crop.GetCareQualityMultiplier() // 计算最终产量 finalYield := float64(baseYield) * soilMultiplier * seasonMultiplier * healthMultiplier * careMultiplier return int(math.Round(finalYield)), nil } // CalculateQuality 计算品质 func (ps *PlantService) CalculateQuality(crop *Crop, soil *Soil, season Season) (CropQuality, error) { if crop == nil { return CropQualityCommon, ErrInvalidCrop } if soil == nil { return CropQualityCommon, ErrInvalidSoil } // 计算质量分数 qualityScore := 0.0 // 土壤质量贡献(30%) qualityScore += soil.GetQualityScore() * 0.3 // 作物健康贡献(25%) qualityScore += crop.GetHealthScore() * 0.25 // 照料质量贡献(25%) qualityScore += crop.GetCareQualityScore() * 0.25 // 季节影响贡献(20%) seasonBonus := season.GetQualityMultiplier() * 20.0 qualityScore += seasonBonus // 应用质量规则 qualityScore = ps.applyQualityRules(crop, soil, qualityScore) // 转换为品质等级 return ps.scoreToQuality(qualityScore), nil } // ValidatePlantingConditions 验证种植条件 func (ps *PlantService) ValidatePlantingConditions(seedType SeedType, soil *Soil, season Season, climateZone string) error { if !seedType.IsValid() { return ErrInvalidSeedType } if soil == nil { return ErrInvalidSoil } // 检查土壤适宜性 if !soil.IsSuitableFor(seedType) { return errors.New("soil is not suitable") } // 检查季节适宜性 if !ps.isSeasonSuitableForSeed(seedType, season) { return ErrSeasonNotSuitable } // 检查气候区域适宜性 zone := ps.GetClimateZone(climateZone) if zone != nil && !zone.IsSuitableFor(seedType) { return ErrClimateNotSuitable } return nil } // CalculateFertilizerEffect 计算肥料效果 func (ps *PlantService) CalculateFertilizerEffect(fertilizer *Fertilizer, soil *Soil, crop *Crop) (*FertilizerEffect, error) { if fertilizer == nil { return nil, ErrInvalidFertilizer } if soil == nil { return nil, ErrInvalidSoil } template := ps.GetFertilizerTemplate(fertilizer.GetType()) if template == nil { return nil, ErrFertilizerTemplateNotFound } // 计算基础效果 baseEffect := template.GetBaseEffect() // 土壤类型影响 soilMultiplier := template.GetSoilMultiplier(soil.GetType()) // 作物类型影响 cropMultiplier := 1.0 if crop != nil { cropMultiplier = template.GetCropMultiplier(crop.GetSeedType()) } // 计算最终效果 finalEffect := baseEffect * soilMultiplier * cropMultiplier * fertilizer.GetAmount() return &FertilizerEffect{ FertilityBoost: finalEffect * 0.4, NutrientBoost: finalEffect * 0.6, GrowthSpeedBoost: finalEffect * 0.2, Duration: template.GetEffectDuration(), }, nil } // CalculateToolEfficiency 计算工具效率 func (ps *PlantService) CalculateToolEfficiency(tool *FarmTool, operation string, target interface{}) (float64, error) { if tool == nil { return 1.0, errors.New("Farm tool not found") } if !tool.IsUsable() { return 1.0, errors.New("Farm tool is not usable") } template := ps.GetToolTemplate(tool.GetType()) if template == nil { return 1.0, ErrToolTemplateNotFound } // 基础效率 baseEfficiency := tool.GetEfficiency() // 操作类型影响 operationMultiplier := template.GetOperationMultiplier(operation) // 工具等级影响 levelMultiplier := 1.0 + float64(tool.GetLevel())*0.1 // 耐久度影响 durabilityMultiplier := tool.GetDurability() / tool.MaxDurability if durabilityMultiplier < 0.5 { durabilityMultiplier = 0.5 // 最低50%效率 } // 计算最终效率 finalEfficiency := baseEfficiency * operationMultiplier * levelMultiplier * durabilityMultiplier return finalEfficiency, nil } // DetectPestsAndDiseases 检测病虫害 func (ps *PlantService) DetectPestsAndDiseases(crop *Crop, soil *Soil, season Season, climateZone string) ([]*PestDiseaseEvent, error) { if crop == nil { return nil, ErrInvalidCrop } events := make([]*PestDiseaseEvent, 0) // 应用病虫害规则 for _, rule := range ps.pestDiseaseRules { if rule.ShouldTrigger(crop, soil, season, climateZone) { event := rule.CreateEvent(crop) events = append(events, event) } } return events, nil } // CalculateWaterRequirement 计算水分需求 func (ps *PlantService) CalculateWaterRequirement(crop *Crop, soil *Soil, season Season, climateZone string) (float64, error) { if crop == nil { return 0, ErrInvalidCrop } // 基础水分消耗 baseConsumption := crop.SeedType.GetWaterConsumption() // 生长阶段影响 stageMultiplier := ps.getGrowthStageWaterMultiplier(crop.GetGrowthStage()) // 季节影响 seasonMultiplier := season.GetWaterConsumptionMultiplier() // 土壤影响 soilMultiplier := 1.0 if soil != nil { // 排水好的土壤需要更多水分 drainageRate := soil.GetType().GetDrainageRate() soilMultiplier = 0.8 + drainageRate*0.4 // 0.8-1.2倍率 } // 气候区域影响 climateMultiplier := 1.0 zone := ps.GetClimateZone(climateZone) if zone != nil { climateMultiplier = zone.GetWaterRequirementMultiplier() } // 计算最终需求 finalRequirement := baseConsumption * stageMultiplier * seasonMultiplier * soilMultiplier * climateMultiplier return finalRequirement, nil } // CalculateNutrientRequirement 计算营养需求 func (ps *PlantService) CalculateNutrientRequirement(crop *Crop, soil *Soil, season Season) (map[string]float64, error) { if crop == nil { return nil, ErrInvalidCrop } // 基础营养消耗 baseConsumption := crop.SeedType.GetNutrientConsumption() // 生长阶段影响 stageMultiplier := ps.getGrowthStageNutrientMultiplier(crop.GetGrowthStage()) // 季节影响 seasonMultiplier := season.GetNutrientConsumptionMultiplier() // 土壤营养保持率影响 soilMultiplier := 1.0 if soil != nil { retentionRate := soil.GetType().GetNutrientRetention() soilMultiplier = 2.0 - retentionRate // 保持率低需要更多营养 } // 计算各种营养需求 requirements := map[string]float64{ "nitrogen": baseConsumption * 0.4 * stageMultiplier * seasonMultiplier * soilMultiplier, "phosphorus": baseConsumption * 0.3 * stageMultiplier * seasonMultiplier * soilMultiplier, "potassium": baseConsumption * 0.3 * stageMultiplier * seasonMultiplier * soilMultiplier, } return requirements, nil } // GetOptimalHarvestTime 获取最佳收获时间 func (ps *PlantService) GetOptimalHarvestTime(crop *Crop) (time.Time, error) { if crop == nil { return time.Time{}, ErrInvalidCrop } // 基础收获时间 baseHarvestTime := crop.ExpectedHarvestTime // 考虑生长进度调整 if crop.GetGrowthProgress() < 100.0 { // 根据当前进度和生长速度估算剩余时间 remainingProgress := 100.0 - crop.GetGrowthProgress() growthRate := crop.SeedType.GetGrowthRate() // 应用当前环境因素 environmentMultiplier := ps.calculateCurrentEnvironmentMultiplier(crop) actualGrowthRate := growthRate * environmentMultiplier remainingHours := remainingProgress / actualGrowthRate adjustedHarvestTime := time.Now().Add(time.Duration(remainingHours) * time.Hour) return adjustedHarvestTime, nil } return baseHarvestTime, nil } // 私有方法 // initializeDefaultTemplates 初始化默认模板 func (ps *PlantService) initializeDefaultTemplates() { // 初始化种子模板 ps.initializeSeedTemplates() // 初始化土壤模板 ps.initializeSoilTemplates() // 初始化肥料模板 ps.initializeFertilizerTemplates() // 初始化工具模板 ps.initializeToolTemplates() } // initializeSeedTemplates 初始化种子模板 func (ps *PlantService) initializeSeedTemplates() { // 小麦模板 wheatTemplate := &SeedTemplate{ SeedType: SeedTypeWheat, OptimalSeasons: []Season{SeasonSpring, SeasonAutumn}, OptimalSoilTypes: []SoilType{SoilTypeLoam, SoilTypeSilt}, MinTemperature: 5.0, MaxTemperature: 30.0, OptimalPH: 6.5, WaterTolerance: 0.8, NutrientNeeds: map[string]float64{"nitrogen": 0.6, "phosphorus": 0.3, "potassium": 0.4}, GrowthModifiers: map[string]float64{"temperature": 1.2, "moisture": 1.1}, } ps.RegisterSeedTemplate(SeedTypeWheat, wheatTemplate) // 玉米模板 cornTemplate := &SeedTemplate{ SeedType: SeedTypeCorn, OptimalSeasons: []Season{SeasonSummer}, OptimalSoilTypes: []SoilType{SoilTypeLoam, SoilTypeSandy}, MinTemperature: 15.0, MaxTemperature: 35.0, OptimalPH: 6.8, WaterTolerance: 0.9, NutrientNeeds: map[string]float64{"nitrogen": 0.8, "phosphorus": 0.4, "potassium": 0.6}, GrowthModifiers: map[string]float64{"temperature": 1.3, "sunlight": 1.2}, } ps.RegisterSeedTemplate(SeedTypeCorn, cornTemplate) // 番茄模板 tomatoTemplate := &SeedTemplate{ SeedType: SeedTypeTomato, OptimalSeasons: []Season{SeasonSpring, SeasonSummer}, OptimalSoilTypes: []SoilType{SoilTypeLoam}, MinTemperature: 18.0, MaxTemperature: 28.0, OptimalPH: 6.2, WaterTolerance: 0.7, NutrientNeeds: map[string]float64{"nitrogen": 0.5, "phosphorus": 0.6, "potassium": 0.8}, GrowthModifiers: map[string]float64{"temperature": 1.1, "moisture": 1.3}, } ps.RegisterSeedTemplate(SeedTypeTomato, tomatoTemplate) } // initializeSoilTemplates 初始化土壤模板 func (ps *PlantService) initializeSoilTemplates() { // 壤土模板 loamTemplate := &SoilTemplate{ SoilType: SoilTypeLoam, BaseProductivity: 1.2, WaterRetention: 0.7, NutrientCapacity: 0.8, DrainageRate: 0.6, OptimalPHRange: PHRange{Min: 6.0, Max: 7.5}, SuitableCrops: []SeedType{SeedTypeWheat, SeedTypeCorn, SeedTypeTomato, SeedTypeCabbage}, } ps.RegisterSoilTemplate(SoilTypeLoam, loamTemplate) // 沙土模板 sandyTemplate := &SoilTemplate{ SoilType: SoilTypeSandy, BaseProductivity: 0.8, WaterRetention: 0.3, NutrientCapacity: 0.4, DrainageRate: 0.9, OptimalPHRange: PHRange{Min: 5.5, Max: 7.0}, SuitableCrops: []SeedType{SeedTypePotato, SeedTypeCarrot}, } ps.RegisterSoilTemplate(SoilTypeSandy, sandyTemplate) // 粘土模板 clayTemplate := &SoilTemplate{ SoilType: SoilTypeClay, BaseProductivity: 0.9, WaterRetention: 0.9, NutrientCapacity: 0.9, DrainageRate: 0.2, OptimalPHRange: PHRange{Min: 6.5, Max: 8.0}, SuitableCrops: []SeedType{SeedTypeRice}, } ps.RegisterSoilTemplate(SoilTypeClay, clayTemplate) } // initializeFertilizerTemplates 初始化肥料模板 func (ps *PlantService) initializeFertilizerTemplates() { // 有机肥模板 organicTemplate := &FertilizerTemplate{ FertilizerType: FertilizerTypeOrganic, BaseEffect: 1.0, EffectDuration: 72 * time.Hour, SoilMultipliers: map[SoilType]float64{SoilTypeLoam: 1.2, SoilTypeSandy: 1.1, SoilTypeClay: 1.0}, CropMultipliers: map[SeedType]float64{SeedTypeWheat: 1.1, SeedTypeCorn: 1.0, SeedTypeTomato: 1.2}, NutrientProfile: map[string]float64{"nitrogen": 0.3, "phosphorus": 0.2, "potassium": 0.25, "organic": 0.8}, } ps.RegisterFertilizerTemplate(FertilizerTypeOrganic, organicTemplate) // 化学肥料模板 chemicalTemplate := &FertilizerTemplate{ FertilizerType: FertilizerTypeChemical, BaseEffect: 1.5, EffectDuration: 48 * time.Hour, SoilMultipliers: map[SoilType]float64{SoilTypeLoam: 1.0, SoilTypeSandy: 1.3, SoilTypeClay: 0.9}, CropMultipliers: map[SeedType]float64{SeedTypeWheat: 1.3, SeedTypeCorn: 1.4, SeedTypeTomato: 1.1}, NutrientProfile: map[string]float64{"nitrogen": 0.6, "phosphorus": 0.4, "potassium": 0.5, "organic": 0.1}, } ps.RegisterFertilizerTemplate(FertilizerTypeChemical, chemicalTemplate) } // initializeToolTemplates 初始化工具模板 func (ps *PlantService) initializeToolTemplates() { // 锄头模板 hoeTemplate := &ToolTemplate{ ToolType: ToolTypeHoe, BaseEfficiency: 1.0, OperationMultipliers: map[string]float64{"soil_preparation": 1.5, "weeding": 1.3, "planting": 1.1}, DurabilityConsumption: 5.0, MaintenanceRequirement: 0.1, } ps.RegisterToolTemplate(ToolTypeHoe, hoeTemplate) // 洒水壶模板 wateringCanTemplate := &ToolTemplate{ ToolType: ToolTypeWateringCan, BaseEfficiency: 1.0, OperationMultipliers: map[string]float64{"watering": 1.8, "fertilizing": 1.2}, DurabilityConsumption: 3.0, MaintenanceRequirement: 0.05, } ps.RegisterToolTemplate(ToolTypeWateringCan, wateringCanTemplate) // 收割机模板 harvesterTemplate := &ToolTemplate{ ToolType: ToolTypeHarvester, BaseEfficiency: 2.0, OperationMultipliers: map[string]float64{"harvesting": 2.5, "processing": 1.5}, DurabilityConsumption: 8.0, MaintenanceRequirement: 0.2, } ps.RegisterToolTemplate(ToolTypeHarvester, harvesterTemplate) } // initializeDefaultRules 初始化默认规则 func (ps *PlantService) initializeDefaultRules() { // 添加基础生长规则 ps.AddGrowthRule(&GrowthRule{ Name: "optimal_temperature", Description: "最适温度加速生长", Condition: "temperature_in_range", Multiplier: 1.2, Priority: 1, }) ps.AddGrowthRule(&GrowthRule{ Name: "adequate_water", Description: "充足水分促进生长", Condition: "water_level_high", Multiplier: 1.15, Priority: 2, }) ps.AddGrowthRule(&GrowthRule{ Name: "nutrient_deficiency", Description: "营养不足减缓生长", Condition: "nutrient_level_low", Multiplier: 0.7, Priority: 3, }) // 添加病虫害规则 ps.pestDiseaseRules = append(ps.pestDiseaseRules, &PestDiseaseRule{ Name: "aphid_infestation", Description: "蚜虫侵害", TriggerConditions: map[string]interface{}{ "temperature_min": 20.0, "humidity_min": 70.0, "season": []Season{SeasonSpring, SeasonSummer}, }, Probability: 0.15, Severity: "medium", AffectedCrops: []SeedType{SeedTypeTomato, SeedTypeCabbage}, }) // 添加质量规则 ps.qualityRules = append(ps.qualityRules, &QualityRule{ Name: "perfect_conditions", Description: "完美条件提升品质", Conditions: map[string]interface{}{ "soil_quality_min": 80.0, "health_min": 90.0, "care_quality_min": 85.0, }, QualityBonus: 15.0, }) } // initializeSeasonalEffects 初始化季节效果 func (ps *PlantService) initializeSeasonalEffects() { // 春季效果 ps.seasonalEffects[SeasonSpring] = map[SeedType]float64{ SeedTypeWheat: 1.2, SeedTypeTomato: 1.3, SeedTypeCarrot: 1.1, SeedTypeCabbage: 1.2, SeedTypeStrawberry: 1.4, } // 夏季效果 ps.seasonalEffects[SeasonSummer] = map[SeedType]float64{ SeedTypeCorn: 1.4, SeedTypeTomato: 1.2, SeedTypeStrawberry: 1.1, SeedTypeApple: 1.1, SeedTypeOrange: 1.2, } // 秋季效果 ps.seasonalEffects[SeasonAutumn] = map[SeedType]float64{ SeedTypeWheat: 1.1, SeedTypeRice: 1.2, SeedTypePotato: 1.3, SeedTypeApple: 1.4, SeedTypeOrange: 1.3, } // 冬季效果 ps.seasonalEffects[SeasonWinter] = map[SeedType]float64{ SeedTypeCabbage: 0.9, SeedTypeCarrot: 0.8, } } // initializeClimateZones 初始化气候区域 func (ps *PlantService) initializeClimateZones() { // 温带气候 temperateZone := &ClimateZone{ ZoneID: "temperate", Name: "温带气候", Description: "四季分明,适合多种作物", TemperatureRange: TemperatureRange{Min: -5, Max: 35, Average: 15}, HumidityRange: HumidityRange{Min: 40, Max: 80, Average: 60}, SuitableCrops: []SeedType{SeedTypeWheat, SeedTypeCorn, SeedTypeTomato, SeedTypePotato}, SeasonalModifiers: map[Season]float64{SeasonSpring: 1.2, SeasonSummer: 1.1, SeasonAutumn: 1.0, SeasonWinter: 0.7}, WaterRequirementMultiplier: 1.0, } ps.RegisterClimateZone("temperate", temperateZone) // 热带气候 tropicalZone := &ClimateZone{ ZoneID: "tropical", Name: "热带气候", Description: "高温多湿,适合热带作物", TemperatureRange: TemperatureRange{Min: 20, Max: 40, Average: 28}, HumidityRange: HumidityRange{Min: 70, Max: 95, Average: 85}, SuitableCrops: []SeedType{SeedTypeRice, SeedTypeOrange, SeedTypeStrawberry}, SeasonalModifiers: map[Season]float64{SeasonSpring: 1.1, SeasonSummer: 1.3, SeasonAutumn: 1.1, SeasonWinter: 1.0}, WaterRequirementMultiplier: 1.3, } ps.RegisterClimateZone("tropical", tropicalZone) } // 辅助方法 // getOptimalSeasonsForSeed 获取种子的最佳季节 func (ps *PlantService) getOptimalSeasonsForSeed(seedType SeedType) []Season { template := ps.GetSeedTemplate(seedType) if template != nil { return template.OptimalSeasons } // 默认春季和夏季 return []Season{SeasonSpring, SeasonSummer} } // calculateNextSeasonTime 计算下一个季节时间 func (ps *PlantService) calculateNextSeasonTime(currentTime time.Time, targetSeason Season) time.Time { year := currentTime.Year() switch targetSeason { case SeasonSpring: return time.Date(year, 3, 1, 0, 0, 0, 0, currentTime.Location()) case SeasonSummer: return time.Date(year, 6, 1, 0, 0, 0, 0, currentTime.Location()) case SeasonAutumn: return time.Date(year, 9, 1, 0, 0, 0, 0, currentTime.Location()) case SeasonWinter: return time.Date(year, 12, 1, 0, 0, 0, 0, currentTime.Location()) default: return currentTime } } // applyGrowthRules 应用生长规则 func (ps *PlantService) applyGrowthRules(crop *Crop, baseGrowthRate float64) float64 { actualRate := baseGrowthRate for _, rule := range ps.growthRules { if rule.AppliesTo(crop) { actualRate *= rule.Multiplier } } return actualRate } // getSeasonalEffect 获取季节效果 func (ps *PlantService) getSeasonalEffect(season Season, seedType SeedType) float64 { if effects, exists := ps.seasonalEffects[season]; exists { if effect, exists := effects[seedType]; exists { return effect } } return 1.0 // 默认无效果 } // isSeasonSuitableForSeed 检查季节是否适合种子 func (ps *PlantService) isSeasonSuitableForSeed(seedType SeedType, season Season) bool { optimalSeasons := ps.getOptimalSeasonsForSeed(seedType) for _, optimalSeason := range optimalSeasons { if optimalSeason == season { return true } } return false } // applyQualityRules 应用质量规则 func (ps *PlantService) applyQualityRules(crop *Crop, soil *Soil, baseScore float64) float64 { adjustedScore := baseScore for _, rule := range ps.qualityRules { if rule.AppliesTo(crop, soil) { adjustedScore += rule.QualityBonus } } return adjustedScore } // scoreToQuality 分数转换为品质 func (ps *PlantService) scoreToQuality(score float64) CropQuality { if score >= 95 { return CropQualityLegendary } else if score >= 85 { return CropQualityEpic } else if score >= 75 { return CropQualityRare } else if score >= 65 { return CropQualityUncommon } else { return CropQualityCommon } } // getGrowthStageWaterMultiplier 获取生长阶段水分倍率 func (ps *PlantService) getGrowthStageWaterMultiplier(stage GrowthStage) float64 { switch stage { case GrowthStageSeed: return 0.8 case GrowthStageSeedling: return 1.2 case GrowthStageGrowing: return 1.5 case GrowthStageFlowering: return 1.3 case GrowthStageMature: return 1.0 default: return 1.0 } } // getGrowthStageNutrientMultiplier 获取生长阶段营养倍率 func (ps *PlantService) getGrowthStageNutrientMultiplier(stage GrowthStage) float64 { switch stage { case GrowthStageSeed: return 0.5 case GrowthStageSeedling: return 1.3 case GrowthStageGrowing: return 1.8 case GrowthStageFlowering: return 1.4 case GrowthStageMature: return 0.8 default: return 1.0 } } // calculateCurrentEnvironmentMultiplier 计算当前环境倍率 func (ps *PlantService) calculateCurrentEnvironmentMultiplier(crop *Crop) float64 { multiplier := 1.0 // 健康状态影响 healthMultiplier := crop.GetHealthScore() / 100.0 multiplier *= healthMultiplier // 水分和营养影响 if crop.GetWaterLevel() > 70.0 && crop.GetNutrientLevel() > 70.0 { multiplier *= 1.2 } else if crop.GetWaterLevel() < 30.0 || crop.GetNutrientLevel() < 30.0 { multiplier *= 0.8 } return multiplier } // getDefaultClimateZone 获取默认气候区域 func (ps *PlantService) getDefaultClimateZone() *ClimateZone { return ps.GetClimateZone("temperate") } // 模板和规则结构体定义 // SeedTemplate 种子模板 type SeedTemplate struct { SeedType SeedType OptimalSeasons []Season OptimalSoilTypes []SoilType MinTemperature float64 MaxTemperature float64 OptimalPH float64 WaterTolerance float64 NutrientNeeds map[string]float64 GrowthModifiers map[string]float64 } // SoilTemplate 土壤模板 type SoilTemplate struct { SoilType SoilType BaseProductivity float64 WaterRetention float64 NutrientCapacity float64 DrainageRate float64 OptimalPHRange PHRange SuitableCrops []SeedType } // FertilizerTemplate 肥料模板 type FertilizerTemplate struct { FertilizerType FertilizerType BaseEffect float64 EffectDuration time.Duration SoilMultipliers map[SoilType]float64 CropMultipliers map[SeedType]float64 NutrientProfile map[string]float64 } // GetBaseEffect 获取基础效果 func (ft *FertilizerTemplate) GetBaseEffect() float64 { return ft.BaseEffect } // GetSoilMultiplier 获取土壤倍率 func (ft *FertilizerTemplate) GetSoilMultiplier(soilType SoilType) float64 { if multiplier, exists := ft.SoilMultipliers[soilType]; exists { return multiplier } return 1.0 } // GetCropMultiplier 获取作物倍率 func (ft *FertilizerTemplate) GetCropMultiplier(seedType SeedType) float64 { if multiplier, exists := ft.CropMultipliers[seedType]; exists { return multiplier } return 1.0 } // GetEffectDuration 获取效果持续时间 func (ft *FertilizerTemplate) GetEffectDuration() time.Duration { return ft.EffectDuration } // ToolTemplate 工具模板 type ToolTemplate struct { ToolType ToolType BaseEfficiency float64 OperationMultipliers map[string]float64 DurabilityConsumption float64 MaintenanceRequirement float64 } // GetOperationMultiplier 获取操作倍率 func (tt *ToolTemplate) GetOperationMultiplier(operation string) float64 { if multiplier, exists := tt.OperationMultipliers[operation]; exists { return multiplier } return 1.0 } // GrowthRule 生长规则 type GrowthRule struct { Name string Description string Condition string Multiplier float64 Priority int } // AppliesTo 检查规则是否适用 func (gr *GrowthRule) AppliesTo(crop *Crop) bool { switch gr.Condition { case "temperature_in_range": return true // 简化实现 case "water_level_high": return crop.GetWaterLevel() > 70.0 case "nutrient_level_low": return crop.GetNutrientLevel() < 30.0 default: return false } } // PestDiseaseRule 病虫害规则 type PestDiseaseRule struct { Name string Description string TriggerConditions map[string]interface{} Probability float64 Severity string AffectedCrops []SeedType } // ShouldTrigger 检查是否应该触发 func (pdr *PestDiseaseRule) ShouldTrigger(crop *Crop, soil *Soil, season Season, climateZone string) bool { // 简化实现 for _, affectedCrop := range pdr.AffectedCrops { if affectedCrop == crop.GetSeedType() { return true } } return false } // CreateEvent 创建事件 func (pdr *PestDiseaseRule) CreateEvent(crop *Crop) *PestDiseaseEvent { return &PestDiseaseEvent{ Name: pdr.Name, Description: pdr.Description, Severity: pdr.Severity, CropID: crop.GetID(), OccurredAt: time.Now(), } } // QualityRule 质量规则 type QualityRule struct { Name string Description string Conditions map[string]interface{} QualityBonus float64 } // AppliesTo 检查规则是否适用 func (qr *QualityRule) AppliesTo(crop *Crop, soil *Soil) bool { // 简化实现 if soilQualityMin, exists := qr.Conditions["soil_quality_min"]; exists { if soil.GetQualityScore() < soilQualityMin.(float64) { return false } } if healthMin, exists := qr.Conditions["health_min"]; exists { if crop.GetHealthScore() < healthMin.(float64) { return false } } return true } // ClimateZone 气候区域 type ClimateZone struct { ZoneID string Name string Description string TemperatureRange TemperatureRange HumidityRange HumidityRange SuitableCrops []SeedType SeasonalModifiers map[Season]float64 WaterRequirementMultiplier float64 } // IsSuitableFor 检查是否适合作物 func (cz *ClimateZone) IsSuitableFor(seedType SeedType) bool { for _, suitableCrop := range cz.SuitableCrops { if suitableCrop == seedType { return true } } return false } // GetWaterRequirementMultiplier 获取水分需求倍率 func (cz *ClimateZone) GetWaterRequirementMultiplier() float64 { return cz.WaterRequirementMultiplier } // TemperatureRange 温度范围 type TemperatureRange struct { Min float64 Max float64 Average float64 } // HumidityRange 湿度范围 type HumidityRange struct { Min float64 Max float64 Average float64 } // PHRange pH范围 type PHRange struct { Min float64 Max float64 } // FertilizerEffect 肥料效果 type FertilizerEffect struct { FertilityBoost float64 NutrientBoost float64 GrowthSpeedBoost float64 Duration time.Duration } // PestDiseaseEvent 病虫害事件 type PestDiseaseEvent struct { Name string Description string Severity string CropID string OccurredAt time.Time } // 错误定义 var ( ErrInvalidCrop = fmt.Errorf("invalid crop") ErrInvalidSoil = fmt.Errorf("invalid soil") ErrClimateNotSuitable = fmt.Errorf("climate not suitable") ErrFertilizerTemplateNotFound = fmt.Errorf("fertilizer template not found") ErrToolTemplateNotFound = fmt.Errorf("tool template not found") ) ================================================ FILE: internal/domain/scene/plant/value_object.go ================================================ package plant import ( "fmt" "time" ) // SeedType 种子类型 type SeedType int const ( SeedTypeWheat SeedType = iota + 1 SeedTypeCorn SeedTypeRice SeedTypeTomato SeedTypePotato SeedTypeCarrot SeedTypeCabbage SeedTypeStrawberry SeedTypeApple SeedTypeOrange ) // String 返回种子类型字符串 func (st SeedType) String() string { switch st { case SeedTypeWheat: return "wheat" case SeedTypeCorn: return "corn" case SeedTypeRice: return "rice" case SeedTypeTomato: return "tomato" case SeedTypePotato: return "potato" case SeedTypeCarrot: return "carrot" case SeedTypeCabbage: return "cabbage" case SeedTypeStrawberry: return "strawberry" case SeedTypeApple: return "apple" case SeedTypeOrange: return "orange" default: return "unknown" } } // GetDescription 获取描述 func (st SeedType) GetDescription() string { switch st { case SeedTypeWheat: return "小麦" case SeedTypeCorn: return "玉米" case SeedTypeRice: return "水稻" case SeedTypeTomato: return "番茄" case SeedTypePotato: return "土豆" case SeedTypeCarrot: return "胡萝卜" case SeedTypeCabbage: return "卷心菜" case SeedTypeStrawberry: return "草莓" case SeedTypeApple: return "苹果" case SeedTypeOrange: return "橙子" default: return "未知种子" } } // IsValid 检查种子类型是否有效 func (st SeedType) IsValid() bool { return st >= SeedTypeWheat && st <= SeedTypeOrange } // GetGrowthDuration 获取生长周期 func (st SeedType) GetGrowthDuration() time.Duration { switch st { case SeedTypeWheat: return 120 * time.Hour // 5天 case SeedTypeCorn: return 168 * time.Hour // 7天 case SeedTypeRice: return 144 * time.Hour // 6天 case SeedTypeTomato: return 96 * time.Hour // 4天 case SeedTypePotato: return 72 * time.Hour // 3天 case SeedTypeCarrot: return 48 * time.Hour // 2天 case SeedTypeCabbage: return 60 * time.Hour // 2.5天 case SeedTypeStrawberry: return 36 * time.Hour // 1.5天 case SeedTypeApple: return 240 * time.Hour // 10天 case SeedTypeOrange: return 216 * time.Hour // 9天 default: return 72 * time.Hour // 默认3天 } } // GetGrowthRate 获取生长速度(每小时进度百分比) func (st SeedType) GetGrowthRate() float64 { duration := st.GetGrowthDuration() return 100.0 / duration.Hours() // 100%进度除以总小时数 } // GetBaseYield 获取基础产量 func (st SeedType) GetBaseYield() int { switch st { case SeedTypeWheat: return 8 case SeedTypeCorn: return 12 case SeedTypeRice: return 10 case SeedTypeTomato: return 6 case SeedTypePotato: return 4 case SeedTypeCarrot: return 3 case SeedTypeCabbage: return 5 case SeedTypeStrawberry: return 2 case SeedTypeApple: return 15 case SeedTypeOrange: return 12 default: return 5 } } // GetBaseValue 获取基础价值 func (st SeedType) GetBaseValue() float64 { switch st { case SeedTypeWheat: return 10.0 case SeedTypeCorn: return 15.0 case SeedTypeRice: return 12.0 case SeedTypeTomato: return 8.0 case SeedTypePotato: return 6.0 case SeedTypeCarrot: return 5.0 case SeedTypeCabbage: return 7.0 case SeedTypeStrawberry: return 4.0 case SeedTypeApple: return 25.0 case SeedTypeOrange: return 20.0 default: return 8.0 } } // GetBaseExperience 获取基础经验 func (st SeedType) GetBaseExperience() int { switch st { case SeedTypeWheat: return 20 case SeedTypeCorn: return 30 case SeedTypeRice: return 25 case SeedTypeTomato: return 15 case SeedTypePotato: return 10 case SeedTypeCarrot: return 8 case SeedTypeCabbage: return 12 case SeedTypeStrawberry: return 6 case SeedTypeApple: return 50 case SeedTypeOrange: return 40 default: return 15 } } // GetWaterConsumption 获取水分消耗(每小时) func (st SeedType) GetWaterConsumption() float64 { switch st { case SeedTypeWheat: return 1.5 case SeedTypeCorn: return 2.0 case SeedTypeRice: return 3.0 // 水稻需要更多水 case SeedTypeTomato: return 2.5 case SeedTypePotato: return 1.8 case SeedTypeCarrot: return 1.2 case SeedTypeCabbage: return 1.6 case SeedTypeStrawberry: return 2.2 case SeedTypeApple: return 1.0 case SeedTypeOrange: return 1.2 default: return 1.5 } } // GetNutrientConsumption 获取营养消耗(每小时) func (st SeedType) GetNutrientConsumption() float64 { switch st { case SeedTypeWheat: return 1.0 case SeedTypeCorn: return 1.5 case SeedTypeRice: return 1.2 case SeedTypeTomato: return 1.8 case SeedTypePotato: return 1.3 case SeedTypeCarrot: return 0.8 case SeedTypeCabbage: return 1.1 case SeedTypeStrawberry: return 1.6 case SeedTypeApple: return 0.8 case SeedTypeOrange: return 0.9 default: return 1.2 } } // GetPreferredSoilType 获取偏好土壤类型 func (st SeedType) GetPreferredSoilType() SoilType { switch st { case SeedTypeWheat: return SoilTypeLoam case SeedTypeCorn: return SoilTypeLoam case SeedTypeRice: return SoilTypeClay // 水稻喜欢粘土 case SeedTypeTomato: return SoilTypeLoam case SeedTypePotato: return SoilTypeSandy case SeedTypeCarrot: return SoilTypeSandy case SeedTypeCabbage: return SoilTypeLoam case SeedTypeStrawberry: return SoilTypeLoam case SeedTypeApple: return SoilTypeLoam case SeedTypeOrange: return SoilTypeLoam default: return SoilTypeLoam } } // GetCategory 获取作物类别 func (st SeedType) GetCategory() CropCategory { switch st { case SeedTypeWheat, SeedTypeCorn, SeedTypeRice: return CropCategoryGrain case SeedTypeTomato, SeedTypeCabbage: return CropCategoryVegetable case SeedTypePotato, SeedTypeCarrot: return CropCategoryRoot case SeedTypeStrawberry: return CropCategoryBerry case SeedTypeApple, SeedTypeOrange: return CropCategoryFruit default: return CropCategoryVegetable } } // GrowthStage 生长阶段 type GrowthStage int const ( GrowthStageSeed GrowthStage = iota + 1 GrowthStageSeedling GrowthStageGrowing GrowthStageFlowering GrowthStageMature ) // String 返回生长阶段字符串 func (gs GrowthStage) String() string { switch gs { case GrowthStageSeed: return "seed" case GrowthStageSeedling: return "seedling" case GrowthStageGrowing: return "growing" case GrowthStageFlowering: return "flowering" case GrowthStageMature: return "mature" default: return "unknown" } } // GetDescription 获取描述 func (gs GrowthStage) GetDescription() string { switch gs { case GrowthStageSeed: return "种子期" case GrowthStageSeedling: return "幼苗期" case GrowthStageGrowing: return "生长期" case GrowthStageFlowering: return "开花期" case GrowthStageMature: return "成熟期" default: return "未知阶段" } } // GetProgressRange 获取进度范围 func (gs GrowthStage) GetProgressRange() (float64, float64) { switch gs { case GrowthStageSeed: return 0.0, 25.0 case GrowthStageSeedling: return 25.0, 50.0 case GrowthStageGrowing: return 50.0, 75.0 case GrowthStageFlowering: return 75.0, 100.0 case GrowthStageMature: return 100.0, 100.0 default: return 0.0, 0.0 } } // SoilType 土壤类型 type SoilType int const ( SoilTypeSandy SoilType = iota + 1 SoilTypeClay SoilTypeLoam SoilTypeSilt SoilTypePeat SoilTypeChalk ) // String 返回土壤类型字符串 func (st SoilType) String() string { switch st { case SoilTypeSandy: return "sandy" case SoilTypeClay: return "clay" case SoilTypeLoam: return "loam" case SoilTypeSilt: return "silt" case SoilTypePeat: return "peat" case SoilTypeChalk: return "chalk" default: return "unknown" } } // GetDescription 获取描述 func (st SoilType) GetDescription() string { switch st { case SoilTypeSandy: return "沙土" case SoilTypeClay: return "粘土" case SoilTypeLoam: return "壤土" case SoilTypeSilt: return "淤泥土" case SoilTypePeat: return "泥炭土" case SoilTypeChalk: return "白垩土" default: return "未知土壤" } } // GetDrainageRate 获取排水率 func (st SoilType) GetDrainageRate() float64 { switch st { case SoilTypeSandy: return 0.9 // 沙土排水快 case SoilTypeClay: return 0.2 // 粘土排水慢 case SoilTypeLoam: return 0.6 // 壤土排水适中 case SoilTypeSilt: return 0.4 case SoilTypePeat: return 0.3 case SoilTypeChalk: return 0.8 default: return 0.5 } } // GetNutrientRetention 获取营养保持率 func (st SoilType) GetNutrientRetention() float64 { switch st { case SoilTypeSandy: return 0.3 // 沙土营养流失快 case SoilTypeClay: return 0.8 // 粘土营养保持好 case SoilTypeLoam: return 0.7 // 壤土营养保持较好 case SoilTypeSilt: return 0.6 case SoilTypePeat: return 0.9 // 泥炭土营养丰富 case SoilTypeChalk: return 0.4 default: return 0.5 } } // GetBaseProductivity 获取基础生产力 func (st SoilType) GetBaseProductivity() float64 { switch st { case SoilTypeSandy: return 0.8 case SoilTypeClay: return 0.9 case SoilTypeLoam: return 1.2 // 壤土最适合种植 case SoilTypeSilt: return 1.0 case SoilTypePeat: return 1.1 case SoilTypeChalk: return 0.7 default: return 1.0 } } // Soil 土壤值对象 type Soil struct { Type SoilType Fertility float64 // 肥力 0-100 PH float64 // 酸碱度 0-14 Moisture float64 // 湿度 0-100 Organic float64 // 有机物含量 0-100 Nitrogen float64 // 氮含量 0-100 Phosphorus float64 // 磷含量 0-100 Potassium float64 // 钾含量 0-100 LastTested time.Time CreatedAt time.Time UpdatedAt time.Time } // NewSoil 创建土壤 func NewSoil(soilType SoilType, fertility, ph, moisture float64) *Soil { now := time.Now() return &Soil{ Type: soilType, Fertility: fertility, PH: ph, Moisture: moisture, Organic: 30.0, // 默认有机物含量 Nitrogen: 40.0, // 默认氮含量 Phosphorus: 35.0, // 默认磷含量 Potassium: 45.0, // 默认钾含量 LastTested: now, CreatedAt: now, UpdatedAt: now, } } // GetType 获取土壤类型 func (s *Soil) GetType() SoilType { return s.Type } // GetFertility 获取肥力 func (s *Soil) GetFertility() float64 { return s.Fertility } // GetPH 获取酸碱度 func (s *Soil) GetPH() float64 { return s.PH } // GetMoisture 获取湿度 func (s *Soil) GetMoisture() float64 { return s.Moisture } // IsSuitableFor 检查是否适合种植指定作物 func (s *Soil) IsSuitableFor(seedType SeedType) bool { preferredSoil := seedType.GetPreferredSoilType() // 完全匹配最好 if s.Type == preferredSoil { return true } // 壤土适合大多数作物 if s.Type == SoilTypeLoam { return true } // 检查土壤条件是否满足最低要求 return s.Fertility >= 30.0 && s.PH >= 5.5 && s.PH <= 8.5 } // GetProductivityMultiplier 获取生产力倍率 func (s *Soil) GetProductivityMultiplier() float64 { baseProductivity := s.Type.GetBaseProductivity() // 肥力影响 fertilityMultiplier := 0.5 + (s.Fertility/100.0)*0.8 // 0.5-1.3倍率 // pH影响(6.0-7.5为最佳范围) phMultiplier := 1.0 if s.PH < 5.0 || s.PH > 9.0 { phMultiplier = 0.6 // 极端pH值 } else if s.PH < 6.0 || s.PH > 8.0 { phMultiplier = 0.8 // 偏酸或偏碱 } else { phMultiplier = 1.2 // 最佳pH范围 } // 有机物影响 organicMultiplier := 0.8 + (s.Organic/100.0)*0.4 // 0.8-1.2倍率 return baseProductivity * fertilityMultiplier * phMultiplier * organicMultiplier } // GetGrowthMultiplier 获取生长倍率 func (s *Soil) GetGrowthMultiplier(seedType SeedType) float64 { baseMultiplier := s.GetProductivityMultiplier() // 土壤类型匹配度 preferredSoil := seedType.GetPreferredSoilType() typeMultiplier := 1.0 if s.Type == preferredSoil { typeMultiplier = 1.2 // 完全匹配 } else if s.Type == SoilTypeLoam { typeMultiplier = 1.1 // 壤土通用性好 } else { typeMultiplier = 0.9 // 不匹配 } return baseMultiplier * typeMultiplier } // GetYieldMultiplier 获取产量倍率 func (s *Soil) GetYieldMultiplier(seedType SeedType) float64 { // 基础倍率 baseMultiplier := s.GetProductivityMultiplier() // 营养元素影响 nutrientMultiplier := (s.Nitrogen + s.Phosphorus + s.Potassium) / 300.0 // 平均值 if nutrientMultiplier > 1.0 { nutrientMultiplier = 1.0 } nutrientMultiplier = 0.7 + nutrientMultiplier*0.6 // 0.7-1.3倍率 return baseMultiplier * nutrientMultiplier } // GetQualityScore 获取质量分数 func (s *Soil) GetQualityScore() float64 { score := 0.0 // 肥力贡献(30%) score += s.Fertility * 0.3 // pH贡献(20%) if s.PH >= 6.0 && s.PH <= 7.5 { score += 20.0 // 最佳pH } else if s.PH >= 5.5 && s.PH <= 8.0 { score += 15.0 // 良好pH } else { score += 10.0 // 一般pH } // 有机物贡献(25%) score += s.Organic * 0.25 // 营养元素贡献(25%) averageNutrient := (s.Nitrogen + s.Phosphorus + s.Potassium) / 3.0 score += averageNutrient * 0.25 return score } // GetValue 获取土壤价值 func (s *Soil) GetValue() float64 { baseValue := s.Type.GetBaseProductivity() * 100.0 qualityMultiplier := s.GetQualityScore() / 100.0 return baseValue * qualityMultiplier } // ApplyFertilizer 应用肥料 func (s *Soil) ApplyFertilizer(fertilizer *Fertilizer) { s.Fertility += fertilizer.GetFertilityBoost() s.Nitrogen += fertilizer.GetNitrogenContent() s.Phosphorus += fertilizer.GetPhosphorusContent() s.Potassium += fertilizer.GetPotassiumContent() s.Organic += fertilizer.GetOrganicContent() // 限制数值范围 s.limitValues() s.UpdatedAt = time.Now() } // AddMoisture 增加湿度 func (s *Soil) AddMoisture(amount float64) { s.Moisture += amount if s.Moisture > 100.0 { s.Moisture = 100.0 } s.UpdatedAt = time.Now() } // ApplyToCrop 应用到作物 func (s *Soil) ApplyToCrop(crop *Crop) { // 土壤会影响作物的营养水平 if s.GetQualityScore() > 80.0 { crop.NutrientLevel += 1.0 // 高质量土壤缓慢提升营养 } else if s.GetQualityScore() < 40.0 { crop.NutrientLevel -= 0.5 // 低质量土壤降低营养 } // 限制营养水平范围 if crop.NutrientLevel > 100.0 { crop.NutrientLevel = 100.0 } else if crop.NutrientLevel < 0.0 { crop.NutrientLevel = 0.0 } } // ApplyImprovement 应用改良 func (s *Soil) ApplyImprovement(value float64) { s.Fertility += value s.limitValues() s.UpdatedAt = time.Now() } // limitValues 限制数值范围 func (s *Soil) limitValues() { if s.Fertility > 100.0 { s.Fertility = 100.0 } if s.Nitrogen > 100.0 { s.Nitrogen = 100.0 } if s.Phosphorus > 100.0 { s.Phosphorus = 100.0 } if s.Potassium > 100.0 { s.Potassium = 100.0 } if s.Organic > 100.0 { s.Organic = 100.0 } } // Fertilizer 肥料值对象 type Fertilizer struct { Type FertilizerType Amount float64 NutrientValue float64 FertilityBoost float64 NitrogenContent float64 PhosphorusContent float64 PotassiumContent float64 OrganicContent float64 GrowthBonus *GrowthBonus } // NewFertilizer 创建肥料 func NewFertilizer(fertilizerType FertilizerType, amount float64) *Fertilizer { return &Fertilizer{ Type: fertilizerType, Amount: amount, NutrientValue: fertilizerType.GetNutrientValue() * amount, FertilityBoost: fertilizerType.GetFertilityBoost() * amount, NitrogenContent: fertilizerType.GetNitrogenContent() * amount, PhosphorusContent: fertilizerType.GetPhosphorusContent() * amount, PotassiumContent: fertilizerType.GetPotassiumContent() * amount, OrganicContent: fertilizerType.GetOrganicContent() * amount, GrowthBonus: fertilizerType.GetGrowthBonus(), } } // GetType 获取类型 func (f *Fertilizer) GetType() FertilizerType { return f.Type } // GetAmount 获取数量 func (f *Fertilizer) GetAmount() float64 { return f.Amount } // GetNutrientValue 获取营养价值 func (f *Fertilizer) GetNutrientValue() float64 { return f.NutrientValue } // GetFertilityBoost 获取肥力提升 func (f *Fertilizer) GetFertilityBoost() float64 { return f.FertilityBoost } // GetNitrogenContent 获取氮含量 func (f *Fertilizer) GetNitrogenContent() float64 { return f.NitrogenContent } // GetPhosphorusContent 获取磷含量 func (f *Fertilizer) GetPhosphorusContent() float64 { return f.PhosphorusContent } // GetPotassiumContent 获取钾含量 func (f *Fertilizer) GetPotassiumContent() float64 { return f.PotassiumContent } // GetOrganicContent 获取有机物含量 func (f *Fertilizer) GetOrganicContent() float64 { return f.OrganicContent } // GetGrowthBonus 获取生长奖励 func (f *Fertilizer) GetGrowthBonus() *GrowthBonus { return f.GrowthBonus } // FertilizerType 肥料类型 type FertilizerType int const ( FertilizerTypeOrganic FertilizerType = iota + 1 FertilizerTypeChemical FertilizerTypeCompost FertilizerTypeManure FertilizerTypeLiquid FertilizerTypeGranular ) // String 返回肥料类型字符串 func (ft FertilizerType) String() string { switch ft { case FertilizerTypeOrganic: return "organic" case FertilizerTypeChemical: return "chemical" case FertilizerTypeCompost: return "compost" case FertilizerTypeManure: return "manure" case FertilizerTypeLiquid: return "liquid" case FertilizerTypeGranular: return "granular" default: return "unknown" } } // GetDescription 获取描述 func (ft FertilizerType) GetDescription() string { switch ft { case FertilizerTypeOrganic: return "有机肥" case FertilizerTypeChemical: return "化学肥料" case FertilizerTypeCompost: return "堆肥" case FertilizerTypeManure: return "粪肥" case FertilizerTypeLiquid: return "液体肥料" case FertilizerTypeGranular: return "颗粒肥料" default: return "未知肥料" } } // GetNutrientValue 获取营养价值 func (ft FertilizerType) GetNutrientValue() float64 { switch ft { case FertilizerTypeOrganic: return 15.0 case FertilizerTypeChemical: return 25.0 case FertilizerTypeCompost: return 12.0 case FertilizerTypeManure: return 18.0 case FertilizerTypeLiquid: return 20.0 case FertilizerTypeGranular: return 22.0 default: return 15.0 } } // GetFertilityBoost 获取肥力提升 func (ft FertilizerType) GetFertilityBoost() float64 { switch ft { case FertilizerTypeOrganic: return 10.0 case FertilizerTypeChemical: return 15.0 case FertilizerTypeCompost: return 8.0 case FertilizerTypeManure: return 12.0 case FertilizerTypeLiquid: return 13.0 case FertilizerTypeGranular: return 14.0 default: return 10.0 } } // GetNitrogenContent 获取氮含量 func (ft FertilizerType) GetNitrogenContent() float64 { switch ft { case FertilizerTypeOrganic: return 8.0 case FertilizerTypeChemical: return 15.0 case FertilizerTypeCompost: return 6.0 case FertilizerTypeManure: return 10.0 case FertilizerTypeLiquid: return 12.0 case FertilizerTypeGranular: return 13.0 default: return 8.0 } } // GetPhosphorusContent 获取磷含量 func (ft FertilizerType) GetPhosphorusContent() float64 { switch ft { case FertilizerTypeOrganic: return 5.0 case FertilizerTypeChemical: return 10.0 case FertilizerTypeCompost: return 4.0 case FertilizerTypeManure: return 6.0 case FertilizerTypeLiquid: return 8.0 case FertilizerTypeGranular: return 9.0 default: return 5.0 } } // GetPotassiumContent 获取钾含量 func (ft FertilizerType) GetPotassiumContent() float64 { switch ft { case FertilizerTypeOrganic: return 7.0 case FertilizerTypeChemical: return 12.0 case FertilizerTypeCompost: return 5.0 case FertilizerTypeManure: return 8.0 case FertilizerTypeLiquid: return 10.0 case FertilizerTypeGranular: return 11.0 default: return 7.0 } } // GetOrganicContent 获取有机物含量 func (ft FertilizerType) GetOrganicContent() float64 { switch ft { case FertilizerTypeOrganic: return 20.0 case FertilizerTypeChemical: return 2.0 case FertilizerTypeCompost: return 25.0 case FertilizerTypeManure: return 18.0 case FertilizerTypeLiquid: return 5.0 case FertilizerTypeGranular: return 3.0 default: return 10.0 } } // GetGrowthBonus 获取生长奖励 func (ft FertilizerType) GetGrowthBonus() *GrowthBonus { switch ft { case FertilizerTypeChemical: return &GrowthBonus{ ID: fmt.Sprintf("chemical_boost_%d", time.Now().UnixNano()), Type: "growth_speed", Multiplier: 1.3, Duration: 48 * time.Hour, StartTime: time.Now(), } case FertilizerTypeLiquid: return &GrowthBonus{ ID: fmt.Sprintf("liquid_boost_%d", time.Now().UnixNano()), Type: "nutrient_absorption", Multiplier: 1.2, Duration: 24 * time.Hour, StartTime: time.Now(), } default: return nil } } // 其他值对象 // FarmSize 农场大小 type FarmSize int const ( FarmSizeSmall FarmSize = iota + 1 FarmSizeMedium FarmSizeLarge FarmSizeHuge ) // String 返回农场大小字符串 func (fs FarmSize) String() string { switch fs { case FarmSizeSmall: return "small" case FarmSizeMedium: return "medium" case FarmSizeLarge: return "large" case FarmSizeHuge: return "huge" default: return "unknown" } } // GetMaxPlots 获取最大地块数 func (fs FarmSize) GetMaxPlots() int { switch fs { case FarmSizeSmall: return 4 case FarmSizeMedium: return 9 case FarmSizeLarge: return 16 case FarmSizeHuge: return 25 default: return 4 } } // GetBaseValue 获取基础价值 func (fs FarmSize) GetBaseValue() float64 { switch fs { case FarmSizeSmall: return 1000.0 case FarmSizeMedium: return 2500.0 case FarmSizeLarge: return 5000.0 case FarmSizeHuge: return 10000.0 default: return 1000.0 } } // GetExpansionCost 获取扩展成本 func (fs FarmSize) GetExpansionCost(currentSize FarmSize) *ExpansionCost { if fs <= currentSize { return nil } baseCost := fs.GetBaseValue() - currentSize.GetBaseValue() return &ExpansionCost{ Gold: baseCost, Materials: int(baseCost / 100), Time: time.Duration(int(baseCost/500)) * time.Hour, } } // PlotSize 地块大小 type PlotSize int const ( PlotSizeSmall PlotSize = iota + 1 PlotSizeMedium PlotSizeLarge ) // String 返回地块大小字符串 func (ps PlotSize) String() string { switch ps { case PlotSizeSmall: return "small" case PlotSizeMedium: return "medium" case PlotSizeLarge: return "large" default: return "unknown" } } // GetCapacity 获取容量 func (ps PlotSize) GetCapacity() int { switch ps { case PlotSizeSmall: return 1 case PlotSizeMedium: return 4 case PlotSizeLarge: return 9 default: return 1 } } // ToolType 工具类型 type ToolType int const ( ToolTypeHoe ToolType = iota + 1 ToolTypeWateringCan ToolTypeFertilizerSpreader ToolTypeHarvester ToolTypePesticide ToolTypeTractor ) // String 返回工具类型字符串 func (tt ToolType) String() string { switch tt { case ToolTypeHoe: return "hoe" case ToolTypeWateringCan: return "watering_can" case ToolTypeFertilizerSpreader: return "fertilizer_spreader" case ToolTypeHarvester: return "harvester" case ToolTypePesticide: return "pesticide" case ToolTypeTractor: return "tractor" default: return "unknown" } } // GetDescription 获取描述 func (tt ToolType) GetDescription() string { switch tt { case ToolTypeHoe: return "锄头" case ToolTypeWateringCan: return "洒水壶" case ToolTypeFertilizerSpreader: return "施肥器" case ToolTypeHarvester: return "收割机" case ToolTypePesticide: return "杀虫剂" case ToolTypeTractor: return "拖拉机" default: return "未知工具" } } // GetBaseValue 获取基础价值 func (tt ToolType) GetBaseValue() float64 { switch tt { case ToolTypeHoe: return 50.0 case ToolTypeWateringCan: return 30.0 case ToolTypeFertilizerSpreader: return 80.0 case ToolTypeHarvester: return 200.0 case ToolTypePesticide: return 40.0 case ToolTypeTractor: return 500.0 default: return 50.0 } } // GetEffect 获取效果 func (tt ToolType) GetEffect(level int, efficiency float64) *ToolEffect { baseValue := float64(level) * efficiency switch tt { case ToolTypeHoe: return &ToolEffect{Type: "soil_improvement", Value: baseValue * 2.0} case ToolTypeWateringCan: return &ToolEffect{Type: "watering_efficiency", Value: baseValue * 1.5} case ToolTypeFertilizerSpreader: return &ToolEffect{Type: "fertilizer_efficiency", Value: baseValue * 1.8} case ToolTypeHarvester: return &ToolEffect{Type: "harvest_speed", Value: baseValue * 2.5} case ToolTypePesticide: return &ToolEffect{Type: "pest_control", Value: baseValue * 3.0} case ToolTypeTractor: return &ToolEffect{Type: "overall_efficiency", Value: baseValue * 1.2} default: return &ToolEffect{Type: "unknown", Value: baseValue} } } // CropQuality 作物品质 type CropQuality int const ( CropQualityCommon CropQuality = iota + 1 CropQualityUncommon CropQualityRare CropQualityEpic CropQualityLegendary ) // String 返回品质字符串 func (cq CropQuality) String() string { switch cq { case CropQualityCommon: return "common" case CropQualityUncommon: return "uncommon" case CropQualityRare: return "rare" case CropQualityEpic: return "epic" case CropQualityLegendary: return "legendary" default: return "unknown" } } // GetDescription 获取描述 func (cq CropQuality) GetDescription() string { switch cq { case CropQualityCommon: return "普通" case CropQualityUncommon: return "优良" case CropQualityRare: return "稀有" case CropQualityEpic: return "史诗" case CropQualityLegendary: return "传说" default: return "未知品质" } } // GetValueMultiplier 获取价值倍率 func (cq CropQuality) GetValueMultiplier() float64 { switch cq { case CropQualityCommon: return 1.0 case CropQualityUncommon: return 1.5 case CropQualityRare: return 2.0 case CropQualityEpic: return 3.0 case CropQualityLegendary: return 5.0 default: return 1.0 } } // GetExperienceMultiplier 获取经验倍率 func (cq CropQuality) GetExperienceMultiplier() float64 { switch cq { case CropQualityCommon: return 0.0 case CropQualityUncommon: return 0.2 case CropQualityRare: return 0.5 case CropQualityEpic: return 1.0 case CropQualityLegendary: return 2.0 default: return 0.0 } } // CropCategory 作物类别 type CropCategory int const ( CropCategoryGrain CropCategory = iota + 1 CropCategoryVegetable CropCategoryFruit CropCategoryRoot CropCategoryBerry CropCategoryHerb ) // String 返回类别字符串 func (cc CropCategory) String() string { switch cc { case CropCategoryGrain: return "grain" case CropCategoryVegetable: return "vegetable" case CropCategoryFruit: return "fruit" case CropCategoryRoot: return "root" case CropCategoryBerry: return "berry" case CropCategoryHerb: return "herb" default: return "unknown" } } // GetDescription 获取描述 func (cc CropCategory) GetDescription() string { switch cc { case CropCategoryGrain: return "谷物" case CropCategoryVegetable: return "蔬菜" case CropCategoryFruit: return "水果" case CropCategoryRoot: return "根茎类" case CropCategoryBerry: return "浆果" case CropCategoryHerb: return "草药" default: return "未知类别" } } // 辅助结构体 // ExpansionCost 扩展成本 type ExpansionCost struct { Gold float64 Materials int Time time.Duration } // FarmResources 农场资源 type FarmResources struct { Gold float64 Seeds map[SeedType]int Fertilizer map[FertilizerType]float64 Water float64 Harvest map[SeedType]map[CropQuality]int UpdatedAt time.Time } // NewFarmResources 创建农场资源 func NewFarmResources() *FarmResources { return &FarmResources{ Gold: 1000.0, // 初始金币 Seeds: make(map[SeedType]int), Fertilizer: make(map[FertilizerType]float64), Water: 100.0, // 初始水量 Harvest: make(map[SeedType]map[CropQuality]int), UpdatedAt: time.Now(), } } // HasEnoughSeeds 检查种子是否足够 func (fr *FarmResources) HasEnoughSeeds(seedType SeedType, quantity int) bool { return fr.Seeds[seedType] >= quantity } // ConsumeSeeds 消耗种子 func (fr *FarmResources) ConsumeSeeds(seedType SeedType, quantity int) { fr.Seeds[seedType] -= quantity if fr.Seeds[seedType] < 0 { fr.Seeds[seedType] = 0 } fr.UpdatedAt = time.Now() } // HasEnoughFertilizer 检查肥料是否足够 func (fr *FarmResources) HasEnoughFertilizer(fertilizerType FertilizerType, amount float64) bool { return fr.Fertilizer[fertilizerType] >= amount } // ConsumeFertilizer 消耗肥料 func (fr *FarmResources) ConsumeFertilizer(fertilizerType FertilizerType, amount float64) { fr.Fertilizer[fertilizerType] -= amount if fr.Fertilizer[fertilizerType] < 0 { fr.Fertilizer[fertilizerType] = 0 } fr.UpdatedAt = time.Now() } // HasEnoughWater 检查水是否足够 func (fr *FarmResources) HasEnoughWater(amount float64) bool { return fr.Water >= amount } // ConsumeWater 消耗水 func (fr *FarmResources) ConsumeWater(amount float64) { fr.Water -= amount if fr.Water < 0 { fr.Water = 0 } fr.UpdatedAt = time.Now() } // AddHarvest 添加收获物 func (fr *FarmResources) AddHarvest(seedType SeedType, quantity int, quality CropQuality) { if fr.Harvest[seedType] == nil { fr.Harvest[seedType] = make(map[CropQuality]int) } fr.Harvest[seedType][quality] += quantity fr.UpdatedAt = time.Now() } // CanAfford 检查是否能承担成本 func (fr *FarmResources) CanAfford(cost *ExpansionCost) bool { return fr.Gold >= cost.Gold } // GetTotalValue 获取总价值 func (fr *FarmResources) GetTotalValue() float64 { totalValue := fr.Gold // 种子价值 for seedType, quantity := range fr.Seeds { totalValue += seedType.GetBaseValue() * float64(quantity) * 0.5 // 种子价值为作物价值的一半 } // 收获物价值 for seedType, qualityMap := range fr.Harvest { for quality, quantity := range qualityMap { baseValue := seedType.GetBaseValue() qualityMultiplier := quality.GetValueMultiplier() totalValue += baseValue * qualityMultiplier * float64(quantity) } } return totalValue } // FarmStatistics 农场统计 type FarmStatistics struct { TotalPlantings int TotalHarvests int TotalYield int TotalExperience int PlantingsByType map[SeedType]int HarvestsByType map[SeedType]int FertilizerUsage map[FertilizerType]float64 WateringCount int ToolUsage map[ToolType]int CreatedAt time.Time UpdatedAt time.Time } // NewFarmStatistics 创建农场统计 func NewFarmStatistics() *FarmStatistics { now := time.Now() return &FarmStatistics{ PlantingsByType: make(map[SeedType]int), HarvestsByType: make(map[SeedType]int), FertilizerUsage: make(map[FertilizerType]float64), ToolUsage: make(map[ToolType]int), CreatedAt: now, UpdatedAt: now, } } // AddPlantingActivity 添加种植活动 func (fs *FarmStatistics) AddPlantingActivity(seedType SeedType, quantity int) { fs.TotalPlantings += quantity fs.PlantingsByType[seedType] += quantity fs.UpdatedAt = time.Now() } // AddHarvestActivity 添加收获活动 func (fs *FarmStatistics) AddHarvestActivity(seedType SeedType, yield int, quality CropQuality) { fs.TotalHarvests++ fs.TotalYield += yield fs.HarvestsByType[seedType] += yield fs.UpdatedAt = time.Now() } // AddFertilizerUsage 添加肥料使用 func (fs *FarmStatistics) AddFertilizerUsage(fertilizerType FertilizerType, amount float64) { fs.FertilizerUsage[fertilizerType] += amount fs.UpdatedAt = time.Now() } // AddWateringActivity 添加浇水活动 func (fs *FarmStatistics) AddWateringActivity(plotCount int, waterAmount float64) { fs.WateringCount += plotCount fs.UpdatedAt = time.Now() } // AddToolUsage 添加工具使用 func (fs *FarmStatistics) AddToolUsage(toolType ToolType) { fs.ToolUsage[toolType]++ fs.UpdatedAt = time.Now() } // SeasonModifier 季节修正 type SeasonModifier struct { CurrentSeason Season GrowthMultiplier float64 YieldMultiplier float64 QualityMultiplier float64 WaterConsumption float64 NutrientConsumption float64 SeasonEffects map[SeedType]float64 CreatedAt time.Time UpdatedAt time.Time } // NewSeasonModifier 创建季节修正 func NewSeasonModifier() *SeasonModifier { now := time.Now() currentSeason := getCurrentSeason(now) return &SeasonModifier{ CurrentSeason: currentSeason, GrowthMultiplier: currentSeason.GetGrowthMultiplier(), YieldMultiplier: currentSeason.GetYieldMultiplier(), QualityMultiplier: currentSeason.GetQualityMultiplier(), WaterConsumption: currentSeason.GetWaterConsumptionMultiplier(), NutrientConsumption: currentSeason.GetNutrientConsumptionMultiplier(), SeasonEffects: make(map[SeedType]float64), CreatedAt: now, UpdatedAt: now, } } // ApplyToCrop 应用到作物 func (sm *SeasonModifier) ApplyToCrop(crop *Crop) { // 应用季节效果到作物 if effect, exists := sm.SeasonEffects[crop.SeedType]; exists { bonus := &GrowthBonus{ ID: fmt.Sprintf("season_%s_%d", sm.CurrentSeason.String(), time.Now().UnixNano()), Type: "seasonal_effect", Multiplier: effect, Duration: 24 * time.Hour, StartTime: time.Now(), } crop.AddBonus(bonus) } } // GetProductivityMultiplier 获取生产力倍率 func (sm *SeasonModifier) GetProductivityMultiplier() float64 { return sm.GrowthMultiplier * sm.YieldMultiplier } // GetYieldMultiplier 获取产量倍率 func (sm *SeasonModifier) GetYieldMultiplier(seedType SeedType) float64 { baseMultiplier := sm.YieldMultiplier if effect, exists := sm.SeasonEffects[seedType]; exists { return baseMultiplier * effect } return baseMultiplier } // Season 季节 type Season int const ( SeasonSpring Season = iota + 1 SeasonSummer SeasonAutumn SeasonWinter ) // String 返回季节字符串 func (s Season) String() string { switch s { case SeasonSpring: return "spring" case SeasonSummer: return "summer" case SeasonAutumn: return "autumn" case SeasonWinter: return "winter" default: return "unknown" } } // GetGrowthMultiplier 获取生长倍率 func (s Season) GetGrowthMultiplier() float64 { switch s { case SeasonSpring: return 1.3 // 春季生长最快 case SeasonSummer: return 1.1 case SeasonAutumn: return 0.9 case SeasonWinter: return 0.6 // 冬季生长最慢 default: return 1.0 } } // GetYieldMultiplier 获取产量倍率 func (s Season) GetYieldMultiplier() float64 { switch s { case SeasonSpring: return 1.2 case SeasonSummer: return 1.3 // 夏季产量最高 case SeasonAutumn: return 1.1 case SeasonWinter: return 0.7 default: return 1.0 } } // GetQualityMultiplier 获取品质倍率 func (s Season) GetQualityMultiplier() float64 { switch s { case SeasonSpring: return 1.1 case SeasonSummer: return 1.0 case SeasonAutumn: return 1.2 // 秋季品质最好 case SeasonWinter: return 0.8 default: return 1.0 } } // GetWaterConsumptionMultiplier 获取水分消耗倍率 func (s Season) GetWaterConsumptionMultiplier() float64 { switch s { case SeasonSpring: return 1.0 case SeasonSummer: return 1.5 // 夏季需要更多水分 case SeasonAutumn: return 0.8 case SeasonWinter: return 0.6 default: return 1.0 } } // GetNutrientConsumptionMultiplier 获取营养消耗倍率 func (s Season) GetNutrientConsumptionMultiplier() float64 { switch s { case SeasonSpring: return 1.2 // 春季生长需要更多营养 case SeasonSummer: return 1.1 case SeasonAutumn: return 0.9 case SeasonWinter: return 0.7 default: return 1.0 } } // AutomationSettings 自动化设置 type AutomationSettings struct { AutoWatering bool AutoFertilizing bool AutoHarvesting bool AutoPestControl bool WaterThreshold float64 NutrientThreshold float64 HarvestDelay time.Duration CreatedAt time.Time UpdatedAt time.Time } // NewAutomationSettings 创建自动化设置 func NewAutomationSettings() *AutomationSettings { now := time.Now() return &AutomationSettings{ AutoWatering: false, AutoFertilizing: false, AutoHarvesting: false, AutoPestControl: false, WaterThreshold: 30.0, NutrientThreshold: 30.0, HarvestDelay: 1 * time.Hour, CreatedAt: now, UpdatedAt: now, } } // 辅助函数 // getCurrentSeason 获取当前季节 func getCurrentSeason(currentTime time.Time) Season { month := currentTime.Month() switch { case month >= 3 && month <= 5: return SeasonSpring case month >= 6 && month <= 8: return SeasonSummer case month >= 9 && month <= 11: return SeasonAutumn default: return SeasonWinter } } ================================================ FILE: internal/domain/scene/repository.go ================================================ package scene import ( "context" "time" ) // Repository 场景仓储接口 type Repository interface { // 基础CRUD操作 Save(ctx context.Context, scene *Scene) error FindByID(ctx context.Context, sceneID string) (*Scene, error) Delete(ctx context.Context, sceneID string) error Exists(ctx context.Context, sceneID string) (bool, error) // 批量操作 SaveBatch(ctx context.Context, scenes []*Scene) error FindByIDs(ctx context.Context, sceneIDs []string) ([]*Scene, error) FindAll(ctx context.Context) ([]*Scene, error) // 场景查询 FindByType(ctx context.Context, sceneType SceneType) ([]*Scene, error) FindByStatus(ctx context.Context, status SceneStatus) ([]*Scene, error) FindAvailableScenes(ctx context.Context) ([]*Scene, error) FindScenesWithSpace(ctx context.Context, minSpace int) ([]*Scene, error) // 实体管理 SaveEntity(ctx context.Context, sceneID string, entity Entity) error RemoveEntity(ctx context.Context, sceneID string, entityID string) error FindEntitiesByType(ctx context.Context, sceneID string, entityType EntityType) ([]Entity, error) FindEntitiesInRadius(ctx context.Context, sceneID string, center *Position, radius float64) ([]Entity, error) // 玩家管理 AddPlayerToScene(ctx context.Context, sceneID string, playerID string) error RemovePlayerFromScene(ctx context.Context, sceneID string, playerID string) error FindPlayerScene(ctx context.Context, playerID string) (*Scene, error) GetScenePlayerCount(ctx context.Context, sceneID string) (int, error) GetScenePlayers(ctx context.Context, sceneID string) ([]string, error) // 统计查询 GetSceneStats(ctx context.Context, sceneID string) (*SceneStats, error) GetSceneHistory(ctx context.Context, sceneID string, limit int) ([]*SceneHistoryRecord, error) GetPopularScenes(ctx context.Context, limit int) ([]*ScenePopularity, error) // 配置管理 GetSceneConfig(ctx context.Context, sceneID string) (*SceneConfig, error) SaveSceneConfig(ctx context.Context, config *SceneConfig) error GetAllSceneConfigs(ctx context.Context) ([]*SceneConfig, error) } // SceneStats 场景统计信息 type SceneStats struct { SceneID string `json:"scene_id"` SceneName string `json:"scene_name"` SceneType SceneType `json:"scene_type"` CurrentPlayers int `json:"current_players"` MaxPlayers int `json:"max_players"` TotalEntities int `json:"total_entities"` EntitiesByType map[EntityType]int `json:"entities_by_type"` ActiveMonsters int `json:"active_monsters"` ActiveNPCs int `json:"active_npcs"` DroppedItems int `json:"dropped_items"` AveragePlayerLevel int `json:"average_player_level"` PeakPlayerCount int `json:"peak_player_count"` LastUpdate time.Time `json:"last_update"` Uptime time.Duration `json:"uptime"` } // SceneHistoryRecord 场景历史记录 type SceneHistoryRecord struct { ID string `json:"id"` SceneID string `json:"scene_id"` EventType string `json:"event_type"` // player_entered, player_left, monster_spawned, etc. EntityID string `json:"entity_id"` EntityType EntityType `json:"entity_type"` Position *Position `json:"position,omitempty"` Details map[string]interface{} `json:"details,omitempty"` OccurredAt time.Time `json:"occurred_at"` } // ScenePopularity 场景热度 type ScenePopularity struct { SceneID string `json:"scene_id"` SceneName string `json:"scene_name"` SceneType SceneType `json:"scene_type"` PlayerCount int `json:"current_player_count"` PeakCount int `json:"peak_player_count"` TotalVisits int64 `json:"total_visits"` AverageStay time.Duration `json:"average_stay_duration"` PopularityScore float64 `json:"popularity_score"` LastActive time.Time `json:"last_active"` } // SceneConfig 场景配置 type SceneConfig struct { ID string `json:"id"` Name string `json:"name"` SceneType SceneType `json:"scene_type"` Width float64 `json:"width"` Height float64 `json:"height"` MaxPlayers int `json:"max_players"` MinLevel int `json:"min_level"` MaxLevel int `json:"max_level"` PvPEnabled bool `json:"pvp_enabled"` RespawnEnabled bool `json:"respawn_enabled"` DropEnabled bool `json:"drop_enabled"` SpawnPoints []*SpawnPointConfig `json:"spawn_points"` Portals []*PortalConfig `json:"portals"` NPCs []*NPCConfig `json:"npcs"` Environment *EnvironmentConfig `json:"environment"` Restrictions *SceneRestrictions `json:"restrictions"` Enabled bool `json:"enabled"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // SpawnPointConfig 刷新点配置 type SpawnPointConfig struct { ID string `json:"id"` Position *Position `json:"position"` SpawnType SpawnType `json:"spawn_type"` TargetID string `json:"target_id"` Interval time.Duration `json:"interval"` MaxCount int `json:"max_count"` Active bool `json:"active"` Conditions map[string]interface{} `json:"conditions,omitempty"` } // PortalConfig 传送门配置 type PortalConfig struct { ID string `json:"id"` Name string `json:"name"` Position *Position `json:"position"` TargetSceneID string `json:"target_scene_id"` TargetPosition *Position `json:"target_position"` RequiredLevel int `json:"required_level"` RequiredItems []string `json:"required_items"` Cost int64 `json:"cost"` Active bool `json:"active"` Bidirectional bool `json:"bidirectional"` } // NPCConfig NPC配置 type NPCConfig struct { ID string `json:"id"` Name string `json:"name"` NPCType NPCType `json:"npc_type"` Position *Position `json:"position"` Level int `json:"level"` Health int64 `json:"health"` AI *AIConfig `json:"ai"` Dialogues []string `json:"dialogues"` Shop *ShopConfig `json:"shop,omitempty"` Quests []string `json:"quests"` Active bool `json:"active"` Respawn bool `json:"respawn"` } // AIConfig AI配置 type AIConfig struct { BehaviorType BehaviorType `json:"behavior_type"` PatrolPath []*Position `json:"patrol_path"` AggroRange float64 `json:"aggro_range"` ChaseRange float64 `json:"chase_range"` ReturnRange float64 `json:"return_range"` AttackRange float64 `json:"attack_range"` MoveSpeed float64 `json:"move_speed"` AttackSpeed float64 `json:"attack_speed"` } // ShopConfig 商店配置 type ShopConfig struct { Items []string `json:"items"` RefreshRate time.Duration `json:"refresh_rate"` DiscountRate float64 `json:"discount_rate"` } // EnvironmentConfig 环境配置 type EnvironmentConfig struct { Weather string `json:"weather"` TimeOfDay string `json:"time_of_day"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` WindSpeed float64 `json:"wind_speed"` Visibility float64 `json:"visibility"` AmbientSound string `json:"ambient_sound"` BackgroundMusic string `json:"background_music"` } // SceneRestrictions 场景限制 type SceneRestrictions struct { ClassRestrictions []string `json:"class_restrictions"` RaceRestrictions []string `json:"race_restrictions"` GuildOnly bool `json:"guild_only"` PartyOnly bool `json:"party_only"` VIPOnly bool `json:"vip_only"` TimeRestrictions *TimeRestriction `json:"time_restrictions"` } // TimeRestriction 时间限制 type TimeRestriction struct { StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` DaysOfWeek []int `json:"days_of_week"` // 0=Sunday, 1=Monday, etc. } // SceneQueryFilter 场景查询过滤器 type SceneQueryFilter struct { SceneTypes []SceneType `json:"scene_types,omitempty"` Statuses []SceneStatus `json:"statuses,omitempty"` MinPlayers *int `json:"min_players,omitempty"` MaxPlayers *int `json:"max_players,omitempty"` MinLevel *int `json:"min_level,omitempty"` MaxLevel *int `json:"max_level,omitempty"` PvPEnabled *bool `json:"pvp_enabled,omitempty"` HasSpace bool `json:"has_space"` ActiveOnly bool `json:"active_only"` SortBy string `json:"sort_by"` // player_count, popularity, name SortOrder string `json:"sort_order"` // asc, desc Limit int `json:"limit"` Offset int `json:"offset"` } // EntityQueryFilter 实体查询过滤器 type EntityQueryFilter struct { SceneID string `json:"scene_id"` EntityTypes []EntityType `json:"entity_types,omitempty"` ActiveOnly bool `json:"active_only"` Center *Position `json:"center,omitempty"` Radius *float64 `json:"radius,omitempty"` MinLevel *int `json:"min_level,omitempty"` MaxLevel *int `json:"max_level,omitempty"` Limit int `json:"limit"` Offset int `json:"offset"` } ================================================ FILE: internal/domain/scene/sacred/aggregate.go ================================================ package sacred import ( "errors" "time" ) // SacredPlaceAggregate 圣地聚合根 type SacredPlaceAggregate struct { id string name string description string level *SacredLevel challenges map[string]*Challenge blessings map[string]*Blessing status SacredStatus owner string createdAt time.Time updatedAt time.Time version int events []DomainEvent } // NewSacredPlaceAggregate 创建圣地聚合根 func NewSacredPlaceAggregate(id, name, description, owner string) *SacredPlaceAggregate { now := time.Now() return &SacredPlaceAggregate{ id: id, name: name, description: description, level: NewSacredLevel(1, 0), challenges: make(map[string]*Challenge), blessings: make(map[string]*Blessing), status: SacredStatusActive, owner: owner, createdAt: now, updatedAt: now, version: 1, events: make([]DomainEvent, 0), } } // GetID 获取ID func (s *SacredPlaceAggregate) GetID() string { return s.id } // GetName 获取名称 func (s *SacredPlaceAggregate) GetName() string { return s.name } // GetDescription 获取描述 func (s *SacredPlaceAggregate) GetDescription() string { return s.description } // GetLevel 获取等级 func (s *SacredPlaceAggregate) GetLevel() *SacredLevel { return s.level } // GetStatus 获取状态 func (s *SacredPlaceAggregate) GetStatus() SacredStatus { return s.status } // GetOwner 获取拥有者 func (s *SacredPlaceAggregate) GetOwner() string { return s.owner } // GetVersion 获取版本 func (s *SacredPlaceAggregate) GetVersion() int { return s.version } // GetEvents 获取领域事件 func (s *SacredPlaceAggregate) GetEvents() []DomainEvent { return s.events } // ClearEvents 清除领域事件 func (s *SacredPlaceAggregate) ClearEvents() { s.events = make([]DomainEvent, 0) } // SetName 设置名称 func (s *SacredPlaceAggregate) SetName(name string) error { if name == "" { return ErrInvalidSacredName } oldName := s.name s.name = name s.updatedAt = time.Now() s.version++ // 发布名称变更事件 event := NewSacredNameChangedEvent(s.id, oldName, name) s.addEvent(event) return nil } // SetDescription 设置描述 func (s *SacredPlaceAggregate) SetDescription(description string) { s.description = description s.updatedAt = time.Now() s.version++ } // UpgradeLevel 升级等级 func (s *SacredPlaceAggregate) UpgradeLevel(experience int) error { if experience <= 0 { return ErrInvalidExperience } oldLevel := s.level.Level newLevel, err := s.level.AddExperience(experience) if err != nil { return err } s.updatedAt = time.Now() s.version++ // 如果等级提升,发布升级事件 if newLevel > oldLevel { event := NewSacredLevelUpEvent(s.id, oldLevel, newLevel, s.level.Experience) s.addEvent(event) } return nil } // AddChallenge 添加挑战 func (s *SacredPlaceAggregate) AddChallenge(challenge *Challenge) error { if challenge == nil { return ErrInvalidChallenge } if _, exists := s.challenges[challenge.GetID()]; exists { return ErrChallengeAlreadyExists } // 检查等级要求 if challenge.GetRequiredLevel() > s.level.Level { return errors.New("challenge level is too high") } s.challenges[challenge.GetID()] = challenge s.updatedAt = time.Now() s.version++ // 发布挑战添加事件 event := NewChallengeAddedEvent(s.id, challenge.GetID(), challenge.GetType(), challenge.GetDifficulty()) s.addEvent(event) return nil } // RemoveChallenge 移除挑战 func (s *SacredPlaceAggregate) RemoveChallenge(challengeID string) error { challenge, exists := s.challenges[challengeID] if !exists { return ErrChallengeNotFound } // 检查挑战是否正在进行 if challenge.GetStatus() == ChallengeStatusInProgress { return ErrChallengeInProgress } delete(s.challenges, challengeID) s.updatedAt = time.Now() s.version++ // 发布挑战移除事件 event := NewChallengeRemovedEvent(s.id, challengeID, challenge.GetType()) s.addEvent(event) return nil } // StartChallenge 开始挑战 func (s *SacredPlaceAggregate) StartChallenge(challengeID, playerID string) (*ChallengeResult, error) { challenge, exists := s.challenges[challengeID] if !exists { return nil, ErrChallengeNotFound } // 检查挑战状态 if challenge.GetStatus() != ChallengeStatusAvailable { return nil, ErrChallengeNotAvailable } // 检查冷却时间 if !challenge.CanStart() { return nil, ErrChallengeOnCooldown } // 开始挑战 result, err := challenge.Start(playerID) if err != nil { return nil, err } s.updatedAt = time.Now() s.version++ // 发布挑战开始事件 event := NewChallengeStartedEvent(s.id, challengeID, playerID, challenge.GetType()) s.addEvent(event) return result, nil } // CompleteChallenge 完成挑战 func (s *SacredPlaceAggregate) CompleteChallenge(challengeID, playerID string, success bool, score int) (*ChallengeReward, error) { challenge, exists := s.challenges[challengeID] if !exists { return nil, ErrChallengeNotFound } // 完成挑战 reward, err := challenge.Complete(playerID, success, score) if err != nil { return nil, err } s.updatedAt = time.Now() s.version++ // 如果成功,增加经验 if success { s.UpgradeLevel(reward.Experience) } // 发布挑战完成事件 event := NewChallengeCompletedEvent(s.id, challengeID, playerID, success, score, reward) s.addEvent(event) return reward, nil } // AddBlessing 添加祝福 func (s *SacredPlaceAggregate) AddBlessing(blessing *Blessing) error { if blessing == nil { return ErrInvalidBlessing } if _, exists := s.blessings[blessing.GetID()]; exists { return ErrBlessingAlreadyExists } s.blessings[blessing.GetID()] = blessing s.updatedAt = time.Now() s.version++ // 发布祝福添加事件 event := NewBlessingAddedEvent(s.id, blessing.GetID(), blessing.GetType(), blessing.GetDuration()) s.addEvent(event) return nil } // RemoveBlessing 移除祝福 func (s *SacredPlaceAggregate) RemoveBlessing(blessingID string) error { blessing, exists := s.blessings[blessingID] if !exists { return ErrBlessingNotFound } delete(s.blessings, blessingID) s.updatedAt = time.Now() s.version++ // 发布祝福移除事件 event := NewBlessingRemovedEvent(s.id, blessingID, blessing.GetType()) s.addEvent(event) return nil } // ActivateBlessing 激活祝福 func (s *SacredPlaceAggregate) ActivateBlessing(blessingID, playerID string) (*BlessingEffect, error) { blessing, exists := s.blessings[blessingID] if !exists { return nil, ErrBlessingNotFound } // 检查祝福状态 if !blessing.IsAvailable() { return nil, ErrBlessingNotAvailable } // 激活祝福 effect, err := blessing.Activate(playerID) if err != nil { return nil, err } s.updatedAt = time.Now() s.version++ // 发布祝福激活事件 event := NewBlessingActivatedEvent(s.id, blessingID, playerID, blessing.GetType(), effect) s.addEvent(event) return effect, nil } // GetChallenge 获取挑战 func (s *SacredPlaceAggregate) GetChallenge(challengeID string) (*Challenge, error) { challenge, exists := s.challenges[challengeID] if !exists { return nil, ErrChallengeNotFound } return challenge, nil } // GetAllChallenges 获取所有挑战 func (s *SacredPlaceAggregate) GetAllChallenges() map[string]*Challenge { return s.challenges } // GetAvailableChallenges 获取可用挑战 func (s *SacredPlaceAggregate) GetAvailableChallenges() []*Challenge { var available []*Challenge for _, challenge := range s.challenges { if challenge.GetStatus() == ChallengeStatusAvailable && challenge.CanStart() { available = append(available, challenge) } } return available } // GetBlessing 获取祝福 func (s *SacredPlaceAggregate) GetBlessing(blessingID string) (*Blessing, error) { blessing, exists := s.blessings[blessingID] if !exists { return nil, ErrBlessingNotFound } return blessing, nil } // GetAllBlessings 获取所有祝福 func (s *SacredPlaceAggregate) GetAllBlessings() map[string]*Blessing { return s.blessings } // GetActiveBlessings 获取激活的祝福 func (s *SacredPlaceAggregate) GetActiveBlessings() []*Blessing { var active []*Blessing for _, blessing := range s.blessings { if blessing.IsActive() { active = append(active, blessing) } } return active } // SetStatus 设置状态 func (s *SacredPlaceAggregate) SetStatus(status SacredStatus) error { if !status.IsValid() { return ErrInvalidSacredStatus } oldStatus := s.status s.status = status s.updatedAt = time.Now() s.version++ // 发布状态变更事件 event := NewSacredStatusChangedEvent(s.id, oldStatus, status) s.addEvent(event) return nil } // Activate 激活圣地 func (s *SacredPlaceAggregate) Activate() error { return s.SetStatus(SacredStatusActive) } // Deactivate 停用圣地 func (s *SacredPlaceAggregate) Deactivate() error { return s.SetStatus(SacredStatusInactive) } // Lock 锁定圣地 func (s *SacredPlaceAggregate) Lock() error { return s.SetStatus(SacredStatusLocked) } // IsActive 检查是否激活 func (s *SacredPlaceAggregate) IsActive() bool { return s.status == SacredStatusActive } // CanAccess 检查是否可访问 func (s *SacredPlaceAggregate) CanAccess(playerID string) bool { if !s.IsActive() { return false } // 检查是否为拥有者或有权限 return s.owner == playerID || s.hasAccessPermission(playerID) } // hasAccessPermission 检查访问权限 func (s *SacredPlaceAggregate) hasAccessPermission(playerID string) bool { // 这里可以实现更复杂的权限逻辑 // 例如:公会成员、好友、VIP等 return true // 暂时允许所有人访问 } // GetStatistics 获取统计信息 func (s *SacredPlaceAggregate) GetStatistics() *SacredStatistics { totalChallenges := len(s.challenges) completedChallenges := 0 activeBlessings := 0 for _, challenge := range s.challenges { if challenge.GetStatus() == ChallengeStatusCompleted { completedChallenges++ } } for _, blessing := range s.blessings { if blessing.IsActive() { activeBlessings++ } } return &SacredStatistics{ SacredID: s.id, Level: s.level.Level, Experience: s.level.Experience, TotalChallenges: totalChallenges, CompletedChallenges: completedChallenges, ActiveBlessings: activeBlessings, CreatedAt: s.createdAt, LastActiveAt: s.updatedAt, } } // UpdateActivity 更新活动时间 func (s *SacredPlaceAggregate) UpdateActivity() { s.updatedAt = time.Now() s.version++ } // addEvent 添加领域事件 func (s *SacredPlaceAggregate) addEvent(event DomainEvent) { s.events = append(s.events, event) } // ToMap 转换为映射 func (s *SacredPlaceAggregate) ToMap() map[string]interface{} { return map[string]interface{}{ "id": s.id, "name": s.name, "description": s.description, "level": s.level.ToMap(), "status": s.status.String(), "owner": s.owner, "created_at": s.createdAt, "updated_at": s.updatedAt, "version": s.version, "challenges": len(s.challenges), "blessings": len(s.blessings), } } // SacredStatus 圣地状态 type SacredStatus int const ( SacredStatusActive SacredStatus = iota + 1 // 激活 SacredStatusInactive // 未激活 SacredStatusLocked // 锁定 SacredStatusMaintenance // 维护中 ) // String 返回状态字符串 func (s SacredStatus) String() string { switch s { case SacredStatusActive: return "active" case SacredStatusInactive: return "inactive" case SacredStatusLocked: return "locked" case SacredStatusMaintenance: return "maintenance" default: return "unknown" } } // IsValid 检查状态是否有效 func (s SacredStatus) IsValid() bool { return s >= SacredStatusActive && s <= SacredStatusMaintenance } // SacredStatistics 圣地统计信息 type SacredStatistics struct { SacredID string Level int Experience int TotalChallenges int CompletedChallenges int ActiveBlessings int CreatedAt time.Time LastActiveAt time.Time } // 相关错误定义 - 错误定义在errors.go中 // var ( // ErrInvalidSacredName = fmt.Errorf("invalid sacred name") // ErrInvalidExperience = fmt.Errorf("invalid experience") // ErrInvalidChallenge = fmt.Errorf("invalid challenge") // ErrChallengeAlreadyExists = fmt.Errorf("challenge already exists") // ErrInsufficientSacredLevel = fmt.Errorf("insufficient sacred level") // ErrChallengeNotFound = fmt.Errorf("challenge not found") // ErrChallengeInProgress = fmt.Errorf("challenge in progress") // ErrChallengeNotAvailable = fmt.Errorf("challenge not available") // ErrChallengeOnCooldown = fmt.Errorf("challenge on cooldown") // ErrInvalidBlessing = fmt.Errorf("invalid blessing") // ErrBlessingAlreadyExists = fmt.Errorf("blessing already exists") // ErrBlessingNotFound = fmt.Errorf("blessing not found") // ErrBlessingNotAvailable = fmt.Errorf("blessing not available") // ErrInvalidSacredStatus = fmt.Errorf("invalid sacred status") // ) ================================================ FILE: internal/domain/scene/sacred/entity.go ================================================ package sacred import ( "fmt" "time" ) // Challenge 挑战实体 type Challenge struct { id string name string description string type_ ChallengeType difficulty ChallengeDifficulty requiredLevel int status ChallengeStatus duration time.Duration cooldown time.Duration lastStartTime time.Time lastEndTime time.Time participants map[string]*ChallengeParticipant rewards *ChallengeReward conditions []*ChallengeCondition createdAt time.Time updatedAt time.Time } // NewChallenge 创建挑战 func NewChallenge(id, name, description string, challengeType ChallengeType, difficulty ChallengeDifficulty, requiredLevel int) *Challenge { now := time.Now() return &Challenge{ id: id, name: name, description: description, type_: challengeType, difficulty: difficulty, requiredLevel: requiredLevel, status: ChallengeStatusAvailable, duration: time.Hour, // 默认1小时 cooldown: time.Hour * 24, // 默认24小时冷却 participants: make(map[string]*ChallengeParticipant), rewards: NewChallengeReward(challengeType, difficulty), conditions: make([]*ChallengeCondition, 0), createdAt: now, updatedAt: now, } } // GetID 获取ID func (c *Challenge) GetID() string { return c.id } // GetName 获取名称 func (c *Challenge) GetName() string { return c.name } // GetDescription 获取描述 func (c *Challenge) GetDescription() string { return c.description } // GetType 获取类型 func (c *Challenge) GetType() ChallengeType { return c.type_ } // GetDifficulty 获取难度 func (c *Challenge) GetDifficulty() ChallengeDifficulty { return c.difficulty } // GetRequiredLevel 获取所需等级 func (c *Challenge) GetRequiredLevel() int { return c.requiredLevel } // GetStatus 获取状态 func (c *Challenge) GetStatus() ChallengeStatus { return c.status } // GetDuration 获取持续时间 func (c *Challenge) GetDuration() time.Duration { return c.duration } // GetCooldown 获取冷却时间 func (c *Challenge) GetCooldown() time.Duration { return c.cooldown } // GetRewards 获取奖励 func (c *Challenge) GetRewards() *ChallengeReward { return c.rewards } // CanStart 检查是否可以开始 func (c *Challenge) CanStart() bool { if c.status != ChallengeStatusAvailable { return false } // 检查冷却时间 if !c.lastEndTime.IsZero() && time.Since(c.lastEndTime) < c.cooldown { return false } return true } // Start 开始挑战 func (c *Challenge) Start(playerID string) (*ChallengeResult, error) { if !c.CanStart() { return nil, fmt.Errorf("challenge cannot be started") } // 创建参与者 participant := NewChallengeParticipant(playerID, c.id) c.participants[playerID] = participant // 更新状态 c.status = ChallengeStatusInProgress c.lastStartTime = time.Now() c.updatedAt = time.Now() // 创建挑战结果 result := &ChallengeResult{ ChallengeID: c.id, PlayerID: playerID, StartTime: c.lastStartTime, Status: "started", } return result, nil } // Complete 完成挑战 func (c *Challenge) Complete(playerID string, success bool, score int) (*ChallengeReward, error) { participant, exists := c.participants[playerID] if !exists { return nil, fmt.Errorf("participant not found") } // 完成参与者记录 participant.Complete(success, score) // 更新挑战状态 c.status = ChallengeStatusCompleted c.lastEndTime = time.Now() c.updatedAt = time.Now() // 计算奖励 reward := c.calculateReward(success, score) return reward, nil } // calculateReward 计算奖励 func (c *Challenge) calculateReward(success bool, score int) *ChallengeReward { if !success { return &ChallengeReward{ Gold: 0, Experience: 0, Items: make(map[string]int), } } // 基础奖励 baseReward := c.rewards // 根据分数调整奖励 multiplier := float64(score) / 100.0 if multiplier > 2.0 { multiplier = 2.0 } if multiplier < 0.1 { multiplier = 0.1 } return &ChallengeReward{ Gold: int(float64(baseReward.Gold) * multiplier), Experience: int(float64(baseReward.Experience) * multiplier), Items: baseReward.Items, } } // AddCondition 添加条件 func (c *Challenge) AddCondition(condition *ChallengeCondition) { c.conditions = append(c.conditions, condition) c.updatedAt = time.Now() } // CheckConditions 检查条件 func (c *Challenge) CheckConditions(playerData map[string]interface{}) bool { for _, condition := range c.conditions { if !condition.Check(playerData) { return false } } return true } // SetDuration 设置持续时间 func (c *Challenge) SetDuration(duration time.Duration) { c.duration = duration c.updatedAt = time.Now() } // SetCooldown 设置冷却时间 func (c *Challenge) SetCooldown(cooldown time.Duration) { c.cooldown = cooldown c.updatedAt = time.Now() } // GetRemainingCooldown 获取剩余冷却时间 func (c *Challenge) GetRemainingCooldown() time.Duration { if c.lastEndTime.IsZero() { return 0 } elapsed := time.Since(c.lastEndTime) if elapsed >= c.cooldown { return 0 } return c.cooldown - elapsed } // ToMap 转换为映射 func (c *Challenge) ToMap() map[string]interface{} { return map[string]interface{}{ "id": c.id, "name": c.name, "description": c.description, "type": c.type_.String(), "difficulty": c.difficulty.String(), "required_level": c.requiredLevel, "status": c.status.String(), "duration": c.duration.String(), "cooldown": c.cooldown.String(), "participants": len(c.participants), "created_at": c.createdAt, "updated_at": c.updatedAt, } } // ChallengeParticipant 挑战参与者 type ChallengeParticipant struct { PlayerID string ChallengeID string StartTime time.Time EndTime time.Time Success bool Score int Attempts int Status string } // NewChallengeParticipant 创建挑战参与者 func NewChallengeParticipant(playerID, challengeID string) *ChallengeParticipant { return &ChallengeParticipant{ PlayerID: playerID, ChallengeID: challengeID, StartTime: time.Now(), Attempts: 1, Status: "in_progress", } } // Complete 完成挑战 func (cp *ChallengeParticipant) Complete(success bool, score int) { cp.EndTime = time.Now() cp.Success = success cp.Score = score cp.Status = "completed" } // GetDuration 获取用时 func (cp *ChallengeParticipant) GetDuration() time.Duration { if cp.EndTime.IsZero() { return time.Since(cp.StartTime) } return cp.EndTime.Sub(cp.StartTime) } // ChallengeCondition 挑战条件 type ChallengeCondition struct { Type string Field string Operator string Value interface{} Message string } // NewChallengeCondition 创建挑战条件 func NewChallengeCondition(conditionType, field, operator string, value interface{}, message string) *ChallengeCondition { return &ChallengeCondition{ Type: conditionType, Field: field, Operator: operator, Value: value, Message: message, } } // Check 检查条件 func (cc *ChallengeCondition) Check(data map[string]interface{}) bool { value, exists := data[cc.Field] if !exists { return false } switch cc.Operator { case "eq": return value == cc.Value case "ne": return value != cc.Value case "gt": if v1, ok := value.(int); ok { if v2, ok := cc.Value.(int); ok { return v1 > v2 } } case "gte": if v1, ok := value.(int); ok { if v2, ok := cc.Value.(int); ok { return v1 >= v2 } } case "lt": if v1, ok := value.(int); ok { if v2, ok := cc.Value.(int); ok { return v1 < v2 } } case "lte": if v1, ok := value.(int); ok { if v2, ok := cc.Value.(int); ok { return v1 <= v2 } } } return false } // ChallengeResult 挑战结果 type ChallengeResult struct { ChallengeID string PlayerID string StartTime time.Time EndTime time.Time Success bool Score int Status string Message string } // ChallengeReward 挑战奖励 type ChallengeReward struct { Gold int Experience int Items map[string]int Special map[string]interface{} } // NewChallengeReward 创建挑战奖励 func NewChallengeReward(challengeType ChallengeType, difficulty ChallengeDifficulty) *ChallengeReward { baseGold := 100 baseExp := 50 // 根据难度调整基础奖励 multiplier := difficulty.GetMultiplier() return &ChallengeReward{ Gold: int(float64(baseGold) * multiplier), Experience: int(float64(baseExp) * multiplier), Items: make(map[string]int), Special: make(map[string]interface{}), } } // AddItem 添加物品奖励 func (cr *ChallengeReward) AddItem(itemID string, quantity int) { cr.Items[itemID] = quantity } // AddSpecial 添加特殊奖励 func (cr *ChallengeReward) AddSpecial(key string, value interface{}) { cr.Special[key] = value } // Blessing 祝福实体 type Blessing struct { id string name string description string type_ BlessingType effects []*BlessingEffect duration time.Duration cooldown time.Duration status BlessingStatus activatedAt time.Time expiresAt time.Time lastUsedAt time.Time usageCount int maxUsage int createdAt time.Time updatedAt time.Time } // NewBlessing 创建祝福 func NewBlessing(id, name, description string, blessingType BlessingType, duration time.Duration) *Blessing { now := time.Now() return &Blessing{ id: id, name: name, description: description, type_: blessingType, effects: make([]*BlessingEffect, 0), duration: duration, cooldown: time.Hour * 24, // 默认24小时冷却 status: BlessingStatusAvailable, maxUsage: 1, // 默认只能使用一次 createdAt: now, updatedAt: now, } } // GetID 获取ID func (b *Blessing) GetID() string { return b.id } // GetName 获取名称 func (b *Blessing) GetName() string { return b.name } // GetDescription 获取描述 func (b *Blessing) GetDescription() string { return b.description } // GetType 获取类型 func (b *Blessing) GetType() BlessingType { return b.type_ } // GetDuration 获取持续时间 func (b *Blessing) GetDuration() time.Duration { return b.duration } // GetStatus 获取状态 func (b *Blessing) GetStatus() BlessingStatus { return b.status } // IsAvailable 检查是否可用 func (b *Blessing) IsAvailable() bool { if b.status != BlessingStatusAvailable { return false } // 检查使用次数 if b.usageCount >= b.maxUsage { return false } // 检查冷却时间 if !b.lastUsedAt.IsZero() && time.Since(b.lastUsedAt) < b.cooldown { return false } return true } // IsActive 检查是否激活 func (b *Blessing) IsActive() bool { return b.status == BlessingStatusActive && time.Now().Before(b.expiresAt) } // Activate 激活祝福 func (b *Blessing) Activate(playerID string) (*BlessingEffect, error) { if !b.IsAvailable() { return nil, fmt.Errorf("blessing is not available") } // 更新状态 b.status = BlessingStatusActive b.activatedAt = time.Now() b.expiresAt = b.activatedAt.Add(b.duration) b.lastUsedAt = b.activatedAt b.usageCount++ b.updatedAt = time.Now() // 创建祝福效果 effect := &BlessingEffect{ BlessingID: b.id, PlayerID: playerID, Type: b.type_, ActivatedAt: b.activatedAt, ExpiresAt: b.expiresAt, Effects: b.effects, } return effect, nil } // Deactivate 停用祝福 func (b *Blessing) Deactivate() { b.status = BlessingStatusInactive b.updatedAt = time.Now() } // AddEffect 添加效果 func (b *Blessing) AddEffect(effect *BlessingEffect) { b.effects = append(b.effects, effect) b.updatedAt = time.Now() } // GetRemainingDuration 获取剩余持续时间 func (b *Blessing) GetRemainingDuration() time.Duration { if b.status != BlessingStatusActive { return 0 } if time.Now().After(b.expiresAt) { return 0 } return b.expiresAt.Sub(time.Now()) } // GetRemainingCooldown 获取剩余冷却时间 func (b *Blessing) GetRemainingCooldown() time.Duration { if b.lastUsedAt.IsZero() { return 0 } elapsed := time.Since(b.lastUsedAt) if elapsed >= b.cooldown { return 0 } return b.cooldown - elapsed } // SetMaxUsage 设置最大使用次数 func (b *Blessing) SetMaxUsage(maxUsage int) { b.maxUsage = maxUsage b.updatedAt = time.Now() } // SetCooldown 设置冷却时间 func (b *Blessing) SetCooldown(cooldown time.Duration) { b.cooldown = cooldown b.updatedAt = time.Now() } // ToMap 转换为映射 func (b *Blessing) ToMap() map[string]interface{} { return map[string]interface{}{ "id": b.id, "name": b.name, "description": b.description, "type": b.type_.String(), "duration": b.duration.String(), "cooldown": b.cooldown.String(), "status": b.status.String(), "usage_count": b.usageCount, "max_usage": b.maxUsage, "activated_at": b.activatedAt, "expires_at": b.expiresAt, "created_at": b.createdAt, "updated_at": b.updatedAt, } } // BlessingEffect 祝福效果 type BlessingEffect struct { BlessingID string PlayerID string Type BlessingType ActivatedAt time.Time ExpiresAt time.Time Effects []*BlessingEffect Attributes map[string]float64 Modifiers map[string]float64 } // NewBlessingEffect 创建祝福效果 func NewBlessingEffect(blessingID, playerID string, blessingType BlessingType, duration time.Duration) *BlessingEffect { now := time.Now() return &BlessingEffect{ BlessingID: blessingID, PlayerID: playerID, Type: blessingType, ActivatedAt: now, ExpiresAt: now.Add(duration), Attributes: make(map[string]float64), Modifiers: make(map[string]float64), } } // IsActive 检查是否激活 func (be *BlessingEffect) IsActive() bool { return time.Now().Before(be.ExpiresAt) } // GetRemainingDuration 获取剩余时间 func (be *BlessingEffect) GetRemainingDuration() time.Duration { if !be.IsActive() { return 0 } return be.ExpiresAt.Sub(time.Now()) } // AddAttribute 添加属性加成 func (be *BlessingEffect) AddAttribute(name string, value float64) { be.Attributes[name] = value } // AddModifier 添加修饰符 func (be *BlessingEffect) AddModifier(name string, value float64) { be.Modifiers[name] = value } // GetAttribute 获取属性值 func (be *BlessingEffect) GetAttribute(name string) float64 { return be.Attributes[name] } // GetModifier 获取修饰符值 func (be *BlessingEffect) GetModifier(name string) float64 { return be.Modifiers[name] } ================================================ FILE: internal/domain/scene/sacred/errors.go ================================================ package sacred import ( "fmt" "strings" "time" ) // 基础错误变量 var ( // 圣地相关错误 ErrSacredNotFound = fmt.Errorf("sacred place not found") ErrSacredAlreadyExists = fmt.Errorf("sacred place already exists") ErrSacredNotActive = fmt.Errorf("sacred place is not active") ErrSacredLocked = fmt.Errorf("sacred place is locked") ErrSacredMaintenance = fmt.Errorf("sacred place is under maintenance") ErrSacredAccessDenied = fmt.Errorf("access to sacred place denied") ErrSacredCapacityFull = fmt.Errorf("sacred place capacity is full") ErrInvalidSacredName = fmt.Errorf("invalid sacred place name") ErrInvalidSacredStatus = fmt.Errorf("invalid sacred place status") ErrSacredOwnerMismatch = fmt.Errorf("sacred place owner mismatch") // 挑战相关错误 ErrChallengeNotFound = fmt.Errorf("challenge not found") ErrChallengeAlreadyExists = fmt.Errorf("challenge already exists") ErrChallengeNotAvailable = fmt.Errorf("challenge is not available") ErrChallengeInProgress = fmt.Errorf("challenge is in progress") ErrChallengeCompleted = fmt.Errorf("challenge already completed") ErrChallengeFailed = fmt.Errorf("challenge failed") ErrChallengeExpired = fmt.Errorf("challenge has expired") ErrChallengeOnCooldown = fmt.Errorf("challenge is on cooldown") ErrInvalidChallenge = fmt.Errorf("invalid challenge") ErrInvalidChallengeType = fmt.Errorf("invalid challenge type") ErrInvalidDifficulty = fmt.Errorf("invalid challenge difficulty") ErrInsufficientLevel = fmt.Errorf("insufficient level for challenge") ErrChallengeConditionsNotMet = fmt.Errorf("challenge conditions not met") // 祝福相关错误 ErrBlessingNotFound = fmt.Errorf("blessing not found") ErrBlessingAlreadyExists = fmt.Errorf("blessing already exists") ErrBlessingNotAvailable = fmt.Errorf("blessing is not available") ErrBlessingExpired = fmt.Errorf("blessing has expired") ErrBlessingOnCooldown = fmt.Errorf("blessing is on cooldown") ErrBlessingLimitReached = fmt.Errorf("blessing usage limit reached") ErrInvalidBlessing = fmt.Errorf("invalid blessing") ErrInvalidBlessingType = fmt.Errorf("invalid blessing type") ErrBlessingConflict = fmt.Errorf("blessing conflicts with existing effects") ErrMaxActiveBlessings = fmt.Errorf("maximum active blessings reached") // 圣物相关错误 ErrRelicNotFound = fmt.Errorf("relic not found") ErrRelicAlreadyExists = fmt.Errorf("relic already exists") ErrRelicNotOwned = fmt.Errorf("relic is not owned by player") ErrRelicCannotUpgrade = fmt.Errorf("relic cannot be upgraded") ErrRelicMaxLevel = fmt.Errorf("relic is at maximum level") ErrRelicRequirementsNotMet = fmt.Errorf("relic requirements not met") ErrInvalidRelic = fmt.Errorf("invalid relic") ErrInvalidRelicType = fmt.Errorf("invalid relic type") ErrInvalidRelicRarity = fmt.Errorf("invalid relic rarity") ErrRelicInventoryFull = fmt.Errorf("relic inventory is full") // 等级和经验相关错误 ErrInvalidLevel = fmt.Errorf("invalid level") ErrInvalidExperience = fmt.Errorf("invalid experience") ErrMaxLevelReached = fmt.Errorf("maximum level reached") ErrInsufficientExperience = fmt.Errorf("insufficient experience") ErrLevelDowngrade = fmt.Errorf("level downgrade not allowed") // 权限相关错误 ErrPermissionDenied = fmt.Errorf("permission denied") ErrUnauthorized = fmt.Errorf("unauthorized operation") ErrAccessRestricted = fmt.Errorf("access restricted") ErrInsufficientPrivileges = fmt.Errorf("insufficient privileges") ErrOwnershipRequired = fmt.Errorf("ownership required") // 资源相关错误 ErrInsufficientResources = fmt.Errorf("insufficient resources") ErrInsufficientGold = fmt.Errorf("insufficient gold") ErrInsufficientMana = fmt.Errorf("insufficient mana") ErrInsufficientEnergy = fmt.Errorf("insufficient energy") ErrResourceNotFound = fmt.Errorf("resource not found") ErrResourceLocked = fmt.Errorf("resource is locked") // 时间相关错误 ErrInvalidTime = fmt.Errorf("invalid time") ErrTimeExpired = fmt.Errorf("time has expired") ErrTooEarly = fmt.Errorf("too early for this operation") ErrTooLate = fmt.Errorf("too late for this operation") ErrCooldownActive = fmt.Errorf("cooldown is active") ErrDurationTooShort = fmt.Errorf("duration is too short") ErrDurationTooLong = fmt.Errorf("duration is too long") // 配置相关错误 ErrInvalidConfiguration = fmt.Errorf("invalid configuration") ErrConfigurationNotFound = fmt.Errorf("configuration not found") ErrConfigurationCorrupted = fmt.Errorf("configuration corrupted") ErrMissingConfiguration = fmt.Errorf("missing configuration") // 数据相关错误 ErrDataCorrupted = fmt.Errorf("data corrupted") ErrDataNotFound = fmt.Errorf("data not found") ErrDataInconsistent = fmt.Errorf("data inconsistent") ErrInvalidData = fmt.Errorf("invalid data") ErrDataConflict = fmt.Errorf("data conflict") // 系统相关错误 ErrSystemError = fmt.Errorf("system error") ErrServiceUnavailable = fmt.Errorf("service unavailable") ErrTimeout = fmt.Errorf("operation timeout") ErrInternalError = fmt.Errorf("internal error") ErrExternalServiceError = fmt.Errorf("external service error") // 并发相关错误 ErrConcurrentModification = fmt.Errorf("concurrent modification") ErrResourceBusy = fmt.Errorf("resource is busy") ErrDeadlock = fmt.Errorf("deadlock detected") ErrRaceCondition = fmt.Errorf("race condition detected") // 验证相关错误 ErrValidationFailed = fmt.Errorf("validation failed") ErrInvalidInput = fmt.Errorf("invalid input") ErrMissingRequiredField = fmt.Errorf("missing required field") ErrFieldTooLong = fmt.Errorf("field is too long") ErrFieldTooShort = fmt.Errorf("field is too short") ErrInvalidFormat = fmt.Errorf("invalid format") // 业务规则相关错误 ErrBusinessRuleViolation = fmt.Errorf("business rule violation") ErrOperationNotAllowed = fmt.Errorf("operation not allowed") ErrStateTransitionInvalid = fmt.Errorf("invalid state transition") ErrPreconditionFailed = fmt.Errorf("precondition failed") ErrPostconditionFailed = fmt.Errorf("postcondition failed") ) // SacredError 圣地系统错误 type SacredError struct { Code string Message string Details map[string]interface{} Cause error Timestamp time.Time Context map[string]string Severity ErrorSeverity Category ErrorCategory } // Error 实现error接口 func (e *SacredError) Error() string { if e.Cause != nil { return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // Unwrap 返回原始错误 func (e *SacredError) Unwrap() error { return e.Cause } // WithDetail 添加详细信息 func (e *SacredError) WithDetail(key string, value interface{}) *SacredError { if e.Details == nil { e.Details = make(map[string]interface{}) } e.Details[key] = value return e } // WithContext 添加上下文信息 func (e *SacredError) WithContext(key, value string) *SacredError { if e.Context == nil { e.Context = make(map[string]string) } e.Context[key] = value return e } // WithSeverity 设置严重程度 func (e *SacredError) WithSeverity(severity ErrorSeverity) *SacredError { e.Severity = severity return e } // WithCategory 设置错误类别 func (e *SacredError) WithCategory(category ErrorCategory) *SacredError { e.Category = category return e } // IsRetryable 检查是否可重试 func (e *SacredError) IsRetryable() bool { return e.Category == ErrorCategoryTemporary || e.Category == ErrorCategoryNetwork } // IsCritical 检查是否为关键错误 func (e *SacredError) IsCritical() bool { return e.Severity == ErrorSeverityCritical || e.Severity == ErrorSeverityFatal } // ErrorSeverity 错误严重程度 type ErrorSeverity int const ( ErrorSeverityInfo ErrorSeverity = iota + 1 // 信息 ErrorSeverityWarning // 警告 ErrorSeverityError // 错误 ErrorSeverityCritical // 关键 ErrorSeverityFatal // 致命 ) // String 返回严重程度字符串 func (es ErrorSeverity) String() string { switch es { case ErrorSeverityInfo: return "info" case ErrorSeverityWarning: return "warning" case ErrorSeverityError: return "error" case ErrorSeverityCritical: return "critical" case ErrorSeverityFatal: return "fatal" default: return "unknown" } } // ErrorCategory 错误类别 type ErrorCategory int const ( ErrorCategoryValidation ErrorCategory = iota + 1 // 验证错误 ErrorCategoryBusiness // 业务错误 ErrorCategorySystem // 系统错误 ErrorCategoryNetwork // 网络错误 ErrorCategoryDatabase // 数据库错误 ErrorCategoryPermission // 权限错误 ErrorCategoryResource // 资源错误 ErrorCategoryTemporary // 临时错误 ErrorCategoryConfiguration // 配置错误 ErrorCategoryConcurrency // 并发错误 ) // String 返回类别字符串 func (ec ErrorCategory) String() string { switch ec { case ErrorCategoryValidation: return "validation" case ErrorCategoryBusiness: return "business" case ErrorCategorySystem: return "system" case ErrorCategoryNetwork: return "network" case ErrorCategoryDatabase: return "database" case ErrorCategoryPermission: return "permission" case ErrorCategoryResource: return "resource" case ErrorCategoryTemporary: return "temporary" case ErrorCategoryConfiguration: return "configuration" case ErrorCategoryConcurrency: return "concurrency" default: return "unknown" } } // ValidationError 验证错误 type ValidationError struct { *SacredError Field string Value interface{} Constraint string Rule string } // NewValidationError 创建验证错误 func NewValidationError(field, constraint, rule string, value interface{}) *ValidationError { return &ValidationError{ SacredError: &SacredError{ Code: "VALIDATION_ERROR", Message: fmt.Sprintf("validation failed for field '%s': %s", field, constraint), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryValidation, }, Field: field, Value: value, Constraint: constraint, Rule: rule, } } // BusinessRuleError 业务规则错误 type BusinessRuleError struct { *SacredError Rule string Violation string Expected interface{} Actual interface{} Suggestion string } // NewBusinessRuleError 创建业务规则错误 func NewBusinessRuleError(rule, violation string, expected, actual interface{}) *BusinessRuleError { return &BusinessRuleError{ SacredError: &SacredError{ Code: "BUSINESS_RULE_ERROR", Message: fmt.Sprintf("business rule violation: %s - %s", rule, violation), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryBusiness, }, Rule: rule, Violation: violation, Expected: expected, Actual: actual, } } // WithSuggestion 添加建议 func (e *BusinessRuleError) WithSuggestion(suggestion string) *BusinessRuleError { e.Suggestion = suggestion return e } // ConcurrencyError 并发错误 type ConcurrencyError struct { *SacredError Resource string Operation string ConflictID string RetryAfter time.Duration MaxRetries int CurrentTry int } // NewConcurrencyError 创建并发错误 func NewConcurrencyError(resource, operation, conflictID string) *ConcurrencyError { return &ConcurrencyError{ SacredError: &SacredError{ Code: "CONCURRENCY_ERROR", Message: fmt.Sprintf("concurrent access conflict on %s during %s", resource, operation), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityWarning, Category: ErrorCategoryConcurrency, }, Resource: resource, Operation: operation, ConflictID: conflictID, MaxRetries: 3, CurrentTry: 1, } } // WithRetryAfter 设置重试时间 func (e *ConcurrencyError) WithRetryAfter(duration time.Duration) *ConcurrencyError { e.RetryAfter = duration return e } // CanRetry 检查是否可以重试 func (e *ConcurrencyError) CanRetry() bool { return e.CurrentTry < e.MaxRetries } // IncrementTry 增加尝试次数 func (e *ConcurrencyError) IncrementTry() { e.CurrentTry++ } // ConfigurationError 配置错误 type ConfigurationError struct { *SacredError ConfigKey string ConfigValue interface{} ExpectedType string ValidValues []interface{} } // NewConfigurationError 创建配置错误 func NewConfigurationError(configKey string, configValue interface{}, expectedType string) *ConfigurationError { return &ConfigurationError{ SacredError: &SacredError{ Code: "CONFIGURATION_ERROR", Message: fmt.Sprintf("invalid configuration for key '%s': expected %s", configKey, expectedType), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryConfiguration, }, ConfigKey: configKey, ConfigValue: configValue, ExpectedType: expectedType, } } // WithValidValues 设置有效值 func (e *ConfigurationError) WithValidValues(values ...interface{}) *ConfigurationError { e.ValidValues = values return e } // SystemError 系统错误 type SystemError struct { *SacredError Component string Operation string ErrorCode int Recoverable bool RetryCount int MaxRetries int StackTrace string } // NewSystemError 创建系统错误 func NewSystemError(component, operation string, errorCode int, cause error) *SystemError { return &SystemError{ SacredError: &SacredError{ Code: "SYSTEM_ERROR", Message: fmt.Sprintf("system error in %s during %s (code: %d)", component, operation, errorCode), Cause: cause, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityCritical, Category: ErrorCategorySystem, }, Component: component, Operation: operation, ErrorCode: errorCode, MaxRetries: 3, } } // SetRecoverable 设置是否可恢复 func (e *SystemError) SetRecoverable(recoverable bool) *SystemError { e.Recoverable = recoverable return e } // IncrementRetry 增加重试次数 func (e *SystemError) IncrementRetry() *SystemError { e.RetryCount++ return e } // CanRetry 检查是否可以重试 func (e *SystemError) CanRetry() bool { return e.Recoverable && e.RetryCount < e.MaxRetries } // WithStackTrace 添加堆栈跟踪 func (e *SystemError) WithStackTrace(stackTrace string) *SystemError { e.StackTrace = stackTrace return e } // PermissionError 权限错误 type PermissionError struct { *SacredError UserID string Resource string RequiredRole string CurrentRole string RequiredPermissions []string CurrentPermissions []string } // NewPermissionError 创建权限错误 func NewPermissionError(userID, resource, requiredRole, currentRole string) *PermissionError { return &PermissionError{ SacredError: &SacredError{ Code: "PERMISSION_ERROR", Message: fmt.Sprintf("permission denied for user %s on resource %s", userID, resource), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryPermission, }, UserID: userID, Resource: resource, RequiredRole: requiredRole, CurrentRole: currentRole, } } // WithPermissions 设置权限信息 func (e *PermissionError) WithPermissions(required, current []string) *PermissionError { e.RequiredPermissions = required e.CurrentPermissions = current return e } // ResourceError 资源错误 type ResourceError struct { *SacredError ResourceType string ResourceID string Required interface{} Available interface{} Unit string } // NewResourceError 创建资源错误 func NewResourceError(resourceType, resourceID string, required, available interface{}, unit string) *ResourceError { return &ResourceError{ SacredError: &SacredError{ Code: "RESOURCE_ERROR", Message: fmt.Sprintf("insufficient %s: required %v, available %v %s", resourceType, required, available, unit), Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryResource, }, ResourceType: resourceType, ResourceID: resourceID, Required: required, Available: available, Unit: unit, } } // ErrorCollection 错误集合 type ErrorCollection struct { Errors []error Context string Timestamp time.Time Severity ErrorSeverity } // NewErrorCollection 创建错误集合 func NewErrorCollection(context string) *ErrorCollection { return &ErrorCollection{ Errors: make([]error, 0), Context: context, Timestamp: time.Now(), Severity: ErrorSeverityError, } } // Add 添加错误 func (ec *ErrorCollection) Add(err error) { if err != nil { ec.Errors = append(ec.Errors, err) // 更新严重程度 if sacredErr, ok := err.(*SacredError); ok { if sacredErr.Severity > ec.Severity { ec.Severity = sacredErr.Severity } } } } // HasErrors 检查是否有错误 func (ec *ErrorCollection) HasErrors() bool { return len(ec.Errors) > 0 } // Count 获取错误数量 func (ec *ErrorCollection) Count() int { return len(ec.Errors) } // Error 实现error接口 func (ec *ErrorCollection) Error() string { if len(ec.Errors) == 0 { return "no errors" } var messages []string for i, err := range ec.Errors { messages = append(messages, fmt.Sprintf("%d: %v", i+1, err)) } return fmt.Sprintf("multiple errors in %s: [%s]", ec.Context, strings.Join(messages, "; ")) } // First 获取第一个错误 func (ec *ErrorCollection) First() error { if len(ec.Errors) > 0 { return ec.Errors[0] } return nil } // Last 获取最后一个错误 func (ec *ErrorCollection) Last() error { if len(ec.Errors) > 0 { return ec.Errors[len(ec.Errors)-1] } return nil } // FilterBySeverity 按严重程度过滤 func (ec *ErrorCollection) FilterBySeverity(severity ErrorSeverity) []error { var filtered []error for _, err := range ec.Errors { if sacredErr, ok := err.(*SacredError); ok { if sacredErr.Severity == severity { filtered = append(filtered, err) } } } return filtered } // FilterByCategory 按类别过滤 func (ec *ErrorCollection) FilterByCategory(category ErrorCategory) []error { var filtered []error for _, err := range ec.Errors { if sacredErr, ok := err.(*SacredError); ok { if sacredErr.Category == category { filtered = append(filtered, err) } } } return filtered } // 错误工厂函数 // NewSacredError 创建圣地错误 func NewSacredError(code, message string) *SacredError { return &SacredError{ Code: code, Message: message, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryBusiness, } } // NewSacredErrorWithCause 创建带原因的圣地错误 func NewSacredErrorWithCause(code, message string, cause error) *SacredError { return &SacredError{ Code: code, Message: message, Cause: cause, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategoryBusiness, } } // WrapError 包装错误 func WrapError(err error, code, message string) *SacredError { return &SacredError{ Code: code, Message: message, Cause: err, Timestamp: time.Now(), Details: make(map[string]interface{}), Context: make(map[string]string), Severity: ErrorSeverityError, Category: ErrorCategorySystem, } } // 错误检查函数 // IsValidationError 检查是否为验证错误 func IsValidationError(err error) bool { _, ok := err.(*ValidationError) return ok } // IsBusinessRuleError 检查是否为业务规则错误 func IsBusinessRuleError(err error) bool { _, ok := err.(*BusinessRuleError) return ok } // IsConcurrencyError 检查是否为并发错误 func IsConcurrencyError(err error) bool { _, ok := err.(*ConcurrencyError) return ok } // IsConfigurationError 检查是否为配置错误 func IsConfigurationError(err error) bool { _, ok := err.(*ConfigurationError) return ok } // IsSystemError 检查是否为系统错误 func IsSystemError(err error) bool { _, ok := err.(*SystemError) return ok } // IsPermissionError 检查是否为权限错误 func IsPermissionError(err error) bool { _, ok := err.(*PermissionError) return ok } // IsResourceError 检查是否为资源错误 func IsResourceError(err error) bool { _, ok := err.(*ResourceError) return ok } // IsSacredError 检查是否为圣地错误 func IsSacredError(err error) bool { _, ok := err.(*SacredError) return ok } // 错误分类函数 // IsRetryableError 检查错误是否可重试 func IsRetryableError(err error) bool { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.IsRetryable() } if sysErr, ok := err.(*SystemError); ok { return sysErr.CanRetry() } if concErr, ok := err.(*ConcurrencyError); ok { return concErr.CanRetry() } return false } // IsTemporaryError 检查错误是否为临时错误 func IsTemporaryError(err error) bool { switch err { case ErrServiceUnavailable, ErrTimeout, ErrResourceBusy: return true default: return IsRetryableError(err) } } // IsPermanentError 检查错误是否为永久错误 func IsPermanentError(err error) bool { switch err { case ErrPermissionDenied, ErrUnauthorized, ErrDataCorrupted: return true default: return IsValidationError(err) || IsConfigurationError(err) } } // IsCriticalError 检查错误是否为关键错误 func IsCriticalError(err error) bool { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.IsCritical() } return false } // 辅助函数 // GetErrorCode 获取错误代码 func GetErrorCode(err error) string { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.Code } return "UNKNOWN_ERROR" } // GetErrorSeverity 获取错误严重程度 func GetErrorSeverity(err error) ErrorSeverity { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.Severity } return ErrorSeverityError } // GetErrorCategory 获取错误类别 func GetErrorCategory(err error) ErrorCategory { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.Category } return ErrorCategorySystem } // GetErrorDetails 获取错误详情 func GetErrorDetails(err error) map[string]interface{} { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.Details } return nil } // GetErrorContext 获取错误上下文 func GetErrorContext(err error) map[string]string { if sacredErr, ok := err.(*SacredError); ok { return sacredErr.Context } return nil } // FormatError 格式化错误信息 func FormatError(err error) string { if err == nil { return "no error" } if sacredErr, ok := err.(*SacredError); ok { var parts []string parts = append(parts, fmt.Sprintf("Code: %s", sacredErr.Code)) parts = append(parts, fmt.Sprintf("Message: %s", sacredErr.Message)) parts = append(parts, fmt.Sprintf("Severity: %s", sacredErr.Severity.String())) parts = append(parts, fmt.Sprintf("Category: %s", sacredErr.Category.String())) parts = append(parts, fmt.Sprintf("Time: %s", sacredErr.Timestamp.Format(time.RFC3339))) if len(sacredErr.Details) > 0 { parts = append(parts, fmt.Sprintf("Details: %+v", sacredErr.Details)) } if len(sacredErr.Context) > 0 { parts = append(parts, fmt.Sprintf("Context: %+v", sacredErr.Context)) } if sacredErr.Cause != nil { parts = append(parts, fmt.Sprintf("Cause: %v", sacredErr.Cause)) } return strings.Join(parts, ", ") } return err.Error() } // CreateErrorResponse 创建错误响应 func CreateErrorResponse(err error) map[string]interface{} { response := map[string]interface{}{ "error": true, "message": err.Error(), "timestamp": time.Now(), } if sacredErr, ok := err.(*SacredError); ok { response["code"] = sacredErr.Code response["severity"] = sacredErr.Severity.String() response["category"] = sacredErr.Category.String() response["retryable"] = sacredErr.IsRetryable() if len(sacredErr.Details) > 0 { response["details"] = sacredErr.Details } if len(sacredErr.Context) > 0 { response["context"] = sacredErr.Context } } return response } ================================================ FILE: internal/domain/scene/sacred/events.go ================================================ package sacred import ( "fmt" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetVersion() int GetPayload() map[string]interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string EventType string AggregateID string OccurredAt time.Time Version int Payload map[string]interface{} } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetVersion 获取版本 func (e *BaseDomainEvent) GetVersion() int { return e.Version } // GetPayload 获取载荷 func (e *BaseDomainEvent) GetPayload() map[string]interface{} { return e.Payload } // SacredNameChangedEvent 圣地名称变更事件 type SacredNameChangedEvent struct { *BaseDomainEvent SacredID string OldName string NewName string } // NewSacredNameChangedEvent 创建圣地名称变更事件 func NewSacredNameChangedEvent(sacredID, oldName, newName string) *SacredNameChangedEvent { now := time.Now() event := &SacredNameChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_name_changed_%d", now.UnixNano()), EventType: "sacred.name_changed", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, OldName: oldName, NewName: newName, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["old_name"] = oldName event.Payload["new_name"] = newName return event } // SacredLevelUpEvent 圣地升级事件 type SacredLevelUpEvent struct { *BaseDomainEvent SacredID string OldLevel int NewLevel int Experience int Rewards map[string]interface{} } // NewSacredLevelUpEvent 创建圣地升级事件 func NewSacredLevelUpEvent(sacredID string, oldLevel, newLevel, experience int) *SacredLevelUpEvent { now := time.Now() event := &SacredLevelUpEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_level_up_%d", now.UnixNano()), EventType: "sacred.level_up", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, OldLevel: oldLevel, NewLevel: newLevel, Experience: experience, Rewards: make(map[string]interface{}), } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["old_level"] = oldLevel event.Payload["new_level"] = newLevel event.Payload["experience"] = experience event.Payload["level_gain"] = newLevel - oldLevel return event } // ChallengeAddedEvent 挑战添加事件 type ChallengeAddedEvent struct { *BaseDomainEvent SacredID string ChallengeID string ChallengeType ChallengeType Difficulty ChallengeDifficulty } // NewChallengeAddedEvent 创建挑战添加事件 func NewChallengeAddedEvent(sacredID, challengeID string, challengeType ChallengeType, difficulty ChallengeDifficulty) *ChallengeAddedEvent { now := time.Now() event := &ChallengeAddedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("challenge_added_%d", now.UnixNano()), EventType: "sacred.challenge_added", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, ChallengeID: challengeID, ChallengeType: challengeType, Difficulty: difficulty, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["challenge_id"] = challengeID event.Payload["challenge_type"] = challengeType.String() event.Payload["difficulty"] = difficulty.String() return event } // ChallengeRemovedEvent 挑战移除事件 type ChallengeRemovedEvent struct { *BaseDomainEvent SacredID string ChallengeID string ChallengeType ChallengeType Reason string } // NewChallengeRemovedEvent 创建挑战移除事件 func NewChallengeRemovedEvent(sacredID, challengeID string, challengeType ChallengeType) *ChallengeRemovedEvent { now := time.Now() event := &ChallengeRemovedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("challenge_removed_%d", now.UnixNano()), EventType: "sacred.challenge_removed", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, ChallengeID: challengeID, ChallengeType: challengeType, Reason: "manual_removal", } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["challenge_id"] = challengeID event.Payload["challenge_type"] = challengeType.String() event.Payload["reason"] = event.Reason return event } // ChallengeStartedEvent 挑战开始事件 type ChallengeStartedEvent struct { *BaseDomainEvent SacredID string ChallengeID string PlayerID string ChallengeType ChallengeType StartTime time.Time } // NewChallengeStartedEvent 创建挑战开始事件 func NewChallengeStartedEvent(sacredID, challengeID, playerID string, challengeType ChallengeType) *ChallengeStartedEvent { now := time.Now() event := &ChallengeStartedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("challenge_started_%d", now.UnixNano()), EventType: "sacred.challenge_started", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, ChallengeID: challengeID, PlayerID: playerID, ChallengeType: challengeType, StartTime: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["challenge_id"] = challengeID event.Payload["player_id"] = playerID event.Payload["challenge_type"] = challengeType.String() event.Payload["start_time"] = now return event } // ChallengeCompletedEvent 挑战完成事件 type ChallengeCompletedEvent struct { *BaseDomainEvent SacredID string ChallengeID string PlayerID string Success bool Score int Reward *ChallengeReward CompletionTime time.Time Duration time.Duration } // NewChallengeCompletedEvent 创建挑战完成事件 func NewChallengeCompletedEvent(sacredID, challengeID, playerID string, success bool, score int, reward *ChallengeReward) *ChallengeCompletedEvent { now := time.Now() event := &ChallengeCompletedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("challenge_completed_%d", now.UnixNano()), EventType: "sacred.challenge_completed", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, ChallengeID: challengeID, PlayerID: playerID, Success: success, Score: score, Reward: reward, CompletionTime: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["challenge_id"] = challengeID event.Payload["player_id"] = playerID event.Payload["success"] = success event.Payload["score"] = score event.Payload["completion_time"] = now if reward != nil { event.Payload["reward_gold"] = reward.Gold event.Payload["reward_experience"] = reward.Experience event.Payload["reward_items"] = len(reward.Items) } return event } // BlessingAddedEvent 祝福添加事件 type BlessingAddedEvent struct { *BaseDomainEvent SacredID string BlessingID string BlessingType BlessingType Duration time.Duration } // NewBlessingAddedEvent 创建祝福添加事件 func NewBlessingAddedEvent(sacredID, blessingID string, blessingType BlessingType, duration time.Duration) *BlessingAddedEvent { now := time.Now() event := &BlessingAddedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("blessing_added_%d", now.UnixNano()), EventType: "sacred.blessing_added", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, BlessingID: blessingID, BlessingType: blessingType, Duration: duration, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["blessing_id"] = blessingID event.Payload["blessing_type"] = blessingType.String() event.Payload["duration"] = duration.String() return event } // BlessingRemovedEvent 祝福移除事件 type BlessingRemovedEvent struct { *BaseDomainEvent SacredID string BlessingID string BlessingType BlessingType Reason string } // NewBlessingRemovedEvent 创建祝福移除事件 func NewBlessingRemovedEvent(sacredID, blessingID string, blessingType BlessingType) *BlessingRemovedEvent { now := time.Now() event := &BlessingRemovedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("blessing_removed_%d", now.UnixNano()), EventType: "sacred.blessing_removed", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, BlessingID: blessingID, BlessingType: blessingType, Reason: "manual_removal", } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["blessing_id"] = blessingID event.Payload["blessing_type"] = blessingType.String() event.Payload["reason"] = event.Reason return event } // BlessingActivatedEvent 祝福激活事件 type BlessingActivatedEvent struct { *BaseDomainEvent SacredID string BlessingID string PlayerID string BlessingType BlessingType Effect *BlessingEffect ActivatedAt time.Time ExpiresAt time.Time } // NewBlessingActivatedEvent 创建祝福激活事件 func NewBlessingActivatedEvent(sacredID, blessingID, playerID string, blessingType BlessingType, effect *BlessingEffect) *BlessingActivatedEvent { now := time.Now() event := &BlessingActivatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("blessing_activated_%d", now.UnixNano()), EventType: "sacred.blessing_activated", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, BlessingID: blessingID, PlayerID: playerID, BlessingType: blessingType, Effect: effect, ActivatedAt: now, ExpiresAt: effect.ExpiresAt, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["blessing_id"] = blessingID event.Payload["player_id"] = playerID event.Payload["blessing_type"] = blessingType.String() event.Payload["activated_at"] = now event.Payload["expires_at"] = effect.ExpiresAt event.Payload["duration"] = effect.ExpiresAt.Sub(now).String() return event } // SacredStatusChangedEvent 圣地状态变更事件 type SacredStatusChangedEvent struct { *BaseDomainEvent SacredID string OldStatus SacredStatus NewStatus SacredStatus Reason string } // NewSacredStatusChangedEvent 创建圣地状态变更事件 func NewSacredStatusChangedEvent(sacredID string, oldStatus, newStatus SacredStatus) *SacredStatusChangedEvent { now := time.Now() event := &SacredStatusChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_status_changed_%d", now.UnixNano()), EventType: "sacred.status_changed", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, OldStatus: oldStatus, NewStatus: newStatus, Reason: "status_update", } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["old_status"] = oldStatus.String() event.Payload["new_status"] = newStatus.String() event.Payload["reason"] = event.Reason return event } // RelicObtainedEvent 圣物获得事件 type RelicObtainedEvent struct { *BaseDomainEvent SacredID string PlayerID string RelicID string RelicType RelicType Rarity RelicRarity Source string ObtainedAt time.Time } // NewRelicObtainedEvent 创建圣物获得事件 func NewRelicObtainedEvent(sacredID, playerID, relicID string, relicType RelicType, rarity RelicRarity, source string) *RelicObtainedEvent { now := time.Now() event := &RelicObtainedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("relic_obtained_%d", now.UnixNano()), EventType: "sacred.relic_obtained", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, PlayerID: playerID, RelicID: relicID, RelicType: relicType, Rarity: rarity, Source: source, ObtainedAt: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["player_id"] = playerID event.Payload["relic_id"] = relicID event.Payload["relic_type"] = relicType.String() event.Payload["rarity"] = rarity.String() event.Payload["source"] = source event.Payload["obtained_at"] = now return event } // RelicUpgradedEvent 圣物升级事件 type RelicUpgradedEvent struct { *BaseDomainEvent SacredID string PlayerID string RelicID string OldLevel int NewLevel int OldPower float64 NewPower float64 UpgradedAt time.Time } // NewRelicUpgradedEvent 创建圣物升级事件 func NewRelicUpgradedEvent(sacredID, playerID, relicID string, oldLevel, newLevel int, oldPower, newPower float64) *RelicUpgradedEvent { now := time.Now() event := &RelicUpgradedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("relic_upgraded_%d", now.UnixNano()), EventType: "sacred.relic_upgraded", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, PlayerID: playerID, RelicID: relicID, OldLevel: oldLevel, NewLevel: newLevel, OldPower: oldPower, NewPower: newPower, UpgradedAt: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["player_id"] = playerID event.Payload["relic_id"] = relicID event.Payload["old_level"] = oldLevel event.Payload["new_level"] = newLevel event.Payload["level_gain"] = newLevel - oldLevel event.Payload["old_power"] = oldPower event.Payload["new_power"] = newPower event.Payload["power_gain"] = newPower - oldPower event.Payload["upgraded_at"] = now return event } // PlayerEnteredSacredEvent 玩家进入圣地事件 type PlayerEnteredSacredEvent struct { *BaseDomainEvent SacredID string PlayerID string EnteredAt time.Time Source string } // NewPlayerEnteredSacredEvent 创建玩家进入圣地事件 func NewPlayerEnteredSacredEvent(sacredID, playerID, source string) *PlayerEnteredSacredEvent { now := time.Now() event := &PlayerEnteredSacredEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("player_entered_sacred_%d", now.UnixNano()), EventType: "sacred.player_entered", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, PlayerID: playerID, EnteredAt: now, Source: source, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["player_id"] = playerID event.Payload["entered_at"] = now event.Payload["source"] = source return event } // PlayerLeftSacredEvent 玩家离开圣地事件 type PlayerLeftSacredEvent struct { *BaseDomainEvent SacredID string PlayerID string LeftAt time.Time Duration time.Duration Activities []string } // NewPlayerLeftSacredEvent 创建玩家离开圣地事件 func NewPlayerLeftSacredEvent(sacredID, playerID string, enteredAt time.Time, activities []string) *PlayerLeftSacredEvent { now := time.Now() duration := now.Sub(enteredAt) event := &PlayerLeftSacredEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("player_left_sacred_%d", now.UnixNano()), EventType: "sacred.player_left", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, PlayerID: playerID, LeftAt: now, Duration: duration, Activities: activities, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["player_id"] = playerID event.Payload["left_at"] = now event.Payload["duration"] = duration.String() event.Payload["activities"] = activities event.Payload["activity_count"] = len(activities) return event } // SacredMaintenanceEvent 圣地维护事件 type SacredMaintenanceEvent struct { *BaseDomainEvent SacredID string MaintenanceType string StartTime time.Time EstimatedEnd time.Time Reason string AffectedFeatures []string } // NewSacredMaintenanceEvent 创建圣地维护事件 func NewSacredMaintenanceEvent(sacredID, maintenanceType, reason string, duration time.Duration, affectedFeatures []string) *SacredMaintenanceEvent { now := time.Now() event := &SacredMaintenanceEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_maintenance_%d", now.UnixNano()), EventType: "sacred.maintenance", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, MaintenanceType: maintenanceType, StartTime: now, EstimatedEnd: now.Add(duration), Reason: reason, AffectedFeatures: affectedFeatures, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["maintenance_type"] = maintenanceType event.Payload["start_time"] = now event.Payload["estimated_end"] = event.EstimatedEnd event.Payload["duration"] = duration.String() event.Payload["reason"] = reason event.Payload["affected_features"] = affectedFeatures event.Payload["affected_count"] = len(affectedFeatures) return event } // SacredAchievementUnlockedEvent 圣地成就解锁事件 type SacredAchievementUnlockedEvent struct { *BaseDomainEvent SacredID string PlayerID string AchievementID string AchievementName string Description string Rewards map[string]interface{} UnlockedAt time.Time } // NewSacredAchievementUnlockedEvent 创建圣地成就解锁事件 func NewSacredAchievementUnlockedEvent(sacredID, playerID, achievementID, achievementName, description string, rewards map[string]interface{}) *SacredAchievementUnlockedEvent { now := time.Now() event := &SacredAchievementUnlockedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_achievement_unlocked_%d", now.UnixNano()), EventType: "sacred.achievement_unlocked", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, PlayerID: playerID, AchievementID: achievementID, AchievementName: achievementName, Description: description, Rewards: rewards, UnlockedAt: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["player_id"] = playerID event.Payload["achievement_id"] = achievementID event.Payload["achievement_name"] = achievementName event.Payload["description"] = description event.Payload["rewards"] = rewards event.Payload["unlocked_at"] = now return event } // SacredSeasonChangedEvent 圣地季节变化事件 type SacredSeasonChangedEvent struct { *BaseDomainEvent SacredID string OldSeason string NewSeason string SeasonEffects map[string]interface{} ChangedAt time.Time } // NewSacredSeasonChangedEvent 创建圣地季节变化事件 func NewSacredSeasonChangedEvent(sacredID, oldSeason, newSeason string, seasonEffects map[string]interface{}) *SacredSeasonChangedEvent { now := time.Now() event := &SacredSeasonChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_season_changed_%d", now.UnixNano()), EventType: "sacred.season_changed", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, OldSeason: oldSeason, NewSeason: newSeason, SeasonEffects: seasonEffects, ChangedAt: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["old_season"] = oldSeason event.Payload["new_season"] = newSeason event.Payload["season_effects"] = seasonEffects event.Payload["changed_at"] = now return event } // SacredRankingUpdatedEvent 圣地排名更新事件 type SacredRankingUpdatedEvent struct { *BaseDomainEvent SacredID string RankingType string OldRank int NewRank int Score float64 UpdatedAt time.Time } // NewSacredRankingUpdatedEvent 创建圣地排名更新事件 func NewSacredRankingUpdatedEvent(sacredID, rankingType string, oldRank, newRank int, score float64) *SacredRankingUpdatedEvent { now := time.Now() event := &SacredRankingUpdatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("sacred_ranking_updated_%d", now.UnixNano()), EventType: "sacred.ranking_updated", AggregateID: sacredID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SacredID: sacredID, RankingType: rankingType, OldRank: oldRank, NewRank: newRank, Score: score, UpdatedAt: now, } // 设置载荷 event.Payload["sacred_id"] = sacredID event.Payload["ranking_type"] = rankingType event.Payload["old_rank"] = oldRank event.Payload["new_rank"] = newRank event.Payload["rank_change"] = newRank - oldRank event.Payload["score"] = score event.Payload["updated_at"] = now return event } // 事件处理器接口 // EventHandler 事件处理器接口 type EventHandler interface { Handle(event DomainEvent) error CanHandle(eventType string) bool GetHandlerName() string } // EventBus 事件总线接口 type EventBus interface { Publish(event DomainEvent) error PublishBatch(events []DomainEvent) error Subscribe(eventType string, handler EventHandler) error Unsubscribe(eventType string, handler EventHandler) error GetSubscribers(eventType string) []EventHandler } // EventStore 事件存储接口 type EventStore interface { Save(event DomainEvent) error SaveBatch(events []DomainEvent) error Load(aggregateID string) ([]DomainEvent, error) LoadFromVersion(aggregateID string, version int) ([]DomainEvent, error) LoadByEventType(eventType string, limit int) ([]DomainEvent, error) LoadByTimeRange(startTime, endTime time.Time) ([]DomainEvent, error) LoadByAggregateType(aggregateType string, limit int) ([]DomainEvent, error) Delete(eventID string) error DeleteByAggregate(aggregateID string) error Count() (int64, error) CountByEventType(eventType string) (int64, error) CountByAggregate(aggregateID string) (int64, error) } // EventProjector 事件投影器接口 type EventProjector interface { Project(event DomainEvent) error ProjectBatch(events []DomainEvent) error Rebuild(aggregateID string) error GetProjectionName() string GetLastProcessedVersion(aggregateID string) (int, error) SetLastProcessedVersion(aggregateID string, version int) error } // EventSnapshot 事件快照接口 type EventSnapshot interface { SaveSnapshot(aggregateID string, version int, data interface{}) error LoadSnapshot(aggregateID string) (interface{}, int, error) DeleteSnapshot(aggregateID string) error GetSnapshotFrequency() int ShouldCreateSnapshot(aggregateID string, currentVersion int) bool } // 事件中间件 // EventMiddleware 事件中间件接口 type EventMiddleware interface { Before(event DomainEvent) error After(event DomainEvent) error OnError(event DomainEvent, err error) error } // EventValidator 事件验证器 type EventValidator struct { rules map[string][]ValidationRule } // ValidationRule 验证规则 type ValidationRule interface { Validate(event DomainEvent) error GetRuleName() string } // NewEventValidator 创建事件验证器 func NewEventValidator() *EventValidator { return &EventValidator{ rules: make(map[string][]ValidationRule), } } // AddRule 添加验证规则 func (ev *EventValidator) AddRule(eventType string, rule ValidationRule) { if ev.rules[eventType] == nil { ev.rules[eventType] = make([]ValidationRule, 0) } ev.rules[eventType] = append(ev.rules[eventType], rule) } // Validate 验证事件 func (ev *EventValidator) Validate(event DomainEvent) error { rules, exists := ev.rules[event.GetEventType()] if !exists { return nil // 没有规则则通过 } for _, rule := range rules { if err := rule.Validate(event); err != nil { return fmt.Errorf("validation failed for rule %s: %w", rule.GetRuleName(), err) } } return nil } // EventMetrics 事件指标 type EventMetrics struct { EventType string Count int64 LastOccurred time.Time AverageSize float64 ProcessingTime time.Duration } // EventMonitor 事件监控器 type EventMonitor interface { RecordEvent(event DomainEvent, processingTime time.Duration) GetMetrics(eventType string) (*EventMetrics, error) GetAllMetrics() (map[string]*EventMetrics, error) Reset() error } ================================================ FILE: internal/domain/scene/sacred/repository.go ================================================ package sacred import ( "math" "time" ) // SacredPlaceRepository 圣地仓储接口 type SacredPlaceRepository interface { // 基础CRUD操作 Save(sacredPlace *SacredPlaceAggregate) error FindByID(id string) (*SacredPlaceAggregate, error) FindByOwner(ownerID string) ([]*SacredPlaceAggregate, error) Update(sacredPlace *SacredPlaceAggregate) error Delete(id string) error // 查询操作 FindByStatus(status SacredStatus) ([]*SacredPlaceAggregate, error) FindByLevel(minLevel, maxLevel int) ([]*SacredPlaceAggregate, error) FindByName(name string) ([]*SacredPlaceAggregate, error) FindActive() ([]*SacredPlaceAggregate, error) // 分页查询 FindWithPagination(query *SacredPlaceQuery) (*SacredPlacePageResult, error) // 统计操作 Count() (int64, error) CountByStatus(status SacredStatus) (int64, error) CountByOwner(ownerID string) (int64, error) // 批量操作 SaveBatch(sacredPlaces []*SacredPlaceAggregate) error DeleteBatch(ids []string) error // 高级查询 FindNearby(location *Location, radius float64) ([]*SacredPlaceAggregate, error) FindTopByLevel(limit int) ([]*SacredPlaceAggregate, error) FindRecentlyActive(since time.Time) ([]*SacredPlaceAggregate, error) } // ChallengeRepository 挑战仓储接口 type ChallengeRepository interface { // 基础CRUD操作 Save(challenge *Challenge) error FindByID(id string) (*Challenge, error) FindBySacredPlace(sacredPlaceID string) ([]*Challenge, error) Update(challenge *Challenge) error Delete(id string) error // 查询操作 FindByType(challengeType ChallengeType) ([]*Challenge, error) FindByDifficulty(difficulty ChallengeDifficulty) ([]*Challenge, error) FindByStatus(status ChallengeStatus) ([]*Challenge, error) FindAvailable() ([]*Challenge, error) FindCompleted(playerID string) ([]*Challenge, error) // 分页查询 FindWithPagination(query *ChallengeQuery) (*ChallengePageResult, error) // 统计操作 Count() (int64, error) CountByType(challengeType ChallengeType) (int64, error) CountByDifficulty(difficulty ChallengeDifficulty) (int64, error) CountCompleted(playerID string) (int64, error) // 参与者相关 FindParticipants(challengeID string) ([]*ChallengeParticipant, error) SaveParticipant(participant *ChallengeParticipant) error UpdateParticipant(participant *ChallengeParticipant) error // 排行榜 GetLeaderboard(challengeID string, limit int) ([]*ChallengeParticipant, error) GetPlayerRanking(challengeID, playerID string) (int, error) } // BlessingRepository 祝福仓储接口 type BlessingRepository interface { // 基础CRUD操作 Save(blessing *Blessing) error FindByID(id string) (*Blessing, error) FindBySacredPlace(sacredPlaceID string) ([]*Blessing, error) Update(blessing *Blessing) error Delete(id string) error // 查询操作 FindByType(blessingType BlessingType) ([]*Blessing, error) FindByStatus(status BlessingStatus) ([]*Blessing, error) FindAvailable() ([]*Blessing, error) FindActive(playerID string) ([]*Blessing, error) // 分页查询 FindWithPagination(query *BlessingQuery) (*BlessingPageResult, error) // 统计操作 Count() (int64, error) CountByType(blessingType BlessingType) (int64, error) CountActive(playerID string) (int64, error) // 效果相关 SaveBlessingEffect(effect *BlessingEffect) error FindEffectsByPlayer(playerID string) ([]*BlessingEffect, error) DeleteExpiredEffects() error } // RelicRepository 圣物仓储接口 type RelicRepository interface { // 基础CRUD操作 Save(relic *SacredRelic) error FindByID(id string) (*SacredRelic, error) FindByOwner(ownerID string) ([]*SacredRelic, error) Update(relic *SacredRelic) error Delete(id string) error // 查询操作 FindByType(relicType RelicType) ([]*SacredRelic, error) FindByRarity(rarity RelicRarity) ([]*SacredRelic, error) FindByLevel(minLevel, maxLevel int) ([]*SacredRelic, error) // 分页查询 FindWithPagination(query *RelicQuery) (*RelicPageResult, error) // 统计操作 Count() (int64, error) CountByType(relicType RelicType) (int64, error) CountByRarity(rarity RelicRarity) (int64, error) CountByOwner(ownerID string) (int64, error) // 高级查询 FindTopByPower(limit int) ([]*SacredRelic, error) FindByAttributes(attributes map[string]float64) ([]*SacredRelic, error) FindUpgradeable(ownerID string) ([]*SacredRelic, error) } // SacredStatisticsRepository 圣地统计仓储接口 type SacredStatisticsRepository interface { // 保存统计数据 SaveStatistics(stats *SacredStatistics) error UpdateStatistics(stats *SacredStatistics) error // 查询统计数据 FindStatistics(sacredID string) (*SacredStatistics, error) FindStatisticsByOwner(ownerID string) ([]*SacredStatistics, error) // 排行榜统计 GetLevelRanking(limit int) ([]*SacredStatistics, error) GetExperienceRanking(limit int) ([]*SacredStatistics, error) GetChallengeRanking(limit int) ([]*SacredStatistics, error) // 趋势分析 GetLevelTrend(sacredID string, days int) ([]*LevelTrendData, error) GetActivityTrend(sacredID string, days int) ([]*ActivityTrendData, error) // 聚合统计 GetGlobalStatistics() (*GlobalSacredStatistics, error) GetOwnerStatistics(ownerID string) (*OwnerSacredStatistics, error) } // 查询条件结构体 // SacredPlaceQuery 圣地查询条件 type SacredPlaceQuery struct { OwnerID string Name string Status *SacredStatus MinLevel *int MaxLevel *int CreatedAfter *time.Time CreatedBefore *time.Time UpdatedAfter *time.Time UpdatedBefore *time.Time Location *Location Radius *float64 OrderBy string OrderDesc bool Offset int Limit int } // ChallengeQuery 挑战查询条件 type ChallengeQuery struct { SacredPlaceID string Type *ChallengeType Difficulty *ChallengeDifficulty Status *ChallengeStatus MinLevel *int MaxLevel *int PlayerID string CreatedAfter *time.Time CreatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // BlessingQuery 祝福查询条件 type BlessingQuery struct { SacredPlaceID string Type *BlessingType Status *BlessingStatus PlayerID string ActiveOnly bool AvailableOnly bool CreatedAfter *time.Time CreatedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // RelicQuery 圣物查询条件 type RelicQuery struct { OwnerID string Type *RelicType Rarity *RelicRarity MinLevel *int MaxLevel *int MinPower *float64 MaxPower *float64 Attributes map[string]float64 ObtainedAfter *time.Time ObtainedBefore *time.Time OrderBy string OrderDesc bool Offset int Limit int } // 分页结果结构体 // SacredPlacePageResult 圣地分页结果 type SacredPlacePageResult struct { Items []*SacredPlaceAggregate Total int64 Offset int Limit int HasMore bool } // ChallengePageResult 挑战分页结果 type ChallengePageResult struct { Items []*Challenge Total int64 Offset int Limit int HasMore bool } // BlessingPageResult 祝福分页结果 type BlessingPageResult struct { Items []*Blessing Total int64 Offset int Limit int HasMore bool } // RelicPageResult 圣物分页结果 type RelicPageResult struct { Items []*SacredRelic Total int64 Offset int Limit int HasMore bool } // 统计数据结构体 // LevelTrendData 等级趋势数据 type LevelTrendData struct { Date time.Time Level int Experience int Growth int } // ActivityTrendData 活动趋势数据 type ActivityTrendData struct { Date time.Time ChallengesStarted int ChallengesCompleted int BlessingsActivated int PlayersActive int } // GlobalSacredStatistics 全局圣地统计 type GlobalSacredStatistics struct { TotalSacredPlaces int64 ActiveSacredPlaces int64 TotalChallenges int64 CompletedChallenges int64 TotalBlessings int64 ActiveBlessings int64 TotalRelics int64 AverageLevel float64 TopLevel int MostActiveSacred string MostPopularChallenge ChallengeType MostUsedBlessing BlessingType UpdatedAt time.Time } // OwnerSacredStatistics 拥有者圣地统计 type OwnerSacredStatistics struct { OwnerID string TotalSacredPlaces int ActiveSacredPlaces int TotalLevel int AverageLevel float64 HighestLevel int TotalChallenges int CompletedChallenges int SuccessRate float64 TotalBlessings int ActiveBlessings int TotalRelics int TotalExperience int64 Ranking int LastActiveAt time.Time CreatedAt time.Time } // Location 位置信息 type Location struct { Latitude float64 Longitude float64 Region string Zone string } // NewLocation 创建位置 func NewLocation(latitude, longitude float64, region, zone string) *Location { return &Location{ Latitude: latitude, Longitude: longitude, Region: region, Zone: zone, } } // DistanceTo 计算到另一个位置的距离 func (l *Location) DistanceTo(other *Location) float64 { // 简化的距离计算(实际应用中可能需要更精确的地理计算) dx := l.Latitude - other.Latitude dy := l.Longitude - other.Longitude return math.Sqrt(dx*dx + dy*dy) } // IsWithinRadius 检查是否在指定半径内 func (l *Location) IsWithinRadius(center *Location, radius float64) bool { return l.DistanceTo(center) <= radius } // 缓存接口 // SacredCacheRepository 圣地缓存仓储接口 type SacredCacheRepository interface { // 圣地缓存 SetSacredPlace(id string, sacredPlace *SacredPlaceAggregate, ttl time.Duration) error GetSacredPlace(id string) (*SacredPlaceAggregate, error) DeleteSacredPlace(id string) error // 挑战缓存 SetChallenge(id string, challenge *Challenge, ttl time.Duration) error GetChallenge(id string) (*Challenge, error) DeleteChallenge(id string) error // 祝福缓存 SetBlessing(id string, blessing *Blessing, ttl time.Duration) error GetBlessing(id string) (*Blessing, error) DeleteBlessing(id string) error // 圣物缓存 SetRelic(id string, relic *SacredRelic, ttl time.Duration) error GetRelic(id string) (*SacredRelic, error) DeleteRelic(id string) error // 统计缓存 SetStatistics(key string, stats interface{}, ttl time.Duration) error GetStatistics(key string, result interface{}) error DeleteStatistics(key string) error // 排行榜缓存 SetRanking(key string, ranking interface{}, ttl time.Duration) error GetRanking(key string, result interface{}) error DeleteRanking(key string) error // 批量操作 SetBatch(items map[string]interface{}, ttl time.Duration) error GetBatch(keys []string) (map[string]interface{}, error) DeleteBatch(keys []string) error // 缓存管理 Clear() error Exists(key string) (bool, error) SetTTL(key string, ttl time.Duration) error GetTTL(key string) (time.Duration, error) } // 事务接口 // SacredTransactionRepository 圣地事务仓储接口 type SacredTransactionRepository interface { // 事务管理 BeginTransaction() (SacredTransaction, error) CommitTransaction(tx SacredTransaction) error RollbackTransaction(tx SacredTransaction) error // 在事务中执行操作 ExecuteInTransaction(fn func(tx SacredTransaction) error) error } // SacredTransaction 圣地事务接口 type SacredTransaction interface { // 圣地操作 SaveSacredPlace(sacredPlace *SacredPlaceAggregate) error UpdateSacredPlace(sacredPlace *SacredPlaceAggregate) error DeleteSacredPlace(id string) error // 挑战操作 SaveChallenge(challenge *Challenge) error UpdateChallenge(challenge *Challenge) error DeleteChallenge(id string) error // 祝福操作 SaveBlessing(blessing *Blessing) error UpdateBlessing(blessing *Blessing) error DeleteBlessing(id string) error // 圣物操作 SaveRelic(relic *SacredRelic) error UpdateRelic(relic *SacredRelic) error DeleteRelic(id string) error // 统计操作 UpdateStatistics(stats *SacredStatistics) error // 事务状态 IsActive() bool GetID() string } // 仓储工厂接口 // SacredRepositoryFactory 圣地仓储工厂接口 type SacredRepositoryFactory interface { // 创建仓储实例 CreateSacredPlaceRepository() SacredPlaceRepository CreateChallengeRepository() ChallengeRepository CreateBlessingRepository() BlessingRepository CreateRelicRepository() RelicRepository CreateStatisticsRepository() SacredStatisticsRepository CreateCacheRepository() SacredCacheRepository CreateTransactionRepository() SacredTransactionRepository // 健康检查 HealthCheck() error // 关闭连接 Close() error } ================================================ FILE: internal/domain/scene/sacred/service.go ================================================ package sacred import ( "fmt" "math" "math/rand" "time" ) // SacredService 圣地领域服务 type SacredService struct { challengeTemplates map[ChallengeType]*ChallengeTemplate blessingTemplates map[BlessingType]*BlessingTemplate relicTemplates map[RelicType][]*RelicTemplate difficultyCurves map[ChallengeDifficulty]*DifficultyCurve rewardCalculator *RewardCalculator balanceRules *BalanceRules } // NewSacredService 创建圣地服务 func NewSacredService() *SacredService { service := &SacredService{ challengeTemplates: make(map[ChallengeType]*ChallengeTemplate), blessingTemplates: make(map[BlessingType]*BlessingTemplate), relicTemplates: make(map[RelicType][]*RelicTemplate), difficultyCurves: make(map[ChallengeDifficulty]*DifficultyCurve), rewardCalculator: NewRewardCalculator(), balanceRules: NewBalanceRules(), } // 初始化默认模板和规则 service.initializeDefaultTemplates() service.initializeDifficultyCurves() return service } // CreateSacredPlace 创建圣地 func (s *SacredService) CreateSacredPlace(id, name, description, owner string) (*SacredPlaceAggregate, error) { if id == "" || name == "" || owner == "" { return nil, fmt.Errorf("invalid parameters for sacred place creation") } sacredPlace := NewSacredPlaceAggregate(id, name, description, owner) // 添加默认挑战 defaultChallenges := s.generateDefaultChallenges(sacredPlace.GetLevel().Level) for _, challenge := range defaultChallenges { sacredPlace.AddChallenge(challenge) } // 添加默认祝福 defaultBlessings := s.generateDefaultBlessings(sacredPlace.GetLevel().Level) for _, blessing := range defaultBlessings { sacredPlace.AddBlessing(blessing) } return sacredPlace, nil } // GenerateChallenge 生成挑战 func (s *SacredService) GenerateChallenge(challengeType ChallengeType, difficulty ChallengeDifficulty, sacredLevel int) (*Challenge, error) { template, exists := s.challengeTemplates[challengeType] if !exists { return nil, fmt.Errorf("challenge template not found for type: %s", challengeType.String()) } // 生成唯一ID id := fmt.Sprintf("challenge_%s_%s_%d", challengeType.String(), difficulty.String(), time.Now().UnixNano()) // 根据模板创建挑战 challenge := NewChallenge( id, template.GenerateName(difficulty), template.GenerateDescription(difficulty), challengeType, difficulty, difficulty.GetRequiredLevel(), ) // 设置持续时间和冷却时间 challenge.SetDuration(template.GetDuration(difficulty)) challenge.SetCooldown(template.GetCooldown(difficulty)) // 添加条件 conditions := template.GenerateConditions(difficulty, sacredLevel) for _, condition := range conditions { challenge.AddCondition(condition) } return challenge, nil } // GenerateBlessing 生成祝福 func (s *SacredService) GenerateBlessing(blessingType BlessingType, sacredLevel int) (*Blessing, error) { template, exists := s.blessingTemplates[blessingType] if !exists { return nil, fmt.Errorf("blessing template not found for type: %s", blessingType.String()) } // 生成唯一ID id := fmt.Sprintf("blessing_%s_%d", blessingType.String(), time.Now().UnixNano()) // 根据模板创建祝福 blessing := NewBlessing( id, template.GenerateName(sacredLevel), template.GenerateDescription(sacredLevel), blessingType, template.GetDuration(sacredLevel), ) // 设置冷却时间和最大使用次数 blessing.SetCooldown(template.GetCooldown(sacredLevel)) blessing.SetMaxUsage(template.GetMaxUsage(sacredLevel)) // 添加效果 effects := template.GenerateEffects(sacredLevel) for _, effect := range effects { blessing.AddEffect(effect) } return blessing, nil } // GenerateRelic 生成圣物 func (s *SacredService) GenerateRelic(relicType RelicType, rarity RelicRarity) (*SacredRelic, error) { templates, exists := s.relicTemplates[relicType] if !exists || len(templates) == 0 { return nil, fmt.Errorf("relic templates not found for type: %s", relicType.String()) } // 随机选择模板 template := templates[rand.Intn(len(templates))] // 生成唯一ID id := fmt.Sprintf("relic_%s_%s_%d", relicType.String(), rarity.String(), time.Now().UnixNano()) // 根据模板创建圣物 relic := NewSacredRelic( id, template.GenerateName(rarity), template.GenerateDescription(rarity), relicType, rarity, ) // 添加属性 attributes := template.GenerateAttributes(rarity) for name, value := range attributes { relic.AddAttribute(name, value) } // 添加效果 effects := template.GenerateEffects(rarity) for _, effect := range effects { relic.AddEffect(effect) } // 添加需求 requirements := template.GenerateRequirements(rarity) for name, value := range requirements { relic.AddRequirement(name, value) } return relic, nil } // CalculateChallengeReward 计算挑战奖励 func (s *SacredService) CalculateChallengeReward(challengeType ChallengeType, difficulty ChallengeDifficulty, success bool, score int, playerLevel int) *ChallengeReward { return s.rewardCalculator.CalculateChallengeReward(challengeType, difficulty, success, score, playerLevel) } // CalculateBlessingEffect 计算祝福效果 func (s *SacredService) CalculateBlessingEffect(blessingType BlessingType, sacredLevel int, playerLevel int) *BlessingEffect { effect := NewBlessingEffect( fmt.Sprintf("blessing_effect_%d", time.Now().UnixNano()), "", // playerID will be set when activated blessingType, time.Hour, // default duration ) // 根据类型计算效果 switch blessingType { case BlessingTypeAttribute: effect.AddAttribute("strength", float64(sacredLevel*5+playerLevel)) effect.AddAttribute("agility", float64(sacredLevel*3+playerLevel/2)) case BlessingTypeSkill: effect.AddModifier("skill_damage", 1.0+float64(sacredLevel)*0.1) effect.AddModifier("skill_cooldown", 0.9-float64(sacredLevel)*0.05) case BlessingTypeExperience: effect.AddModifier("exp_multiplier", 1.0+float64(sacredLevel)*0.2) case BlessingTypeWealth: effect.AddModifier("gold_multiplier", 1.0+float64(sacredLevel)*0.15) case BlessingTypeProtection: effect.AddAttribute("defense", float64(sacredLevel*10+playerLevel*2)) effect.AddModifier("damage_reduction", float64(sacredLevel)*0.05) case BlessingTypeHealing: effect.AddAttribute("health_regen", float64(sacredLevel*2+playerLevel/5)) effect.AddModifier("healing_received", 1.0+float64(sacredLevel)*0.1) case BlessingTypeSpeed: effect.AddAttribute("movement_speed", float64(sacredLevel*5)) effect.AddModifier("action_speed", 1.0+float64(sacredLevel)*0.08) case BlessingTypeLuck: effect.AddAttribute("luck", float64(sacredLevel*3+playerLevel/3)) effect.AddModifier("critical_chance", float64(sacredLevel)*0.02) } return effect } // ValidateChallenge 验证挑战 func (s *SacredService) ValidateChallenge(challenge *Challenge, playerData map[string]interface{}) error { if challenge == nil { return fmt.Errorf("challenge is nil") } // 检查挑战状态 if !challenge.CanStart() { return fmt.Errorf("challenge cannot be started") } // 检查玩家等级 playerLevel, ok := playerData["level"].(int) if !ok || playerLevel < challenge.GetRequiredLevel() { return fmt.Errorf("insufficient player level") } // 检查挑战条件 if !challenge.CheckConditions(playerData) { return fmt.Errorf("challenge conditions not met") } return nil } // ValidateBlessing 验证祝福 func (s *SacredService) ValidateBlessing(blessing *Blessing, playerData map[string]interface{}) error { if blessing == nil { return fmt.Errorf("blessing is nil") } // 检查祝福状态 if !blessing.IsAvailable() { return fmt.Errorf("blessing is not available") } // 检查平衡规则 if !s.balanceRules.CanActivateBlessing(blessing.GetType(), playerData) { return fmt.Errorf("blessing activation violates balance rules") } return nil } // CalculateOptimalDifficulty 计算最佳难度 func (s *SacredService) CalculateOptimalDifficulty(playerLevel int, playerSkill float64) ChallengeDifficulty { // 基于玩家等级和技能计算推荐难度 baseScore := float64(playerLevel) + playerSkill*10 if baseScore < 20 { return ChallengeDifficultyEasy } else if baseScore < 50 { return ChallengeDifficultyNormal } else if baseScore < 100 { return ChallengeDifficultyHard } else if baseScore < 200 { return ChallengeDifficultyExpert } else { return ChallengeDifficultyLegendary } } // GetRecommendedChallenges 获取推荐挑战 func (s *SacredService) GetRecommendedChallenges(playerData map[string]interface{}, sacredLevel int) []*Challenge { playerLevel, _ := playerData["level"].(int) playerSkill, _ := playerData["skill"].(float64) optimalDifficulty := s.CalculateOptimalDifficulty(playerLevel, playerSkill) var recommendations []*Challenge // 为每种挑战类型生成推荐 for challengeType := ChallengeTypeCombat; challengeType <= ChallengeTypeSpecial; challengeType++ { if challenge, err := s.GenerateChallenge(challengeType, optimalDifficulty, sacredLevel); err == nil { recommendations = append(recommendations, challenge) } } return recommendations } // GetAvailableBlessings 获取可用祝福 func (s *SacredService) GetAvailableBlessings(playerData map[string]interface{}, sacredLevel int) []*Blessing { var available []*Blessing // 为每种祝福类型生成可用祝福 for blessingType := BlessingTypeAttribute; blessingType <= BlessingTypeLuck; blessingType++ { if blessing, err := s.GenerateBlessing(blessingType, sacredLevel); err == nil { if s.ValidateBlessing(blessing, playerData) == nil { available = append(available, blessing) } } } return available } // 私有方法 // generateDefaultChallenges 生成默认挑战 func (s *SacredService) generateDefaultChallenges(sacredLevel int) []*Challenge { var challenges []*Challenge // 根据圣地等级生成适当的挑战 difficulties := []ChallengeDifficulty{ChallengeDifficultyEasy, ChallengeDifficultyNormal} if sacredLevel >= 10 { difficulties = append(difficulties, ChallengeDifficultyHard) } if sacredLevel >= 25 { difficulties = append(difficulties, ChallengeDifficultyExpert) } if sacredLevel >= 50 { difficulties = append(difficulties, ChallengeDifficultyLegendary) } // 为每种难度生成战斗挑战 for _, difficulty := range difficulties { if challenge, err := s.GenerateChallenge(ChallengeTypeCombat, difficulty, sacredLevel); err == nil { challenges = append(challenges, challenge) } } return challenges } // generateDefaultBlessings 生成默认祝福 func (s *SacredService) generateDefaultBlessings(sacredLevel int) []*Blessing { var blessings []*Blessing // 生成基础祝福 basicTypes := []BlessingType{BlessingTypeAttribute, BlessingTypeExperience, BlessingTypeWealth} for _, blessingType := range basicTypes { if blessing, err := s.GenerateBlessing(blessingType, sacredLevel); err == nil { blessings = append(blessings, blessing) } } // 根据等级解锁高级祝福 if sacredLevel >= 10 { advancedTypes := []BlessingType{BlessingTypeProtection, BlessingTypeHealing} for _, blessingType := range advancedTypes { if blessing, err := s.GenerateBlessing(blessingType, sacredLevel); err == nil { blessings = append(blessings, blessing) } } } if sacredLevel >= 25 { specialTypes := []BlessingType{BlessingTypeSpeed, BlessingTypeLuck} for _, blessingType := range specialTypes { if blessing, err := s.GenerateBlessing(blessingType, sacredLevel); err == nil { blessings = append(blessings, blessing) } } } return blessings } // initializeDefaultTemplates 初始化默认模板 func (s *SacredService) initializeDefaultTemplates() { // 初始化挑战模板 s.challengeTemplates[ChallengeTypeCombat] = NewChallengeTemplate( "战斗挑战", "测试战斗技巧的挑战", time.Minute*30, time.Hour*6, ) s.challengeTemplates[ChallengeTypePuzzle] = NewChallengeTemplate( "解谜挑战", "需要智慧解决的谜题", time.Minute*15, time.Hour*4, ) s.challengeTemplates[ChallengeTypeEndurance] = NewChallengeTemplate( "耐力挑战", "考验持久力的挑战", time.Hour, time.Hour*12, ) // 初始化祝福模板 s.blessingTemplates[BlessingTypeAttribute] = NewBlessingTemplate( "属性祝福", "提升基础属性", time.Hour*2, time.Hour*24, 3, ) s.blessingTemplates[BlessingTypeExperience] = NewBlessingTemplate( "经验祝福", "增加经验获取", time.Hour, time.Hour*12, 5, ) // 初始化圣物模板 s.initializeRelicTemplates() } // initializeRelicTemplates 初始化圣物模板 func (s *SacredService) initializeRelicTemplates() { // 武器模板 weaponTemplates := []*RelicTemplate{ NewRelicTemplate("圣剑", "神圣的武器", []string{"attack", "critical"}, []string{"增加攻击力", "提高暴击率"}), NewRelicTemplate("法杖", "魔法武器", []string{"magic_power", "mana"}, []string{"增加魔法攻击", "提高法力值"}), } s.relicTemplates[RelicTypeWeapon] = weaponTemplates // 护甲模板 armorTemplates := []*RelicTemplate{ NewRelicTemplate("圣甲", "神圣的护甲", []string{"defense", "health"}, []string{"增加防御力", "提高生命值"}), NewRelicTemplate("法袍", "魔法护甲", []string{"magic_defense", "mana_regen"}, []string{"增加魔法防御", "提高法力回复"}), } s.relicTemplates[RelicTypeArmor] = armorTemplates // 饰品模板 accessoryTemplates := []*RelicTemplate{ NewRelicTemplate("圣环", "神圣的戒指", []string{"luck", "experience"}, []string{"增加幸运值", "提高经验获取"}), NewRelicTemplate("护符", "保护饰品", []string{"resistance", "health_regen"}, []string{"增加抗性", "提高生命回复"}), } s.relicTemplates[RelicTypeAccessory] = accessoryTemplates } // initializeDifficultyCurves 初始化难度曲线 func (s *SacredService) initializeDifficultyCurves() { s.difficultyCurves[ChallengeDifficultyEasy] = &DifficultyCurve{ HealthMultiplier: 0.5, DamageMultiplier: 0.7, SpeedMultiplier: 0.8, RewardMultiplier: 0.5, ExpMultiplier: 0.3, } s.difficultyCurves[ChallengeDifficultyNormal] = &DifficultyCurve{ HealthMultiplier: 1.0, DamageMultiplier: 1.0, SpeedMultiplier: 1.0, RewardMultiplier: 1.0, ExpMultiplier: 1.0, } s.difficultyCurves[ChallengeDifficultyHard] = &DifficultyCurve{ HealthMultiplier: 1.5, DamageMultiplier: 1.3, SpeedMultiplier: 1.2, RewardMultiplier: 1.5, ExpMultiplier: 1.8, } s.difficultyCurves[ChallengeDifficultyExpert] = &DifficultyCurve{ HealthMultiplier: 2.0, DamageMultiplier: 1.8, SpeedMultiplier: 1.5, RewardMultiplier: 2.5, ExpMultiplier: 3.0, } s.difficultyCurves[ChallengeDifficultyLegendary] = &DifficultyCurve{ HealthMultiplier: 3.0, DamageMultiplier: 2.5, SpeedMultiplier: 2.0, RewardMultiplier: 5.0, ExpMultiplier: 8.0, } } // 辅助结构体 // ChallengeTemplate 挑战模板 type ChallengeTemplate struct { Name string Description string BaseDuration time.Duration BaseCooldown time.Duration } // NewChallengeTemplate 创建挑战模板 func NewChallengeTemplate(name, description string, baseDuration, baseCooldown time.Duration) *ChallengeTemplate { return &ChallengeTemplate{ Name: name, Description: description, BaseDuration: baseDuration, BaseCooldown: baseCooldown, } } // GenerateName 生成名称 func (ct *ChallengeTemplate) GenerateName(difficulty ChallengeDifficulty) string { return fmt.Sprintf("%s (%s)", ct.Name, difficulty.String()) } // GenerateDescription 生成描述 func (ct *ChallengeTemplate) GenerateDescription(difficulty ChallengeDifficulty) string { return fmt.Sprintf("%s - 难度: %s", ct.Description, difficulty.String()) } // GetDuration 获取持续时间 func (ct *ChallengeTemplate) GetDuration(difficulty ChallengeDifficulty) time.Duration { multiplier := difficulty.GetMultiplier() return time.Duration(float64(ct.BaseDuration) * multiplier) } // GetCooldown 获取冷却时间 func (ct *ChallengeTemplate) GetCooldown(difficulty ChallengeDifficulty) time.Duration { multiplier := difficulty.GetMultiplier() return time.Duration(float64(ct.BaseCooldown) * multiplier) } // GenerateConditions 生成条件 func (ct *ChallengeTemplate) GenerateConditions(difficulty ChallengeDifficulty, sacredLevel int) []*ChallengeCondition { conditions := []*ChallengeCondition{ NewChallengeCondition("level", "level", "gte", difficulty.GetRequiredLevel(), "等级不足"), } // 根据难度添加额外条件 if difficulty >= ChallengeDifficultyHard { conditions = append(conditions, NewChallengeCondition("equipment", "power", "gte", 1000, "装备威力不足")) } return conditions } // BlessingTemplate 祝福模板 type BlessingTemplate struct { Name string Description string BaseDuration time.Duration BaseCooldown time.Duration BaseMaxUsage int } // NewBlessingTemplate 创建祝福模板 func NewBlessingTemplate(name, description string, baseDuration, baseCooldown time.Duration, baseMaxUsage int) *BlessingTemplate { return &BlessingTemplate{ Name: name, Description: description, BaseDuration: baseDuration, BaseCooldown: baseCooldown, BaseMaxUsage: baseMaxUsage, } } // GenerateName 生成名称 func (bt *BlessingTemplate) GenerateName(sacredLevel int) string { return fmt.Sprintf("%s (Lv.%d)", bt.Name, sacredLevel) } // GenerateDescription 生成描述 func (bt *BlessingTemplate) GenerateDescription(sacredLevel int) string { return fmt.Sprintf("%s - 圣地等级: %d", bt.Description, sacredLevel) } // GetDuration 获取持续时间 func (bt *BlessingTemplate) GetDuration(sacredLevel int) time.Duration { multiplier := 1.0 + float64(sacredLevel)*0.1 return time.Duration(float64(bt.BaseDuration) * multiplier) } // GetCooldown 获取冷却时间 func (bt *BlessingTemplate) GetCooldown(sacredLevel int) time.Duration { multiplier := math.Max(0.5, 1.0-float64(sacredLevel)*0.02) return time.Duration(float64(bt.BaseCooldown) * multiplier) } // GetMaxUsage 获取最大使用次数 func (bt *BlessingTemplate) GetMaxUsage(sacredLevel int) int { return bt.BaseMaxUsage + sacredLevel/10 } // GenerateEffects 生成效果 func (bt *BlessingTemplate) GenerateEffects(sacredLevel int) []*BlessingEffect { // 这里可以根据圣地等级生成不同的效果 return []*BlessingEffect{} } // RelicTemplate 圣物模板 type RelicTemplate struct { Name string Description string Attributes []string Effects []string } // NewRelicTemplate 创建圣物模板 func NewRelicTemplate(name, description string, attributes, effects []string) *RelicTemplate { return &RelicTemplate{ Name: name, Description: description, Attributes: attributes, Effects: effects, } } // GenerateName 生成名称 func (rt *RelicTemplate) GenerateName(rarity RelicRarity) string { return fmt.Sprintf("%s (%s)", rt.Name, rarity.String()) } // GenerateDescription 生成描述 func (rt *RelicTemplate) GenerateDescription(rarity RelicRarity) string { return fmt.Sprintf("%s - 稀有度: %s", rt.Description, rarity.String()) } // GenerateAttributes 生成属性 func (rt *RelicTemplate) GenerateAttributes(rarity RelicRarity) map[string]float64 { attributes := make(map[string]float64) basePower := rarity.GetBasePower() for _, attr := range rt.Attributes { attributes[attr] = basePower * (0.5 + rand.Float64()) } return attributes } // GenerateEffects 生成效果 func (rt *RelicTemplate) GenerateEffects(rarity RelicRarity) []string { // 根据稀有度返回部分效果 maxEffects := int(rarity) // 稀有度越高,效果越多 if maxEffects > len(rt.Effects) { maxEffects = len(rt.Effects) } return rt.Effects[:maxEffects] } // GenerateRequirements 生成需求 func (rt *RelicTemplate) GenerateRequirements(rarity RelicRarity) map[string]interface{} { requirements := make(map[string]interface{}) requirements["level"] = int(rarity) * 10 // 稀有度越高,等级要求越高 return requirements } // DifficultyCurve 难度曲线 type DifficultyCurve struct { HealthMultiplier float64 DamageMultiplier float64 SpeedMultiplier float64 RewardMultiplier float64 ExpMultiplier float64 } // RewardCalculator 奖励计算器 type RewardCalculator struct{} // NewRewardCalculator 创建奖励计算器 func NewRewardCalculator() *RewardCalculator { return &RewardCalculator{} } // CalculateChallengeReward 计算挑战奖励 func (rc *RewardCalculator) CalculateChallengeReward(challengeType ChallengeType, difficulty ChallengeDifficulty, success bool, score int, playerLevel int) *ChallengeReward { if !success { return &ChallengeReward{ Gold: 0, Experience: 0, Items: make(map[string]int), Special: make(map[string]interface{}), } } baseGold := 100 baseExp := 50 // 难度倍数 difficultyMultiplier := difficulty.GetMultiplier() // 分数倍数 scoreMultiplier := math.Max(0.1, math.Min(2.0, float64(score)/100.0)) // 玩家等级影响 levelMultiplier := 1.0 + float64(playerLevel)*0.05 // 计算最终奖励 finalGold := int(float64(baseGold) * difficultyMultiplier * scoreMultiplier * levelMultiplier) finalExp := int(float64(baseExp) * difficultyMultiplier * scoreMultiplier * levelMultiplier) reward := &ChallengeReward{ Gold: finalGold, Experience: finalExp, Items: make(map[string]int), Special: make(map[string]interface{}), } // 根据挑战类型添加特殊奖励 switch challengeType { case ChallengeTypeCombat: reward.AddItem("combat_token", 1) case ChallengeTypePuzzle: reward.AddItem("wisdom_crystal", 1) case ChallengeTypeEndurance: reward.AddItem("endurance_potion", 1) } return reward } // BalanceRules 平衡规则 type BalanceRules struct { maxActiveBlessings int blessingCooldowns map[BlessingType]time.Duration } // NewBalanceRules 创建平衡规则 func NewBalanceRules() *BalanceRules { return &BalanceRules{ maxActiveBlessings: 3, blessingCooldowns: make(map[BlessingType]time.Duration), } } // CanActivateBlessing 检查是否可以激活祝福 func (br *BalanceRules) CanActivateBlessing(blessingType BlessingType, playerData map[string]interface{}) bool { // 检查激活的祝福数量 activeBlessings, _ := playerData["active_blessings"].(int) if activeBlessings >= br.maxActiveBlessings { return false } // 检查特定类型的冷却时间 if cooldown, exists := br.blessingCooldowns[blessingType]; exists { lastUsed, _ := playerData[fmt.Sprintf("last_used_%s", blessingType.String())].(time.Time) if !lastUsed.IsZero() && time.Since(lastUsed) < cooldown { return false } } return true } ================================================ FILE: internal/domain/scene/sacred/value_object.go ================================================ package sacred import ( "fmt" "time" ) // SacredLevel 圣地等级值对象 type SacredLevel struct { Level int Experience int MaxExp int } // NewSacredLevel 创建圣地等级 func NewSacredLevel(level, experience int) *SacredLevel { return &SacredLevel{ Level: level, Experience: experience, MaxExp: calculateMaxExp(level), } } // AddExperience 添加经验 func (sl *SacredLevel) AddExperience(exp int) (int, error) { if exp <= 0 { return sl.Level, fmt.Errorf("experience must be positive") } sl.Experience += exp // 检查是否可以升级 for sl.Experience >= sl.MaxExp { sl.Experience -= sl.MaxExp sl.Level++ sl.MaxExp = calculateMaxExp(sl.Level) } return sl.Level, nil } // GetProgress 获取升级进度 func (sl *SacredLevel) GetProgress() float64 { if sl.MaxExp == 0 { return 0 } return float64(sl.Experience) / float64(sl.MaxExp) } // GetRemainingExp 获取升级所需经验 func (sl *SacredLevel) GetRemainingExp() int { return sl.MaxExp - sl.Experience } // CanUpgrade 检查是否可以升级 func (sl *SacredLevel) CanUpgrade() bool { return sl.Experience >= sl.MaxExp } // ToMap 转换为映射 func (sl *SacredLevel) ToMap() map[string]interface{} { return map[string]interface{}{ "level": sl.Level, "experience": sl.Experience, "max_exp": sl.MaxExp, "progress": sl.GetProgress(), "remaining": sl.GetRemainingExp(), } } // calculateMaxExp 计算等级所需最大经验 func calculateMaxExp(level int) int { // 经验公式:level * 100 + (level-1) * 50 return level*100 + (level-1)*50 } // ChallengeType 挑战类型 type ChallengeType int const ( ChallengeTypeCombat ChallengeType = iota + 1 // 战斗挑战 ChallengeTypePuzzle // 解谜挑战 ChallengeTypeEndurance // 耐力挑战 ChallengeTypeSpeed // 速度挑战 ChallengeTypeStrategy // 策略挑战 ChallengeTypeCooperation // 合作挑战 ChallengeTypeSpecial // 特殊挑战 ) // String 返回类型字符串 func (ct ChallengeType) String() string { switch ct { case ChallengeTypeCombat: return "combat" case ChallengeTypePuzzle: return "puzzle" case ChallengeTypeEndurance: return "endurance" case ChallengeTypeSpeed: return "speed" case ChallengeTypeStrategy: return "strategy" case ChallengeTypeCooperation: return "cooperation" case ChallengeTypeSpecial: return "special" default: return "unknown" } } // IsValid 检查类型是否有效 func (ct ChallengeType) IsValid() bool { return ct >= ChallengeTypeCombat && ct <= ChallengeTypeSpecial } // GetDescription 获取类型描述 func (ct ChallengeType) GetDescription() string { switch ct { case ChallengeTypeCombat: return "测试战斗技巧和策略的挑战" case ChallengeTypePuzzle: return "需要智慧和逻辑思维的解谜挑战" case ChallengeTypeEndurance: return "考验持久力和毅力的挑战" case ChallengeTypeSpeed: return "需要快速反应和敏捷的挑战" case ChallengeTypeStrategy: return "需要深度思考和规划的策略挑战" case ChallengeTypeCooperation: return "需要团队合作完成的挑战" case ChallengeTypeSpecial: return "独特的特殊挑战" default: return "未知类型的挑战" } } // ChallengeDifficulty 挑战难度 type ChallengeDifficulty int const ( ChallengeDifficultyEasy ChallengeDifficulty = iota + 1 // 简单 ChallengeDifficultyNormal // 普通 ChallengeDifficultyHard // 困难 ChallengeDifficultyExpert // 专家 ChallengeDifficultyLegendary // 传奇 ) // String 返回难度字符串 func (cd ChallengeDifficulty) String() string { switch cd { case ChallengeDifficultyEasy: return "easy" case ChallengeDifficultyNormal: return "normal" case ChallengeDifficultyHard: return "hard" case ChallengeDifficultyExpert: return "expert" case ChallengeDifficultyLegendary: return "legendary" default: return "unknown" } } // IsValid 检查难度是否有效 func (cd ChallengeDifficulty) IsValid() bool { return cd >= ChallengeDifficultyEasy && cd <= ChallengeDifficultyLegendary } // GetMultiplier 获取难度倍数 func (cd ChallengeDifficulty) GetMultiplier() float64 { switch cd { case ChallengeDifficultyEasy: return 0.5 case ChallengeDifficultyNormal: return 1.0 case ChallengeDifficultyHard: return 1.5 case ChallengeDifficultyExpert: return 2.0 case ChallengeDifficultyLegendary: return 3.0 default: return 1.0 } } // GetRequiredLevel 获取所需等级 func (cd ChallengeDifficulty) GetRequiredLevel() int { switch cd { case ChallengeDifficultyEasy: return 1 case ChallengeDifficultyNormal: return 5 case ChallengeDifficultyHard: return 10 case ChallengeDifficultyExpert: return 20 case ChallengeDifficultyLegendary: return 50 default: return 1 } } // GetColor 获取难度颜色 func (cd ChallengeDifficulty) GetColor() string { switch cd { case ChallengeDifficultyEasy: return "green" case ChallengeDifficultyNormal: return "blue" case ChallengeDifficultyHard: return "yellow" case ChallengeDifficultyExpert: return "red" case ChallengeDifficultyLegendary: return "purple" default: return "gray" } } // ChallengeStatus 挑战状态 type ChallengeStatus int const ( ChallengeStatusAvailable ChallengeStatus = iota + 1 // 可用 ChallengeStatusInProgress // 进行中 ChallengeStatusCompleted // 已完成 ChallengeStatusFailed // 失败 ChallengeStatusLocked // 锁定 ChallengeStatusExpired // 过期 ) // String 返回状态字符串 func (cs ChallengeStatus) String() string { switch cs { case ChallengeStatusAvailable: return "available" case ChallengeStatusInProgress: return "in_progress" case ChallengeStatusCompleted: return "completed" case ChallengeStatusFailed: return "failed" case ChallengeStatusLocked: return "locked" case ChallengeStatusExpired: return "expired" default: return "unknown" } } // IsValid 检查状态是否有效 func (cs ChallengeStatus) IsValid() bool { return cs >= ChallengeStatusAvailable && cs <= ChallengeStatusExpired } // CanStart 检查是否可以开始 func (cs ChallengeStatus) CanStart() bool { return cs == ChallengeStatusAvailable } // IsFinished 检查是否已结束 func (cs ChallengeStatus) IsFinished() bool { return cs == ChallengeStatusCompleted || cs == ChallengeStatusFailed || cs == ChallengeStatusExpired } // BlessingType 祝福类型 type BlessingType int const ( BlessingTypeAttribute BlessingType = iota + 1 // 属性祝福 BlessingTypeSkill // 技能祝福 BlessingTypeExperience // 经验祝福 BlessingTypeWealth // 财富祝福 BlessingTypeProtection // 保护祝福 BlessingTypeHealing // 治疗祝福 BlessingTypeSpeed // 速度祝福 BlessingTypeLuck // 幸运祝福 ) // String 返回类型字符串 func (bt BlessingType) String() string { switch bt { case BlessingTypeAttribute: return "attribute" case BlessingTypeSkill: return "skill" case BlessingTypeExperience: return "experience" case BlessingTypeWealth: return "wealth" case BlessingTypeProtection: return "protection" case BlessingTypeHealing: return "healing" case BlessingTypeSpeed: return "speed" case BlessingTypeLuck: return "luck" default: return "unknown" } } // IsValid 检查类型是否有效 func (bt BlessingType) IsValid() bool { return bt >= BlessingTypeAttribute && bt <= BlessingTypeLuck } // GetDescription 获取类型描述 func (bt BlessingType) GetDescription() string { switch bt { case BlessingTypeAttribute: return "提升角色基础属性的祝福" case BlessingTypeSkill: return "增强技能效果的祝福" case BlessingTypeExperience: return "增加经验获取的祝福" case BlessingTypeWealth: return "增加财富收入的祝福" case BlessingTypeProtection: return "提供保护效果的祝福" case BlessingTypeHealing: return "提供治疗效果的祝福" case BlessingTypeSpeed: return "提升移动和行动速度的祝福" case BlessingTypeLuck: return "增加幸运值的祝福" default: return "未知类型的祝福" } } // GetIcon 获取图标 func (bt BlessingType) GetIcon() string { switch bt { case BlessingTypeAttribute: return "💪" case BlessingTypeSkill: return "⚡" case BlessingTypeExperience: return "📚" case BlessingTypeWealth: return "💰" case BlessingTypeProtection: return "🛡️" case BlessingTypeHealing: return "❤️" case BlessingTypeSpeed: return "💨" case BlessingTypeLuck: return "🍀" default: return "❓" } } // BlessingStatus 祝福状态 type BlessingStatus int const ( BlessingStatusAvailable BlessingStatus = iota + 1 // 可用 BlessingStatusActive // 激活 BlessingStatusInactive // 未激活 BlessingStatusExpired // 过期 BlessingStatusLocked // 锁定 ) // String 返回状态字符串 func (bs BlessingStatus) String() string { switch bs { case BlessingStatusAvailable: return "available" case BlessingStatusActive: return "active" case BlessingStatusInactive: return "inactive" case BlessingStatusExpired: return "expired" case BlessingStatusLocked: return "locked" default: return "unknown" } } // IsValid 检查状态是否有效 func (bs BlessingStatus) IsValid() bool { return bs >= BlessingStatusAvailable && bs <= BlessingStatusLocked } // CanActivate 检查是否可以激活 func (bs BlessingStatus) CanActivate() bool { return bs == BlessingStatusAvailable } // IsActive 检查是否激活 func (bs BlessingStatus) IsActive() bool { return bs == BlessingStatusActive } // SacredRelic 圣物值对象 type SacredRelic struct { ID string Name string Description string Type RelicType Rarity RelicRarity Level int Attributes map[string]float64 Effects []string Requirements map[string]interface{} ObtainedAt time.Time } // NewSacredRelic 创建圣物 func NewSacredRelic(id, name, description string, relicType RelicType, rarity RelicRarity) *SacredRelic { return &SacredRelic{ ID: id, Name: name, Description: description, Type: relicType, Rarity: rarity, Level: 1, Attributes: make(map[string]float64), Effects: make([]string, 0), Requirements: make(map[string]interface{}), ObtainedAt: time.Now(), } } // GetPower 获取圣物威力 func (sr *SacredRelic) GetPower() float64 { basePower := sr.Rarity.GetBasePower() levelMultiplier := float64(sr.Level) return basePower * levelMultiplier } // CanUpgrade 检查是否可以升级 func (sr *SacredRelic) CanUpgrade() bool { return sr.Level < sr.Rarity.GetMaxLevel() } // Upgrade 升级圣物 func (sr *SacredRelic) Upgrade() error { if !sr.CanUpgrade() { return fmt.Errorf("relic cannot be upgraded further") } sr.Level++ // 升级时增强属性 for attr, value := range sr.Attributes { sr.Attributes[attr] = value * 1.1 // 每级增加10% } return nil } // AddAttribute 添加属性 func (sr *SacredRelic) AddAttribute(name string, value float64) { sr.Attributes[name] = value } // AddEffect 添加效果 func (sr *SacredRelic) AddEffect(effect string) { sr.Effects = append(sr.Effects, effect) } // AddRequirement 添加需求 func (sr *SacredRelic) AddRequirement(name string, value interface{}) { sr.Requirements[name] = value } // CheckRequirements 检查需求 func (sr *SacredRelic) CheckRequirements(playerData map[string]interface{}) bool { for req, reqValue := range sr.Requirements { playerValue, exists := playerData[req] if !exists { return false } // 简单的数值比较 if reqInt, ok := reqValue.(int); ok { if playerInt, ok := playerValue.(int); ok { if playerInt < reqInt { return false } } } } return true } // ToMap 转换为映射 func (sr *SacredRelic) ToMap() map[string]interface{} { return map[string]interface{}{ "id": sr.ID, "name": sr.Name, "description": sr.Description, "type": sr.Type.String(), "rarity": sr.Rarity.String(), "level": sr.Level, "power": sr.GetPower(), "attributes": sr.Attributes, "effects": sr.Effects, "requirements": sr.Requirements, "obtained_at": sr.ObtainedAt, } } // RelicType 圣物类型 type RelicType int const ( RelicTypeWeapon RelicType = iota + 1 // 武器 RelicTypeArmor // 护甲 RelicTypeAccessory // 饰品 RelicTypeConsumable // 消耗品 RelicTypeSpecial // 特殊 ) // String 返回类型字符串 func (rt RelicType) String() string { switch rt { case RelicTypeWeapon: return "weapon" case RelicTypeArmor: return "armor" case RelicTypeAccessory: return "accessory" case RelicTypeConsumable: return "consumable" case RelicTypeSpecial: return "special" default: return "unknown" } } // RelicRarity 圣物稀有度 type RelicRarity int const ( RelicRarityCommon RelicRarity = iota + 1 // 普通 RelicRarityUncommon // 不常见 RelicRarityRare // 稀有 RelicRarityEpic // 史诗 RelicRarityLegendary // 传奇 RelicRarityMythic // 神话 ) // String 返回稀有度字符串 func (rr RelicRarity) String() string { switch rr { case RelicRarityCommon: return "common" case RelicRarityUncommon: return "uncommon" case RelicRarityRare: return "rare" case RelicRarityEpic: return "epic" case RelicRarityLegendary: return "legendary" case RelicRarityMythic: return "mythic" default: return "unknown" } } // GetBasePower 获取基础威力 func (rr RelicRarity) GetBasePower() float64 { switch rr { case RelicRarityCommon: return 10.0 case RelicRarityUncommon: return 25.0 case RelicRarityRare: return 50.0 case RelicRarityEpic: return 100.0 case RelicRarityLegendary: return 200.0 case RelicRarityMythic: return 500.0 default: return 1.0 } } // GetMaxLevel 获取最大等级 func (rr RelicRarity) GetMaxLevel() int { switch rr { case RelicRarityCommon: return 10 case RelicRarityUncommon: return 20 case RelicRarityRare: return 30 case RelicRarityEpic: return 50 case RelicRarityLegendary: return 80 case RelicRarityMythic: return 100 default: return 1 } } // GetColor 获取颜色 func (rr RelicRarity) GetColor() string { switch rr { case RelicRarityCommon: return "gray" case RelicRarityUncommon: return "green" case RelicRarityRare: return "blue" case RelicRarityEpic: return "purple" case RelicRarityLegendary: return "orange" case RelicRarityMythic: return "red" default: return "white" } } // SacredPortal 圣地传送门值对象 type SacredPortal struct { ID string Name string Destination string RequiredLevel int Cost int Cooldown time.Duration LastUsed time.Time Active bool } // NewSacredPortal 创建传送门 func NewSacredPortal(id, name, destination string, requiredLevel, cost int, cooldown time.Duration) *SacredPortal { return &SacredPortal{ ID: id, Name: name, Destination: destination, RequiredLevel: requiredLevel, Cost: cost, Cooldown: cooldown, Active: true, } } // CanUse 检查是否可以使用 func (sp *SacredPortal) CanUse(playerLevel int, playerGold int) bool { if !sp.Active { return false } if playerLevel < sp.RequiredLevel { return false } if playerGold < sp.Cost { return false } // 检查冷却时间 if !sp.LastUsed.IsZero() && time.Since(sp.LastUsed) < sp.Cooldown { return false } return true } // Use 使用传送门 func (sp *SacredPortal) Use() error { if !sp.Active { return fmt.Errorf("portal is not active") } sp.LastUsed = time.Now() return nil } // GetRemainingCooldown 获取剩余冷却时间 func (sp *SacredPortal) GetRemainingCooldown() time.Duration { if sp.LastUsed.IsZero() { return 0 } elapsed := time.Since(sp.LastUsed) if elapsed >= sp.Cooldown { return 0 } return sp.Cooldown - elapsed } // Activate 激活传送门 func (sp *SacredPortal) Activate() { sp.Active = true } // Deactivate 停用传送门 func (sp *SacredPortal) Deactivate() { sp.Active = false } // ToMap 转换为映射 func (sp *SacredPortal) ToMap() map[string]interface{} { return map[string]interface{}{ "id": sp.ID, "name": sp.Name, "destination": sp.Destination, "required_level": sp.RequiredLevel, "cost": sp.Cost, "cooldown": sp.Cooldown.String(), "last_used": sp.LastUsed, "active": sp.Active, "remaining_cooldown": sp.GetRemainingCooldown().String(), } } // SacredAura 圣地光环值对象 type SacredAura struct { Type AuraType Intensity float64 Radius float64 Effects map[string]float64 Duration time.Duration ActivatedAt time.Time } // NewSacredAura 创建圣地光环 func NewSacredAura(auraType AuraType, intensity, radius float64, duration time.Duration) *SacredAura { return &SacredAura{ Type: auraType, Intensity: intensity, Radius: radius, Effects: make(map[string]float64), Duration: duration, ActivatedAt: time.Now(), } } // IsActive 检查是否激活 func (sa *SacredAura) IsActive() bool { return time.Since(sa.ActivatedAt) < sa.Duration } // GetRemainingDuration 获取剩余时间 func (sa *SacredAura) GetRemainingDuration() time.Duration { if !sa.IsActive() { return 0 } return sa.Duration - time.Since(sa.ActivatedAt) } // AddEffect 添加效果 func (sa *SacredAura) AddEffect(name string, value float64) { sa.Effects[name] = value } // GetEffect 获取效果值 func (sa *SacredAura) GetEffect(name string) float64 { return sa.Effects[name] * sa.Intensity } // AuraType 光环类型 type AuraType int const ( AuraTypeHealing AuraType = iota + 1 // 治疗光环 AuraTypeProtection // 保护光环 AuraTypeStrength // 力量光环 AuraTypeWisdom // 智慧光环 AuraTypeSpeed // 速度光环 AuraTypeLuck // 幸运光环 ) // String 返回光环类型字符串 func (at AuraType) String() string { switch at { case AuraTypeHealing: return "healing" case AuraTypeProtection: return "protection" case AuraTypeStrength: return "strength" case AuraTypeWisdom: return "wisdom" case AuraTypeSpeed: return "speed" case AuraTypeLuck: return "luck" default: return "unknown" } } ================================================ FILE: internal/domain/scene/scene.go ================================================ package scene import ( // "errors" "fmt" "math" "sync" "time" ) // Scene 场景聚合根 type Scene struct { id string name string sceneType SceneType status SceneStatus width float64 height float64 maxPlayers int currentPlayers int entities map[string]Entity players map[string]*Player npcs map[string]*NPC monsters map[string]*Monster items map[string]*Item portals map[string]*Portal aoi *AOIManager spawnPoints []*SpawnPoint lastUpdate time.Time events []DomainEvent mu sync.RWMutex } // NewScene 创建新场景 func NewScene(id, name string, sceneType SceneType, width, height float64, maxPlayers int) *Scene { return &Scene{ id: id, name: name, sceneType: sceneType, status: SceneStatusActive, width: width, height: height, maxPlayers: maxPlayers, currentPlayers: 0, entities: make(map[string]Entity), players: make(map[string]*Player), npcs: make(map[string]*NPC), monsters: make(map[string]*Monster), items: make(map[string]*Item), portals: make(map[string]*Portal), aoi: NewAOIManager(width, height, 100.0), // 默认AOI半径100 spawnPoints: make([]*SpawnPoint, 0), lastUpdate: time.Now(), events: make([]DomainEvent, 0), } } // SceneType 场景类型 type SceneType int const ( SceneTypeCity SceneType = iota + 1 SceneTypeDungeon SceneTypeBattlefield SceneTypeWilderness SceneTypeInstance SceneTypeGuild SceneTypePvP SceneTypeRaid ) // SceneStatus 场景状态 type SceneStatus int const ( SceneStatusActive SceneStatus = iota + 1 SceneStatusMaintenance SceneStatusClosed SceneStatusFull ) // Entity 实体接口 type Entity interface { GetID() string GetPosition() *Position SetPosition(*Position) GetEntityType() EntityType Update(deltaTime time.Duration) IsActive() bool } // EntityType 实体类型 type EntityType int const ( EntityTypePlayer EntityType = iota + 1 EntityTypeNPC EntityTypeMonster EntityTypeItem EntityTypePortal EntityTypeBuilding EntityTypeProjectile ) // Position 位置 type Position struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` Direction float64 `json:"direction"` // 朝向角度 UpdatedAt time.Time `json:"updated_at"` } // NewPosition 创建新位置 func NewPosition(x, y, z, direction float64) *Position { return &Position{ X: x, Y: y, Z: z, Direction: direction, UpdatedAt: time.Now(), } } // Distance 计算距离 func (p *Position) Distance(other *Position) float64 { dx := p.X - other.X dy := p.Y - other.Y dz := p.Z - other.Z return math.Sqrt(dx*dx + dy*dy + dz*dz) } // Player 玩家实体 type Player struct { id string name string level int position *Position health int64 maxHealth int64 mana int64 maxMana int64 status PlayerStatus lastAction time.Time active bool } // PlayerStatus 玩家状态 type PlayerStatus int const ( PlayerStatusNormal PlayerStatus = iota + 1 PlayerStatusCombat PlayerStatusDead PlayerStatusAFK PlayerStatusTrading PlayerStatusCasting ) // NPC 非玩家角色实体 type NPC struct { id string name string npcType NPCType position *Position health int64 maxHealth int64 status NPCStatus ai *AIBehavior active bool } // NPCType NPC类型 type NPCType int const ( NPCTypeVendor NPCType = iota + 1 NPCTypeGuard NPCTypeQuest NPCTypeTrainer NPCTypeBanker NPCTypeTransporter ) // NPCStatus NPC状态 type NPCStatus int const ( NPCStatusIdle NPCStatus = iota + 1 NPCStatusPatrolling NPCStatusCombat NPCStatusInteracting NPCStatusDead ) // Monster 怪物实体 type Monster struct { id string name string monsterType MonsterType position *Position health int64 maxHealth int64 level int status MonsterStatus ai *AIBehavior spawnPoint *SpawnPoint lastAttack time.Time active bool } // MonsterType 怪物类型 type MonsterType int const ( MonsterTypeNormal MonsterType = iota + 1 MonsterTypeElite MonsterTypeBoss MonsterTypeWorldBoss MonsterTypeMinion ) // MonsterStatus 怪物状态 type MonsterStatus int const ( MonsterStatusIdle MonsterStatus = iota + 1 MonsterStatusPatrolling MonsterStatusCombat MonsterStatusChasing MonsterStatusReturning MonsterStatusDead MonsterStatusRespawning ) // Item 物品实体 type Item struct { id string itemID string // 物品模板ID position *Position quantity int64 owner string // 拾取者限制 expireTime *time.Time active bool } // Portal 传送门实体 type Portal struct { id string name string position *Position targetSceneID string targetPosition *Position requiredLevel int requiredItems []string cost int64 active bool } // SpawnPoint 刷新点 type SpawnPoint struct { id string position *Position spawnType SpawnType targetID string // 刷新的实体ID interval time.Duration lastSpawn time.Time maxCount int currentCount int active bool } // SpawnType 刷新类型 type SpawnType int const ( SpawnTypeMonster SpawnType = iota + 1 SpawnTypeNPC SpawnTypeItem SpawnTypePlayer ) // AIBehavior AI行为 type AIBehavior struct { behaviorType BehaviorType patrolPath []*Position currentTarget string aggroRange float64 chaseRange float64 returnRange float64 attackRange float64 lastUpdate time.Time } // BehaviorType 行为类型 type BehaviorType int const ( BehaviorTypeIdle BehaviorType = iota + 1 BehaviorTypePatrol BehaviorTypeGuard BehaviorTypeAggressive BehaviorTypeDefensive BehaviorTypeFlee ) // AOIManager AOI管理器 type AOIManager struct { width float64 height float64 gridSize float64 grids map[string]*AOIGrid entities map[string]*AOIEntity mu sync.RWMutex } // NewAOIManager 创建AOI管理器 func NewAOIManager(width, height, gridSize float64) *AOIManager { return &AOIManager{ width: width, height: height, gridSize: gridSize, grids: make(map[string]*AOIGrid), entities: make(map[string]*AOIEntity), } } // AOIGrid AOI网格 type AOIGrid struct { x int y int entities map[string]*AOIEntity } // AOIEntity AOI实体 type AOIEntity struct { id string entity Entity gridX int gridY int watchers map[string]bool // 观察者列表 } // DomainEvent 领域事件接口 type DomainEvent interface { EventType() string OccurredAt() time.Time SceneID() string } // PlayerEnteredEvent 玩家进入场景事件 type PlayerEnteredEvent struct { sceneID string playerID string playerName string position *Position occurredAt time.Time } func (e PlayerEnteredEvent) EventType() string { return "player.entered" } func (e PlayerEnteredEvent) OccurredAt() time.Time { return e.occurredAt } func (e PlayerEnteredEvent) SceneID() string { return e.sceneID } // PlayerLeftEvent 玩家离开场景事件 type PlayerLeftEvent struct { sceneID string playerID string playerName string occurredAt time.Time } func (e PlayerLeftEvent) EventType() string { return "player.left" } func (e PlayerLeftEvent) OccurredAt() time.Time { return e.occurredAt } func (e PlayerLeftEvent) SceneID() string { return e.sceneID } // EntityMovedEvent 实体移动事件 type EntityMovedEvent struct { sceneID string entityID string entityType EntityType oldPosition *Position newPosition *Position occurredAt time.Time } func (e EntityMovedEvent) EventType() string { return "entity.moved" } func (e EntityMovedEvent) OccurredAt() time.Time { return e.occurredAt } func (e EntityMovedEvent) SceneID() string { return e.sceneID } // MonsterSpawnedEvent 怪物刷新事件 type MonsterSpawnedEvent struct { sceneID string monsterID string monsterType MonsterType position *Position occurredAt time.Time } func (e MonsterSpawnedEvent) EventType() string { return "monster.spawned" } func (e MonsterSpawnedEvent) OccurredAt() time.Time { return e.occurredAt } func (e MonsterSpawnedEvent) SceneID() string { return e.sceneID } // ItemDroppedEvent 物品掉落事件 type ItemDroppedEvent struct { sceneID string itemID string itemType string position *Position quantity int64 occurredAt time.Time } func (e ItemDroppedEvent) EventType() string { return "item.dropped" } func (e ItemDroppedEvent) OccurredAt() time.Time { return e.occurredAt } func (e ItemDroppedEvent) SceneID() string { return e.sceneID } // Scene 业务方法实现 // ID 获取场景ID func (s *Scene) ID() string { return s.id } // Name 获取场景名称 func (s *Scene) Name() string { return s.name } // Status 获取场景状态 func (s *Scene) Status() SceneStatus { s.mu.RLock() defer s.mu.RUnlock() return s.status } // Type 获取场景类型 func (s *Scene) Type() SceneType { s.mu.RLock() defer s.mu.RUnlock() return s.sceneType } // Width 获取场景宽度 func (s *Scene) GetWidth() float64 { s.mu.RLock() defer s.mu.RUnlock() return s.width } // Height 获取场景高度 func (s *Scene) GetHeight() float64 { s.mu.RLock() defer s.mu.RUnlock() return s.height } // MaxPlayers 获取最大玩家数 func (s *Scene) GetMaxPlayers() int { s.mu.RLock() defer s.mu.RUnlock() return s.maxPlayers } // PlayerCount 获取当前玩家数量 func (s *Scene) PlayerCount() int { s.mu.RLock() defer s.mu.RUnlock() return s.currentPlayers } // AddPlayer 添加玩家到场景 func (s *Scene) AddPlayer(player *Player) error { s.mu.Lock() defer s.mu.Unlock() if s.status != SceneStatusActive { return ErrSceneNotActive } if s.currentPlayers >= s.maxPlayers { return ErrSceneFull } if _, exists := s.players[player.id]; exists { return ErrPlayerAlreadyInScene } // 添加玩家 s.players[player.id] = player s.entities[player.id] = player s.currentPlayers++ // 添加到AOI s.aoi.AddEntity(player.id, player) s.lastUpdate = time.Now() // 发布事件 s.addEvent(PlayerEnteredEvent{ sceneID: s.id, playerID: player.id, playerName: player.name, position: player.position, occurredAt: time.Now(), }) return nil } // RemovePlayer 从场景移除玩家 func (s *Scene) RemovePlayer(playerID string) error { s.mu.Lock() defer s.mu.Unlock() player, exists := s.players[playerID] if !exists { return ErrPlayerNotInScene } // 从AOI移除 s.aoi.RemoveEntity(playerID) // 移除玩家 delete(s.players, playerID) delete(s.entities, playerID) s.currentPlayers-- s.lastUpdate = time.Now() // 发布事件 s.addEvent(PlayerLeftEvent{ sceneID: s.id, playerID: playerID, playerName: player.name, occurredAt: time.Now(), }) return nil } // MoveEntity 移动实体 func (s *Scene) MoveEntity(entityID string, newPosition *Position) error { s.mu.Lock() defer s.mu.Unlock() entity, exists := s.entities[entityID] if !exists { return ErrEntityNotFound } // 检查位置是否有效 if !s.isValidPosition(newPosition) { return ErrInvalidPosition } oldPosition := entity.GetPosition() entity.SetPosition(newPosition) // 更新AOI s.aoi.UpdateEntity(entityID, newPosition) s.lastUpdate = time.Now() // 发布事件 s.addEvent(EntityMovedEvent{ sceneID: s.id, entityID: entityID, entityType: entity.GetEntityType(), oldPosition: oldPosition, newPosition: newPosition, occurredAt: time.Now(), }) return nil } // SpawnMonster 刷新怪物 func (s *Scene) SpawnMonster(monster *Monster, spawnPoint *SpawnPoint) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.monsters[monster.id]; exists { return ErrMonsterAlreadyExists } // 设置刷新点 monster.spawnPoint = spawnPoint monster.position = &Position{ X: spawnPoint.position.X, Y: spawnPoint.position.Y, Z: spawnPoint.position.Z, Direction: spawnPoint.position.Direction, UpdatedAt: time.Now(), } // 添加怪物 s.monsters[monster.id] = monster s.entities[monster.id] = monster // 添加到AOI s.aoi.AddEntity(monster.id, monster) // 更新刷新点 spawnPoint.currentCount++ spawnPoint.lastSpawn = time.Now() s.lastUpdate = time.Now() // 发布事件 s.addEvent(MonsterSpawnedEvent{ sceneID: s.id, monsterID: monster.id, monsterType: monster.monsterType, position: monster.position, occurredAt: time.Now(), }) return nil } // DropItem 掉落物品 func (s *Scene) DropItem(item *Item, position *Position) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.items[item.id]; exists { return ErrItemAlreadyExists } item.position = position s.items[item.id] = item s.entities[item.id] = item // 添加到AOI s.aoi.AddEntity(item.id, item) s.lastUpdate = time.Now() // 发布事件 s.addEvent(ItemDroppedEvent{ sceneID: s.id, itemID: item.id, itemType: item.itemID, position: position, quantity: item.quantity, occurredAt: time.Now(), }) return nil } // GetNearbyEntities 获取附近实体 func (s *Scene) GetNearbyEntities(entityID string, radius float64) ([]Entity, error) { s.mu.RLock() defer s.mu.RUnlock() entity, exists := s.entities[entityID] if !exists { return nil, ErrEntityNotFound } return s.aoi.GetNearbyEntities(entity.GetPosition(), radius), nil } // Update 更新场景 func (s *Scene) Update(deltaTime time.Duration) { s.mu.Lock() defer s.mu.Unlock() now := time.Now() // 更新所有实体 for _, entity := range s.entities { if entity.IsActive() { entity.Update(deltaTime) } } // 处理刷新点 s.processSpawnPoints(now) // 清理过期物品 s.cleanupExpiredItems(now) s.lastUpdate = now } // processSpawnPoints 处理刷新点 func (s *Scene) processSpawnPoints(now time.Time) { for _, spawnPoint := range s.spawnPoints { if !spawnPoint.active { continue } if spawnPoint.currentCount >= spawnPoint.maxCount { continue } if now.Sub(spawnPoint.lastSpawn) < spawnPoint.interval { continue } // 这里应该根据刷新点类型创建对应实体 // 简化实现,实际应该从配置或工厂创建 switch spawnPoint.spawnType { case SpawnTypeMonster: // 创建怪物逻辑 case SpawnTypeNPC: // 创建NPC逻辑 case SpawnTypeItem: // 创建物品逻辑 } } } // cleanupExpiredItems 清理过期物品 func (s *Scene) cleanupExpiredItems(now time.Time) { for itemID, item := range s.items { if item.expireTime != nil && now.After(*item.expireTime) { s.aoi.RemoveEntity(itemID) delete(s.items, itemID) delete(s.entities, itemID) } } } // isValidPosition 检查位置是否有效 func (s *Scene) isValidPosition(pos *Position) bool { return pos.X >= 0 && pos.X <= s.width && pos.Y >= 0 && pos.Y <= s.height } // addEvent 添加领域事件 func (s *Scene) addEvent(event DomainEvent) { s.events = append(s.events, event) } // GetEvents 获取领域事件 func (s *Scene) GetEvents() []DomainEvent { s.mu.RLock() defer s.mu.RUnlock() return s.events } // ClearEvents 清除领域事件 func (s *Scene) ClearEvents() { s.mu.Lock() defer s.mu.Unlock() s.events = make([]DomainEvent, 0) } // AOIManager 方法实现 // AddEntity 添加实体到AOI func (aoi *AOIManager) AddEntity(entityID string, entity Entity) { aoi.mu.Lock() defer aoi.mu.Unlock() pos := entity.GetPosition() gridX, gridY := aoi.getGridCoords(pos.X, pos.Y) aoiEntity := &AOIEntity{ id: entityID, entity: entity, gridX: gridX, gridY: gridY, watchers: make(map[string]bool), } aoi.entities[entityID] = aoiEntity // 添加到网格 gridKey := aoi.getGridKey(gridX, gridY) if _, exists := aoi.grids[gridKey]; !exists { aoi.grids[gridKey] = &AOIGrid{ x: gridX, y: gridY, entities: make(map[string]*AOIEntity), } } aoi.grids[gridKey].entities[entityID] = aoiEntity } // RemoveEntity 从AOI移除实体 func (aoi *AOIManager) RemoveEntity(entityID string) { aoi.mu.Lock() defer aoi.mu.Unlock() aoiEntity, exists := aoi.entities[entityID] if !exists { return } // 从网格移除 gridKey := aoi.getGridKey(aoiEntity.gridX, aoiEntity.gridY) if grid, exists := aoi.grids[gridKey]; exists { delete(grid.entities, entityID) if len(grid.entities) == 0 { delete(aoi.grids, gridKey) } } delete(aoi.entities, entityID) } // UpdateEntity 更新实体位置 func (aoi *AOIManager) UpdateEntity(entityID string, newPosition *Position) { aoi.mu.Lock() defer aoi.mu.Unlock() aoiEntity, exists := aoi.entities[entityID] if !exists { return } newGridX, newGridY := aoi.getGridCoords(newPosition.X, newPosition.Y) // 如果网格没有变化,直接返回 if newGridX == aoiEntity.gridX && newGridY == aoiEntity.gridY { return } // 从旧网格移除 oldGridKey := aoi.getGridKey(aoiEntity.gridX, aoiEntity.gridY) if grid, exists := aoi.grids[oldGridKey]; exists { delete(grid.entities, entityID) if len(grid.entities) == 0 { delete(aoi.grids, oldGridKey) } } // 添加到新网格 newGridKey := aoi.getGridKey(newGridX, newGridY) if _, exists := aoi.grids[newGridKey]; !exists { aoi.grids[newGridKey] = &AOIGrid{ x: newGridX, y: newGridY, entities: make(map[string]*AOIEntity), } } aoi.grids[newGridKey].entities[entityID] = aoiEntity // 更新实体网格坐标 aoiEntity.gridX = newGridX aoiEntity.gridY = newGridY } // GetNearbyEntities 获取附近实体 func (aoi *AOIManager) GetNearbyEntities(position *Position, radius float64) []Entity { aoi.mu.RLock() defer aoi.mu.RUnlock() var entities []Entity gridRadius := int(math.Ceil(radius / aoi.gridSize)) centerGridX, centerGridY := aoi.getGridCoords(position.X, position.Y) // 遍历周围网格 for x := centerGridX - gridRadius; x <= centerGridX+gridRadius; x++ { for y := centerGridY - gridRadius; y <= centerGridY+gridRadius; y++ { gridKey := aoi.getGridKey(x, y) if grid, exists := aoi.grids[gridKey]; exists { for _, aoiEntity := range grid.entities { entityPos := aoiEntity.entity.GetPosition() if position.Distance(entityPos) <= radius { entities = append(entities, aoiEntity.entity) } } } } } return entities } // getGridCoords 获取网格坐标 func (aoi *AOIManager) getGridCoords(x, y float64) (int, int) { gridX := int(x / aoi.gridSize) gridY := int(y / aoi.gridSize) return gridX, gridY } // getGridKey 获取网格键 func (aoi *AOIManager) getGridKey(x, y int) string { return fmt.Sprintf("%d,%d", x, y) } // 实体接口实现 // Player 实现 Entity 接口 func (p *Player) GetID() string { return p.id } func (p *Player) GetPosition() *Position { return p.position } func (p *Player) SetPosition(pos *Position) { p.position = pos } func (p *Player) GetEntityType() EntityType { return EntityTypePlayer } func (p *Player) Update(deltaTime time.Duration) { // 玩家更新逻辑 p.lastAction = time.Now() } func (p *Player) IsActive() bool { return p.active && p.status != PlayerStatusDead } // Monster 实现 Entity 接口 func (m *Monster) GetID() string { return m.id } func (m *Monster) GetPosition() *Position { return m.position } func (m *Monster) SetPosition(pos *Position) { m.position = pos } func (m *Monster) GetEntityType() EntityType { return EntityTypeMonster } func (m *Monster) Update(deltaTime time.Duration) { // 怪物AI更新逻辑 if m.ai != nil { m.updateAI(deltaTime) } } func (m *Monster) IsActive() bool { return m.active && m.status != MonsterStatusDead } func (m *Monster) updateAI(deltaTime time.Duration) { // AI行为更新逻辑 switch m.ai.behaviorType { case BehaviorTypePatrol: // 巡逻逻辑 case BehaviorTypeAggressive: // 攻击逻辑 case BehaviorTypeGuard: // 守卫逻辑 } } // NPC 实现 Entity 接口 func (n *NPC) GetID() string { return n.id } func (n *NPC) GetPosition() *Position { return n.position } func (n *NPC) SetPosition(pos *Position) { n.position = pos } func (n *NPC) GetEntityType() EntityType { return EntityTypeNPC } func (n *NPC) Update(deltaTime time.Duration) { // NPC更新逻辑 if n.ai != nil { n.updateAI(deltaTime) } } func (n *NPC) IsActive() bool { return n.active && n.status != NPCStatusDead } func (n *NPC) updateAI(deltaTime time.Duration) { // NPC AI逻辑 } // Item 实现 Entity 接口 func (i *Item) GetID() string { return i.id } func (i *Item) GetPosition() *Position { return i.position } func (i *Item) SetPosition(pos *Position) { i.position = pos } func (i *Item) GetEntityType() EntityType { return EntityTypeItem } func (i *Item) Update(deltaTime time.Duration) { // 物品更新逻辑(检查过期等) } func (i *Item) IsActive() bool { return i.active } // Portal 实现 Entity 接口 func (p *Portal) GetID() string { return p.id } func (p *Portal) GetPosition() *Position { return p.position } func (p *Portal) SetPosition(pos *Position) { p.position = pos } func (p *Portal) GetEntityType() EntityType { return EntityTypePortal } func (p *Portal) Update(deltaTime time.Duration) { // 传送门更新逻辑 } func (p *Portal) IsActive() bool { return p.active } ================================================ FILE: internal/domain/scene/weather/aggregate.go ================================================ package weather import ( "fmt" "math/rand" "time" ) // WeatherAggregate 天气聚合根 type WeatherAggregate struct { id string sceneID string currentWeather *WeatherState weatherHistory []*WeatherState weatherForecast []*WeatherForecast weatherEffects map[string]*WeatherEffect seasonalPattern *SeasonalPattern lastUpdateTime time.Time nextChangeTime time.Time changeInterval time.Duration randomSeed int64 createdAt time.Time updatedAt time.Time version int } // NewWeatherAggregate 创建天气聚合根 func NewWeatherAggregate(sceneID string) *WeatherAggregate { now := time.Now() return &WeatherAggregate{ id: generateWeatherID(sceneID), sceneID: sceneID, currentWeather: NewWeatherState(WeatherTypeSunny, WeatherIntensityNormal), weatherHistory: make([]*WeatherState, 0), weatherForecast: make([]*WeatherForecast, 0), weatherEffects: make(map[string]*WeatherEffect), seasonalPattern: NewSeasonalPattern(), lastUpdateTime: now, nextChangeTime: now.Add(30 * time.Minute), // 默认30分钟变化一次 changeInterval: 30 * time.Minute, randomSeed: now.UnixNano(), createdAt: now, updatedAt: now, version: 1, } } // ReconstructWeatherAggregate 从持久化数据重建天气聚合根 func ReconstructWeatherAggregate( id string, sceneID string, weatherType WeatherType, intensity float64, temperature float64, humidity float64, windSpeed float64, visibility float64, startTime time.Time, endTime time.Time, duration time.Duration, isSpecial bool, description string, effects []*WeatherEffect, createdAt time.Time, updatedAt time.Time, ) *WeatherAggregate { // 创建天气效果映射 weatherEffects := make(map[string]*WeatherEffect) for _, effect := range effects { weatherEffects[effect.GetEffectType()] = effect } // 根据强度创建 WeatherIntensity weatherIntensity := WeatherIntensityNormal if intensity <= 0.5 { weatherIntensity = WeatherIntensityLight } else if intensity >= 1.5 { weatherIntensity = WeatherIntensityHeavy } // 创建当前天气状态 currentWeather := &WeatherState{ WeatherType: weatherType, Intensity: weatherIntensity, Temperature: temperature, Humidity: humidity, WindSpeed: windSpeed, Visibility: visibility, StartTime: startTime, EndTime: endTime, Duration: duration, } return &WeatherAggregate{ id: id, sceneID: sceneID, currentWeather: currentWeather, weatherHistory: make([]*WeatherState, 0), weatherForecast: make([]*WeatherForecast, 0), weatherEffects: weatherEffects, seasonalPattern: NewSeasonalPattern(), lastUpdateTime: updatedAt, nextChangeTime: endTime, changeInterval: 30 * time.Minute, randomSeed: createdAt.UnixNano(), createdAt: createdAt, updatedAt: updatedAt, version: 1, } } // GetID 获取天气ID func (w *WeatherAggregate) GetID() string { return w.id } // GetSceneID 获取场景ID func (w *WeatherAggregate) GetSceneID() string { return w.sceneID } // GetRegionID 获取区域ID(与场景ID相同) func (w *WeatherAggregate) GetRegionID() string { return w.sceneID } // GetEffects 获取天气效果(别名方法) func (w *WeatherAggregate) GetEffects() map[string]*WeatherEffect { return w.weatherEffects } // GetWeatherType 获取当前天气类型 func (w *WeatherAggregate) GetWeatherType() WeatherType { if w.currentWeather == nil { return WeatherTypeSunny } return w.currentWeather.WeatherType } // GetIntensity 获取当前天气强度 func (w *WeatherAggregate) GetIntensity() WeatherIntensity { if w.currentWeather == nil { return WeatherIntensityNormal } return w.currentWeather.Intensity } // GetTemperature 获取当前温度 func (w *WeatherAggregate) GetTemperature() float64 { if w.currentWeather == nil { return 20.0 // 默认温度 } return w.currentWeather.Temperature } // GetHumidity 获取当前湿度 func (w *WeatherAggregate) GetHumidity() float64 { if w.currentWeather == nil { return 50.0 // 默认湿度 } return w.currentWeather.Humidity } // GetWindSpeed 获取当前风速 func (w *WeatherAggregate) GetWindSpeed() float64 { if w.currentWeather == nil { return 5.0 // 默认风速 } return w.currentWeather.WindSpeed } // GetVisibility 获取当前能见度 func (w *WeatherAggregate) GetVisibility() float64 { if w.currentWeather == nil { return 10.0 // 默认能见度(公里) } return w.currentWeather.Visibility } // GetCurrentWeather 获取当前天气 func (w *WeatherAggregate) GetCurrentWeather() *WeatherState { return w.currentWeather } // ChangeWeather 改变天气 func (w *WeatherAggregate) ChangeWeather(weatherType WeatherType, intensity WeatherIntensity) error { if !weatherType.IsValid() { return ErrInvalidWeatherType } if !intensity.IsValid() { return ErrInvalidWeatherIntensity } // 保存当前天气到历史记录 if w.currentWeather != nil { w.addToHistory(w.currentWeather) } // 创建新的天气状态 newWeather := NewWeatherState(weatherType, intensity) newWeather.StartTime = time.Now() // 计算持续时间 duration := w.calculateWeatherDuration(weatherType, intensity) newWeather.Duration = duration newWeather.EndTime = newWeather.StartTime.Add(duration) w.currentWeather = newWeather w.lastUpdateTime = time.Now() w.nextChangeTime = newWeather.EndTime // 更新天气效果 w.updateWeatherEffects() w.updateVersion() return nil } // UpdateWeather 更新天气(自动变化) func (w *WeatherAggregate) UpdateWeather() error { now := time.Now() // 检查是否需要变化天气 if now.Before(w.nextChangeTime) { return nil // 还未到变化时间 } // 根据季节模式和随机因素决定下一个天气 nextWeather := w.calculateNextWeather() return w.ChangeWeather(nextWeather.WeatherType, nextWeather.Intensity) } // GetWeatherEffects 获取天气效果 func (w *WeatherAggregate) GetWeatherEffects() map[string]*WeatherEffect { return w.weatherEffects } // GetWeatherEffect 获取指定的天气效果 func (w *WeatherAggregate) GetWeatherEffect(effectType string) *WeatherEffect { return w.weatherEffects[effectType] } // AddWeatherEffect 添加天气效果 func (w *WeatherAggregate) AddWeatherEffect(effect *WeatherEffect) { w.weatherEffects[effect.GetEffectType()] = effect w.updateVersion() } // RemoveWeatherEffect 移除天气效果 func (w *WeatherAggregate) RemoveWeatherEffect(effectType string) { delete(w.weatherEffects, effectType) w.updateVersion() } // GetWeatherHistory 获取天气历史 func (w *WeatherAggregate) GetWeatherHistory() []*WeatherState { return w.weatherHistory } // GetWeatherForecast 获取天气预报 func (w *WeatherAggregate) GetWeatherForecast() []*WeatherForecast { return w.weatherForecast } // GenerateForecast 生成天气预报 func (w *WeatherAggregate) GenerateForecast(hours int) error { if hours <= 0 || hours > 168 { // 最多预报一周 return ErrInvalidForecastPeriod } w.weatherForecast = make([]*WeatherForecast, 0) currentTime := time.Now() // currentWeather := w.currentWeather for i := 1; i <= hours; i++ { forecastTime := currentTime.Add(time.Duration(i) * time.Hour) // 基于当前天气和季节模式预测 predictedWeather := w.predictWeatherAt(forecastTime) forecast := &WeatherForecast{ Time: forecastTime, WeatherType: predictedWeather.WeatherType, Intensity: predictedWeather.Intensity, Confidence: w.calculateForecastConfidence(i), Description: w.generateWeatherDescription(predictedWeather.WeatherType, predictedWeather.Intensity), } w.weatherForecast = append(w.weatherForecast, forecast) } w.updateVersion() return nil } // GetSeasonalPattern 获取季节模式 func (w *WeatherAggregate) GetSeasonalPattern() *SeasonalPattern { return w.seasonalPattern } // UpdateSeasonalPattern 更新季节模式 func (w *WeatherAggregate) UpdateSeasonalPattern(pattern *SeasonalPattern) { w.seasonalPattern = pattern w.updateVersion() } // GetLastUpdateTime 获取最后更新时间 func (w *WeatherAggregate) GetLastUpdateTime() time.Time { return w.lastUpdateTime } // GetNextChangeTime 获取下次变化时间 func (w *WeatherAggregate) GetNextChangeTime() time.Time { return w.nextChangeTime } // SetChangeInterval 设置变化间隔 func (w *WeatherAggregate) SetChangeInterval(interval time.Duration) error { if interval < time.Minute || interval > 24*time.Hour { return ErrInvalidChangeInterval } w.changeInterval = interval w.nextChangeTime = w.lastUpdateTime.Add(interval) w.updateVersion() return nil } // GetChangeInterval 获取变化间隔 func (w *WeatherAggregate) GetChangeInterval() time.Duration { return w.changeInterval } // IsWeatherActive 检查天气是否激活 func (w *WeatherAggregate) IsWeatherActive() bool { return w.currentWeather != nil && !w.currentWeather.IsExpired() } // GetRemainingDuration 获取当前天气剩余时间 func (w *WeatherAggregate) GetRemainingDuration() time.Duration { if w.currentWeather == nil { return 0 } now := time.Now() if now.After(w.currentWeather.EndTime) { return 0 } return w.currentWeather.EndTime.Sub(now) } // CalculateWeatherInfluence 计算天气对指定属性的影响 func (w *WeatherAggregate) CalculateWeatherInfluence(attributeType string) float64 { if w.currentWeather == nil { return 1.0 // 无影响 } influence := 1.0 // 基于天气类型的影响 switch w.currentWeather.WeatherType { case WeatherTypeSunny: if attributeType == "visibility" { influence = 1.2 } else if attributeType == "movement_speed" { influence = 1.1 } case WeatherTypeRainy: if attributeType == "visibility" { influence = 0.8 } else if attributeType == "fire_damage" { influence = 0.7 } case WeatherTypeSnowy: if attributeType == "movement_speed" { influence = 0.8 } else if attributeType == "ice_damage" { influence = 1.3 } case WeatherTypeStormy: if attributeType == "lightning_damage" { influence = 1.5 } else if attributeType == "accuracy" { influence = 0.9 } case WeatherTypeFoggy: if attributeType == "visibility" { influence = 0.5 } else if attributeType == "detection_range" { influence = 0.6 } } // 基于强度调整影响 intensityMultiplier := w.currentWeather.Intensity.GetMultiplier() if influence != 1.0 { // 只对有影响的属性应用强度倍率 if influence > 1.0 { influence = 1.0 + (influence-1.0)*intensityMultiplier } else { influence = 1.0 - (1.0-influence)*intensityMultiplier } } return influence } // GetVersion 获取版本 func (w *WeatherAggregate) GetVersion() int { return w.version } // GetUpdatedAt 获取更新时间 func (w *WeatherAggregate) GetUpdatedAt() time.Time { return w.updatedAt } // GetCreatedAt 获取创建时间 func (w *WeatherAggregate) GetCreatedAt() time.Time { return w.createdAt } // GetStartTime 获取当前天气开始时间 func (w *WeatherAggregate) GetStartTime() time.Time { if w.currentWeather == nil { return time.Time{} } return w.currentWeather.StartTime } // GetEndTime 获取当前天气结束时间 func (w *WeatherAggregate) GetEndTime() time.Time { if w.currentWeather == nil { return time.Time{} } return w.currentWeather.EndTime } // GetDuration 获取当前天气持续时间 func (w *WeatherAggregate) GetDuration() time.Duration { if w.currentWeather == nil { return 0 } return w.currentWeather.Duration } // IsSpecialWeather 检查是否为特殊天气 func (w *WeatherAggregate) IsSpecialWeather() bool { if w.currentWeather == nil { return false } // 特殊天气包括暴风雨、雪天、雾天等 specialWeathers := []WeatherType{WeatherTypeStormy, WeatherTypeSnowy, WeatherTypeFoggy} // TODO: 修复WeatherTypeHail和WeatherTypeBlizzard for _, special := range specialWeathers { if w.currentWeather.WeatherType == special { return true } } return false } // GetDescription 获取当前天气描述 func (w *WeatherAggregate) GetDescription() string { if w.currentWeather == nil { return "无天气信息" } return w.generateWeatherDescription(w.currentWeather.WeatherType, w.currentWeather.Intensity) } // 私有方法 // addToHistory 添加到历史记录 func (w *WeatherAggregate) addToHistory(weather *WeatherState) { w.weatherHistory = append(w.weatherHistory, weather) // 限制历史记录数量(保留最近100条) if len(w.weatherHistory) > 100 { w.weatherHistory = w.weatherHistory[1:] } } // calculateWeatherDuration 计算天气持续时间 func (w *WeatherAggregate) calculateWeatherDuration(weatherType WeatherType, intensity WeatherIntensity) time.Duration { baseDuration := w.changeInterval // 根据天气类型调整持续时间 switch weatherType { case WeatherTypeSunny: baseDuration = baseDuration * 2 // 晴天持续更久 case WeatherTypeStormy: baseDuration = baseDuration / 2 // 暴风雨持续较短 case WeatherTypeFoggy: baseDuration = baseDuration / 3 // 雾天持续很短 } // 根据强度调整 intensityFactor := intensity.GetDurationFactor() baseDuration = time.Duration(float64(baseDuration) * intensityFactor) // 添加随机因素(±20%) randomFactor := 0.8 + rand.Float64()*0.4 baseDuration = time.Duration(float64(baseDuration) * randomFactor) return baseDuration } // calculateNextWeather 计算下一个天气 func (w *WeatherAggregate) calculateNextWeather() *WeatherState { now := time.Now() currentSeason := w.seasonalPattern.GetCurrentSeason(now) // 获取季节天气概率 weatherProbabilities := w.seasonalPattern.GetWeatherProbabilities(currentSeason) // 考虑当前天气的影响(天气转换规律) currentType := w.currentWeather.WeatherType transitionProbabilities := w.getWeatherTransitionProbabilities(currentType) // 合并概率 combinedProbabilities := w.combineWeatherProbabilities(weatherProbabilities, transitionProbabilities) // 随机选择下一个天气 nextWeatherType := w.selectWeatherByProbability(combinedProbabilities) nextIntensity := w.selectRandomIntensity(nextWeatherType) return NewWeatherState(nextWeatherType, nextIntensity) } // updateWeatherEffects 更新天气效果 func (w *WeatherAggregate) updateWeatherEffects() { // 清除旧的效果 w.weatherEffects = make(map[string]*WeatherEffect) if w.currentWeather == nil { return } // 根据当前天气添加效果 effects := w.generateWeatherEffects(w.currentWeather.WeatherType, w.currentWeather.Intensity) for _, effect := range effects { w.weatherEffects[effect.GetEffectType()] = effect } } // generateWeatherEffects 生成天气效果 func (w *WeatherAggregate) generateWeatherEffects(weatherType WeatherType, intensity WeatherIntensity) []*WeatherEffect { effects := make([]*WeatherEffect, 0) switch weatherType { case WeatherTypeSunny: effects = append(effects, NewWeatherEffect("visibility_boost", "visibility", 1.2*intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("movement_speed_boost", "movement", 1.1*intensity.GetMultiplier(), w.currentWeather.Duration)) case WeatherTypeRainy: effects = append(effects, NewWeatherEffect("visibility_reduction", "visibility", 0.8/intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("fire_damage_reduction", "fire", 0.7/intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("water_damage_boost", "water", 1.2*intensity.GetMultiplier(), w.currentWeather.Duration)) case WeatherTypeSnowy: effects = append(effects, NewWeatherEffect("movement_speed_reduction", "movement", 0.8/intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("ice_damage_boost", "ice", 1.3*intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("cold_resistance_reduction", "cold", 0.9/intensity.GetMultiplier(), w.currentWeather.Duration)) case WeatherTypeStormy: effects = append(effects, NewWeatherEffect("lightning_damage_boost", "lightning", 1.5*intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("accuracy_reduction", "accuracy", 0.9/intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("wind_resistance_reduction", "wind", 0.8/intensity.GetMultiplier(), w.currentWeather.Duration)) case WeatherTypeFoggy: effects = append(effects, NewWeatherEffect("visibility_severe_reduction", "visibility", 0.5/intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("detection_range_reduction", "detection", 0.6/intensity.GetMultiplier(), w.currentWeather.Duration)) effects = append(effects, NewWeatherEffect("stealth_boost", "stealth", 1.3*intensity.GetMultiplier(), w.currentWeather.Duration)) } return effects } // predictWeatherAt 预测指定时间的天气 func (w *WeatherAggregate) predictWeatherAt(targetTime time.Time) *WeatherState { // 简化的预测算法,实际可以更复杂 hoursDiff := int(targetTime.Sub(time.Now()).Hours()) // 基于小时数和季节模式预测 currentSeason := w.seasonalPattern.GetCurrentSeason(targetTime) weatherProbabilities := w.seasonalPattern.GetWeatherProbabilities(currentSeason) // 添加时间因素的随机性 rand.Seed(w.randomSeed + int64(hoursDiff)) weatherType := w.selectWeatherByProbability(weatherProbabilities) intensity := w.selectRandomIntensity(weatherType) return NewWeatherState(weatherType, intensity) } // calculateForecastConfidence 计算预报置信度 func (w *WeatherAggregate) calculateForecastConfidence(hoursAhead int) float64 { // 预报时间越远,置信度越低 baseConfidence := 0.95 decayRate := 0.02 confidence := baseConfidence - float64(hoursAhead)*decayRate if confidence < 0.3 { confidence = 0.3 // 最低置信度 } return confidence } // generateWeatherDescription 生成天气描述 func (w *WeatherAggregate) generateWeatherDescription(weatherType WeatherType, intensity WeatherIntensity) string { baseDescription := weatherType.GetDescription() intensityDescription := intensity.GetDescription() return intensityDescription + baseDescription } // getWeatherTransitionProbabilities 获取天气转换概率 func (w *WeatherAggregate) getWeatherTransitionProbabilities(currentType WeatherType) map[WeatherType]float64 { // 定义天气转换规律 transitions := make(map[WeatherType]float64) switch currentType { case WeatherTypeSunny: transitions[WeatherTypeSunny] = 0.6 transitions[WeatherTypeCloudy] = 0.25 transitions[WeatherTypeRainy] = 0.1 transitions[WeatherTypeWindy] = 0.05 case WeatherTypeCloudy: transitions[WeatherTypeCloudy] = 0.4 transitions[WeatherTypeSunny] = 0.3 transitions[WeatherTypeRainy] = 0.2 transitions[WeatherTypeStormy] = 0.1 case WeatherTypeRainy: transitions[WeatherTypeRainy] = 0.5 transitions[WeatherTypeCloudy] = 0.3 transitions[WeatherTypeStormy] = 0.15 transitions[WeatherTypeSunny] = 0.05 case WeatherTypeStormy: transitions[WeatherTypeStormy] = 0.3 transitions[WeatherTypeRainy] = 0.4 transitions[WeatherTypeCloudy] = 0.2 transitions[WeatherTypeWindy] = 0.1 default: // 默认均匀分布 transitions[WeatherTypeSunny] = 0.3 transitions[WeatherTypeCloudy] = 0.25 transitions[WeatherTypeRainy] = 0.2 transitions[WeatherTypeWindy] = 0.15 transitions[WeatherTypeStormy] = 0.1 } return transitions } // combineWeatherProbabilities 合并天气概率 func (w *WeatherAggregate) combineWeatherProbabilities(seasonal, transition map[WeatherType]float64) map[WeatherType]float64 { combined := make(map[WeatherType]float64) // 季节概率权重0.7,转换概率权重0.3 seasonalWeight := 0.7 transitionWeight := 0.3 allWeatherTypes := []WeatherType{WeatherTypeSunny, WeatherTypeCloudy, WeatherTypeRainy, WeatherTypeWindy, WeatherTypeStormy, WeatherTypeSnowy, WeatherTypeFoggy} for _, weatherType := range allWeatherTypes { seasonalProb := seasonal[weatherType] transitionProb := transition[weatherType] combined[weatherType] = seasonalProb*seasonalWeight + transitionProb*transitionWeight } return combined } // selectWeatherByProbability 根据概率选择天气 func (w *WeatherAggregate) selectWeatherByProbability(probabilities map[WeatherType]float64) WeatherType { rand.Seed(time.Now().UnixNano() + w.randomSeed) randomValue := rand.Float64() cumulativeProbability := 0.0 for weatherType, probability := range probabilities { cumulativeProbability += probability if randomValue <= cumulativeProbability { return weatherType } } // 默认返回晴天 return WeatherTypeSunny } // selectRandomIntensity 随机选择强度 func (w *WeatherAggregate) selectRandomIntensity(weatherType WeatherType) WeatherIntensity { rand.Seed(time.Now().UnixNano() + w.randomSeed) // 根据天气类型调整强度概率 switch weatherType { case WeatherTypeSunny, WeatherTypeCloudy: // 温和天气更可能是正常强度 if rand.Float64() < 0.7 { return WeatherIntensityNormal } else if rand.Float64() < 0.9 { return WeatherIntensityLight } else { return WeatherIntensityHeavy } case WeatherTypeStormy: // 暴风雨更可能是强烈的 if rand.Float64() < 0.5 { return WeatherIntensityHeavy } else if rand.Float64() < 0.8 { return WeatherIntensityNormal } else { return WeatherIntensityExtreme } default: // 其他天气均匀分布 intensities := []WeatherIntensity{WeatherIntensityLight, WeatherIntensityNormal, WeatherIntensityHeavy} return intensities[rand.Intn(len(intensities))] } } // updateVersion 更新版本 func (w *WeatherAggregate) updateVersion() { w.version++ w.updatedAt = time.Now() } // generateWeatherID 生成天气ID func generateWeatherID(sceneID string) string { if sceneID == "" { sceneID = "default" } return fmt.Sprintf("weather_%s_%d", sceneID, time.Now().UnixNano()) } ================================================ FILE: internal/domain/scene/weather/entity.go ================================================ package weather import ( "fmt" "time" ) // WeatherState 天气状态实体 type WeatherState struct { ID string WeatherType WeatherType Intensity WeatherIntensity StartTime time.Time EndTime time.Time Duration time.Duration Temperature float64 // 温度(摄氏度) Humidity float64 // 湿度(百分比) WindSpeed float64 // 风速(km/h) Visibility float64 // 能见度(km) Pressure float64 // 气压(hPa) Description string CreatedAt time.Time UpdatedAt time.Time } // NewWeatherState 创建天气状态 func NewWeatherState(weatherType WeatherType, intensity WeatherIntensity) *WeatherState { now := time.Now() return &WeatherState{ ID: generateWeatherID(""), WeatherType: weatherType, Intensity: intensity, StartTime: now, Temperature: weatherType.GetBaseTemperature(), Humidity: weatherType.GetBaseHumidity(), WindSpeed: weatherType.GetBaseWindSpeed(), Visibility: weatherType.GetBaseVisibility(), Pressure: 1013.25, // 标准大气压 Description: fmt.Sprintf("%s %s", intensity.GetDescription(), weatherType.GetDescription()), CreatedAt: now, UpdatedAt: now, } } // GetID 获取ID func (ws *WeatherState) GetID() string { return ws.ID } // GetWeatherType 获取天气类型 func (ws *WeatherState) GetWeatherType() WeatherType { return ws.WeatherType } // GetIntensity 获取强度 func (ws *WeatherState) GetIntensity() WeatherIntensity { return ws.Intensity } // GetStartTime 获取开始时间 func (ws *WeatherState) GetStartTime() time.Time { return ws.StartTime } // GetEndTime 获取结束时间 func (ws *WeatherState) GetEndTime() time.Time { return ws.EndTime } // GetDuration 获取持续时间 func (ws *WeatherState) GetDuration() time.Duration { return ws.Duration } // IsActive 检查天气是否激活 func (ws *WeatherState) IsActive() bool { now := time.Now() return now.After(ws.StartTime) && now.Before(ws.EndTime) } // IsExpired 检查天气是否过期 func (ws *WeatherState) IsExpired() bool { return time.Now().After(ws.EndTime) } // GetRemainingTime 获取剩余时间 func (ws *WeatherState) GetRemainingTime() time.Duration { now := time.Now() if now.After(ws.EndTime) { return 0 } return ws.EndTime.Sub(now) } // GetElapsedTime 获取已过时间 func (ws *WeatherState) GetElapsedTime() time.Duration { now := time.Now() if now.Before(ws.StartTime) { return 0 } if now.After(ws.EndTime) { return ws.Duration } return now.Sub(ws.StartTime) } // GetProgress 获取进度(0-1) func (ws *WeatherState) GetProgress() float64 { if ws.Duration == 0 { return 1.0 } elapsed := ws.GetElapsedTime() progress := float64(elapsed) / float64(ws.Duration) if progress > 1.0 { return 1.0 } if progress < 0.0 { return 0.0 } return progress } // UpdateTemperature 更新温度 func (ws *WeatherState) UpdateTemperature(temperature float64) { ws.Temperature = temperature ws.UpdatedAt = time.Now() } // UpdateHumidity 更新湿度 func (ws *WeatherState) UpdateHumidity(humidity float64) { ws.Humidity = humidity ws.UpdatedAt = time.Now() } // UpdateWindSpeed 更新风速 func (ws *WeatherState) UpdateWindSpeed(windSpeed float64) { ws.WindSpeed = windSpeed ws.UpdatedAt = time.Now() } // UpdateVisibility 更新能见度 func (ws *WeatherState) UpdateVisibility(visibility float64) { ws.Visibility = visibility ws.UpdatedAt = time.Now() } // UpdatePressure 更新气压 func (ws *WeatherState) UpdatePressure(pressure float64) { ws.Pressure = pressure ws.UpdatedAt = time.Now() } // GetTemperature 获取温度 func (ws *WeatherState) GetTemperature() float64 { return ws.Temperature } // GetHumidity 获取湿度 func (ws *WeatherState) GetHumidity() float64 { return ws.Humidity } // GetWindSpeed 获取风速 func (ws *WeatherState) GetWindSpeed() float64 { return ws.WindSpeed } // GetVisibility 获取能见度 func (ws *WeatherState) GetVisibility() float64 { return ws.Visibility } // GetPressure 获取气压 func (ws *WeatherState) GetPressure() float64 { return ws.Pressure } // GetDescription 获取描述 func (ws *WeatherState) GetDescription() string { return ws.Description } // UpdateDescription 更新描述 func (ws *WeatherState) UpdateDescription(description string) { ws.Description = description ws.UpdatedAt = time.Now() } // GetCreatedAt 获取创建时间 func (ws *WeatherState) GetCreatedAt() time.Time { return ws.CreatedAt } // GetUpdatedAt 获取更新时间 func (ws *WeatherState) GetUpdatedAt() time.Time { return ws.UpdatedAt } // ToMap 转换为映射 func (ws *WeatherState) ToMap() map[string]interface{} { return map[string]interface{}{ "id": ws.ID, "weather_type": ws.WeatherType.String(), "intensity": ws.Intensity.String(), "start_time": ws.StartTime, "end_time": ws.EndTime, "duration": ws.Duration, "temperature": ws.Temperature, "humidity": ws.Humidity, "wind_speed": ws.WindSpeed, "visibility": ws.Visibility, "pressure": ws.Pressure, "description": ws.Description, "is_active": ws.IsActive(), "progress": ws.GetProgress(), "created_at": ws.CreatedAt, "updated_at": ws.UpdatedAt, } } // WeatherEffect 天气效果实体 type WeatherEffect struct { ID string EffectType string TargetType string Multiplier float64 Duration time.Duration StartTime time.Time EndTime time.Time IsActive bool CreatedAt time.Time UpdatedAt time.Time } // NewWeatherEffect 创建天气效果 func NewWeatherEffect(effectType, targetType string, multiplier float64, duration time.Duration) *WeatherEffect { now := time.Now() return &WeatherEffect{ ID: generateEffectID(), EffectType: effectType, TargetType: targetType, Multiplier: multiplier, Duration: duration, StartTime: now, EndTime: now.Add(duration), IsActive: true, CreatedAt: now, UpdatedAt: now, } } // GetID 获取ID func (we *WeatherEffect) GetID() string { return we.ID } // GetEffectType 获取效果类型 func (we *WeatherEffect) GetEffectType() string { return we.EffectType } // GetTargetType 获取目标类型 func (we *WeatherEffect) GetTargetType() string { return we.TargetType } // GetMultiplier 获取倍率 func (we *WeatherEffect) GetMultiplier() float64 { return we.Multiplier } // GetModifier 获取修饰符(别名方法,与GetMultiplier相同) func (we *WeatherEffect) GetModifier() float64 { return we.Multiplier } // GetDuration 获取持续时间 func (we *WeatherEffect) GetDuration() time.Duration { return we.Duration } // GetStartTime 获取开始时间 func (we *WeatherEffect) GetStartTime() time.Time { return we.StartTime } // GetEndTime 获取结束时间 func (we *WeatherEffect) GetEndTime() time.Time { return we.EndTime } // IsEffectActive 检查效果是否激活 func (we *WeatherEffect) IsEffectActive() bool { now := time.Now() return we.IsActive && now.After(we.StartTime) && now.Before(we.EndTime) } // IsEffectExpired 检查效果是否过期 func (we *WeatherEffect) IsEffectExpired() bool { return time.Now().After(we.EndTime) } // GetRemainingTime 获取剩余时间 func (we *WeatherEffect) GetRemainingTime() time.Duration { now := time.Now() if now.After(we.EndTime) { return 0 } return we.EndTime.Sub(now) } // Activate 激活效果 func (we *WeatherEffect) Activate() { we.IsActive = true we.UpdatedAt = time.Now() } // Deactivate 停用效果 func (we *WeatherEffect) Deactivate() { we.IsActive = false we.UpdatedAt = time.Now() } // ExtendDuration 延长持续时间 func (we *WeatherEffect) ExtendDuration(additionalDuration time.Duration) { we.Duration += additionalDuration we.EndTime = we.EndTime.Add(additionalDuration) we.UpdatedAt = time.Now() } // UpdateMultiplier 更新倍率 func (we *WeatherEffect) UpdateMultiplier(multiplier float64) { we.Multiplier = multiplier we.UpdatedAt = time.Now() } // GetCreatedAt 获取创建时间 func (we *WeatherEffect) GetCreatedAt() time.Time { return we.CreatedAt } // GetUpdatedAt 获取更新时间 func (we *WeatherEffect) GetUpdatedAt() time.Time { return we.UpdatedAt } // ToMap 转换为映射 func (we *WeatherEffect) ToMap() map[string]interface{} { return map[string]interface{}{ "id": we.ID, "effect_type": we.EffectType, "target_type": we.TargetType, "multiplier": we.Multiplier, "duration": we.Duration, "start_time": we.StartTime, "end_time": we.EndTime, "is_active": we.IsActive, "is_effect_active": we.IsEffectActive(), "remaining_time": we.GetRemainingTime(), "created_at": we.CreatedAt, "updated_at": we.UpdatedAt, } } // WeatherEvent 天气事件实体 type WeatherEvent struct { ID string EventType WeatherEventType Severity WeatherEventSeverity Title string Description string StartTime time.Time EndTime time.Time Duration time.Duration Effects []*WeatherEffect Triggers []string // 触发条件 Rewards []string // 奖励 IsActive bool CreatedAt time.Time UpdatedAt time.Time } // NewWeatherEvent 创建天气事件 func NewWeatherEvent(eventType WeatherEventType, severity WeatherEventSeverity, title, description string, duration time.Duration) *WeatherEvent { now := time.Now() return &WeatherEvent{ ID: generateEventID(), EventType: eventType, Severity: severity, Title: title, Description: description, StartTime: now, EndTime: now.Add(duration), Duration: duration, Effects: make([]*WeatherEffect, 0), Triggers: make([]string, 0), Rewards: make([]string, 0), IsActive: true, CreatedAt: now, UpdatedAt: now, } } // GetID 获取ID func (we *WeatherEvent) GetID() string { return we.ID } // GetEventType 获取事件类型 func (we *WeatherEvent) GetEventType() WeatherEventType { return we.EventType } // GetSeverity 获取严重程度 func (we *WeatherEvent) GetSeverity() WeatherEventSeverity { return we.Severity } // GetTitle 获取标题 func (we *WeatherEvent) GetTitle() string { return we.Title } // GetDescription 获取描述 func (we *WeatherEvent) GetDescription() string { return we.Description } // GetStartTime 获取开始时间 func (we *WeatherEvent) GetStartTime() time.Time { return we.StartTime } // GetEndTime 获取结束时间 func (we *WeatherEvent) GetEndTime() time.Time { return we.EndTime } // GetDuration 获取持续时间 func (we *WeatherEvent) GetDuration() time.Duration { return we.Duration } // GetEffects 获取效果列表 func (we *WeatherEvent) GetEffects() []*WeatherEffect { return we.Effects } // AddEffect 添加效果 func (we *WeatherEvent) AddEffect(effect *WeatherEffect) { we.Effects = append(we.Effects, effect) we.UpdatedAt = time.Now() } // RemoveEffect 移除效果 func (we *WeatherEvent) RemoveEffect(effectID string) { for i, effect := range we.Effects { if effect.GetID() == effectID { we.Effects = append(we.Effects[:i], we.Effects[i+1:]...) we.UpdatedAt = time.Now() break } } } // GetTriggers 获取触发条件 func (we *WeatherEvent) GetTriggers() []string { return we.Triggers } // AddTrigger 添加触发条件 func (we *WeatherEvent) AddTrigger(trigger string) { we.Triggers = append(we.Triggers, trigger) we.UpdatedAt = time.Now() } // GetRewards 获取奖励 func (we *WeatherEvent) GetRewards() []string { return we.Rewards } // AddReward 添加奖励 func (we *WeatherEvent) AddReward(reward string) { we.Rewards = append(we.Rewards, reward) we.UpdatedAt = time.Now() } // IsEventActive 检查事件是否激活 func (we *WeatherEvent) IsEventActive() bool { now := time.Now() return we.IsActive && now.After(we.StartTime) && now.Before(we.EndTime) } // IsEventExpired 检查事件是否过期 func (we *WeatherEvent) IsEventExpired() bool { return time.Now().After(we.EndTime) } // Activate 激活事件 func (we *WeatherEvent) Activate() { we.IsActive = true we.UpdatedAt = time.Now() } // Deactivate 停用事件 func (we *WeatherEvent) Deactivate() { we.IsActive = false we.UpdatedAt = time.Now() } // GetCreatedAt 获取创建时间 func (we *WeatherEvent) GetCreatedAt() time.Time { return we.CreatedAt } // GetUpdatedAt 获取更新时间 func (we *WeatherEvent) GetUpdatedAt() time.Time { return we.UpdatedAt } // ToMap 转换为映射 func (we *WeatherEvent) ToMap() map[string]interface{} { return map[string]interface{}{ "id": we.ID, "event_type": we.EventType.String(), "severity": we.Severity.String(), "title": we.Title, "description": we.Description, "start_time": we.StartTime, "end_time": we.EndTime, "duration": we.Duration, "effects": len(we.Effects), "triggers": we.Triggers, "rewards": we.Rewards, "is_active": we.IsActive, "is_event_active": we.IsEventActive(), "created_at": we.CreatedAt, "updated_at": we.UpdatedAt, } } // 辅助函数 // generateWeatherID 生成天气ID - 已在aggregate.go中定义 // generateEffectID 生成效果ID func generateEffectID() string { return fmt.Sprintf("effect_%d", time.Now().UnixNano()) } // generateEventID 生成事件ID func generateEventID() string { return fmt.Sprintf("event_%d", time.Now().UnixNano()) } ================================================ FILE: internal/domain/scene/weather/errors.go ================================================ package weather import ( "errors" "fmt" "time" ) // 基础错误定义 var ( // 天气类型相关错误 ErrInvalidWeatherType = errors.New("invalid weather type") ErrUnsupportedWeatherType = errors.New("unsupported weather type") ErrWeatherTypeNotFound = errors.New("weather type not found") ErrWeatherTypeConflict = errors.New("weather type conflict") // 天气强度相关错误 ErrInvalidWeatherIntensity = errors.New("invalid weather intensity") ErrIntensityOutOfRange = errors.New("weather intensity out of range") ErrIntensityNotSupported = errors.New("weather intensity not supported") // 天气状态相关错误 ErrInvalidWeatherState = errors.New("invalid weather state") ErrWeatherStateExpired = errors.New("weather state expired") ErrWeatherStateNotActive = errors.New("weather state not active") ErrWeatherStateConflict = errors.New("weather state conflict") ErrWeatherStateNotFound = errors.New("weather state not found") // 天气转换相关错误 ErrInvalidWeatherTransition = errors.New("invalid weather transition") ErrWeatherTransitionBlocked = errors.New("weather transition blocked") ErrTransitionTooFrequent = errors.New("weather transition too frequent") ErrTransitionNotAllowed = errors.New("weather transition not allowed") // 天气效果相关错误 ErrInvalidWeatherEffect = errors.New("invalid weather effect") ErrWeatherEffectExpired = errors.New("weather effect expired") ErrWeatherEffectNotActive = errors.New("weather effect not active") ErrWeatherEffectConflict = errors.New("weather effect conflict") ErrWeatherEffectNotFound = errors.New("weather effect not found") ErrEffectMultiplierInvalid = errors.New("effect multiplier invalid") // 天气事件相关错误 ErrInvalidWeatherEvent = errors.New("invalid weather event") ErrWeatherEventExpired = errors.New("weather event expired") ErrWeatherEventNotActive = errors.New("weather event not active") ErrWeatherEventConflict = errors.New("weather event conflict") ErrWeatherEventNotFound = errors.New("weather event not found") ErrEventTriggerFailed = errors.New("weather event trigger failed") ErrEventSeverityInvalid = errors.New("weather event severity invalid") // 天气预报相关错误 ErrInvalidForecastPeriod = errors.New("invalid forecast period") ErrForecastNotAvailable = errors.New("weather forecast not available") ErrForecastExpired = errors.New("weather forecast expired") ErrForecastGenerationFailed = errors.New("weather forecast generation failed") ErrForecastAccuracyLow = errors.New("weather forecast accuracy too low") // 季节模式相关错误 ErrInvalidSeason = errors.New("invalid season") ErrSeasonalPatternNotFound = errors.New("seasonal pattern not found") ErrSeasonalPatternInvalid = errors.New("seasonal pattern invalid") ErrSeasonTransitionFailed = errors.New("season transition failed") // 气候区域相关错误 ErrInvalidClimateZone = errors.New("invalid climate zone") ErrClimateZoneNotFound = errors.New("climate zone not found") ErrClimateZoneConflict = errors.New("climate zone conflict") // 时间相关错误 ErrInvalidTimeRange = errors.New("invalid time range") ErrInvalidDuration = errors.New("invalid duration") ErrInvalidChangeInterval = errors.New("invalid change interval") ErrTimeoutExceeded = errors.New("timeout exceeded") // 配置相关错误 ErrInvalidConfiguration = errors.New("invalid weather configuration") ErrConfigurationNotFound = errors.New("weather configuration not found") ErrConfigurationConflict = errors.New("weather configuration conflict") // 数据相关错误 ErrDataCorrupted = errors.New("weather data corrupted") ErrDataNotFound = errors.New("weather data not found") ErrDataValidationFailed = errors.New("weather data validation failed") ErrDataSerializationFailed = errors.New("weather data serialization failed") ErrWeatherNotFound = errors.New("weather not found") // 系统相关错误 ErrSystemNotInitialized = errors.New("weather system not initialized") ErrSystemOverloaded = errors.New("weather system overloaded") ErrSystemMaintenance = errors.New("weather system under maintenance") ErrResourceExhausted = errors.New("weather system resources exhausted") // 并发相关错误 ErrConcurrentModification = errors.New("concurrent weather modification") ErrLockAcquisitionFailed = errors.New("weather lock acquisition failed") ErrVersionMismatch = errors.New("weather version mismatch") // 权限相关错误 ErrPermissionDenied = errors.New("weather operation permission denied") ErrUnauthorizedAccess = errors.New("unauthorized weather access") ErrInsufficientPrivileges = errors.New("insufficient weather privileges") ) // WeatherError 天气错误结构体 type WeatherError struct { Code string Message string Cause error Context map[string]interface{} Timestamp int64 } // Error 实现error接口 func (e *WeatherError) Error() string { if e.Cause != nil { return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Cause) } return fmt.Sprintf("[%s] %s", e.Code, e.Message) } // Unwrap 返回原始错误 func (e *WeatherError) Unwrap() error { return e.Cause } // WithContext 添加上下文信息 func (e *WeatherError) WithContext(key string, value interface{}) *WeatherError { if e.Context == nil { e.Context = make(map[string]interface{}) } e.Context[key] = value return e } // GetContext 获取上下文信息 func (e *WeatherError) GetContext(key string) interface{} { if e.Context == nil { return nil } return e.Context[key] } // NewWeatherError 创建天气错误 func NewWeatherError(code, message string, cause error) *WeatherError { return &WeatherError{ Code: code, Message: message, Cause: cause, Context: make(map[string]interface{}), Timestamp: getCurrentTimestamp(), } } // ValidationError 验证错误 type ValidationError struct { Field string Value interface{} Rule string Message string } // Error 实现error接口 func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed for field '%s': %s (value: %v, rule: %s)", e.Field, e.Message, e.Value, e.Rule) } // NewValidationError 创建验证错误 func NewValidationError(field string, value interface{}, rule, message string) *ValidationError { return &ValidationError{ Field: field, Value: value, Rule: rule, Message: message, } } // BusinessRuleError 业务规则错误 type BusinessRuleError struct { Rule string Description string Violation string Context map[string]interface{} } // Error 实现error接口 func (e *BusinessRuleError) Error() string { return fmt.Sprintf("business rule violation: %s - %s (%s)", e.Rule, e.Description, e.Violation) } // NewBusinessRuleError 创建业务规则错误 func NewBusinessRuleError(rule, description, violation string) *BusinessRuleError { return &BusinessRuleError{ Rule: rule, Description: description, Violation: violation, Context: make(map[string]interface{}), } } // ConcurrencyError 并发错误 type ConcurrencyError struct { Operation string Resource string ConflictID string RetryCount int MaxRetries int LastAttempt int64 } // Error 实现error接口 func (e *ConcurrencyError) Error() string { return fmt.Sprintf("concurrency error in operation '%s' on resource '%s': conflict with %s (retry %d/%d)", e.Operation, e.Resource, e.ConflictID, e.RetryCount, e.MaxRetries) } // CanRetry 检查是否可以重试 func (e *ConcurrencyError) CanRetry() bool { return e.RetryCount < e.MaxRetries } // NewConcurrencyError 创建并发错误 func NewConcurrencyError(operation, resource, conflictID string, retryCount, maxRetries int) *ConcurrencyError { return &ConcurrencyError{ Operation: operation, Resource: resource, ConflictID: conflictID, RetryCount: retryCount, MaxRetries: maxRetries, LastAttempt: getCurrentTimestamp(), } } // ConfigurationError 配置错误 type ConfigurationError struct { ConfigType string ConfigKey string ConfigValue interface{} Expected string Actual string Suggestion string } // Error 实现error接口 func (e *ConfigurationError) Error() string { return fmt.Sprintf("configuration error in %s.%s: expected %s, got %s (value: %v). Suggestion: %s", e.ConfigType, e.ConfigKey, e.Expected, e.Actual, e.ConfigValue, e.Suggestion) } // NewConfigurationError 创建配置错误 func NewConfigurationError(configType, configKey string, configValue interface{}, expected, actual, suggestion string) *ConfigurationError { return &ConfigurationError{ ConfigType: configType, ConfigKey: configKey, ConfigValue: configValue, Expected: expected, Actual: actual, Suggestion: suggestion, } } // SystemError 系统错误 type SystemError struct { Component string Operation string ErrorCode string Description string Cause error Severity ErrorSeverity Recoverable bool Timestamp int64 } // Error 实现error接口 func (e *SystemError) Error() string { msg := fmt.Sprintf("system error in %s.%s [%s]: %s (severity: %s, recoverable: %t)", e.Component, e.Operation, e.ErrorCode, e.Description, e.Severity, e.Recoverable) if e.Cause != nil { msg += fmt.Sprintf(", cause: %v", e.Cause) } return msg } // Unwrap 返回原始错误 func (e *SystemError) Unwrap() error { return e.Cause } // NewSystemError 创建系统错误 func NewSystemError(component, operation, errorCode, description string, cause error, severity ErrorSeverity, recoverable bool) *SystemError { return &SystemError{ Component: component, Operation: operation, ErrorCode: errorCode, Description: description, Cause: cause, Severity: severity, Recoverable: recoverable, Timestamp: getCurrentTimestamp(), } } // ErrorSeverity 错误严重程度 type ErrorSeverity int const ( ErrorSeverityLow ErrorSeverity = iota + 1 ErrorSeverityMedium ErrorSeverityHigh ErrorSeverityCritical ) // String 返回严重程度字符串 func (es ErrorSeverity) String() string { switch es { case ErrorSeverityLow: return "low" case ErrorSeverityMedium: return "medium" case ErrorSeverityHigh: return "high" case ErrorSeverityCritical: return "critical" default: return "unknown" } } // ErrorCollection 错误集合 type ErrorCollection struct { Errors []error Context string Timestamp int64 } // Error 实现error接口 func (ec *ErrorCollection) Error() string { if len(ec.Errors) == 0 { return "no errors" } if len(ec.Errors) == 1 { return fmt.Sprintf("error in %s: %v", ec.Context, ec.Errors[0]) } msg := fmt.Sprintf("%d errors in %s:", len(ec.Errors), ec.Context) for i, err := range ec.Errors { msg += fmt.Sprintf("\n %d. %v", i+1, err) } return msg } // Add 添加错误 func (ec *ErrorCollection) Add(err error) { if err != nil { ec.Errors = append(ec.Errors, err) } } // HasErrors 检查是否有错误 func (ec *ErrorCollection) HasErrors() bool { return len(ec.Errors) > 0 } // Count 获取错误数量 func (ec *ErrorCollection) Count() int { return len(ec.Errors) } // First 获取第一个错误 func (ec *ErrorCollection) First() error { if len(ec.Errors) > 0 { return ec.Errors[0] } return nil } // Last 获取最后一个错误 func (ec *ErrorCollection) Last() error { if len(ec.Errors) > 0 { return ec.Errors[len(ec.Errors)-1] } return nil } // Clear 清空错误 func (ec *ErrorCollection) Clear() { ec.Errors = ec.Errors[:0] } // NewErrorCollection 创建错误集合 func NewErrorCollection(context string) *ErrorCollection { return &ErrorCollection{ Errors: make([]error, 0), Context: context, Timestamp: getCurrentTimestamp(), } } // 错误工厂函数 // NewInvalidWeatherTypeError 创建无效天气类型错误 func NewInvalidWeatherTypeError(weatherType interface{}) *WeatherError { return NewWeatherError("INVALID_WEATHER_TYPE", "Invalid weather type provided", ErrInvalidWeatherType). WithContext("weather_type", weatherType) } // NewInvalidWeatherIntensityError 创建无效天气强度错误 func NewInvalidWeatherIntensityError(intensity interface{}) *WeatherError { return NewWeatherError("INVALID_WEATHER_INTENSITY", "Invalid weather intensity provided", ErrInvalidWeatherIntensity). WithContext("intensity", intensity) } // NewWeatherStateExpiredError 创建天气状态过期错误 func NewWeatherStateExpiredError(stateID string, expiredAt int64) *WeatherError { return NewWeatherError("WEATHER_STATE_EXPIRED", "Weather state has expired", ErrWeatherStateExpired). WithContext("state_id", stateID). WithContext("expired_at", expiredAt) } // NewWeatherTransitionBlockedError 创建天气转换被阻止错误 func NewWeatherTransitionBlockedError(from, to WeatherType, reason string) *WeatherError { return NewWeatherError("WEATHER_TRANSITION_BLOCKED", "Weather transition is blocked", ErrWeatherTransitionBlocked). WithContext("from_weather", from.String()). WithContext("to_weather", to.String()). WithContext("reason", reason) } // NewForecastGenerationFailedError 创建预报生成失败错误 func NewForecastGenerationFailedError(sceneID string, hours int, cause error) *WeatherError { return NewWeatherError("FORECAST_GENERATION_FAILED", "Failed to generate weather forecast", cause). WithContext("scene_id", sceneID). WithContext("hours", hours) } // NewClimateZoneNotFoundError 创建气候区域未找到错误 func NewClimateZoneNotFoundError(zoneID string) *WeatherError { return NewWeatherError("CLIMATE_ZONE_NOT_FOUND", "Climate zone not found", ErrClimateZoneNotFound). WithContext("zone_id", zoneID) } // NewWeatherSystemOverloadedError 创建天气系统过载错误 func NewWeatherSystemOverloadedError(currentLoad, maxLoad int) *WeatherError { return NewWeatherError("WEATHER_SYSTEM_OVERLOADED", "Weather system is overloaded", ErrSystemOverloaded). WithContext("current_load", currentLoad). WithContext("max_load", maxLoad) } // NewConcurrentWeatherModificationError 创建并发天气修改错误 func NewConcurrentWeatherModificationError(sceneID, operation string, conflictVersion int) *WeatherError { return NewWeatherError("CONCURRENT_WEATHER_MODIFICATION", "Concurrent weather modification detected", ErrConcurrentModification). WithContext("scene_id", sceneID). WithContext("operation", operation). WithContext("conflict_version", conflictVersion) } // 错误检查函数 // IsWeatherError 检查是否为天气错误 func IsWeatherError(err error) bool { _, ok := err.(*WeatherError) return ok } // IsValidationError 检查是否为验证错误 func IsValidationError(err error) bool { _, ok := err.(*ValidationError) return ok } // IsBusinessRuleError 检查是否为业务规则错误 func IsBusinessRuleError(err error) bool { _, ok := err.(*BusinessRuleError) return ok } // IsConcurrencyError 检查是否为并发错误 func IsConcurrencyError(err error) bool { _, ok := err.(*ConcurrencyError) return ok } // IsSystemError 检查是否为系统错误 func IsSystemError(err error) bool { _, ok := err.(*SystemError) return ok } // IsRecoverableError 检查错误是否可恢复 func IsRecoverableError(err error) bool { if sysErr, ok := err.(*SystemError); ok { return sysErr.Recoverable } if concErr, ok := err.(*ConcurrencyError); ok { return concErr.CanRetry() } return false } // GetErrorSeverity 获取错误严重程度 func GetErrorSeverity(err error) ErrorSeverity { if sysErr, ok := err.(*SystemError); ok { return sysErr.Severity } return ErrorSeverityMedium // 默认中等严重程度 } // 辅助函数 // getCurrentTimestamp 获取当前时间戳 func getCurrentTimestamp() int64 { return getCurrentTime().Unix() } // getCurrentTime 获取当前时间(可用于测试时的时间注入) var getCurrentTime = func() time.Time { return time.Now() } ================================================ FILE: internal/domain/scene/weather/events.go ================================================ package weather import ( "fmt" "time" ) // DomainEvent 领域事件接口 type DomainEvent interface { GetEventID() string GetEventType() string GetAggregateID() string GetOccurredAt() time.Time GetVersion() int GetPayload() map[string]interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { EventID string EventType string AggregateID string OccurredAt time.Time Version int Payload map[string]interface{} } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.EventID } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.EventType } // GetAggregateID 获取聚合ID func (e *BaseDomainEvent) GetAggregateID() string { return e.AggregateID } // GetOccurredAt 获取发生时间 func (e *BaseDomainEvent) GetOccurredAt() time.Time { return e.OccurredAt } // GetVersion 获取版本 func (e *BaseDomainEvent) GetVersion() int { return e.Version } // GetPayload 获取载荷 func (e *BaseDomainEvent) GetPayload() map[string]interface{} { return e.Payload } // WeatherChangedEvent 天气变化事件 type WeatherChangedEvent struct { *BaseDomainEvent SceneID string PreviousWeather *WeatherState CurrentWeather *WeatherState ChangeReason string Effects []*WeatherEffect } // NewWeatherChangedEvent 创建天气变化事件 func NewWeatherChangedEvent(sceneID string, previous, current *WeatherState, reason string, effects []*WeatherEffect) *WeatherChangedEvent { now := time.Now() event := &WeatherChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_changed_%d", now.UnixNano()), EventType: "weather.changed", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, PreviousWeather: previous, CurrentWeather: current, ChangeReason: reason, Effects: effects, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["change_reason"] = reason if previous != nil { event.Payload["previous_weather_type"] = previous.WeatherType.String() event.Payload["previous_intensity"] = previous.Intensity.String() } if current != nil { event.Payload["current_weather_type"] = current.WeatherType.String() event.Payload["current_intensity"] = current.Intensity.String() event.Payload["current_temperature"] = current.Temperature event.Payload["current_humidity"] = current.Humidity } event.Payload["effects_count"] = len(effects) return event } // WeatherIntensityChangedEvent 天气强度变化事件 type WeatherIntensityChangedEvent struct { *BaseDomainEvent SceneID string WeatherType WeatherType PreviousIntensity WeatherIntensity CurrentIntensity WeatherIntensity ChangeReason string } // NewWeatherIntensityChangedEvent 创建天气强度变化事件 func NewWeatherIntensityChangedEvent(sceneID string, weatherType WeatherType, previous, current WeatherIntensity, reason string) *WeatherIntensityChangedEvent { now := time.Now() event := &WeatherIntensityChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_intensity_changed_%d", now.UnixNano()), EventType: "weather.intensity_changed", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, WeatherType: weatherType, PreviousIntensity: previous, CurrentIntensity: current, ChangeReason: reason, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["weather_type"] = weatherType.String() event.Payload["previous_intensity"] = previous.String() event.Payload["current_intensity"] = current.String() event.Payload["change_reason"] = reason return event } // WeatherEffectActivatedEvent 天气效果激活事件 type WeatherEffectActivatedEvent struct { *BaseDomainEvent SceneID string Effect *WeatherEffect WeatherType WeatherType Intensity WeatherIntensity Duration time.Duration } // NewWeatherEffectActivatedEvent 创建天气效果激活事件 func NewWeatherEffectActivatedEvent(sceneID string, effect *WeatherEffect, weatherType WeatherType, intensity WeatherIntensity) *WeatherEffectActivatedEvent { now := time.Now() event := &WeatherEffectActivatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_effect_activated_%d", now.UnixNano()), EventType: "weather.effect_activated", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, Effect: effect, WeatherType: weatherType, Intensity: intensity, Duration: effect.Duration, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["effect_id"] = effect.ID event.Payload["effect_type"] = effect.EffectType event.Payload["multiplier"] = effect.Multiplier event.Payload["duration"] = effect.Duration event.Payload["weather_type"] = weatherType.String() event.Payload["intensity"] = intensity.String() return event } // WeatherEffectDeactivatedEvent 天气效果停用事件 type WeatherEffectDeactivatedEvent struct { *BaseDomainEvent SceneID string Effect *WeatherEffect Reason string Duration time.Duration } // NewWeatherEffectDeactivatedEvent 创建天气效果停用事件 func NewWeatherEffectDeactivatedEvent(sceneID string, effect *WeatherEffect, reason string) *WeatherEffectDeactivatedEvent { now := time.Now() event := &WeatherEffectDeactivatedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_effect_deactivated_%d", now.UnixNano()), EventType: "weather.effect_deactivated", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, Effect: effect, Reason: reason, Duration: now.Sub(effect.StartTime), } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["effect_id"] = effect.ID event.Payload["effect_type"] = effect.EffectType event.Payload["reason"] = reason event.Payload["total_duration"] = now.Sub(effect.StartTime) return event } // WeatherEventTriggeredEvent 天气事件触发事件 type WeatherEventTriggeredEvent struct { *BaseDomainEvent SceneID string WeatherEvent *WeatherEvent TriggerWeather *WeatherState Severity WeatherEventSeverity } // NewWeatherEventTriggeredEvent 创建天气事件触发事件 func NewWeatherEventTriggeredEvent(sceneID string, weatherEvent *WeatherEvent, triggerWeather *WeatherState) *WeatherEventTriggeredEvent { now := time.Now() event := &WeatherEventTriggeredEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_event_triggered_%d", now.UnixNano()), EventType: "weather.event_triggered", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, WeatherEvent: weatherEvent, TriggerWeather: triggerWeather, Severity: weatherEvent.Severity, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["event_id"] = weatherEvent.ID event.Payload["event_type"] = weatherEvent.EventType.String() event.Payload["severity"] = weatherEvent.Severity.String() event.Payload["title"] = weatherEvent.Title event.Payload["duration"] = weatherEvent.Duration if triggerWeather != nil { event.Payload["trigger_weather_type"] = triggerWeather.WeatherType.String() event.Payload["trigger_intensity"] = triggerWeather.Intensity.String() } return event } // WeatherEventEndedEvent 天气事件结束事件 type WeatherEventEndedEvent struct { *BaseDomainEvent SceneID string WeatherEvent *WeatherEvent EndReason string TotalDuration time.Duration } // NewWeatherEventEndedEvent 创建天气事件结束事件 func NewWeatherEventEndedEvent(sceneID string, weatherEvent *WeatherEvent, endReason string) *WeatherEventEndedEvent { now := time.Now() event := &WeatherEventEndedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_event_ended_%d", now.UnixNano()), EventType: "weather.event_ended", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, WeatherEvent: weatherEvent, EndReason: endReason, TotalDuration: now.Sub(weatherEvent.StartTime), } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["event_id"] = weatherEvent.ID event.Payload["event_type"] = weatherEvent.EventType.String() event.Payload["end_reason"] = endReason event.Payload["total_duration"] = now.Sub(weatherEvent.StartTime) event.Payload["planned_duration"] = weatherEvent.Duration return event } // WeatherForecastGeneratedEvent 天气预报生成事件 type WeatherForecastGeneratedEvent struct { *BaseDomainEvent SceneID string Forecasts []*WeatherForecast Hours int Confidence float64 } // NewWeatherForecastGeneratedEvent 创建天气预报生成事件 func NewWeatherForecastGeneratedEvent(sceneID string, forecasts []*WeatherForecast, hours int) *WeatherForecastGeneratedEvent { now := time.Now() // 计算平均置信度 totalConfidence := 0.0 for _, forecast := range forecasts { totalConfidence += forecast.Confidence } averageConfidence := totalConfidence / float64(len(forecasts)) event := &WeatherForecastGeneratedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_forecast_generated_%d", now.UnixNano()), EventType: "weather.forecast_generated", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, Forecasts: forecasts, Hours: hours, Confidence: averageConfidence, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["forecast_count"] = len(forecasts) event.Payload["hours"] = hours event.Payload["average_confidence"] = averageConfidence if len(forecasts) > 0 { event.Payload["first_forecast_time"] = forecasts[0].Time event.Payload["last_forecast_time"] = forecasts[len(forecasts)-1].Time } return event } // SeasonChangedEvent 季节变化事件 type SeasonChangedEvent struct { *BaseDomainEvent ZoneID string PreviousSeason Season CurrentSeason Season ChangeTime time.Time Pattern *SeasonalPattern } // NewSeasonChangedEvent 创建季节变化事件 func NewSeasonChangedEvent(zoneID string, previous, current Season, pattern *SeasonalPattern) *SeasonChangedEvent { now := time.Now() event := &SeasonChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("season_changed_%d", now.UnixNano()), EventType: "weather.season_changed", AggregateID: zoneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, ZoneID: zoneID, PreviousSeason: previous, CurrentSeason: current, ChangeTime: now, Pattern: pattern, } // 设置载荷 event.Payload["zone_id"] = zoneID event.Payload["previous_season"] = previous.String() event.Payload["current_season"] = current.String() event.Payload["change_time"] = now return event } // WeatherSystemInitializedEvent 天气系统初始化事件 type WeatherSystemInitializedEvent struct { *BaseDomainEvent SceneID string InitialWeather *WeatherState ClimateZone string SeasonalPattern *SeasonalPattern } // NewWeatherSystemInitializedEvent 创建天气系统初始化事件 func NewWeatherSystemInitializedEvent(sceneID string, initialWeather *WeatherState, climateZone string, pattern *SeasonalPattern) *WeatherSystemInitializedEvent { now := time.Now() event := &WeatherSystemInitializedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_system_initialized_%d", now.UnixNano()), EventType: "weather.system_initialized", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, InitialWeather: initialWeather, ClimateZone: climateZone, SeasonalPattern: pattern, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["climate_zone"] = climateZone if initialWeather != nil { event.Payload["initial_weather_type"] = initialWeather.WeatherType.String() event.Payload["initial_intensity"] = initialWeather.Intensity.String() event.Payload["initial_temperature"] = initialWeather.Temperature } if pattern != nil { event.Payload["current_season"] = pattern.CurrentSeason.String() } return event } // WeatherUpdateFailedEvent 天气更新失败事件 type WeatherUpdateFailedEvent struct { *BaseDomainEvent SceneID string Error error ErrorMessage string RetryCount int LastAttempt time.Time } // NewWeatherUpdateFailedEvent 创建天气更新失败事件 func NewWeatherUpdateFailedEvent(sceneID string, err error, retryCount int) *WeatherUpdateFailedEvent { now := time.Now() event := &WeatherUpdateFailedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_update_failed_%d", now.UnixNano()), EventType: "weather.update_failed", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, Error: err, ErrorMessage: err.Error(), RetryCount: retryCount, LastAttempt: now, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["error_message"] = err.Error() event.Payload["retry_count"] = retryCount event.Payload["last_attempt"] = now return event } // WeatherDataCorruptedEvent 天气数据损坏事件 type WeatherDataCorruptedEvent struct { *BaseDomainEvent SceneID string CorruptedData string CorruptionType string RecoveryAction string } // NewWeatherDataCorruptedEvent 创建天气数据损坏事件 func NewWeatherDataCorruptedEvent(sceneID, corruptedData, corruptionType, recoveryAction string) *WeatherDataCorruptedEvent { now := time.Now() event := &WeatherDataCorruptedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_data_corrupted_%d", now.UnixNano()), EventType: "weather.data_corrupted", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, CorruptedData: corruptedData, CorruptionType: corruptionType, RecoveryAction: recoveryAction, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["corrupted_data"] = corruptedData event.Payload["corruption_type"] = corruptionType event.Payload["recovery_action"] = recoveryAction return event } // WeatherAnomalyDetectedEvent 天气异常检测事件 type WeatherAnomalyDetectedEvent struct { *BaseDomainEvent SceneID string AnomalyType string AnomalyData map[string]interface{} SeverityLevel int DetectionTime time.Time } // NewWeatherAnomalyDetectedEvent 创建天气异常检测事件 func NewWeatherAnomalyDetectedEvent(sceneID, anomalyType string, anomalyData map[string]interface{}, severityLevel int) *WeatherAnomalyDetectedEvent { now := time.Now() event := &WeatherAnomalyDetectedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_anomaly_detected_%d", now.UnixNano()), EventType: "weather.anomaly_detected", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, AnomalyType: anomalyType, AnomalyData: anomalyData, SeverityLevel: severityLevel, DetectionTime: now, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["anomaly_type"] = anomalyType event.Payload["severity_level"] = severityLevel event.Payload["detection_time"] = now for k, v := range anomalyData { event.Payload[k] = v } return event } // WeatherConfigurationChangedEvent 天气配置变化事件 type WeatherConfigurationChangedEvent struct { *BaseDomainEvent SceneID string ConfigurationType string OldConfiguration map[string]interface{} NewConfiguration map[string]interface{} ChangedBy string } // NewWeatherConfigurationChangedEvent 创建天气配置变化事件 func NewWeatherConfigurationChangedEvent(sceneID, configurationType string, oldConfig, newConfig map[string]interface{}, changedBy string) *WeatherConfigurationChangedEvent { now := time.Now() event := &WeatherConfigurationChangedEvent{ BaseDomainEvent: &BaseDomainEvent{ EventID: fmt.Sprintf("weather_configuration_changed_%d", now.UnixNano()), EventType: "weather.configuration_changed", AggregateID: sceneID, OccurredAt: now, Version: 1, Payload: make(map[string]interface{}), }, SceneID: sceneID, ConfigurationType: configurationType, OldConfiguration: oldConfig, NewConfiguration: newConfig, ChangedBy: changedBy, } // 设置载荷 event.Payload["scene_id"] = sceneID event.Payload["configuration_type"] = configurationType event.Payload["changed_by"] = changedBy event.Payload["old_configuration"] = oldConfig event.Payload["new_configuration"] = newConfig return event } // 事件处理器接口 // EventHandler 事件处理器接口 type EventHandler interface { Handle(event DomainEvent) error CanHandle(eventType string) bool } // EventBus 事件总线接口 type EventBus interface { Publish(event DomainEvent) error Subscribe(eventType string, handler EventHandler) error Unsubscribe(eventType string, handler EventHandler) error } // 事件存储接口 // EventStore 事件存储接口 type EventStore interface { Save(event DomainEvent) error Load(aggregateID string) ([]DomainEvent, error) LoadFromVersion(aggregateID string, version int) ([]DomainEvent, error) LoadByEventType(eventType string, limit int) ([]DomainEvent, error) LoadByTimeRange(startTime, endTime time.Time) ([]DomainEvent, error) } ================================================ FILE: internal/domain/scene/weather/repository.go ================================================ package weather import ( "context" "time" ) // WeatherRepository 天气仓储接口 type WeatherRepository interface { // 基础CRUD操作 Save(ctx context.Context, weather *WeatherAggregate) error FindByID(ctx context.Context, id string) (*WeatherAggregate, error) FindBySceneID(ctx context.Context, sceneID string) (*WeatherAggregate, error) Update(ctx context.Context, weather *WeatherAggregate) error Delete(ctx context.Context, id string) error // 查询操作 FindBySceneIDs(ctx context.Context, sceneIDs []string) ([]*WeatherAggregate, error) FindActiveWeather(ctx context.Context, sceneID string) (*WeatherAggregate, error) FindWeatherByTimeRange(ctx context.Context, sceneID string, startTime, endTime time.Time) ([]*WeatherAggregate, error) FindWeatherByType(ctx context.Context, weatherType WeatherType, limit int) ([]*WeatherAggregate, error) // 统计操作 GetWeatherStatistics(ctx context.Context, sceneID string, period time.Duration) (*WeatherStatistics, error) GetWeatherCount(ctx context.Context, sceneID string) (int64, error) GetWeatherCountByType(ctx context.Context, sceneID string, weatherType WeatherType) (int64, error) // 批量操作 SaveBatch(ctx context.Context, weathers []*WeatherAggregate) error DeleteBatch(ctx context.Context, ids []string) error UpdateBatch(ctx context.Context, weathers []*WeatherAggregate) error // 清理操作 CleanupExpiredWeather(ctx context.Context, beforeTime time.Time) (int64, error) CleanupOldHistory(ctx context.Context, sceneID string, keepDays int) (int64, error) } // WeatherStateRepository 天气状态仓储接口 type WeatherStateRepository interface { // 基础CRUD操作 Save(ctx context.Context, state *WeatherState) error FindByID(ctx context.Context, id string) (*WeatherState, error) Update(ctx context.Context, state *WeatherState) error Delete(ctx context.Context, id string) error // 查询操作 FindBySceneID(ctx context.Context, sceneID string, limit int) ([]*WeatherState, error) FindCurrentState(ctx context.Context, sceneID string) (*WeatherState, error) FindActiveStates(ctx context.Context, sceneIDs []string) ([]*WeatherState, error) FindByTimeRange(ctx context.Context, sceneID string, startTime, endTime time.Time) ([]*WeatherState, error) FindByWeatherType(ctx context.Context, weatherType WeatherType, limit int) ([]*WeatherState, error) FindByIntensity(ctx context.Context, intensity WeatherIntensity, limit int) ([]*WeatherState, error) // 历史记录操作 SaveHistory(ctx context.Context, sceneID string, states []*WeatherState) error GetHistory(ctx context.Context, sceneID string, limit int) ([]*WeatherState, error) GetHistoryByTimeRange(ctx context.Context, sceneID string, startTime, endTime time.Time) ([]*WeatherState, error) // 统计操作 GetStateStatistics(ctx context.Context, sceneID string, period time.Duration) (*StateStatistics, error) GetAverageTemperature(ctx context.Context, sceneID string, period time.Duration) (float64, error) GetAverageHumidity(ctx context.Context, sceneID string, period time.Duration) (float64, error) // 清理操作 CleanupExpiredStates(ctx context.Context, beforeTime time.Time) (int64, error) } // WeatherEffectRepository 天气效果仓储接口 type WeatherEffectRepository interface { // 基础CRUD操作 Save(ctx context.Context, effect *WeatherEffect) error FindByID(ctx context.Context, id string) (*WeatherEffect, error) Update(ctx context.Context, effect *WeatherEffect) error Delete(ctx context.Context, id string) error // 查询操作 FindBySceneID(ctx context.Context, sceneID string) ([]*WeatherEffect, error) FindActiveEffects(ctx context.Context, sceneID string) ([]*WeatherEffect, error) FindByEffectType(ctx context.Context, effectType string, limit int) ([]*WeatherEffect, error) FindByTimeRange(ctx context.Context, sceneID string, startTime, endTime time.Time) ([]*WeatherEffect, error) // 批量操作 SaveBatch(ctx context.Context, effects []*WeatherEffect) error DeleteBatch(ctx context.Context, ids []string) error UpdateBatch(ctx context.Context, effects []*WeatherEffect) error // 清理操作 CleanupExpiredEffects(ctx context.Context, beforeTime time.Time) (int64, error) DeactivateEffects(ctx context.Context, sceneID string, effectTypes []string) error } // WeatherEventRepository 天气事件仓储接口 type WeatherEventRepository interface { // 基础CRUD操作 Save(ctx context.Context, event *WeatherEvent) error FindByID(ctx context.Context, id string) (*WeatherEvent, error) Update(ctx context.Context, event *WeatherEvent) error Delete(ctx context.Context, id string) error // 查询操作 FindBySceneID(ctx context.Context, sceneID string, limit int) ([]*WeatherEvent, error) FindActiveEvents(ctx context.Context, sceneID string) ([]*WeatherEvent, error) FindByEventType(ctx context.Context, eventType WeatherEventType, limit int) ([]*WeatherEvent, error) FindBySeverity(ctx context.Context, severity WeatherEventSeverity, limit int) ([]*WeatherEvent, error) FindByTimeRange(ctx context.Context, sceneID string, startTime, endTime time.Time) ([]*WeatherEvent, error) // 统计操作 GetEventStatistics(ctx context.Context, sceneID string, period time.Duration) (*EventStatistics, error) GetEventCount(ctx context.Context, sceneID string) (int64, error) GetEventCountByType(ctx context.Context, eventType WeatherEventType) (int64, error) // 批量操作 SaveBatch(ctx context.Context, events []*WeatherEvent) error DeleteBatch(ctx context.Context, ids []string) error // 清理操作 CleanupExpiredEvents(ctx context.Context, beforeTime time.Time) (int64, error) } // WeatherForecastRepository 天气预报仓储接口 type WeatherForecastRepository interface { // 基础CRUD操作 Save(ctx context.Context, forecast *WeatherForecast) error FindByID(ctx context.Context, id string) (*WeatherForecast, error) Update(ctx context.Context, forecast *WeatherForecast) error Delete(ctx context.Context, id string) error // 查询操作 FindBySceneID(ctx context.Context, sceneID string, limit int) ([]*WeatherForecast, error) FindByTimeRange(ctx context.Context, sceneID string, startTime, endTime time.Time) ([]*WeatherForecast, error) FindLatestForecast(ctx context.Context, sceneID string, hours int) ([]*WeatherForecast, error) FindByWeatherType(ctx context.Context, weatherType WeatherType, limit int) ([]*WeatherForecast, error) // 批量操作 SaveBatch(ctx context.Context, forecasts []*WeatherForecast) error DeleteBatch(ctx context.Context, ids []string) error UpdateBatch(ctx context.Context, forecasts []*WeatherForecast) error // 预报管理 ReplaceForecast(ctx context.Context, sceneID string, forecasts []*WeatherForecast) error GetForecastAccuracy(ctx context.Context, sceneID string, period time.Duration) (float64, error) // 清理操作 CleanupOldForecasts(ctx context.Context, beforeTime time.Time) (int64, error) } // SeasonalPatternRepository 季节模式仓储接口 type SeasonalPatternRepository interface { // 基础CRUD操作 Save(ctx context.Context, pattern *SeasonalPattern) error FindByZoneID(ctx context.Context, zoneID string) (*SeasonalPattern, error) Update(ctx context.Context, pattern *SeasonalPattern) error Delete(ctx context.Context, zoneID string) error // 查询操作 FindAll(ctx context.Context) ([]*SeasonalPattern, error) FindBySeason(ctx context.Context, season Season) ([]*SeasonalPattern, error) // 批量操作 SaveBatch(ctx context.Context, patterns []*SeasonalPattern) error UpdateBatch(ctx context.Context, patterns []*SeasonalPattern) error } // 统计信息结构体 // WeatherStatistics 天气统计信息 type WeatherStatistics struct { SceneID string Period time.Duration StartTime time.Time EndTime time.Time TotalWeatherChanges int64 WeatherTypeCount map[WeatherType]int64 IntensityCount map[WeatherIntensity]int64 AverageTemperature float64 AverageHumidity float64 AverageWindSpeed float64 AverageVisibility float64 MostCommonWeather WeatherType MostCommonIntensity WeatherIntensity LongestWeatherPeriod time.Duration ShortestWeatherPeriod time.Duration CreatedAt time.Time } // StateStatistics 状态统计信息 type StateStatistics struct { SceneID string Period time.Duration StartTime time.Time EndTime time.Time TotalStates int64 ActiveStates int64 ExpiredStates int64 AverageTemperature float64 MinTemperature float64 MaxTemperature float64 AverageHumidity float64 MinHumidity float64 MaxHumidity float64 AverageWindSpeed float64 MaxWindSpeed float64 AverageVisibility float64 MinVisibility float64 CreatedAt time.Time } // EventStatistics 事件统计信息 type EventStatistics struct { SceneID string Period time.Duration StartTime time.Time EndTime time.Time TotalEvents int64 ActiveEvents int64 ExpiredEvents int64 EventTypeCount map[WeatherEventType]int64 SeverityCount map[WeatherEventSeverity]int64 MostCommonEventType WeatherEventType MostCommonSeverity WeatherEventSeverity AverageEventDuration time.Duration LongestEventDuration time.Duration CreatedAt time.Time } // 查询条件结构体 // WeatherQuery 天气查询条件 type WeatherQuery struct { SceneIDs []string WeatherTypes []WeatherType Intensities []WeatherIntensity StartTime *time.Time EndTime *time.Time IsActive *bool Limit int Offset int OrderBy string OrderDesc bool } // EffectQuery 效果查询条件 type EffectQuery struct { SceneIDs []string EffectTypes []string IsActive *bool StartTime *time.Time EndTime *time.Time MinMultiplier *float64 MaxMultiplier *float64 Limit int Offset int OrderBy string OrderDesc bool } // EventQuery 事件查询条件 type EventQuery struct { SceneIDs []string EventTypes []WeatherEventType Severities []WeatherEventSeverity StartTime *time.Time EndTime *time.Time IsActive *bool Limit int Offset int OrderBy string OrderDesc bool } // ForecastQuery 预报查询条件 type ForecastQuery struct { SceneIDs []string WeatherTypes []WeatherType Intensities []WeatherIntensity StartTime *time.Time EndTime *time.Time MinConfidence *float64 Limit int Offset int OrderBy string OrderDesc bool } // 趋势分析结构体 // WeatherTrend 天气趋势 type WeatherTrend struct { SceneID string Period time.Duration StartTime time.Time EndTime time.Time TrendType TrendType WeatherTypeChanges map[WeatherType]float64 // 变化率 TemperatureTrend TemperatureTrend HumidityTrend HumidityTrend VisibilityTrend VisibilityTrend PredictedChanges []PredictedChange Confidence float64 CreatedAt time.Time } // TrendType 趋势类型 type TrendType int const ( TrendTypeStable TrendType = iota + 1 TrendTypeIncreasing TrendTypeDecreasing TrendTypeVolatile TrendTypeCyclical ) // String 返回趋势类型字符串 func (tt TrendType) String() string { switch tt { case TrendTypeStable: return "stable" case TrendTypeIncreasing: return "increasing" case TrendTypeDecreasing: return "decreasing" case TrendTypeVolatile: return "volatile" case TrendTypeCyclical: return "cyclical" default: return "unknown" } } // TemperatureTrend 温度趋势 type TemperatureTrend struct { Direction TrendDirection ChangeRate float64 // 每小时变化率 AverageValue float64 MinValue float64 MaxValue float64 Variance float64 } // HumidityTrend 湿度趋势 type HumidityTrend struct { Direction TrendDirection ChangeRate float64 AverageValue float64 MinValue float64 MaxValue float64 Variance float64 } // VisibilityTrend 能见度趋势 type VisibilityTrend struct { Direction TrendDirection ChangeRate float64 AverageValue float64 MinValue float64 MaxValue float64 Variance float64 } // TrendDirection 趋势方向 type TrendDirection int const ( TrendDirectionUp TrendDirection = iota + 1 TrendDirectionDown TrendDirectionStable TrendDirectionVolatile ) // String 返回趋势方向字符串 func (td TrendDirection) String() string { switch td { case TrendDirectionUp: return "up" case TrendDirectionDown: return "down" case TrendDirectionStable: return "stable" case TrendDirectionVolatile: return "volatile" default: return "unknown" } } // PredictedChange 预测变化 type PredictedChange struct { Time time.Time WeatherType WeatherType Intensity WeatherIntensity Probability float64 Confidence float64 Reason string } // 缓存接口 // WeatherCacheRepository 天气缓存仓储接口 type WeatherCacheRepository interface { // 缓存操作 Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error Get(ctx context.Context, key string, dest interface{}) error Delete(ctx context.Context, key string) error Exists(ctx context.Context, key string) (bool, error) // 批量操作 SetBatch(ctx context.Context, items map[string]interface{}, expiration time.Duration) error GetBatch(ctx context.Context, keys []string) (map[string]interface{}, error) DeleteBatch(ctx context.Context, keys []string) error // 模式操作 DeleteByPattern(ctx context.Context, pattern string) error GetKeysByPattern(ctx context.Context, pattern string) ([]string, error) // 缓存管理 Flush(ctx context.Context) error GetStats(ctx context.Context) (*CacheStats, error) } // CacheStats 缓存统计 type CacheStats struct { Hits int64 Misses int64 Keys int64 MemoryUsage int64 HitRate float64 CreatedAt time.Time } // 事务接口 // WeatherTransaction 天气事务接口 type WeatherTransaction interface { // 事务控制 Begin(ctx context.Context) error Commit(ctx context.Context) error Rollback(ctx context.Context) error // 获取仓储 WeatherRepository() WeatherRepository WeatherStateRepository() WeatherStateRepository WeatherEffectRepository() WeatherEffectRepository WeatherEventRepository() WeatherEventRepository WeatherForecastRepository() WeatherForecastRepository SeasonalPatternRepository() SeasonalPatternRepository } // 仓储工厂接口 // WeatherRepositoryFactory 天气仓储工厂接口 type WeatherRepositoryFactory interface { // 创建仓储 CreateWeatherRepository() WeatherRepository CreateWeatherStateRepository() WeatherStateRepository CreateWeatherEffectRepository() WeatherEffectRepository CreateWeatherEventRepository() WeatherEventRepository CreateWeatherForecastRepository() WeatherForecastRepository CreateSeasonalPatternRepository() SeasonalPatternRepository CreateWeatherCacheRepository() WeatherCacheRepository // 创建事务 CreateTransaction() WeatherTransaction // 健康检查 HealthCheck(ctx context.Context) error // 关闭连接 Close() error } ================================================ FILE: internal/domain/scene/weather/service.go ================================================ package weather import ( // "fmt" "math" "math/rand" "time" ) // WeatherService 天气领域服务 type WeatherService struct { weatherTemplates map[WeatherType]*WeatherTemplate seasonalPatterns map[string]*SeasonalPattern weatherEvents map[WeatherEventType]*WeatherEventTemplate effectCalculators map[string]EffectCalculator forecastGenerators map[string]ForecastGenerator weatherRules []*WeatherRule climateZones map[string]*ClimateZone randomSeed int64 createdAt time.Time updatedAt time.Time } // NewWeatherService 创建天气服务 func NewWeatherService() *WeatherService { now := time.Now() service := &WeatherService{ weatherTemplates: make(map[WeatherType]*WeatherTemplate), seasonalPatterns: make(map[string]*SeasonalPattern), weatherEvents: make(map[WeatherEventType]*WeatherEventTemplate), effectCalculators: make(map[string]EffectCalculator), forecastGenerators: make(map[string]ForecastGenerator), weatherRules: make([]*WeatherRule, 0), climateZones: make(map[string]*ClimateZone), randomSeed: now.UnixNano(), createdAt: now, updatedAt: now, } // 初始化默认模板和规则 service.initializeDefaultTemplates() service.initializeDefaultRules() service.initializeDefaultClimateZones() service.initializeEffectCalculators() service.initializeForecastGenerators() return service } // RegisterWeatherTemplate 注册天气模板 func (ws *WeatherService) RegisterWeatherTemplate(weatherType WeatherType, template *WeatherTemplate) { ws.weatherTemplates[weatherType] = template ws.updatedAt = time.Now() } // GetWeatherTemplate 获取天气模板 func (ws *WeatherService) GetWeatherTemplate(weatherType WeatherType) *WeatherTemplate { return ws.weatherTemplates[weatherType] } // RegisterSeasonalPattern 注册季节模式 func (ws *WeatherService) RegisterSeasonalPattern(zoneID string, pattern *SeasonalPattern) { ws.seasonalPatterns[zoneID] = pattern ws.updatedAt = time.Now() } // GetSeasonalPattern 获取季节模式 func (ws *WeatherService) GetSeasonalPattern(zoneID string) *SeasonalPattern { return ws.seasonalPatterns[zoneID] } // RegisterWeatherEvent 注册天气事件 func (ws *WeatherService) RegisterWeatherEvent(eventType WeatherEventType, template *WeatherEventTemplate) { ws.weatherEvents[eventType] = template ws.updatedAt = time.Now() } // GetWeatherEventTemplate 获取天气事件模板 func (ws *WeatherService) GetWeatherEventTemplate(eventType WeatherEventType) *WeatherEventTemplate { return ws.weatherEvents[eventType] } // AddWeatherRule 添加天气规则 func (ws *WeatherService) AddWeatherRule(rule *WeatherRule) { ws.weatherRules = append(ws.weatherRules, rule) ws.updatedAt = time.Now() } // GetWeatherRules 获取天气规则 func (ws *WeatherService) GetWeatherRules() []*WeatherRule { return ws.weatherRules } // RegisterClimateZone 注册气候区域 func (ws *WeatherService) RegisterClimateZone(zoneID string, zone *ClimateZone) { ws.climateZones[zoneID] = zone ws.updatedAt = time.Now() } // GetClimateZone 获取气候区域 func (ws *WeatherService) GetClimateZone(zoneID string) *ClimateZone { return ws.climateZones[zoneID] } // CalculateNextWeather 计算下一个天气 func (ws *WeatherService) CalculateNextWeather(currentWeather *WeatherState, zoneID string) (*WeatherState, error) { if currentWeather == nil { return nil, ErrInvalidWeatherState } // 获取气候区域和季节模式 climateZone := ws.GetClimateZone(zoneID) if climateZone == nil { climateZone = ws.getDefaultClimateZone() } seasonalPattern := ws.GetSeasonalPattern(zoneID) if seasonalPattern == nil { seasonalPattern = NewSeasonalPattern() } // 获取当前季节 currentSeason := seasonalPattern.GetCurrentSeason(time.Now()) // 获取天气转换概率 transitionProbs := ws.calculateWeatherTransitionProbabilities(currentWeather.WeatherType, currentSeason, climateZone) // 应用天气规则 adjustedProbs := ws.applyWeatherRules(transitionProbs, currentWeather, climateZone) // 选择下一个天气 nextWeatherType := ws.selectWeatherByProbability(adjustedProbs) nextIntensity := ws.calculateWeatherIntensity(nextWeatherType, currentSeason, climateZone) // 创建新的天气状态 nextWeather := NewWeatherState(nextWeatherType, nextIntensity) // 应用气候区域的影响 ws.applyClimateZoneEffects(nextWeather, climateZone) return nextWeather, nil } // GenerateWeatherForecast 生成天气预报 func (ws *WeatherService) GenerateWeatherForecast(currentWeather *WeatherState, zoneID string, hours int) ([]*WeatherForecast, error) { if currentWeather == nil { return nil, ErrInvalidWeatherState } if hours <= 0 || hours > 168 { return nil, ErrInvalidForecastPeriod } forecasts := make([]*WeatherForecast, 0, hours) currentTime := time.Now() predictedWeather := currentWeather for i := 1; i <= hours; i++ { forecastTime := currentTime.Add(time.Duration(i) * time.Hour) // 预测下一个小时的天气 nextWeather, err := ws.CalculateNextWeather(predictedWeather, zoneID) if err != nil { return nil, err } // 计算预报置信度 confidence := ws.calculateForecastConfidence(i) // 创建预报 forecast := NewWeatherForecast(forecastTime, nextWeather.WeatherType, nextWeather.Intensity) forecast.Temperature = nextWeather.Temperature forecast.Humidity = nextWeather.Humidity forecast.WindSpeed = nextWeather.WindSpeed forecast.Visibility = nextWeather.Visibility forecast.Confidence = confidence forecasts = append(forecasts, forecast) predictedWeather = nextWeather } return forecasts, nil } // CalculateWeatherEffects 计算天气效果 func (ws *WeatherService) CalculateWeatherEffects(weather *WeatherState, targetType string) ([]*WeatherEffect, error) { if weather == nil { return nil, ErrInvalidWeatherState } effects := make([]*WeatherEffect, 0) // 获取天气模板 template := ws.GetWeatherTemplate(weather.WeatherType) if template == nil { return effects, nil } // 根据目标类型计算效果 for effectType, baseMultiplier := range template.BaseEffects { if targetType != "" && effectType != targetType { continue } // 应用强度调整 adjustedMultiplier := baseMultiplier * weather.Intensity.GetMultiplier() // 创建效果 effect := NewWeatherEffect(effectType, "general", adjustedMultiplier, weather.Duration) effects = append(effects, effect) } return effects, nil } // CheckWeatherEventTrigger 检查天气事件触发 func (ws *WeatherService) CheckWeatherEventTrigger(weather *WeatherState, zoneID string) (*WeatherEvent, error) { if weather == nil { return nil, ErrInvalidWeatherState } // 检查每种天气事件的触发条件 for eventType, template := range ws.weatherEvents { if ws.shouldTriggerWeatherEvent(weather, eventType, template, zoneID) { // 创建天气事件 event := ws.createWeatherEvent(eventType, template, weather) return event, nil } } return nil, nil } // CalculateWeatherInfluence 计算天气对属性的影响 func (ws *WeatherService) CalculateWeatherInfluence(weather *WeatherState, attributeType string) (float64, error) { if weather == nil { return 1.0, ErrInvalidWeatherState } // 使用效果计算器 if calculator, exists := ws.effectCalculators[attributeType]; exists { return calculator.Calculate(weather), nil } // 默认计算逻辑 return ws.calculateDefaultInfluence(weather, attributeType), nil } // ValidateWeatherTransition 验证天气转换 func (ws *WeatherService) ValidateWeatherTransition(from, to WeatherType, intensity WeatherIntensity) error { if !from.IsValid() || !to.IsValid() { return ErrInvalidWeatherType } if !intensity.IsValid() { return ErrInvalidWeatherIntensity } // 检查转换规则 for _, rule := range ws.weatherRules { if rule.FromWeather == from && rule.ToWeather == to { if rule.MinIntensity != 0 && intensity < rule.MinIntensity { return ErrInvalidWeatherTransition } if rule.MaxIntensity != 0 && intensity > rule.MaxIntensity { return ErrInvalidWeatherTransition } return nil } } return nil } // GetOptimalWeatherForActivity 获取活动的最佳天气 func (ws *WeatherService) GetOptimalWeatherForActivity(activityType string) (*WeatherCondition, error) { // 根据活动类型返回最佳天气条件 switch activityType { case "farming": return NewWeatherCondition(WeatherTypeRainy, WeatherIntensityLight, 2*time.Hour), nil case "combat": return NewWeatherCondition(WeatherTypeSunny, WeatherIntensityNormal, 4*time.Hour), nil case "exploration": return NewWeatherCondition(WeatherTypeCloudy, WeatherIntensityNormal, 3*time.Hour), nil case "crafting": return NewWeatherCondition(WeatherTypeSunny, WeatherIntensityLight, 6*time.Hour), nil default: return NewWeatherCondition(WeatherTypeSunny, WeatherIntensityNormal, 2*time.Hour), nil } } // 私有方法 // initializeDefaultTemplates 初始化默认模板 func (ws *WeatherService) initializeDefaultTemplates() { // 晴天模板 sunnyTemplate := &WeatherTemplate{ WeatherType: WeatherTypeSunny, BaseEffects: map[string]float64{ "visibility": 1.2, "movement_speed": 1.1, "energy_regen": 1.15, "mood": 1.1, }, DurationRange: DurationRange{Min: 2 * time.Hour, Max: 8 * time.Hour}, TransitionRules: map[WeatherType]float64{ WeatherTypeSunny: 0.6, WeatherTypeCloudy: 0.3, WeatherTypeRainy: 0.1, }, } ws.RegisterWeatherTemplate(WeatherTypeSunny, sunnyTemplate) // 雨天模板 rainyTemplate := &WeatherTemplate{ WeatherType: WeatherTypeRainy, BaseEffects: map[string]float64{ "visibility": 0.8, "fire_damage": 0.7, "water_damage": 1.3, "plant_growth": 1.5, }, DurationRange: DurationRange{Min: 1 * time.Hour, Max: 4 * time.Hour}, TransitionRules: map[WeatherType]float64{ WeatherTypeRainy: 0.5, WeatherTypeCloudy: 0.3, WeatherTypeStormy: 0.2, }, } ws.RegisterWeatherTemplate(WeatherTypeRainy, rainyTemplate) // 暴风雨模板 stormyTemplate := &WeatherTemplate{ WeatherType: WeatherTypeStormy, BaseEffects: map[string]float64{ "visibility": 0.5, "lightning_damage": 1.8, "movement_speed": 0.7, "accuracy": 0.8, }, DurationRange: DurationRange{Min: 30 * time.Minute, Max: 2 * time.Hour}, TransitionRules: map[WeatherType]float64{ WeatherTypeStormy: 0.3, WeatherTypeRainy: 0.5, WeatherTypeCloudy: 0.2, }, } ws.RegisterWeatherTemplate(WeatherTypeStormy, stormyTemplate) } // initializeDefaultRules 初始化默认规则 func (ws *WeatherService) initializeDefaultRules() { // 添加一些基本的天气转换规则 ws.AddWeatherRule(&WeatherRule{ FromWeather: WeatherTypeSunny, ToWeather: WeatherTypeStormy, Probability: 0.05, // 晴天直接转暴风雨概率很低 MinIntensity: WeatherIntensityNormal, SeasonFactor: map[Season]float64{SeasonSummer: 0.1, SeasonWinter: 0.01}, }) ws.AddWeatherRule(&WeatherRule{ FromWeather: WeatherTypeRainy, ToWeather: WeatherTypeSnowy, Probability: 0.3, MinIntensity: WeatherIntensityLight, SeasonFactor: map[Season]float64{SeasonWinter: 0.8, SeasonSummer: 0.0}, }) } // initializeDefaultClimateZones 初始化默认气候区域 func (ws *WeatherService) initializeDefaultClimateZones() { // 温带气候 temperateZone := &ClimateZone{ ZoneID: "temperate", Name: "温带气候", Description: "四季分明的温带气候区域", BaseTemperature: 15.0, TemperatureRange: TemperatureRange{Min: -10, Max: 35, Average: 15}, HumidityRange: HumidityRange{Min: 30, Max: 80, Average: 55}, WeatherModifiers: map[WeatherType]float64{ WeatherTypeSunny: 1.0, WeatherTypeRainy: 1.0, WeatherTypeSnowy: 0.8, }, } ws.RegisterClimateZone("temperate", temperateZone) // 热带气候 tropicalZone := &ClimateZone{ ZoneID: "tropical", Name: "热带气候", Description: "高温多雨的热带气候区域", BaseTemperature: 28.0, TemperatureRange: TemperatureRange{Min: 20, Max: 40, Average: 28}, HumidityRange: HumidityRange{Min: 60, Max: 95, Average: 75}, WeatherModifiers: map[WeatherType]float64{ WeatherTypeSunny: 1.2, WeatherTypeRainy: 1.5, WeatherTypeStormy: 1.3, WeatherTypeSnowy: 0.0, // 热带不下雪 }, } ws.RegisterClimateZone("tropical", tropicalZone) } // initializeEffectCalculators 初始化效果计算器 func (ws *WeatherService) initializeEffectCalculators() { // 能见度计算器 ws.effectCalculators["visibility"] = EffectCalculatorFunc(func(weather *WeatherState) float64 { base := weather.WeatherType.GetBaseVisibility() / 10.0 // 标准化到0-2范围 intensityFactor := weather.Intensity.GetMultiplier() return math.Max(0.1, base*intensityFactor) }) // 移动速度计算器 ws.effectCalculators["movement_speed"] = EffectCalculatorFunc(func(weather *WeatherState) float64 { switch weather.WeatherType { case WeatherTypeSunny: return 1.0 + 0.1*weather.Intensity.GetMultiplier() case WeatherTypeSnowy, WeatherTypeStormy: return 1.0 - 0.2*weather.Intensity.GetMultiplier() default: return 1.0 } }) } // initializeForecastGenerators 初始化预报生成器 func (ws *WeatherService) initializeForecastGenerators() { // 短期预报生成器(1-6小时) ws.forecastGenerators["short_term"] = ForecastGeneratorFunc(func(current *WeatherState, hours int) []*WeatherForecast { // 实现短期预报逻辑 return make([]*WeatherForecast, 0) }) // 长期预报生成器(1-7天) ws.forecastGenerators["long_term"] = ForecastGeneratorFunc(func(current *WeatherState, hours int) []*WeatherForecast { // 实现长期预报逻辑 return make([]*WeatherForecast, 0) }) } // calculateWeatherTransitionProbabilities 计算天气转换概率 func (ws *WeatherService) calculateWeatherTransitionProbabilities(currentType WeatherType, season Season, zone *ClimateZone) map[WeatherType]float64 { probs := make(map[WeatherType]float64) // 获取基础转换概率 template := ws.GetWeatherTemplate(currentType) if template != nil { for weatherType, prob := range template.TransitionRules { probs[weatherType] = prob } } // 应用气候区域修正 if zone != nil { for weatherType, modifier := range zone.WeatherModifiers { if prob, exists := probs[weatherType]; exists { probs[weatherType] = prob * modifier } } } // 标准化概率 total := 0.0 for _, prob := range probs { total += prob } if total > 0 { for weatherType := range probs { probs[weatherType] /= total } } return probs } // applyWeatherRules 应用天气规则 func (ws *WeatherService) applyWeatherRules(probs map[WeatherType]float64, current *WeatherState, zone *ClimateZone) map[WeatherType]float64 { adjusted := make(map[WeatherType]float64) for k, v := range probs { adjusted[k] = v } // 应用每个规则 for _, rule := range ws.weatherRules { if rule.FromWeather == current.WeatherType { if prob, exists := adjusted[rule.ToWeather]; exists { // 应用规则修正 adjusted[rule.ToWeather] = prob * rule.Probability } } } return adjusted } // selectWeatherByProbability 根据概率选择天气 func (ws *WeatherService) selectWeatherByProbability(probs map[WeatherType]float64) WeatherType { rand.Seed(time.Now().UnixNano() + ws.randomSeed) randomValue := rand.Float64() cumulativeProb := 0.0 for weatherType, prob := range probs { cumulativeProb += prob if randomValue <= cumulativeProb { return weatherType } } // 默认返回晴天 return WeatherTypeSunny } // calculateWeatherIntensity 计算天气强度 func (ws *WeatherService) calculateWeatherIntensity(weatherType WeatherType, season Season, zone *ClimateZone) WeatherIntensity { rand.Seed(time.Now().UnixNano() + ws.randomSeed) // 基础强度概率 intensityProbs := map[WeatherIntensity]float64{ WeatherIntensityLight: 0.3, WeatherIntensityNormal: 0.5, WeatherIntensityHeavy: 0.2, } // 根据天气类型调整 switch weatherType { case WeatherTypeStormy: intensityProbs[WeatherIntensityHeavy] = 0.4 intensityProbs[WeatherIntensityExtreme] = 0.1 case WeatherTypeSunny: intensityProbs[WeatherIntensityLight] = 0.4 intensityProbs[WeatherIntensityNormal] = 0.6 intensityProbs[WeatherIntensityHeavy] = 0.0 } // 选择强度 randomValue := rand.Float64() cumulativeProb := 0.0 for intensity, prob := range intensityProbs { cumulativeProb += prob if randomValue <= cumulativeProb { return intensity } } return WeatherIntensityNormal } // applyClimateZoneEffects 应用气候区域效果 func (ws *WeatherService) applyClimateZoneEffects(weather *WeatherState, zone *ClimateZone) { if zone == nil { return } // 调整温度 temperatureOffset := zone.BaseTemperature - weather.WeatherType.GetBaseTemperature() weather.UpdateTemperature(weather.Temperature + temperatureOffset) // 调整湿度 if weather.Humidity < zone.HumidityRange.Min { weather.UpdateHumidity(zone.HumidityRange.Min) } else if weather.Humidity > zone.HumidityRange.Max { weather.UpdateHumidity(zone.HumidityRange.Max) } } // calculateForecastConfidence 计算预报置信度 func (ws *WeatherService) calculateForecastConfidence(hoursAhead int) float64 { baseConfidence := 0.95 decayRate := 0.02 confidence := baseConfidence - float64(hoursAhead)*decayRate if confidence < 0.3 { confidence = 0.3 } return confidence } // shouldTriggerWeatherEvent 检查是否应该触发天气事件 func (ws *WeatherService) shouldTriggerWeatherEvent(weather *WeatherState, eventType WeatherEventType, template *WeatherEventTemplate, zoneID string) bool { // 检查天气类型匹配 if !template.CanTriggerWith(weather.WeatherType, weather.Intensity) { return false } // 检查概率 rand.Seed(time.Now().UnixNano() + ws.randomSeed) return rand.Float64() < template.TriggerProbability } // createWeatherEvent 创建天气事件 func (ws *WeatherService) createWeatherEvent(eventType WeatherEventType, template *WeatherEventTemplate, weather *WeatherState) *WeatherEvent { severity := ws.calculateEventSeverity(weather.Intensity) duration := template.BaseDuration event := NewWeatherEvent(eventType, severity, template.Title, template.Description, duration) // 添加效果 for effectType, multiplier := range template.Effects { effect := NewWeatherEffect(effectType, "special", multiplier*weather.Intensity.GetMultiplier(), duration) event.AddEffect(effect) } return event } // calculateEventSeverity 计算事件严重程度 func (ws *WeatherService) calculateEventSeverity(intensity WeatherIntensity) WeatherEventSeverity { switch intensity { case WeatherIntensityLight: return WeatherEventSeverityMinor case WeatherIntensityNormal: return WeatherEventSeverityModerate case WeatherIntensityHeavy: return WeatherEventSeverityMajor case WeatherIntensityExtreme: return WeatherEventSeverityCritical default: return WeatherEventSeverityMinor } } // calculateDefaultInfluence 计算默认影响 func (ws *WeatherService) calculateDefaultInfluence(weather *WeatherState, attributeType string) float64 { // 默认的影响计算逻辑 switch attributeType { case "visibility": return weather.Visibility / 10.0 // 标准化 case "movement_speed": if weather.WeatherType == WeatherTypeSnowy || weather.WeatherType == WeatherTypeStormy { return 0.8 } return 1.0 default: return 1.0 } } // getDefaultClimateZone 获取默认气候区域 func (ws *WeatherService) getDefaultClimateZone() *ClimateZone { return ws.GetClimateZone("temperate") } // 辅助类型和接口 // WeatherTemplate 天气模板 type WeatherTemplate struct { WeatherType WeatherType BaseEffects map[string]float64 DurationRange DurationRange TransitionRules map[WeatherType]float64 } // DurationRange 持续时间范围 type DurationRange struct { Min time.Duration Max time.Duration } // WeatherRule 天气规则 type WeatherRule struct { FromWeather WeatherType ToWeather WeatherType Probability float64 MinIntensity WeatherIntensity MaxIntensity WeatherIntensity SeasonFactor map[Season]float64 Conditions []string } // ClimateZone 气候区域 type ClimateZone struct { ZoneID string Name string Description string BaseTemperature float64 TemperatureRange TemperatureRange HumidityRange HumidityRange WeatherModifiers map[WeatherType]float64 } // HumidityRange 湿度范围 type HumidityRange struct { Min float64 Max float64 Average float64 } // WeatherEventTemplate 天气事件模板 type WeatherEventTemplate struct { EventType WeatherEventType Title string Description string TriggerProbability float64 BaseDuration time.Duration Effects map[string]float64 TriggerConditions []WeatherCondition } // CanTriggerWith 检查是否可以触发 func (wet *WeatherEventTemplate) CanTriggerWith(weatherType WeatherType, intensity WeatherIntensity) bool { for _, condition := range wet.TriggerConditions { if condition.Matches(weatherType, intensity) { return true } } return len(wet.TriggerConditions) == 0 // 如果没有条件,默认可以触发 } // EffectCalculator 效果计算器接口 type EffectCalculator interface { Calculate(weather *WeatherState) float64 } // EffectCalculatorFunc 效果计算器函数类型 type EffectCalculatorFunc func(weather *WeatherState) float64 // Calculate 实现EffectCalculator接口 func (f EffectCalculatorFunc) Calculate(weather *WeatherState) float64 { return f(weather) } // ForecastGenerator 预报生成器接口 type ForecastGenerator interface { Generate(current *WeatherState, hours int) []*WeatherForecast } // ForecastGeneratorFunc 预报生成器函数类型 type ForecastGeneratorFunc func(current *WeatherState, hours int) []*WeatherForecast // Generate 实现ForecastGenerator接口 func (f ForecastGeneratorFunc) Generate(current *WeatherState, hours int) []*WeatherForecast { return f(current, hours) } ================================================ FILE: internal/domain/scene/weather/value_object.go ================================================ package weather import ( "fmt" "time" ) // WeatherType 天气类型 type WeatherType int const ( WeatherTypeSunny WeatherType = iota + 1 WeatherTypeCloudy WeatherTypeRainy WeatherTypeSnowy WeatherTypeWindy WeatherTypeStormy WeatherTypeFoggy WeatherTypeHazy WeatherTypeHail WeatherTypeBlizzard ) // String 返回天气类型字符串 func (wt WeatherType) String() string { switch wt { case WeatherTypeSunny: return "sunny" case WeatherTypeCloudy: return "cloudy" case WeatherTypeRainy: return "rainy" case WeatherTypeSnowy: return "snowy" case WeatherTypeWindy: return "windy" case WeatherTypeStormy: return "stormy" case WeatherTypeFoggy: return "foggy" case WeatherTypeHazy: return "hazy" case WeatherTypeHail: return "hail" case WeatherTypeBlizzard: return "blizzard" default: return "unknown" } } // GetDescription 获取天气描述 func (wt WeatherType) GetDescription() string { switch wt { case WeatherTypeSunny: return "晴朗" case WeatherTypeCloudy: return "多云" case WeatherTypeRainy: return "下雨" case WeatherTypeSnowy: return "下雪" case WeatherTypeWindy: return "大风" case WeatherTypeStormy: return "暴风雨" case WeatherTypeFoggy: return "雾天" case WeatherTypeHazy: return "霾天" case WeatherTypeHail: return "冰雹" case WeatherTypeBlizzard: return "暴雪" default: return "未知天气" } } // IsValid 检查天气类型是否有效 func (wt WeatherType) IsValid() bool { return wt >= WeatherTypeSunny && wt <= WeatherTypeBlizzard } // ParseWeatherType 从字符串解析天气类型 func ParseWeatherType(s string) WeatherType { switch s { case "sunny": return WeatherTypeSunny case "cloudy": return WeatherTypeCloudy case "rainy": return WeatherTypeRainy case "snowy": return WeatherTypeSnowy case "windy": return WeatherTypeWindy case "stormy": return WeatherTypeStormy case "foggy": return WeatherTypeFoggy case "hazy": return WeatherTypeHazy case "hail": return WeatherTypeHail case "blizzard": return WeatherTypeBlizzard default: return WeatherTypeSunny // 默认返回晴天 } } // GetBaseTemperature 获取基础温度 func (wt WeatherType) GetBaseTemperature() float64 { switch wt { case WeatherTypeSunny: return 25.0 case WeatherTypeCloudy: return 20.0 case WeatherTypeRainy: return 15.0 case WeatherTypeSnowy: return -5.0 case WeatherTypeWindy: return 18.0 case WeatherTypeStormy: return 12.0 case WeatherTypeFoggy: return 10.0 case WeatherTypeHazy: return 22.0 case WeatherTypeHail: return 8.0 case WeatherTypeBlizzard: return -15.0 default: return 20.0 } } // GetBaseHumidity 获取基础湿度 func (wt WeatherType) GetBaseHumidity() float64 { switch wt { case WeatherTypeSunny: return 40.0 case WeatherTypeCloudy: return 60.0 case WeatherTypeRainy: return 85.0 case WeatherTypeSnowy: return 70.0 case WeatherTypeWindy: return 50.0 case WeatherTypeStormy: return 90.0 case WeatherTypeFoggy: return 95.0 case WeatherTypeHazy: return 65.0 case WeatherTypeHail: return 80.0 case WeatherTypeBlizzard: return 85.0 default: return 50.0 } } // GetBaseWindSpeed 获取基础风速 func (wt WeatherType) GetBaseWindSpeed() float64 { switch wt { case WeatherTypeSunny: return 5.0 case WeatherTypeCloudy: return 10.0 case WeatherTypeRainy: return 15.0 case WeatherTypeSnowy: return 20.0 case WeatherTypeWindy: return 35.0 case WeatherTypeStormy: return 50.0 case WeatherTypeFoggy: return 3.0 case WeatherTypeHazy: return 8.0 case WeatherTypeHail: return 25.0 case WeatherTypeBlizzard: return 60.0 default: return 10.0 } } // GetBaseVisibility 获取基础能见度 func (wt WeatherType) GetBaseVisibility() float64 { switch wt { case WeatherTypeSunny: return 20.0 case WeatherTypeCloudy: return 15.0 case WeatherTypeRainy: return 8.0 case WeatherTypeSnowy: return 5.0 case WeatherTypeWindy: return 12.0 case WeatherTypeStormy: return 3.0 case WeatherTypeFoggy: return 1.0 case WeatherTypeHazy: return 6.0 case WeatherTypeHail: return 4.0 case WeatherTypeBlizzard: return 2.0 default: return 10.0 } } // WeatherIntensity 天气强度 type WeatherIntensity int const ( WeatherIntensityLight WeatherIntensity = iota + 1 WeatherIntensityNormal WeatherIntensityHeavy WeatherIntensityExtreme ) // String 返回强度字符串 func (wi WeatherIntensity) String() string { switch wi { case WeatherIntensityLight: return "light" case WeatherIntensityNormal: return "normal" case WeatherIntensityHeavy: return "heavy" case WeatherIntensityExtreme: return "extreme" default: return "unknown" } } // GetDescription 获取强度描述 func (wi WeatherIntensity) GetDescription() string { switch wi { case WeatherIntensityLight: return "轻微" case WeatherIntensityNormal: return "正常" case WeatherIntensityHeavy: return "强烈" case WeatherIntensityExtreme: return "极端" default: return "未知强度" } } // IsValid 检查强度是否有效 func (wi WeatherIntensity) IsValid() bool { return wi >= WeatherIntensityLight && wi <= WeatherIntensityExtreme } // GetMultiplier 获取强度倍率 func (wi WeatherIntensity) GetMultiplier() float64 { switch wi { case WeatherIntensityLight: return 0.5 case WeatherIntensityNormal: return 1.0 case WeatherIntensityHeavy: return 1.5 case WeatherIntensityExtreme: return 2.0 default: return 1.0 } } // GetDurationFactor 获取持续时间因子 func (wi WeatherIntensity) GetDurationFactor() float64 { switch wi { case WeatherIntensityLight: return 1.5 // 轻微天气持续更久 case WeatherIntensityNormal: return 1.0 case WeatherIntensityHeavy: return 0.7 // 强烈天气持续较短 case WeatherIntensityExtreme: return 0.5 // 极端天气持续很短 default: return 1.0 } } // Season 季节 type Season int const ( SeasonSpring Season = iota + 1 SeasonSummer SeasonAutumn SeasonWinter ) // String 返回季节字符串 func (s Season) String() string { switch s { case SeasonSpring: return "spring" case SeasonSummer: return "summer" case SeasonAutumn: return "autumn" case SeasonWinter: return "winter" default: return "unknown" } } // GetDescription 获取季节描述 func (s Season) GetDescription() string { switch s { case SeasonSpring: return "春季" case SeasonSummer: return "夏季" case SeasonAutumn: return "秋季" case SeasonWinter: return "冬季" default: return "未知季节" } } // IsValid 检查季节是否有效 func (s Season) IsValid() bool { return s >= SeasonSpring && s <= SeasonWinter } // SeasonalPattern 季节模式 type SeasonalPattern struct { CurrentSeason Season SeasonStartTime time.Time SeasonDuration time.Duration WeatherProbabilities map[Season]map[WeatherType]float64 TemperatureRanges map[Season]TemperatureRange CreatedAt time.Time UpdatedAt time.Time } // NewSeasonalPattern 创建季节模式 func NewSeasonalPattern() *SeasonalPattern { now := time.Now() pattern := &SeasonalPattern{ CurrentSeason: getCurrentSeason(now), SeasonStartTime: getSeasonStartTime(now), SeasonDuration: 90 * 24 * time.Hour, // 90天 WeatherProbabilities: make(map[Season]map[WeatherType]float64), TemperatureRanges: make(map[Season]TemperatureRange), CreatedAt: now, UpdatedAt: now, } // 初始化默认概率和温度范围 pattern.initializeDefaultProbabilities() pattern.initializeTemperatureRanges() return pattern } // GetCurrentSeason 获取当前季节 func (sp *SeasonalPattern) GetCurrentSeason(currentTime time.Time) Season { return getCurrentSeason(currentTime) } // GetWeatherProbabilities 获取天气概率 func (sp *SeasonalPattern) GetWeatherProbabilities(season Season) map[WeatherType]float64 { return sp.WeatherProbabilities[season] } // GetTemperatureRange 获取温度范围 func (sp *SeasonalPattern) GetTemperatureRange(season Season) TemperatureRange { return sp.TemperatureRanges[season] } // UpdateWeatherProbability 更新天气概率 func (sp *SeasonalPattern) UpdateWeatherProbability(season Season, weatherType WeatherType, probability float64) { if sp.WeatherProbabilities[season] == nil { sp.WeatherProbabilities[season] = make(map[WeatherType]float64) } sp.WeatherProbabilities[season][weatherType] = probability sp.UpdatedAt = time.Now() } // UpdateTemperatureRange 更新温度范围 func (sp *SeasonalPattern) UpdateTemperatureRange(season Season, tempRange TemperatureRange) { sp.TemperatureRanges[season] = tempRange sp.UpdatedAt = time.Now() } // initializeDefaultProbabilities 初始化默认概率 func (sp *SeasonalPattern) initializeDefaultProbabilities() { // 春季 sp.WeatherProbabilities[SeasonSpring] = map[WeatherType]float64{ WeatherTypeSunny: 0.4, WeatherTypeCloudy: 0.3, WeatherTypeRainy: 0.2, WeatherTypeWindy: 0.1, } // 夏季 sp.WeatherProbabilities[SeasonSummer] = map[WeatherType]float64{ WeatherTypeSunny: 0.6, WeatherTypeCloudy: 0.2, WeatherTypeRainy: 0.1, WeatherTypeStormy: 0.1, } // 秋季 sp.WeatherProbabilities[SeasonAutumn] = map[WeatherType]float64{ WeatherTypeSunny: 0.3, WeatherTypeCloudy: 0.4, WeatherTypeRainy: 0.2, WeatherTypeWindy: 0.1, } // 冬季 sp.WeatherProbabilities[SeasonWinter] = map[WeatherType]float64{ WeatherTypeCloudy: 0.4, WeatherTypeSnowy: 0.3, WeatherTypeSunny: 0.2, WeatherTypeFoggy: 0.1, } } // initializeTemperatureRanges 初始化温度范围 func (sp *SeasonalPattern) initializeTemperatureRanges() { sp.TemperatureRanges[SeasonSpring] = TemperatureRange{Min: 10, Max: 25, Average: 17.5} sp.TemperatureRanges[SeasonSummer] = TemperatureRange{Min: 20, Max: 35, Average: 27.5} sp.TemperatureRanges[SeasonAutumn] = TemperatureRange{Min: 5, Max: 20, Average: 12.5} sp.TemperatureRanges[SeasonWinter] = TemperatureRange{Min: -10, Max: 10, Average: 0} } // TemperatureRange 温度范围 type TemperatureRange struct { Min float64 Max float64 Average float64 } // IsInRange 检查温度是否在范围内 func (tr TemperatureRange) IsInRange(temperature float64) bool { return temperature >= tr.Min && temperature <= tr.Max } // GetRandomTemperature 获取随机温度 func (tr TemperatureRange) GetRandomTemperature() float64 { // 简化的随机温度生成 return tr.Min + (tr.Max-tr.Min)*0.5 // 返回中间值,实际可以使用随机数 } // WeatherForecast 天气预报 type WeatherForecast struct { Time time.Time WeatherType WeatherType Intensity WeatherIntensity Temperature float64 Humidity float64 WindSpeed float64 Visibility float64 Confidence float64 // 预报置信度 0-1 Description string CreatedAt time.Time } // NewWeatherForecast 创建天气预报 func NewWeatherForecast(time time.Time, weatherType WeatherType, intensity WeatherIntensity) *WeatherForecast { return &WeatherForecast{ Time: time, WeatherType: weatherType, Intensity: intensity, Temperature: weatherType.GetBaseTemperature(), Humidity: weatherType.GetBaseHumidity(), WindSpeed: weatherType.GetBaseWindSpeed(), Visibility: weatherType.GetBaseVisibility(), Confidence: 0.8, // 默认置信度 Description: fmt.Sprintf("%s %s", intensity.GetDescription(), weatherType.GetDescription()), CreatedAt: time, } } // GetTime 获取时间 func (wf *WeatherForecast) GetTime() time.Time { return wf.Time } // GetWeatherType 获取天气类型 func (wf *WeatherForecast) GetWeatherType() WeatherType { return wf.WeatherType } // GetIntensity 获取强度 func (wf *WeatherForecast) GetIntensity() WeatherIntensity { return wf.Intensity } // GetConfidence 获取置信度 func (wf *WeatherForecast) GetConfidence() float64 { return wf.Confidence } // GetDescription 获取描述 func (wf *WeatherForecast) GetDescription() string { return wf.Description } // IsHighConfidence 是否高置信度 func (wf *WeatherForecast) IsHighConfidence() bool { return wf.Confidence >= 0.8 } // ToMap 转换为映射 func (wf *WeatherForecast) ToMap() map[string]interface{} { return map[string]interface{}{ "time": wf.Time, "weather_type": wf.WeatherType.String(), "intensity": wf.Intensity.String(), "temperature": wf.Temperature, "humidity": wf.Humidity, "wind_speed": wf.WindSpeed, "visibility": wf.Visibility, "confidence": wf.Confidence, "description": wf.Description, "created_at": wf.CreatedAt, } } // WeatherEventType 天气事件类型 type WeatherEventType int const ( WeatherEventTypeStorm WeatherEventType = iota + 1 WeatherEventTypeBlizzard WeatherEventTypeHeatWave WeatherEventTypeColdWave WeatherEventTypeDrought WeatherEventTypeFlood WeatherEventTypeHurricane WeatherEventTypeTornado ) // String 返回事件类型字符串 func (wet WeatherEventType) String() string { switch wet { case WeatherEventTypeStorm: return "storm" case WeatherEventTypeBlizzard: return "blizzard" case WeatherEventTypeHeatWave: return "heat_wave" case WeatherEventTypeColdWave: return "cold_wave" case WeatherEventTypeDrought: return "drought" case WeatherEventTypeFlood: return "flood" case WeatherEventTypeHurricane: return "hurricane" case WeatherEventTypeTornado: return "tornado" default: return "unknown" } } // GetDescription 获取事件描述 func (wet WeatherEventType) GetDescription() string { switch wet { case WeatherEventTypeStorm: return "暴风雨" case WeatherEventTypeBlizzard: return "暴雪" case WeatherEventTypeHeatWave: return "热浪" case WeatherEventTypeColdWave: return "寒潮" case WeatherEventTypeDrought: return "干旱" case WeatherEventTypeFlood: return "洪水" case WeatherEventTypeHurricane: return "飓风" case WeatherEventTypeTornado: return "龙卷风" default: return "未知事件" } } // WeatherEventSeverity 天气事件严重程度 type WeatherEventSeverity int const ( WeatherEventSeverityMinor WeatherEventSeverity = iota + 1 WeatherEventSeverityModerate WeatherEventSeverityMajor WeatherEventSeverityCritical WeatherEventSeverityCatastrophic ) // String 返回严重程度字符串 func (wes WeatherEventSeverity) String() string { switch wes { case WeatherEventSeverityMinor: return "minor" case WeatherEventSeverityModerate: return "moderate" case WeatherEventSeverityMajor: return "major" case WeatherEventSeverityCritical: return "critical" case WeatherEventSeverityCatastrophic: return "catastrophic" default: return "unknown" } } // GetDescription 获取严重程度描述 func (wes WeatherEventSeverity) GetDescription() string { switch wes { case WeatherEventSeverityMinor: return "轻微" case WeatherEventSeverityModerate: return "中等" case WeatherEventSeverityMajor: return "严重" case WeatherEventSeverityCritical: return "危急" case WeatherEventSeverityCatastrophic: return "灾难性" default: return "未知程度" } } // GetMultiplier 获取严重程度倍率 func (wes WeatherEventSeverity) GetMultiplier() float64 { switch wes { case WeatherEventSeverityMinor: return 1.2 case WeatherEventSeverityModerate: return 1.5 case WeatherEventSeverityMajor: return 2.0 case WeatherEventSeverityCritical: return 3.0 case WeatherEventSeverityCatastrophic: return 5.0 default: return 1.0 } } // WeatherCondition 天气条件 type WeatherCondition struct { WeatherType WeatherType Intensity WeatherIntensity Duration time.Duration Effects []string } // NewWeatherCondition 创建天气条件 func NewWeatherCondition(weatherType WeatherType, intensity WeatherIntensity, duration time.Duration) *WeatherCondition { return &WeatherCondition{ WeatherType: weatherType, Intensity: intensity, Duration: duration, Effects: make([]string, 0), } } // AddEffect 添加效果 func (wc *WeatherCondition) AddEffect(effect string) { wc.Effects = append(wc.Effects, effect) } // GetEffects 获取效果列表 func (wc *WeatherCondition) GetEffects() []string { return wc.Effects } // Matches 检查是否匹配条件 func (wc *WeatherCondition) Matches(weatherType WeatherType, intensity WeatherIntensity) bool { return wc.WeatherType == weatherType && wc.Intensity == intensity } // 辅助函数 // getCurrentSeason 获取当前季节 func getCurrentSeason(currentTime time.Time) Season { month := currentTime.Month() switch { case month >= 3 && month <= 5: return SeasonSpring case month >= 6 && month <= 8: return SeasonSummer case month >= 9 && month <= 11: return SeasonAutumn default: return SeasonWinter } } // getSeasonStartTime 获取季节开始时间 func getSeasonStartTime(currentTime time.Time) time.Time { year := currentTime.Year() month := currentTime.Month() switch { case month >= 3 && month <= 5: return time.Date(year, 3, 1, 0, 0, 0, 0, currentTime.Location()) case month >= 6 && month <= 8: return time.Date(year, 6, 1, 0, 0, 0, 0, currentTime.Location()) case month >= 9 && month <= 11: return time.Date(year, 9, 1, 0, 0, 0, 0, currentTime.Location()) default: if month == 12 { return time.Date(year, 12, 1, 0, 0, 0, 0, currentTime.Location()) } else { return time.Date(year-1, 12, 1, 0, 0, 0, 0, currentTime.Location()) } } } ================================================ FILE: internal/domain/skill/errors.go ================================================ package skill import "errors" var ( // 技能树相关错误 ErrSkillTreeNotFound = errors.New("skill tree not found") ErrInvalidSkillTree = errors.New("invalid skill tree") // 技能相关错误 ErrSkillNotFound = errors.New("skill not found") ErrSkillNotLearned = errors.New("skill not learned") ErrSkillAlreadyLearned = errors.New("skill already learned") ErrSkillMaxLevel = errors.New("skill is at maximum level") ErrSkillOnCooldown = errors.New("skill is on cooldown") ErrPassiveSkillNotUsable = errors.New("passive skill cannot be used actively") ErrInvalidSkillType = errors.New("invalid skill type") ErrSkillNotAvailable = errors.New("skill is not available") // 技能点相关错误 ErrInsufficientSkillPoints = errors.New("insufficient skill points") ErrInvalidSkillPoints = errors.New("invalid skill points") ErrSkillPointsOverflow = errors.New("skill points overflow") // 前置条件相关错误 ErrPrerequisitesNotMet = errors.New("prerequisites not met") ErrLevelRequirementNotMet = errors.New("level requirement not met") ErrClassRestriction = errors.New("class restriction for skill") ErrRaceRestriction = errors.New("race restriction for skill") // 技能使用相关错误 ErrInsufficientMana = errors.New("insufficient mana") ErrInsufficientStamina = errors.New("insufficient stamina") ErrInvalidTarget = errors.New("invalid target") ErrTargetOutOfRange = errors.New("target out of range") ErrTargetDead = errors.New("target is dead") ErrCastInterrupted = errors.New("cast interrupted") ErrSilenced = errors.New("player is silenced") ErrStunned = errors.New("player is stunned") // 技能效果相关错误 ErrInvalidEffect = errors.New("invalid skill effect") ErrEffectNotFound = errors.New("skill effect not found") ErrEffectExpired = errors.New("skill effect expired") ErrEffectImmune = errors.New("target is immune to effect") // 技能组合相关错误 ErrInvalidCombo = errors.New("invalid skill combo") ErrComboTimeout = errors.New("skill combo timeout") ErrComboInterrupted = errors.New("skill combo interrupted") // 配置相关错误 ErrInvalidSkillConfig = errors.New("invalid skill configuration") ErrSkillConfigNotFound = errors.New("skill configuration not found") ) ================================================ FILE: internal/domain/skill/repository.go ================================================ package skill import ( "context" "time" ) // Repository 技能仓储接口 type Repository interface { // 基础CRUD操作 Save(ctx context.Context, skillTree *SkillTree) error FindByPlayerID(ctx context.Context, playerID string) (*SkillTree, error) Delete(ctx context.Context, playerID string) error Exists(ctx context.Context, playerID string) (bool, error) // 批量操作 SaveBatch(ctx context.Context, skillTrees []*SkillTree) error FindByPlayerIDs(ctx context.Context, playerIDs []string) ([]*SkillTree, error) // 技能查询 FindSkillsByType(ctx context.Context, playerID string, skillType SkillType) ([]*Skill, error) FindLearnedSkills(ctx context.Context, playerID string) ([]*Skill, error) FindAvailableSkills(ctx context.Context, playerID string) ([]*Skill, error) GetSkillLevel(ctx context.Context, playerID string, skillID string) (int, error) // 技能统计 GetSkillStats(ctx context.Context, playerID string) (*SkillStats, error) GetSkillUsageHistory(ctx context.Context, playerID string, limit int) ([]*SkillUsageRecord, error) GetTopSkills(ctx context.Context, playerID string, limit int) ([]*SkillRanking, error) // 技能配置 GetSkillConfig(ctx context.Context, skillID string) (*SkillConfig, error) GetAllSkillConfigs(ctx context.Context) ([]*SkillConfig, error) SaveSkillConfig(ctx context.Context, config *SkillConfig) error } // SkillStats 技能统计信息 type SkillStats struct { PlayerID string `json:"player_id"` TotalSkills int `json:"total_skills"` LearnedSkills int `json:"learned_skills"` SkillPoints int64 `json:"skill_points"` TotalPoints int64 `json:"total_points"` SkillsByType map[SkillType]int `json:"skills_by_type"` HighestLevel int `json:"highest_level"` AverageLevel float64 `json:"average_level"` LastUpdate time.Time `json:"last_update"` } // SkillUsageRecord 技能使用记录 type SkillUsageRecord struct { ID string `json:"id"` PlayerID string `json:"player_id"` SkillID string `json:"skill_id"` SkillName string `json:"skill_name"` TargetID string `json:"target_id"` Damage int64 `json:"damage"` Success bool `json:"success"` UsedAt time.Time `json:"used_at"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // SkillRanking 技能排行 type SkillRanking struct { SkillID string `json:"skill_id"` SkillName string `json:"skill_name"` Level int `json:"level"` UsageCount int64 `json:"usage_count"` TotalDamage int64 `json:"total_damage"` SuccessRate float64 `json:"success_rate"` } // SkillConfig 技能配置 type SkillConfig struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` SkillType SkillType `json:"skill_type"` MaxLevel int `json:"max_level"` Prerequisites []string `json:"prerequisites"` BaseDamage int64 `json:"base_damage"` DamageType DamageType `json:"damage_type"` ManaCost int64 `json:"mana_cost"` Cooldown time.Duration `json:"cooldown"` CastTime time.Duration `json:"cast_time"` Range float64 `json:"range"` Effects []*SkillEffectConfig `json:"effects"` Scaling map[AttributeType]float64 `json:"scaling"` LevelRequirement int `json:"level_requirement"` ClassRestrictions []string `json:"class_restrictions"` RaceRestrictions []string `json:"race_restrictions"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // SkillEffectConfig 技能效果配置 type SkillEffectConfig struct { EffectType EffectType `json:"effect_type"` Value float64 `json:"value"` Duration time.Duration `json:"duration"` Target TargetType `json:"target"` Condition *EffectConditionConfig `json:"condition,omitempty"` } // EffectConditionConfig 效果条件配置 type EffectConditionConfig struct { ConditionType ConditionType `json:"condition_type"` Value interface{} `json:"value"` } // SkillQueryFilter 技能查询过滤器 type SkillQueryFilter struct { PlayerID string `json:"player_id"` SkillTypes []SkillType `json:"skill_types,omitempty"` MinLevel *int `json:"min_level,omitempty"` MaxLevel *int `json:"max_level,omitempty"` LearnedOnly bool `json:"learned_only"` AvailableOnly bool `json:"available_only"` UsableOnly bool `json:"usable_only"` IncludePassive bool `json:"include_passive"` SortBy string `json:"sort_by"` // level, usage_count, damage SortOrder string `json:"sort_order"` // asc, desc Limit int `json:"limit"` Offset int `json:"offset"` } ================================================ FILE: internal/domain/skill/skill.go ================================================ package skill import ( // "errors" "time" ) // SkillTree 技能树聚合根 type SkillTree struct { playerID string skills map[string]*Skill skillPoints int64 totalPoints int64 lastUpdate time.Time events []DomainEvent } // NewSkillTree 创建新技能树 func NewSkillTree(playerID string) *SkillTree { return &SkillTree{ playerID: playerID, skills: make(map[string]*Skill), skillPoints: 0, totalPoints: 0, lastUpdate: time.Now(), events: make([]DomainEvent, 0), } } // Skill 技能实体 type Skill struct { id string name string description string skillType SkillType level int maxLevel int prerequisites []string // 前置技能ID effects []*SkillEffect cooldown time.Duration lastUsed *time.Time manaCost int64 castTime time.Duration range_ float64 damageType DamageType baseDamage int64 scaling map[AttributeType]float64 createdAt time.Time updatedAt time.Time } // NewSkill 创建新技能 func NewSkill(id, name string, skillType SkillType) *Skill { return &Skill{ id: id, name: name, skillType: skillType, level: 0, maxLevel: 10, effects: make([]*SkillEffect, 0), scaling: make(map[AttributeType]float64), createdAt: time.Now(), updatedAt: time.Now(), } } // SkillType 技能类型 type SkillType int const ( SkillTypeActive SkillType = iota + 1 SkillTypePassive SkillTypeToggle SkillTypeChanneled SkillTypeInstant ) // DamageType 伤害类型 type DamageType int const ( DamageTypePhysical DamageType = iota + 1 DamageTypeMagical DamageTypeTrue DamageTypeHealing ) // AttributeType 属性类型 type AttributeType int const ( AttributeTypeStrength AttributeType = iota + 1 AttributeTypeIntelligence AttributeTypeAgility AttributeTypeVitality AttributeTypeSpirit ) // SkillEffect 技能效果 type SkillEffect struct { effectType EffectType value float64 duration time.Duration target TargetType condition *EffectCondition } // EffectType 效果类型 type EffectType int const ( EffectTypeDamage EffectType = iota + 1 EffectTypeHeal EffectTypeBuff EffectTypeDebuff EffectTypeStun EffectTypeSilence EffectTypeRoot EffectTypeSlow EffectTypeHaste EffectTypeShield EffectTypeReflect ) // TargetType 目标类型 type TargetType int const ( TargetTypeSelf TargetType = iota + 1 TargetTypeEnemy TargetTypeAlly TargetTypeAll TargetTypeArea ) // EffectCondition 效果条件 type EffectCondition struct { conditionType ConditionType value interface{} } // ConditionType 条件类型 type ConditionType int const ( ConditionTypeHealthBelow ConditionType = iota + 1 ConditionTypeManaBelow ConditionTypeEnemyCount ConditionTypeBuffActive ConditionTypeDebuffActive ) // SkillCombo 技能连击 type SkillCombo struct { id string name string skills []string // 技能ID序列 timeWindow time.Duration bonusEffect *SkillEffect } // DomainEvent 领域事件接口 type DomainEvent interface { EventType() string OccurredAt() time.Time PlayerID() string } // SkillLearnedEvent 技能学习事件 type SkillLearnedEvent struct { playerID string skillID string occurredAt time.Time } func (e SkillLearnedEvent) EventType() string { return "skill.learned" } func (e SkillLearnedEvent) OccurredAt() time.Time { return e.occurredAt } func (e SkillLearnedEvent) PlayerID() string { return e.playerID } // SkillUpgradedEvent 技能升级事件 type SkillUpgradedEvent struct { playerID string skillID string oldLevel int newLevel int occurredAt time.Time } func (e SkillUpgradedEvent) EventType() string { return "skill.upgraded" } func (e SkillUpgradedEvent) OccurredAt() time.Time { return e.occurredAt } func (e SkillUpgradedEvent) PlayerID() string { return e.playerID } // SkillUsedEvent 技能使用事件 type SkillUsedEvent struct { playerID string skillID string targetID string damage int64 occurredAt time.Time } func (e SkillUsedEvent) EventType() string { return "skill.used" } func (e SkillUsedEvent) OccurredAt() time.Time { return e.occurredAt } func (e SkillUsedEvent) PlayerID() string { return e.playerID } // SkillPointsGainedEvent 技能点获得事件 type SkillPointsGainedEvent struct { playerID string points int64 reason string occurredAt time.Time } func (e SkillPointsGainedEvent) EventType() string { return "skill.points.gained" } func (e SkillPointsGainedEvent) OccurredAt() time.Time { return e.occurredAt } func (e SkillPointsGainedEvent) PlayerID() string { return e.playerID } // SkillTree 业务方法 // PlayerID 获取玩家ID func (st *SkillTree) PlayerID() string { return st.playerID } // SkillPoints 获取技能点 func (st *SkillTree) SkillPoints() int64 { return st.skillPoints } // TotalPoints 获取总技能点 func (st *SkillTree) TotalPoints() int64 { return st.totalPoints } // Skills 获取所有技能 func (st *SkillTree) Skills() map[string]*Skill { return st.skills } // GetSkill 获取指定技能 func (st *SkillTree) GetSkill(skillID string) (*Skill, bool) { skill, exists := st.skills[skillID] return skill, exists } // LearnSkill 学习技能 func (st *SkillTree) LearnSkill(skillID string, skillData *Skill) error { if st.skillPoints <= 0 { return ErrInsufficientSkillPoints } // 检查是否已学习 if _, exists := st.skills[skillID]; exists { return ErrSkillAlreadyLearned } // 检查前置技能 if !st.checkPrerequisites(skillData.prerequisites) { return ErrPrerequisitesNotMet } // 学习技能 skillData.level = 1 skillData.updatedAt = time.Now() st.skills[skillID] = skillData st.skillPoints-- st.lastUpdate = time.Now() // 发布事件 st.addEvent(SkillLearnedEvent{ playerID: st.playerID, skillID: skillID, occurredAt: time.Now(), }) return nil } // UpgradeSkill 升级技能 func (st *SkillTree) UpgradeSkill(skillID string) error { skill, exists := st.skills[skillID] if !exists { return ErrSkillNotLearned } if skill.level >= skill.maxLevel { return ErrSkillMaxLevel } requiredPoints := st.calculateUpgradeCost(skill.level) if st.skillPoints < requiredPoints { return ErrInsufficientSkillPoints } oldLevel := skill.level skill.level++ skill.updatedAt = time.Now() st.skillPoints -= requiredPoints st.lastUpdate = time.Now() // 发布事件 st.addEvent(SkillUpgradedEvent{ playerID: st.playerID, skillID: skillID, oldLevel: oldLevel, newLevel: skill.level, occurredAt: time.Now(), }) return nil } // UseSkill 使用技能 func (st *SkillTree) UseSkill(skillID string, targetID string) (*SkillResult, error) { skill, exists := st.skills[skillID] if !exists { return nil, ErrSkillNotLearned } if skill.skillType == SkillTypePassive { return nil, ErrPassiveSkillNotUsable } // 检查冷却时间 if skill.lastUsed != nil && time.Since(*skill.lastUsed) < skill.cooldown { return nil, ErrSkillOnCooldown } // 计算伤害和效果 result := st.calculateSkillResult(skill, targetID) // 更新使用时间 now := time.Now() skill.lastUsed = &now st.lastUpdate = time.Now() // 发布事件 st.addEvent(SkillUsedEvent{ playerID: st.playerID, skillID: skillID, targetID: targetID, damage: result.Damage, occurredAt: time.Now(), }) return result, nil } // AddSkillPoints 添加技能点 func (st *SkillTree) AddSkillPoints(points int64, reason string) error { if points <= 0 { return ErrInvalidSkillPoints } st.skillPoints += points st.totalPoints += points st.lastUpdate = time.Now() // 发布事件 st.addEvent(SkillPointsGainedEvent{ playerID: st.playerID, points: points, reason: reason, occurredAt: time.Now(), }) return nil } // ResetSkills 重置技能 func (st *SkillTree) ResetSkills() error { // 计算返还的技能点 refundPoints := int64(0) for _, skill := range st.skills { for level := 1; level <= skill.level; level++ { refundPoints += st.calculateUpgradeCost(level - 1) } } // 重置所有技能 st.skills = make(map[string]*Skill) st.skillPoints += refundPoints st.lastUpdate = time.Now() return nil } // checkPrerequisites 检查前置技能 func (st *SkillTree) checkPrerequisites(prerequisites []string) bool { for _, prereq := range prerequisites { if _, exists := st.skills[prereq]; !exists { return false } } return true } // calculateUpgradeCost 计算升级消耗 func (st *SkillTree) calculateUpgradeCost(currentLevel int) int64 { // 基础消耗 + 等级加成 return int64(currentLevel + 1) } // calculateSkillResult 计算技能结果 func (st *SkillTree) calculateSkillResult(skill *Skill, targetID string) *SkillResult { // 基础伤害计算 damage := skill.baseDamage * int64(skill.level) // 这里可以添加更复杂的伤害计算逻辑 // 包括属性加成、暴击、抗性等 return &SkillResult{ SkillID: skill.id, TargetID: targetID, Damage: damage, Effects: skill.effects, Success: true, } } // addEvent 添加领域事件 func (st *SkillTree) addEvent(event DomainEvent) { st.events = append(st.events, event) } // GetEvents 获取领域事件 func (st *SkillTree) GetEvents() []DomainEvent { return st.events } // ClearEvents 清除领域事件 func (st *SkillTree) ClearEvents() { st.events = make([]DomainEvent, 0) } // SkillResult 技能使用结果 type SkillResult struct { SkillID string TargetID string Damage int64 Effects []*SkillEffect Success bool Message string } ================================================ FILE: internal/domain/social/chat/aggregate.go ================================================ package chat import ( "context" "time" ) // ChatChannel 聊天频道聚合根 type ChatChannel struct { ID string Name string Type ChannelType Description string MaxMembers int members map[string]*Member messages []*Message createdAt time.Time updatedAt time.Time version int64 } // ChannelType 频道类型 type ChannelType int const ( ChannelTypeWorld ChannelType = iota // 世界频道 ChannelTypeGuild // 公会频道 ChannelTypeTeam // 队伍频道 ChannelTypePrivate // 私聊频道 ChannelTypeSystem // 系统频道 ) // NewChatChannel 创建新的聊天频道 func NewChatChannel(id, name string, channelType ChannelType) *ChatChannel { return &ChatChannel{ ID: id, Name: name, Type: channelType, members: make(map[string]*Member), messages: make([]*Message, 0), createdAt: time.Now(), updatedAt: time.Now(), version: 1, } } // AddMember 添加成员 func (c *ChatChannel) AddMember(member *Member) error { if len(c.members) >= c.MaxMembers && c.MaxMembers > 0 { return ErrChannelFull } if _, exists := c.members[member.PlayerID]; exists { return ErrMemberAlreadyExists } c.members[member.PlayerID] = member c.updatedAt = time.Now() c.version++ return nil } // RemoveMember 移除成员 func (c *ChatChannel) RemoveMember(playerID string) error { if _, exists := c.members[playerID]; !exists { return ErrMemberNotFound } delete(c.members, playerID) c.updatedAt = time.Now() c.version++ return nil } // SendMessage 发送消息 func (c *ChatChannel) SendMessage(ctx context.Context, message *Message) error { // 验证发送者是否在频道中 if _, exists := c.members[message.SenderID]; !exists && c.Type != ChannelTypeSystem { return ErrSenderNotInChannel } // 验证消息内容 if err := message.Validate(); err != nil { return err } // 添加消息到频道 c.messages = append(c.messages, message) c.updatedAt = time.Now() c.version++ // 限制消息历史数量 if len(c.messages) > MaxMessagesPerChannel { c.messages = c.messages[len(c.messages)-MaxMessagesPerChannel:] } return nil } // GetMembers 获取所有成员 func (c *ChatChannel) GetMembers() []*Member { members := make([]*Member, 0, len(c.members)) for _, member := range c.members { members = append(members, member) } return members } // GetRecentMessages 获取最近的消息 func (c *ChatChannel) GetRecentMessages(limit int) []*Message { if limit <= 0 || limit > len(c.messages) { limit = len(c.messages) } start := len(c.messages) - limit if start < 0 { start = 0 } return c.messages[start:] } // IsMember 检查是否为成员 func (c *ChatChannel) IsMember(playerID string) bool { _, exists := c.members[playerID] return exists } // GetVersion 获取版本号 func (c *ChatChannel) GetVersion() int64 { return c.version } const ( MaxMessagesPerChannel = 100 // 每个频道最大消息数 ) ================================================ FILE: internal/domain/social/chat/chat_service.go ================================================ package chat import ( "context" "fmt" ) // ChatService 聊天领域服务 type ChatService struct { chatRepo ChatRepository } // NewChatService 创建聊天服务 func NewChatService(chatRepo ChatRepository) *ChatService { return &ChatService{ chatRepo: chatRepo, } } // CreateChannel 创建聊天频道 func (s *ChatService) CreateChannel(ctx context.Context, name string, channelType ChannelType, creatorID string) (*ChatChannel, error) { // 验证频道名称 if err := s.validateChannelName(name); err != nil { return nil, err } // 检查频道是否已存在 exists, err := s.chatRepo.ChannelExistsByName(ctx, name) if err != nil { return nil, fmt.Errorf("检查频道是否存在失败: %w", err) } if exists { return nil, ErrChannelAlreadyExists } // 创建频道 channelID := generateChannelID() channel := NewChatChannel(channelID, name, channelType) // 添加创建者为所有者 creator := NewMember(creatorID, "") creator.SetRole(MemberRoleOwner) if err := channel.AddMember(creator); err != nil { return nil, fmt.Errorf("添加创建者失败: %w", err) } // 保存频道 if err := s.chatRepo.SaveChannel(ctx, channel); err != nil { return nil, fmt.Errorf("保存频道失败: %w", err) } return channel, nil } // JoinChannel 加入频道 func (s *ChatService) JoinChannel(ctx context.Context, channelID, playerID, nickname string) error { // 获取频道 channel, err := s.chatRepo.GetChannelByID(ctx, channelID) if err != nil { return fmt.Errorf("获取频道失败: %w", err) } if channel == nil { return ErrChannelNotFound } // 检查是否已经是成员 if channel.IsMember(playerID) { return ErrMemberAlreadyExists } // 创建新成员 member := NewMember(playerID, nickname) // 添加成员到频道 if err := channel.AddMember(member); err != nil { return err } // 保存频道 if err := s.chatRepo.SaveChannel(ctx, channel); err != nil { return fmt.Errorf("保存频道失败: %w", err) } return nil } // LeaveChannel 离开频道 func (s *ChatService) LeaveChannel(ctx context.Context, channelID, playerID string) error { // 获取频道 channel, err := s.chatRepo.GetChannelByID(ctx, channelID) if err != nil { return fmt.Errorf("获取频道失败: %w", err) } if channel == nil { return ErrChannelNotFound } // 移除成员 if err := channel.RemoveMember(playerID); err != nil { return err } // 保存频道 if err := s.chatRepo.SaveChannel(ctx, channel); err != nil { return fmt.Errorf("保存频道失败: %w", err) } return nil } // SendMessage 发送消息 func (s *ChatService) SendMessage(ctx context.Context, channelID, senderID, content string, msgType MessageType) (*Message, error) { // 获取频道 channel, err := s.chatRepo.GetChannelByID(ctx, channelID) if err != nil { return nil, fmt.Errorf("获取频道失败: %w", err) } if channel == nil { return nil, ErrChannelNotFound } // 创建消息 message := NewMessage(channelID, senderID, content, msgType) // 发送消息到频道 if err := channel.SendMessage(ctx, message); err != nil { return nil, err } // 保存消息 if err := s.chatRepo.SaveMessage(ctx, message); err != nil { return nil, fmt.Errorf("保存消息失败: %w", err) } // 保存频道(更新版本) if err := s.chatRepo.SaveChannel(ctx, channel); err != nil { return nil, fmt.Errorf("保存频道失败: %w", err) } return message, nil } // GetChannelMessages 获取频道消息 func (s *ChatService) GetChannelMessages(ctx context.Context, channelID string, limit int) ([]*Message, error) { return s.chatRepo.GetMessagesByChannelID(ctx, channelID, limit) } // validateChannelName 验证频道名称 func (s *ChatService) validateChannelName(name string) error { if len(name) < 2 { return ErrChannelNameTooShort } if len(name) > 50 { return ErrChannelNameTooLong } return nil } // generateChannelID 生成频道ID func generateChannelID() string { return "ch_" + randomString(16) } ================================================ FILE: internal/domain/social/chat/errors.go ================================================ package chat import "errors" // 聊天相关错误定义 var ( // 频道相关错误 ErrChannelNotFound = errors.New("频道不存在") ErrChannelAlreadyExists = errors.New("频道已存在") ErrChannelFull = errors.New("频道已满") ErrChannelNameTooShort = errors.New("频道名称太短") ErrChannelNameTooLong = errors.New("频道名称太长") ErrInvalidChannelID = errors.New("无效的频道ID") // 成员相关错误 ErrMemberNotFound = errors.New("成员不存在") ErrMemberAlreadyExists = errors.New("成员已存在") ErrMemberMuted = errors.New("成员被禁言") ErrInsufficientPermission = errors.New("权限不足") ErrSenderNotInChannel = errors.New("发送者不在频道中") // 消息相关错误 ErrMessageNotFound = errors.New("消息不存在") ErrEmptyContent = errors.New("消息内容为空") ErrMessageTooLong = errors.New("消息内容过长") ErrInvalidSenderID = errors.New("无效的发送者ID") ErrInvalidMessageType = errors.New("无效的消息类型") // 系统相关错误 ErrSystemError = errors.New("系统错误") ErrDatabaseError = errors.New("数据库错误") ErrNetworkError = errors.New("网络错误") ) ================================================ FILE: internal/domain/social/chat/events.go ================================================ package chat import "time" // ChatEvent 聊天事件接口 type ChatEvent interface { GetEventType() string GetTimestamp() time.Time GetChannelID() string } // BaseEvent 基础事件 type BaseEvent struct { EventType string Timestamp time.Time ChannelID string } func (e BaseEvent) GetEventType() string { return e.EventType } func (e BaseEvent) GetTimestamp() time.Time { return e.Timestamp } func (e BaseEvent) GetChannelID() string { return e.ChannelID } // ChannelCreatedEvent 频道创建事件 type ChannelCreatedEvent struct { BaseEvent ChannelName string ChannelType ChannelType CreatorID string } // NewChannelCreatedEvent 创建频道创建事件 func NewChannelCreatedEvent(channelID, channelName string, channelType ChannelType, creatorID string) *ChannelCreatedEvent { return &ChannelCreatedEvent{ BaseEvent: BaseEvent{ EventType: "channel.created", Timestamp: time.Now(), ChannelID: channelID, }, ChannelName: channelName, ChannelType: channelType, CreatorID: creatorID, } } // MemberJoinedEvent 成员加入事件 type MemberJoinedEvent struct { BaseEvent PlayerID string Nickname string } // NewMemberJoinedEvent 创建成员加入事件 func NewMemberJoinedEvent(channelID, playerID, nickname string) *MemberJoinedEvent { return &MemberJoinedEvent{ BaseEvent: BaseEvent{ EventType: "member.joined", Timestamp: time.Now(), ChannelID: channelID, }, PlayerID: playerID, Nickname: nickname, } } // MemberLeftEvent 成员离开事件 type MemberLeftEvent struct { BaseEvent PlayerID string } // NewMemberLeftEvent 创建成员离开事件 func NewMemberLeftEvent(channelID, playerID string) *MemberLeftEvent { return &MemberLeftEvent{ BaseEvent: BaseEvent{ EventType: "member.left", Timestamp: time.Now(), ChannelID: channelID, }, PlayerID: playerID, } } // MessageSentEvent 消息发送事件 type MessageSentEvent struct { BaseEvent MessageID string SenderID string Content string Type MessageType } // NewMessageSentEvent 创建消息发送事件 func NewMessageSentEvent(channelID, messageID, senderID, content string, msgType MessageType) *MessageSentEvent { return &MessageSentEvent{ BaseEvent: BaseEvent{ EventType: "message.sent", Timestamp: time.Now(), ChannelID: channelID, }, MessageID: messageID, SenderID: senderID, Content: content, Type: msgType, } } // MemberMutedEvent 成员被禁言事件 type MemberMutedEvent struct { BaseEvent PlayerID string MutedBy string Duration time.Duration Reason string } // NewMemberMutedEvent 创建成员禁言事件 func NewMemberMutedEvent(channelID, playerID, mutedBy string, duration time.Duration, reason string) *MemberMutedEvent { return &MemberMutedEvent{ BaseEvent: BaseEvent{ EventType: "member.muted", Timestamp: time.Now(), ChannelID: channelID, }, PlayerID: playerID, MutedBy: mutedBy, Duration: duration, Reason: reason, } } // MemberUnmutedEvent 成员解除禁言事件 type MemberUnmutedEvent struct { BaseEvent PlayerID string UnmutedBy string } // NewMemberUnmutedEvent 创建成员解除禁言事件 func NewMemberUnmutedEvent(channelID, playerID, unmutedBy string) *MemberUnmutedEvent { return &MemberUnmutedEvent{ BaseEvent: BaseEvent{ EventType: "member.unmuted", Timestamp: time.Now(), ChannelID: channelID, }, PlayerID: playerID, UnmutedBy: unmutedBy, } } ================================================ FILE: internal/domain/social/chat/member.go ================================================ package chat import ( "time" ) // Member 聊天频道成员实体 type Member struct { PlayerID string Nickname string Role MemberRole JoinedAt time.Time LastActive time.Time Permissions []Permission IsMuted bool MutedUntil *time.Time } // MemberRole 成员角色 type MemberRole int const ( MemberRoleNormal MemberRole = iota // 普通成员 MemberRoleModerator // 管理员 MemberRoleOwner // 频道所有者 ) // Permission 权限 type Permission int const ( PermissionSendMessage Permission = iota // 发送消息 PermissionDeleteMessage // 删除消息 PermissionMuteMembers // 禁言成员 PermissionKickMembers // 踢出成员 PermissionManageChannel // 管理频道 ) // NewMember 创建新成员 func NewMember(playerID, nickname string) *Member { return &Member{ PlayerID: playerID, Nickname: nickname, Role: MemberRoleNormal, JoinedAt: time.Now(), LastActive: time.Now(), Permissions: getDefaultPermissions(MemberRoleNormal), IsMuted: false, } } // HasPermission 检查是否有权限 func (m *Member) HasPermission(permission Permission) bool { for _, p := range m.Permissions { if p == permission { return true } } return false } // SetRole 设置角色 func (m *Member) SetRole(role MemberRole) { m.Role = role m.Permissions = getDefaultPermissions(role) } // Mute 禁言成员 func (m *Member) Mute(duration time.Duration) { m.IsMuted = true mutedUntil := time.Now().Add(duration) m.MutedUntil = &mutedUntil } // Unmute 解除禁言 func (m *Member) Unmute() { m.IsMuted = false m.MutedUntil = nil } // IsCurrentlyMuted 检查当前是否被禁言 func (m *Member) IsCurrentlyMuted() bool { if !m.IsMuted { return false } if m.MutedUntil != nil && time.Now().After(*m.MutedUntil) { m.Unmute() return false } return true } // UpdateLastActive 更新最后活跃时间 func (m *Member) UpdateLastActive() { m.LastActive = time.Now() } // GetMembershipDuration 获取加入时长 func (m *Member) GetMembershipDuration() time.Duration { return time.Since(m.JoinedAt) } // CanSendMessage 检查是否可以发送消息 func (m *Member) CanSendMessage() bool { return m.HasPermission(PermissionSendMessage) && !m.IsCurrentlyMuted() } // getDefaultPermissions 获取角色默认权限 func getDefaultPermissions(role MemberRole) []Permission { switch role { case MemberRoleNormal: return []Permission{PermissionSendMessage} case MemberRoleModerator: return []Permission{ PermissionSendMessage, PermissionDeleteMessage, PermissionMuteMembers, } case MemberRoleOwner: return []Permission{ PermissionSendMessage, PermissionDeleteMessage, PermissionMuteMembers, PermissionKickMembers, PermissionManageChannel, } default: return []Permission{PermissionSendMessage} } } ================================================ FILE: internal/domain/social/chat/message.go ================================================ package chat import ( "strings" "time" ) // Message 聊天消息实体 type Message struct { ID string ChannelID string SenderID string Content string Type MessageType Timestamp time.Time Metadata map[string]interface{} } // MessageType 消息类型 type MessageType int const ( MessageTypeText MessageType = iota // 文本消息 MessageTypeImage // 图片消息 MessageTypeSystem // 系统消息 MessageTypeEmoji // 表情消息 MessageTypeItem // 物品链接消息 ) // NewMessage 创建新消息 func NewMessage(channelID, senderID, content string, msgType MessageType) *Message { return &Message{ ID: generateMessageID(), ChannelID: channelID, SenderID: senderID, Content: content, Type: msgType, Timestamp: time.Now(), Metadata: make(map[string]interface{}), } } // NewSystemMessage 创建系统消息 func NewSystemMessage(channelID, content string) *Message { return &Message{ ID: generateMessageID(), ChannelID: channelID, SenderID: "system", Content: content, Type: MessageTypeSystem, Timestamp: time.Now(), Metadata: make(map[string]interface{}), } } // Validate 验证消息 func (m *Message) Validate() error { if m.ChannelID == "" { return ErrInvalidChannelID } if m.SenderID == "" { return ErrInvalidSenderID } if strings.TrimSpace(m.Content) == "" { return ErrEmptyContent } if len(m.Content) > MaxMessageLength { return ErrMessageTooLong } return nil } // SetMetadata 设置元数据 func (m *Message) SetMetadata(key string, value interface{}) { m.Metadata[key] = value } // GetMetadata 获取元数据 func (m *Message) GetMetadata(key string) (interface{}, bool) { value, exists := m.Metadata[key] return value, exists } // IsSystemMessage 是否为系统消息 func (m *Message) IsSystemMessage() bool { return m.Type == MessageTypeSystem } // GetAge 获取消息年龄 func (m *Message) GetAge() time.Duration { return time.Since(m.Timestamp) } const ( MaxMessageLength = 500 // 最大消息长度 ) // generateMessageID 生成消息ID func generateMessageID() string { // 简单的ID生成,实际项目中应使用更robust的方案 return time.Now().Format("20060102150405") + "-" + randomString(8) } // randomString 生成随机字符串 func randomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[time.Now().UnixNano()%int64(len(charset))] } return string(b) } ================================================ FILE: internal/domain/social/chat/repository.go ================================================ package chat import "context" // ChatRepository 聊天仓储接口 type ChatRepository interface { // 频道相关 SaveChannel(ctx context.Context, channel *ChatChannel) error GetChannelByID(ctx context.Context, channelID string) (*ChatChannel, error) GetChannelByName(ctx context.Context, name string) (*ChatChannel, error) ChannelExistsByName(ctx context.Context, name string) (bool, error) DeleteChannel(ctx context.Context, channelID string) error GetChannelsByType(ctx context.Context, channelType ChannelType) ([]*ChatChannel, error) GetPlayerChannels(ctx context.Context, playerID string) ([]*ChatChannel, error) // 消息相关 SaveMessage(ctx context.Context, message *Message) error GetMessageByID(ctx context.Context, messageID string) (*Message, error) GetMessagesByChannelID(ctx context.Context, channelID string, limit int) ([]*Message, error) DeleteMessage(ctx context.Context, messageID string) error GetMessagesByTimeRange(ctx context.Context, channelID string, start, end int64) ([]*Message, error) // 成员相关 GetChannelMembers(ctx context.Context, channelID string) ([]*Member, error) GetMemberByPlayerID(ctx context.Context, channelID, playerID string) (*Member, error) UpdateMember(ctx context.Context, channelID string, member *Member) error } ================================================ FILE: internal/domain/social/email/attachment.go ================================================ package email import "time" // Attachment 邮件附件实体 type Attachment struct { ID string Type AttachmentType ItemID string Quantity int64 Claimed bool ClaimedAt *time.Time ExpiresAt *time.Time } // AttachmentType 附件类型 type AttachmentType int const ( AttachmentTypeItem AttachmentType = iota // 物品 AttachmentTypeCurrency // 货币 AttachmentTypeExp // 经验 AttachmentTypeVIP // VIP时间 ) // NewAttachment 创建新附件 func NewAttachment(attachmentType AttachmentType, itemID string, quantity int64) *Attachment { return &Attachment{ ID: generateAttachmentID(), Type: attachmentType, ItemID: itemID, Quantity: quantity, Claimed: false, } } // NewItemAttachment 创建物品附件 func NewItemAttachment(itemID string, quantity int64) *Attachment { return NewAttachment(AttachmentTypeItem, itemID, quantity) } // NewCurrencyAttachment 创建货币附件 func NewCurrencyAttachment(currencyType string, amount int64) *Attachment { return NewAttachment(AttachmentTypeCurrency, currencyType, amount) } // NewExpAttachment 创建经验附件 func NewExpAttachment(amount int64) *Attachment { return NewAttachment(AttachmentTypeExp, "exp", amount) } // Claim 领取附件 func (a *Attachment) Claim() error { if a.Claimed { return ErrAttachmentAlreadyClaimed } if a.IsExpired() { return ErrAttachmentExpired } a.Claimed = true claimedAt := time.Now() a.ClaimedAt = &claimedAt return nil } // IsExpired 检查附件是否已过期 func (a *Attachment) IsExpired() bool { if a.ExpiresAt == nil { return false } return time.Now().After(*a.ExpiresAt) } // SetExpiration 设置过期时间 func (a *Attachment) SetExpiration(expiresAt time.Time) { a.ExpiresAt = &expiresAt } // IsClaimed 是否已领取 func (a *Attachment) IsClaimed() bool { return a.Claimed } // IsItem 是否为物品附件 func (a *Attachment) IsItem() bool { return a.Type == AttachmentTypeItem } // IsCurrency 是否为货币附件 func (a *Attachment) IsCurrency() bool { return a.Type == AttachmentTypeCurrency } // IsExp 是否为经验附件 func (a *Attachment) IsExp() bool { return a.Type == AttachmentTypeExp } // generateAttachmentID 生成附件ID func generateAttachmentID() string { return "att_" + randomString(12) } ================================================ FILE: internal/domain/social/email/email.go ================================================ package email import ( "time" ) // Email 邮件聚合根 type Email struct { ID string SenderID string ReceiverID string Subject string Content string Type EmailType Status EmailStatus attachments []*Attachment SentAt time.Time ReadAt *time.Time ExpiresAt *time.Time Version int64 } // EmailType 邮件类型 type EmailType int const ( EmailTypeNormal EmailType = iota // 普通邮件 EmailTypeSystem // 系统邮件 EmailTypeReward // 奖励邮件 EmailTypeNotice // 通知邮件 ) // EmailStatus 邮件状态 type EmailStatus int const ( EmailStatusUnread EmailStatus = iota // 未读 EmailStatusRead // 已读 EmailStatusDeleted // 已删除 EmailStatusExpired // 已过期 ) // NewEmail 创建新邮件 func NewEmail(senderID, receiverID, subject, content string, emailType EmailType) *Email { return &Email{ ID: generateEmailID(), SenderID: senderID, ReceiverID: receiverID, Subject: subject, Content: content, Type: emailType, Status: EmailStatusUnread, attachments: make([]*Attachment, 0), SentAt: time.Now(), Version: 1, } } // NewSystemEmail 创建系统邮件 func NewSystemEmail(receiverID, subject, content string) *Email { email := NewEmail("system", receiverID, subject, content, EmailTypeSystem) // 系统邮件30天后过期 expiresAt := time.Now().Add(30 * 24 * time.Hour) email.ExpiresAt = &expiresAt return email } // AddAttachment 添加附件 func (e *Email) AddAttachment(attachment *Attachment) error { if len(e.attachments) >= MaxAttachmentsPerEmail { return ErrTooManyAttachments } e.attachments = append(e.attachments, attachment) e.Version++ return nil } // MarkAsRead 标记为已读 func (e *Email) MarkAsRead() error { if e.Status == EmailStatusDeleted { return ErrEmailDeleted } if e.IsExpired() { return ErrEmailExpired } if e.Status == EmailStatusUnread { e.Status = EmailStatusRead readAt := time.Now() e.ReadAt = &readAt e.Version++ } return nil } // Delete 删除邮件 func (e *Email) Delete() error { if e.Status == EmailStatusDeleted { return ErrEmailAlreadyDeleted } e.Status = EmailStatusDeleted e.Version++ return nil } // IsExpired 检查是否已过期 func (e *Email) IsExpired() bool { if e.Status == EmailStatusExpired { return true } if e.ExpiresAt != nil && time.Now().After(*e.ExpiresAt) { e.Status = EmailStatusExpired e.Version++ return true } return false } // IsRead 是否已读 func (e *Email) IsRead() bool { return e.Status == EmailStatusRead } // IsDeleted 是否已删除 func (e *Email) IsDeleted() bool { return e.Status == EmailStatusDeleted } // GetAttachments 获取所有附件 func (e *Email) GetAttachments() []*Attachment { return e.attachments } // HasAttachments 是否有附件 func (e *Email) HasAttachments() bool { return len(e.attachments) > 0 } // GetAge 获取邮件年龄 func (e *Email) GetAge() time.Duration { return time.Since(e.SentAt) } const ( MaxAttachmentsPerEmail = 10 // 每封邮件最大附件数 ) // generateEmailID 生成邮件ID func generateEmailID() string { return "email_" + randomString(16) } // randomString 生成随机字符串 func randomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[i%len(charset)] } return string(b) } ================================================ FILE: internal/domain/social/email/email_service.go ================================================ package email import ( "context" "fmt" ) // EmailService 邮件领域服务 type EmailService struct { emailRepo EmailRepository } // NewEmailService 创建邮件服务 func NewEmailService(emailRepo EmailRepository) *EmailService { return &EmailService{ emailRepo: emailRepo, } } // SendEmail 发送邮件 func (s *EmailService) SendEmail(ctx context.Context, senderID, receiverID, subject, content string, emailType EmailType) (*Email, error) { // 验证邮件内容 if err := s.validateEmail(subject, content); err != nil { return nil, err } // 检查接收者邮箱是否已满 count, err := s.emailRepo.GetUnreadEmailCount(ctx, receiverID) if err != nil { return nil, fmt.Errorf("获取未读邮件数量失败: %w", err) } if count >= MaxEmailsPerPlayer { return nil, ErrMailboxFull } // 创建邮件 email := NewEmail(senderID, receiverID, subject, content, emailType) // 保存邮件 if err := s.emailRepo.SaveEmail(ctx, email); err != nil { return nil, fmt.Errorf("保存邮件失败: %w", err) } return email, nil } // SendSystemEmail 发送系统邮件 func (s *EmailService) SendSystemEmail(ctx context.Context, receiverID, subject, content string, attachments []*Attachment) (*Email, error) { // 创建系统邮件 email := NewSystemEmail(receiverID, subject, content) // 添加附件 for _, attachment := range attachments { if err := email.AddAttachment(attachment); err != nil { return nil, fmt.Errorf("添加附件失败: %w", err) } } // 保存邮件 if err := s.emailRepo.SaveEmail(ctx, email); err != nil { return nil, fmt.Errorf("保存邮件失败: %w", err) } return email, nil } // ReadEmail 读取邮件 func (s *EmailService) ReadEmail(ctx context.Context, emailID, playerID string) (*Email, error) { // 获取邮件 email, err := s.emailRepo.GetEmailByID(ctx, emailID) if err != nil { return nil, fmt.Errorf("获取邮件失败: %w", err) } if email == nil { return nil, ErrEmailNotFound } // 验证权限 if email.ReceiverID != playerID { return nil, ErrInsufficientPermission } // 标记为已读 if err := email.MarkAsRead(); err != nil { return nil, err } // 保存更改 if err := s.emailRepo.SaveEmail(ctx, email); err != nil { return nil, fmt.Errorf("保存邮件失败: %w", err) } return email, nil } // ClaimAttachment 领取附件 func (s *EmailService) ClaimAttachment(ctx context.Context, emailID, attachmentID, playerID string) error { // 获取邮件 email, err := s.emailRepo.GetEmailByID(ctx, emailID) if err != nil { return fmt.Errorf("获取邮件失败: %w", err) } if email == nil { return ErrEmailNotFound } // 验证权限 if email.ReceiverID != playerID { return ErrInsufficientPermission } // 查找附件 var targetAttachment *Attachment for _, attachment := range email.GetAttachments() { if attachment.ID == attachmentID { targetAttachment = attachment break } } if targetAttachment == nil { return ErrAttachmentNotFound } // 领取附件 if err := targetAttachment.Claim(); err != nil { return err } // 保存更改 if err := s.emailRepo.SaveEmail(ctx, email); err != nil { return fmt.Errorf("保存邮件失败: %w", err) } return nil } // DeleteEmail 删除邮件 func (s *EmailService) DeleteEmail(ctx context.Context, emailID, playerID string) error { // 获取邮件 email, err := s.emailRepo.GetEmailByID(ctx, emailID) if err != nil { return fmt.Errorf("获取邮件失败: %w", err) } if email == nil { return ErrEmailNotFound } // 验证权限 if email.ReceiverID != playerID { return ErrInsufficientPermission } // 删除邮件 if err := email.Delete(); err != nil { return err } // 保存更改 if err := s.emailRepo.SaveEmail(ctx, email); err != nil { return fmt.Errorf("保存邮件失败: %w", err) } return nil } // GetPlayerEmails 获取玩家邮件列表 func (s *EmailService) GetPlayerEmails(ctx context.Context, playerID string, limit int) ([]*Email, error) { return s.emailRepo.GetEmailsByReceiverID(ctx, playerID, limit) } // validateEmail 验证邮件 func (s *EmailService) validateEmail(subject, content string) error { if len(subject) == 0 { return ErrEmptySubject } if len(subject) > MaxSubjectLength { return ErrSubjectTooLong } if len(content) > MaxContentLength { return ErrContentTooLong } return nil } const ( MaxEmailsPerPlayer = 100 // 每个玩家最大邮件数 MaxSubjectLength = 100 // 最大主题长度 MaxContentLength = 1000 // 最大内容长度 ) ================================================ FILE: internal/domain/social/email/errors.go ================================================ package email import "errors" // 邮件相关错误定义 var ( // 邮件相关错误 ErrEmailNotFound = errors.New("邮件不存在") ErrEmailDeleted = errors.New("邮件已删除") ErrEmailAlreadyDeleted = errors.New("邮件已经被删除") ErrEmailExpired = errors.New("邮件已过期") ErrMailboxFull = errors.New("邮箱已满") ErrEmptySubject = errors.New("邮件主题不能为空") ErrSubjectTooLong = errors.New("邮件主题过长") ErrContentTooLong = errors.New("邮件内容过长") // 附件相关错误 ErrAttachmentNotFound = errors.New("附件不存在") ErrAttachmentAlreadyClaimed = errors.New("附件已被领取") ErrAttachmentExpired = errors.New("附件已过期") ErrTooManyAttachments = errors.New("附件数量过多") // 权限相关错误 ErrInsufficientPermission = errors.New("权限不足") ErrCannotSendToSelf = errors.New("不能给自己发送邮件") // 系统相关错误 ErrSystemError = errors.New("系统错误") ErrDatabaseError = errors.New("数据库错误") ) ================================================ FILE: internal/domain/social/email/events.go ================================================ package email import "time" // EmailEvent 邮件事件接口 type EmailEvent interface { GetEventType() string GetTimestamp() time.Time GetEmailID() string } // BaseEmailEvent 基础邮件事件 type BaseEmailEvent struct { EventType string Timestamp time.Time EmailID string } func (e BaseEmailEvent) GetEventType() string { return e.EventType } func (e BaseEmailEvent) GetTimestamp() time.Time { return e.Timestamp } func (e BaseEmailEvent) GetEmailID() string { return e.EmailID } // EmailSentEvent 邮件发送事件 type EmailSentEvent struct { BaseEmailEvent SenderID string ReceiverID string Subject string Type EmailType } // NewEmailSentEvent 创建邮件发送事件 func NewEmailSentEvent(emailID, senderID, receiverID, subject string, emailType EmailType) *EmailSentEvent { return &EmailSentEvent{ BaseEmailEvent: BaseEmailEvent{ EventType: "email.sent", Timestamp: time.Now(), EmailID: emailID, }, SenderID: senderID, ReceiverID: receiverID, Subject: subject, Type: emailType, } } // EmailReadEvent 邮件阅读事件 type EmailReadEvent struct { BaseEmailEvent ReceiverID string } // NewEmailReadEvent 创建邮件阅读事件 func NewEmailReadEvent(emailID, receiverID string) *EmailReadEvent { return &EmailReadEvent{ BaseEmailEvent: BaseEmailEvent{ EventType: "email.read", Timestamp: time.Now(), EmailID: emailID, }, ReceiverID: receiverID, } } // EmailDeletedEvent 邮件删除事件 type EmailDeletedEvent struct { BaseEmailEvent ReceiverID string } // NewEmailDeletedEvent 创建邮件删除事件 func NewEmailDeletedEvent(emailID, receiverID string) *EmailDeletedEvent { return &EmailDeletedEvent{ BaseEmailEvent: BaseEmailEvent{ EventType: "email.deleted", Timestamp: time.Now(), EmailID: emailID, }, ReceiverID: receiverID, } } // AttachmentClaimedEvent 附件领取事件 type AttachmentClaimedEvent struct { BaseEmailEvent AttachmentID string ReceiverID string Type AttachmentType ItemID string Quantity int64 } // NewAttachmentClaimedEvent 创建附件领取事件 func NewAttachmentClaimedEvent(emailID, attachmentID, receiverID, itemID string, attachmentType AttachmentType, quantity int64) *AttachmentClaimedEvent { return &AttachmentClaimedEvent{ BaseEmailEvent: BaseEmailEvent{ EventType: "email.attachment.claimed", Timestamp: time.Now(), EmailID: emailID, }, AttachmentID: attachmentID, ReceiverID: receiverID, Type: attachmentType, ItemID: itemID, Quantity: quantity, } } // SystemEmailSentEvent 系统邮件发送事件 type SystemEmailSentEvent struct { BaseEmailEvent ReceiverID string Subject string AttachmentCount int } // NewSystemEmailSentEvent 创建系统邮件发送事件 func NewSystemEmailSentEvent(emailID, receiverID, subject string, attachmentCount int) *SystemEmailSentEvent { return &SystemEmailSentEvent{ BaseEmailEvent: BaseEmailEvent{ EventType: "email.system.sent", Timestamp: time.Now(), EmailID: emailID, }, ReceiverID: receiverID, Subject: subject, AttachmentCount: attachmentCount, } } ================================================ FILE: internal/domain/social/email/repository.go ================================================ package email import "context" // EmailRepository 邮件仓储接口 type EmailRepository interface { // 邮件相关 SaveEmail(ctx context.Context, email *Email) error GetEmailByID(ctx context.Context, emailID string) (*Email, error) DeleteEmail(ctx context.Context, emailID string) error GetEmailsByReceiverID(ctx context.Context, receiverID string, limit int) ([]*Email, error) GetEmailsBySenderID(ctx context.Context, senderID string, limit int) ([]*Email, error) // 统计相关 GetUnreadEmailCount(ctx context.Context, playerID string) (int, error) GetTotalEmailCount(ctx context.Context, playerID string) (int, error) // 查询相关 GetEmailsByType(ctx context.Context, receiverID string, emailType EmailType, limit int) ([]*Email, error) GetEmailsByStatus(ctx context.Context, receiverID string, status EmailStatus, limit int) ([]*Email, error) GetExpiredEmails(ctx context.Context, limit int) ([]*Email, error) // 附件相关 GetEmailsWithUnclaimedAttachments(ctx context.Context, receiverID string, limit int) ([]*Email, error) GetAttachmentByID(ctx context.Context, attachmentID string) (*Attachment, error) } ================================================ FILE: internal/domain/social/family/errors.go ================================================ package family import "errors" // 家族相关错误定义 var ( // 家族相关错误 ErrFamilyNotFound = errors.New("家族不存在") ErrFamilyNameExists = errors.New("家族名称已存在") ErrFamilyFull = errors.New("家族成员已满") ErrFamilyNameTooShort = errors.New("家族名称太短") ErrFamilyNameTooLong = errors.New("家族名称太长") // 成员相关错误 ErrMemberNotFound = errors.New("成员不存在") ErrMemberAlreadyExists = errors.New("成员已存在") ErrPlayerAlreadyInFamily = errors.New("玩家已有家族") ErrCannotRemoveLeader = errors.New("不能移除族长") ErrLeaderCannotLeave = errors.New("族长不能离开家族") ErrCannotPromoteToLeader = errors.New("不能提升为族长") // 权限相关错误 ErrInsufficientPermission = errors.New("权限不足") ErrNotFamilyMember = errors.New("不是家族成员") // 系统相关错误 ErrSystemError = errors.New("系统错误") ErrDatabaseError = errors.New("数据库错误") ) ================================================ FILE: internal/domain/social/family/events.go ================================================ package family import "time" // FamilyEvent 家族事件接口 type FamilyEvent interface { GetEventType() string GetTimestamp() time.Time GetFamilyID() string } // BaseFamilyEvent 基础家族事件 type BaseFamilyEvent struct { EventType string Timestamp time.Time FamilyID string } func (e BaseFamilyEvent) GetEventType() string { return e.EventType } func (e BaseFamilyEvent) GetTimestamp() time.Time { return e.Timestamp } func (e BaseFamilyEvent) GetFamilyID() string { return e.FamilyID } // FamilyCreatedEvent 家族创建事件 type FamilyCreatedEvent struct { BaseFamilyEvent FamilyName string LeaderID string } // NewFamilyCreatedEvent 创建家族创建事件 func NewFamilyCreatedEvent(familyID, familyName, leaderID string) *FamilyCreatedEvent { return &FamilyCreatedEvent{ BaseFamilyEvent: BaseFamilyEvent{ EventType: "family.created", Timestamp: time.Now(), FamilyID: familyID, }, FamilyName: familyName, LeaderID: leaderID, } } // MemberJoinedFamilyEvent 成员加入家族事件 type MemberJoinedFamilyEvent struct { BaseFamilyEvent PlayerID string Nickname string } // NewMemberJoinedFamilyEvent 创建成员加入家族事件 func NewMemberJoinedFamilyEvent(familyID, playerID, nickname string) *MemberJoinedFamilyEvent { return &MemberJoinedFamilyEvent{ BaseFamilyEvent: BaseFamilyEvent{ EventType: "family.member.joined", Timestamp: time.Now(), FamilyID: familyID, }, PlayerID: playerID, Nickname: nickname, } } // MemberLeftFamilyEvent 成员离开家族事件 type MemberLeftFamilyEvent struct { BaseFamilyEvent PlayerID string } // NewMemberLeftFamilyEvent 创建成员离开家族事件 func NewMemberLeftFamilyEvent(familyID, playerID string) *MemberLeftFamilyEvent { return &MemberLeftFamilyEvent{ BaseFamilyEvent: BaseFamilyEvent{ EventType: "family.member.left", Timestamp: time.Now(), FamilyID: familyID, }, PlayerID: playerID, } } // LeadershipTransferredEvent 族长转让事件 type LeadershipTransferredEvent struct { BaseFamilyEvent OldLeaderID string NewLeaderID string } // NewLeadershipTransferredEvent 创建族长转让事件 func NewLeadershipTransferredEvent(familyID, oldLeaderID, newLeaderID string) *LeadershipTransferredEvent { return &LeadershipTransferredEvent{ BaseFamilyEvent: BaseFamilyEvent{ EventType: "family.leadership.transferred", Timestamp: time.Now(), FamilyID: familyID, }, OldLeaderID: oldLeaderID, NewLeaderID: newLeaderID, } } // FamilyLevelUpEvent 家族升级事件 type FamilyLevelUpEvent struct { BaseFamilyEvent OldLevel int NewLevel int } // NewFamilyLevelUpEvent 创建家族升级事件 func NewFamilyLevelUpEvent(familyID string, oldLevel, newLevel int) *FamilyLevelUpEvent { return &FamilyLevelUpEvent{ BaseFamilyEvent: BaseFamilyEvent{ EventType: "family.level.up", Timestamp: time.Now(), FamilyID: familyID, }, OldLevel: oldLevel, NewLevel: newLevel, } } ================================================ FILE: internal/domain/social/family/family.go ================================================ package family import ( "time" ) // Family 家族聚合根 type Family struct { ID string Name string Description string Level int Experience int64 LeaderID string members map[string]*FamilyMember MaxMembers int CreatedAt time.Time UpdatedAt time.Time Version int64 } // NewFamily 创建新家族 func NewFamily(id, name, description, leaderID string) *Family { return &Family{ ID: id, Name: name, Description: description, Level: 1, Experience: 0, LeaderID: leaderID, members: make(map[string]*FamilyMember), MaxMembers: DefaultMaxMembers, CreatedAt: time.Now(), UpdatedAt: time.Now(), Version: 1, } } // AddMember 添加成员 func (f *Family) AddMember(member *FamilyMember) error { if len(f.members) >= f.MaxMembers { return ErrFamilyFull } if _, exists := f.members[member.PlayerID]; exists { return ErrMemberAlreadyExists } f.members[member.PlayerID] = member f.UpdatedAt = time.Now() f.Version++ return nil } // RemoveMember 移除成员 func (f *Family) RemoveMember(playerID string) error { if playerID == f.LeaderID { return ErrCannotRemoveLeader } if _, exists := f.members[playerID]; !exists { return ErrMemberNotFound } delete(f.members, playerID) f.UpdatedAt = time.Now() f.Version++ return nil } // PromoteMember 提升成员职位 func (f *Family) PromoteMember(playerID string, newRole FamilyRole) error { member, exists := f.members[playerID] if !exists { return ErrMemberNotFound } if newRole == FamilyRoleLeader { return ErrCannotPromoteToLeader } member.Role = newRole f.UpdatedAt = time.Now() f.Version++ return nil } // TransferLeadership 转让族长 func (f *Family) TransferLeadership(newLeaderID string) error { newLeader, exists := f.members[newLeaderID] if !exists { return ErrMemberNotFound } // 将原族长降为副族长 if oldLeader, exists := f.members[f.LeaderID]; exists { oldLeader.Role = FamilyRoleViceLeader } // 设置新族长 f.LeaderID = newLeaderID newLeader.Role = FamilyRoleLeader f.UpdatedAt = time.Now() f.Version++ return nil } // AddExperience 增加经验 func (f *Family) AddExperience(exp int64) { f.Experience += exp f.checkLevelUp() f.UpdatedAt = time.Now() f.Version++ } // checkLevelUp 检查升级 func (f *Family) checkLevelUp() { requiredExp := f.getRequiredExperience(f.Level + 1) if f.Experience >= requiredExp { f.Level++ f.MaxMembers += MembersPerLevel f.checkLevelUp() // 递归检查是否可以继续升级 } } // getRequiredExperience 获取升级所需经验 func (f *Family) getRequiredExperience(level int) int64 { return int64(level * level * 1000) } // GetMembers 获取所有成员 func (f *Family) GetMembers() []*FamilyMember { members := make([]*FamilyMember, 0, len(f.members)) for _, member := range f.members { members = append(members, member) } return members } // GetMemberCount 获取成员数量 func (f *Family) GetMemberCount() int { return len(f.members) } // IsFull 检查是否已满 func (f *Family) IsFull() bool { return len(f.members) >= f.MaxMembers } const ( DefaultMaxMembers = 20 // 默认最大成员数 MembersPerLevel = 5 // 每级增加的成员数 ) ================================================ FILE: internal/domain/social/family/family_service.go ================================================ package family import ( "context" "fmt" ) // FamilyService 家族领域服务 type FamilyService struct { familyRepo FamilyRepository } // NewFamilyService 创建家族服务 func NewFamilyService(familyRepo FamilyRepository) *FamilyService { return &FamilyService{ familyRepo: familyRepo, } } // CreateFamily 创建家族 func (s *FamilyService) CreateFamily(ctx context.Context, name, description, leaderID string) (*Family, error) { // 验证家族名称 if err := s.validateFamilyName(name); err != nil { return nil, err } // 检查名称是否已存在 exists, err := s.familyRepo.FamilyExistsByName(ctx, name) if err != nil { return nil, fmt.Errorf("检查家族名称失败: %w", err) } if exists { return nil, ErrFamilyNameExists } // 检查玩家是否已有家族 hasFamily, err := s.familyRepo.PlayerHasFamily(ctx, leaderID) if err != nil { return nil, fmt.Errorf("检查玩家家族状态失败: %w", err) } if hasFamily { return nil, ErrPlayerAlreadyInFamily } // 创建家族 familyID := generateFamilyID() family := NewFamily(familyID, name, description, leaderID) // 添加族长为成员 leader := NewFamilyMember(leaderID, "") leader.Role = FamilyRoleLeader if err := family.AddMember(leader); err != nil { return nil, fmt.Errorf("添加族长失败: %w", err) } // 保存家族 if err := s.familyRepo.SaveFamily(ctx, family); err != nil { return nil, fmt.Errorf("保存家族失败: %w", err) } return family, nil } // JoinFamily 加入家族 func (s *FamilyService) JoinFamily(ctx context.Context, familyID, playerID, nickname string) error { // 获取家族 family, err := s.familyRepo.GetFamilyByID(ctx, familyID) if err != nil { return fmt.Errorf("获取家族失败: %w", err) } if family == nil { return ErrFamilyNotFound } // 检查家族是否已满 if family.IsFull() { return ErrFamilyFull } // 检查玩家是否已有家族 hasFamily, err := s.familyRepo.PlayerHasFamily(ctx, playerID) if err != nil { return fmt.Errorf("检查玩家家族状态失败: %w", err) } if hasFamily { return ErrPlayerAlreadyInFamily } // 创建新成员 member := NewFamilyMember(playerID, nickname) // 添加成员到家族 if err := family.AddMember(member); err != nil { return err } // 保存家族 if err := s.familyRepo.SaveFamily(ctx, family); err != nil { return fmt.Errorf("保存家族失败: %w", err) } return nil } // LeaveFamily 离开家族 func (s *FamilyService) LeaveFamily(ctx context.Context, familyID, playerID string) error { // 获取家族 family, err := s.familyRepo.GetFamilyByID(ctx, familyID) if err != nil { return fmt.Errorf("获取家族失败: %w", err) } if family == nil { return ErrFamilyNotFound } // 族长不能直接离开,需要先转让族长 if family.LeaderID == playerID { return ErrLeaderCannotLeave } // 移除成员 if err := family.RemoveMember(playerID); err != nil { return err } // 保存家族 if err := s.familyRepo.SaveFamily(ctx, family); err != nil { return fmt.Errorf("保存家族失败: %w", err) } return nil } // validateFamilyName 验证家族名称 func (s *FamilyService) validateFamilyName(name string) error { if len(name) < 2 { return ErrFamilyNameTooShort } if len(name) > 20 { return ErrFamilyNameTooLong } return nil } // generateFamilyID 生成家族ID func generateFamilyID() string { return "fam_" + randomString(16) } // randomString 生成随机字符串 func randomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[i%len(charset)] } return string(b) } ================================================ FILE: internal/domain/social/family/member.go ================================================ package family import "time" // FamilyMember 家族成员实体 type FamilyMember struct { PlayerID string Nickname string Role FamilyRole Contribution int64 JoinedAt time.Time LastActive time.Time Level int } // FamilyRole 家族职位 type FamilyRole int const ( FamilyRoleMember FamilyRole = iota // 普通成员 FamilyRoleElite // 精英 FamilyRoleViceLeader // 副族长 FamilyRoleLeader // 族长 ) // NewFamilyMember 创建新的家族成员 func NewFamilyMember(playerID, nickname string) *FamilyMember { return &FamilyMember{ PlayerID: playerID, Nickname: nickname, Role: FamilyRoleMember, Contribution: 0, JoinedAt: time.Now(), LastActive: time.Now(), Level: 1, } } // AddContribution 增加贡献度 func (m *FamilyMember) AddContribution(amount int64) { m.Contribution += amount } // UpdateLastActive 更新最后活跃时间 func (m *FamilyMember) UpdateLastActive() { m.LastActive = time.Now() } // GetMembershipDuration 获取加入时长 func (m *FamilyMember) GetMembershipDuration() time.Duration { return time.Since(m.JoinedAt) } // IsLeader 是否为族长 func (m *FamilyMember) IsLeader() bool { return m.Role == FamilyRoleLeader } // IsViceLeader 是否为副族长 func (m *FamilyMember) IsViceLeader() bool { return m.Role == FamilyRoleViceLeader } // HasManagementPermission 是否有管理权限 func (m *FamilyMember) HasManagementPermission() bool { return m.Role >= FamilyRoleViceLeader } // CanKickMembers 是否可以踢出成员 func (m *FamilyMember) CanKickMembers() bool { return m.Role >= FamilyRoleViceLeader } // CanPromoteMembers 是否可以提升成员 func (m *FamilyMember) CanPromoteMembers() bool { return m.Role == FamilyRoleLeader } ================================================ FILE: internal/domain/social/family/repository.go ================================================ package family import "context" // FamilyRepository 家族仓储接口 type FamilyRepository interface { // 家族相关 SaveFamily(ctx context.Context, family *Family) error GetFamilyByID(ctx context.Context, familyID string) (*Family, error) GetFamilyByName(ctx context.Context, name string) (*Family, error) FamilyExistsByName(ctx context.Context, name string) (bool, error) DeleteFamily(ctx context.Context, familyID string) error GetFamiliesByLevel(ctx context.Context, level int) ([]*Family, error) // 成员相关 GetFamilyByPlayerID(ctx context.Context, playerID string) (*Family, error) PlayerHasFamily(ctx context.Context, playerID string) (bool, error) GetFamilyMembers(ctx context.Context, familyID string) ([]*FamilyMember, error) UpdateMember(ctx context.Context, familyID string, member *FamilyMember) error // 查询相关 GetTopFamiliesByLevel(ctx context.Context, limit int) ([]*Family, error) GetTopFamiliesByMemberCount(ctx context.Context, limit int) ([]*Family, error) SearchFamiliesByName(ctx context.Context, keyword string, limit int) ([]*Family, error) } ================================================ FILE: internal/domain/social/friend/errors.go ================================================ package friend import "errors" // 好友相关错误定义 var ( // 好友关系相关错误 ErrFriendshipNotFound = errors.New("好友关系不存在") ErrFriendshipAlreadyExists = errors.New("好友关系已存在") ErrFriendshipDeleted = errors.New("好友关系已删除") ErrFriendshipAlreadyDeleted = errors.New("好友关系已经被删除") ErrInvalidFriendshipStatus = errors.New("无效的好友关系状态") ErrAlreadyFriends = errors.New("已经是好友关系") ErrNotBlocked = errors.New("未被屏蔽") // 好友请求相关错误 ErrRequestNotFound = errors.New("好友请求不存在") ErrRequestAlreadyExists = errors.New("好友请求已存在") ErrRequestExpired = errors.New("好友请求已过期") ErrInvalidRequestStatus = errors.New("无效的请求状态") // 权限相关错误 ErrInsufficientPermission = errors.New("权限不足") ErrCannotAddSelf = errors.New("不能添加自己为好友") // 限制相关错误 ErrTooManyFriends = errors.New("好友数量已达上限") ErrTooManyRequests = errors.New("请求数量已达上限") // 系统相关错误 ErrSystemError = errors.New("系统错误") ErrDatabaseError = errors.New("数据库错误") ErrNetworkError = errors.New("网络错误") ) ================================================ FILE: internal/domain/social/friend/events.go ================================================ package friend import "time" // FriendEvent 好友事件接口 type FriendEvent interface { GetEventType() string GetTimestamp() time.Time GetPlayerID() string } // BaseFriendEvent 基础好友事件 type BaseFriendEvent struct { EventType string Timestamp time.Time PlayerID string } func (e BaseFriendEvent) GetEventType() string { return e.EventType } func (e BaseFriendEvent) GetTimestamp() time.Time { return e.Timestamp } func (e BaseFriendEvent) GetPlayerID() string { return e.PlayerID } // FriendRequestSentEvent 好友请求发送事件 type FriendRequestSentEvent struct { BaseFriendEvent RequestID string ToPlayerID string Message string } // NewFriendRequestSentEvent 创建好友请求发送事件 func NewFriendRequestSentEvent(playerID, requestID, toPlayerID, message string) *FriendRequestSentEvent { return &FriendRequestSentEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.request.sent", Timestamp: time.Now(), PlayerID: playerID, }, RequestID: requestID, ToPlayerID: toPlayerID, Message: message, } } // FriendRequestAcceptedEvent 好友请求接受事件 type FriendRequestAcceptedEvent struct { BaseFriendEvent RequestID string FromPlayerID string FriendshipID string } // NewFriendRequestAcceptedEvent 创建好友请求接受事件 func NewFriendRequestAcceptedEvent(playerID, requestID, fromPlayerID, friendshipID string) *FriendRequestAcceptedEvent { return &FriendRequestAcceptedEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.request.accepted", Timestamp: time.Now(), PlayerID: playerID, }, RequestID: requestID, FromPlayerID: fromPlayerID, FriendshipID: friendshipID, } } // FriendRequestRejectedEvent 好友请求拒绝事件 type FriendRequestRejectedEvent struct { BaseFriendEvent RequestID string FromPlayerID string } // NewFriendRequestRejectedEvent 创建好友请求拒绝事件 func NewFriendRequestRejectedEvent(playerID, requestID, fromPlayerID string) *FriendRequestRejectedEvent { return &FriendRequestRejectedEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.request.rejected", Timestamp: time.Now(), PlayerID: playerID, }, RequestID: requestID, FromPlayerID: fromPlayerID, } } // FriendAddedEvent 好友添加事件 type FriendAddedEvent struct { BaseFriendEvent FriendID string FriendshipID string } // NewFriendAddedEvent 创建好友添加事件 func NewFriendAddedEvent(playerID, friendID, friendshipID string) *FriendAddedEvent { return &FriendAddedEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.added", Timestamp: time.Now(), PlayerID: playerID, }, FriendID: friendID, FriendshipID: friendshipID, } } // FriendRemovedEvent 好友删除事件 type FriendRemovedEvent struct { BaseFriendEvent FriendID string FriendshipID string } // NewFriendRemovedEvent 创建好友删除事件 func NewFriendRemovedEvent(playerID, friendID, friendshipID string) *FriendRemovedEvent { return &FriendRemovedEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.removed", Timestamp: time.Now(), PlayerID: playerID, }, FriendID: friendID, FriendshipID: friendshipID, } } // FriendBlockedEvent 好友屏蔽事件 type FriendBlockedEvent struct { BaseFriendEvent BlockedPlayerID string FriendshipID string } // NewFriendBlockedEvent 创建好友屏蔽事件 func NewFriendBlockedEvent(playerID, blockedPlayerID, friendshipID string) *FriendBlockedEvent { return &FriendBlockedEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.blocked", Timestamp: time.Now(), PlayerID: playerID, }, BlockedPlayerID: blockedPlayerID, FriendshipID: friendshipID, } } // FriendUnblockedEvent 好友解除屏蔽事件 type FriendUnblockedEvent struct { BaseFriendEvent UnblockedPlayerID string FriendshipID string } // NewFriendUnblockedEvent 创建好友解除屏蔽事件 func NewFriendUnblockedEvent(playerID, unblockedPlayerID, friendshipID string) *FriendUnblockedEvent { return &FriendUnblockedEvent{ BaseFriendEvent: BaseFriendEvent{ EventType: "friend.unblocked", Timestamp: time.Now(), PlayerID: playerID, }, UnblockedPlayerID: unblockedPlayerID, FriendshipID: friendshipID, } } ================================================ FILE: internal/domain/social/friend/friend_request.go ================================================ package friend import ( "time" ) // FriendRequest 好友请求实体 type FriendRequest struct { ID string FromPlayerID string ToPlayerID string Message string Status RequestStatus CreatedAt time.Time UpdatedAt time.Time ExpiresAt time.Time } // RequestStatus 请求状态 type RequestStatus int const ( RequestStatusPending RequestStatus = iota // 待处理 RequestStatusAccepted // 已接受 RequestStatusRejected // 已拒绝 RequestStatusExpired // 已过期 RequestStatusCanceled // 已取消 ) // NewFriendRequest 创建新的好友请求 func NewFriendRequest(fromPlayerID, toPlayerID, message string) *FriendRequest { return &FriendRequest{ ID: generateRequestID(), FromPlayerID: fromPlayerID, ToPlayerID: toPlayerID, Message: message, Status: RequestStatusPending, CreatedAt: time.Now(), UpdatedAt: time.Now(), ExpiresAt: time.Now().Add(DefaultRequestExpiration), } } // Accept 接受好友请求 func (r *FriendRequest) Accept() error { if r.Status != RequestStatusPending { return ErrInvalidRequestStatus } if r.IsExpired() { return ErrRequestExpired } r.Status = RequestStatusAccepted r.UpdatedAt = time.Now() return nil } // Reject 拒绝好友请求 func (r *FriendRequest) Reject() error { if r.Status != RequestStatusPending { return ErrInvalidRequestStatus } if r.IsExpired() { return ErrRequestExpired } r.Status = RequestStatusRejected r.UpdatedAt = time.Now() return nil } // Cancel 取消好友请求 func (r *FriendRequest) Cancel() error { if r.Status != RequestStatusPending { return ErrInvalidRequestStatus } r.Status = RequestStatusCanceled r.UpdatedAt = time.Now() return nil } // IsExpired 检查请求是否已过期 func (r *FriendRequest) IsExpired() bool { if r.Status == RequestStatusExpired { return true } if time.Now().After(r.ExpiresAt) { r.Status = RequestStatusExpired r.UpdatedAt = time.Now() return true } return false } // IsPending 检查请求是否待处理 func (r *FriendRequest) IsPending() bool { return r.Status == RequestStatusPending && !r.IsExpired() } // IsAccepted 检查请求是否已接受 func (r *FriendRequest) IsAccepted() bool { return r.Status == RequestStatusAccepted } // IsRejected 检查请求是否已拒绝 func (r *FriendRequest) IsRejected() bool { return r.Status == RequestStatusRejected } // IsCanceled 检查请求是否已取消 func (r *FriendRequest) IsCanceled() bool { return r.Status == RequestStatusCanceled } // GetAge 获取请求年龄 func (r *FriendRequest) GetAge() time.Duration { return time.Since(r.CreatedAt) } // GetTimeUntilExpiration 获取距离过期的时间 func (r *FriendRequest) GetTimeUntilExpiration() time.Duration { if r.IsExpired() { return 0 } return time.Until(r.ExpiresAt) } const ( DefaultRequestExpiration = 7 * 24 * time.Hour // 默认请求过期时间:7天 ) // generateRequestID 生成请求ID func generateRequestID() string { return "req_" + randomString(16) } ================================================ FILE: internal/domain/social/friend/friend_service.go ================================================ package friend import ( "context" "fmt" ) // FriendService 好友领域服务 type FriendService struct { friendRepo FriendRepository } // NewFriendService 创建好友服务 func NewFriendService(friendRepo FriendRepository) *FriendService { return &FriendService{ friendRepo: friendRepo, } } // SendFriendRequest 发送好友请求 func (s *FriendService) SendFriendRequest(ctx context.Context, fromPlayerID, toPlayerID, message string) (*FriendRequest, error) { // 验证不能给自己发送好友请求 if fromPlayerID == toPlayerID { return nil, ErrCannotAddSelf } // 检查是否已经是好友 friendship, err := s.friendRepo.GetFriendship(ctx, fromPlayerID, toPlayerID) if err != nil { return nil, fmt.Errorf("检查好友关系失败: %w", err) } if friendship != nil && friendship.IsActive() { return nil, ErrAlreadyFriends } // 检查是否已有待处理的请求 existingRequest, err := s.friendRepo.GetPendingRequest(ctx, fromPlayerID, toPlayerID) if err != nil { return nil, fmt.Errorf("检查待处理请求失败: %w", err) } if existingRequest != nil { return nil, ErrRequestAlreadyExists } // 检查好友数量限制 friendCount, err := s.friendRepo.GetFriendCount(ctx, fromPlayerID) if err != nil { return nil, fmt.Errorf("获取好友数量失败: %w", err) } if friendCount >= MaxFriendsPerPlayer { return nil, ErrTooManyFriends } // 创建好友请求 request := NewFriendRequest(fromPlayerID, toPlayerID, message) // 保存请求 if err := s.friendRepo.SaveFriendRequest(ctx, request); err != nil { return nil, fmt.Errorf("保存好友请求失败: %w", err) } return request, nil } // AcceptFriendRequest 接受好友请求 func (s *FriendService) AcceptFriendRequest(ctx context.Context, requestID, playerID string) (*Friendship, error) { // 获取请求 request, err := s.friendRepo.GetFriendRequestByID(ctx, requestID) if err != nil { return nil, fmt.Errorf("获取好友请求失败: %w", err) } if request == nil { return nil, ErrRequestNotFound } // 验证权限(只有接收者可以接受请求) if request.ToPlayerID != playerID { return nil, ErrInsufficientPermission } // 接受请求 if err := request.Accept(); err != nil { return nil, err } // 创建好友关系 friendship := NewFriendship(request.FromPlayerID, request.ToPlayerID, request.FromPlayerID) if err := friendship.Accept(); err != nil { return nil, fmt.Errorf("创建好友关系失败: %w", err) } // 保存好友关系 if err := s.friendRepo.SaveFriendship(ctx, friendship); err != nil { return nil, fmt.Errorf("保存好友关系失败: %w", err) } // 更新请求状态 if err := s.friendRepo.SaveFriendRequest(ctx, request); err != nil { return nil, fmt.Errorf("更新请求状态失败: %w", err) } return friendship, nil } // RejectFriendRequest 拒绝好友请求 func (s *FriendService) RejectFriendRequest(ctx context.Context, requestID, playerID string) error { // 获取请求 request, err := s.friendRepo.GetFriendRequestByID(ctx, requestID) if err != nil { return fmt.Errorf("获取好友请求失败: %w", err) } if request == nil { return ErrRequestNotFound } // 验证权限 if request.ToPlayerID != playerID { return ErrInsufficientPermission } // 拒绝请求 if err := request.Reject(); err != nil { return err } // 保存请求 if err := s.friendRepo.SaveFriendRequest(ctx, request); err != nil { return fmt.Errorf("保存请求失败: %w", err) } return nil } // RemoveFriend 删除好友 func (s *FriendService) RemoveFriend(ctx context.Context, playerID, friendID string) error { // 获取好友关系 friendship, err := s.friendRepo.GetFriendship(ctx, playerID, friendID) if err != nil { return fmt.Errorf("获取好友关系失败: %w", err) } if friendship == nil { return ErrFriendshipNotFound } // 删除好友关系 if err := friendship.Delete(); err != nil { return err } // 保存更改 if err := s.friendRepo.SaveFriendship(ctx, friendship); err != nil { return fmt.Errorf("保存好友关系失败: %w", err) } return nil } // BlockFriend 屏蔽好友 func (s *FriendService) BlockFriend(ctx context.Context, playerID, friendID string) error { // 获取好友关系 friendship, err := s.friendRepo.GetFriendship(ctx, playerID, friendID) if err != nil { return fmt.Errorf("获取好友关系失败: %w", err) } if friendship == nil { return ErrFriendshipNotFound } // 屏蔽好友 if err := friendship.Block(); err != nil { return err } // 保存更改 if err := s.friendRepo.SaveFriendship(ctx, friendship); err != nil { return fmt.Errorf("保存好友关系失败: %w", err) } return nil } // GetFriendList 获取好友列表 func (s *FriendService) GetFriendList(ctx context.Context, playerID string) ([]*Friendship, error) { return s.friendRepo.GetFriendsByPlayerID(ctx, playerID) } // GetPendingRequests 获取待处理的好友请求 func (s *FriendService) GetPendingRequests(ctx context.Context, playerID string) ([]*FriendRequest, error) { return s.friendRepo.GetPendingRequestsByPlayerID(ctx, playerID) } const ( MaxFriendsPerPlayer = 100 // 每个玩家最大好友数量 ) ================================================ FILE: internal/domain/social/friend/friendship.go ================================================ package friend import ( "time" ) // Friendship 好友关系聚合根 type Friendship struct { ID string PlayerID string FriendID string Status FriendshipStatus CreatedAt time.Time UpdatedAt time.Time RequestedBy string // 发起请求的玩家ID Notes string // 备注 Version int64 } // FriendshipStatus 好友关系状态 type FriendshipStatus int const ( FriendshipStatusPending FriendshipStatus = iota // 待确认 FriendshipStatusAccepted // 已接受 FriendshipStatusBlocked // 已屏蔽 FriendshipStatusDeleted // 已删除 ) // NewFriendship 创建新的好友关系 func NewFriendship(playerID, friendID, requestedBy string) *Friendship { return &Friendship{ ID: generateFriendshipID(), PlayerID: playerID, FriendID: friendID, Status: FriendshipStatusPending, CreatedAt: time.Now(), UpdatedAt: time.Now(), RequestedBy: requestedBy, Version: 1, } } // Accept 接受好友请求 func (f *Friendship) Accept() error { if f.Status != FriendshipStatusPending { return ErrInvalidFriendshipStatus } f.Status = FriendshipStatusAccepted f.UpdatedAt = time.Now() f.Version++ return nil } // Block 屏蔽好友 func (f *Friendship) Block() error { if f.Status == FriendshipStatusDeleted { return ErrFriendshipDeleted } f.Status = FriendshipStatusBlocked f.UpdatedAt = time.Now() f.Version++ return nil } // Unblock 解除屏蔽 func (f *Friendship) Unblock() error { if f.Status != FriendshipStatusBlocked { return ErrNotBlocked } f.Status = FriendshipStatusAccepted f.UpdatedAt = time.Now() f.Version++ return nil } // Delete 删除好友关系 func (f *Friendship) Delete() error { if f.Status == FriendshipStatusDeleted { return ErrFriendshipAlreadyDeleted } f.Status = FriendshipStatusDeleted f.UpdatedAt = time.Now() f.Version++ return nil } // SetNotes 设置备注 func (f *Friendship) SetNotes(notes string) { f.Notes = notes f.UpdatedAt = time.Now() f.Version++ } // IsActive 检查好友关系是否活跃 func (f *Friendship) IsActive() bool { return f.Status == FriendshipStatusAccepted } // IsPending 检查是否为待确认状态 func (f *Friendship) IsPending() bool { return f.Status == FriendshipStatusPending } // IsBlocked 检查是否被屏蔽 func (f *Friendship) IsBlocked() bool { return f.Status == FriendshipStatusBlocked } // IsDeleted 检查是否已删除 func (f *Friendship) IsDeleted() bool { return f.Status == FriendshipStatusDeleted } // GetOtherPlayerID 获取对方玩家ID func (f *Friendship) GetOtherPlayerID(currentPlayerID string) string { if f.PlayerID == currentPlayerID { return f.FriendID } return f.PlayerID } // GetDuration 获取好友关系持续时间 func (f *Friendship) GetDuration() time.Duration { return time.Since(f.CreatedAt) } // GetVersion 获取版本号 func (f *Friendship) GetVersion() int64 { return f.Version } // generateFriendshipID 生成好友关系ID func generateFriendshipID() string { return "fs_" + randomString(16) } // randomString 生成随机字符串 func randomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[time.Now().UnixNano()%int64(len(charset))] } return string(b) } ================================================ FILE: internal/domain/social/friend/repository.go ================================================ package friend import "context" // FriendRepository 好友仓储接口 type FriendRepository interface { // 好友关系相关 SaveFriendship(ctx context.Context, friendship *Friendship) error GetFriendship(ctx context.Context, playerID, friendID string) (*Friendship, error) GetFriendshipByID(ctx context.Context, friendshipID string) (*Friendship, error) GetFriendsByPlayerID(ctx context.Context, playerID string) ([]*Friendship, error) GetFriendCount(ctx context.Context, playerID string) (int, error) DeleteFriendship(ctx context.Context, friendshipID string) error // 好友请求相关 SaveFriendRequest(ctx context.Context, request *FriendRequest) error GetFriendRequestByID(ctx context.Context, requestID string) (*FriendRequest, error) GetPendingRequest(ctx context.Context, fromPlayerID, toPlayerID string) (*FriendRequest, error) GetPendingRequestsByPlayerID(ctx context.Context, playerID string) ([]*FriendRequest, error) GetSentRequestsByPlayerID(ctx context.Context, playerID string) ([]*FriendRequest, error) DeleteFriendRequest(ctx context.Context, requestID string) error // 查询相关 IsFriend(ctx context.Context, playerID, friendID string) (bool, error) IsBlocked(ctx context.Context, playerID, blockedPlayerID string) (bool, error) GetMutualFriends(ctx context.Context, playerID1, playerID2 string) ([]*Friendship, error) } ================================================ FILE: internal/domain/social/team/errors.go ================================================ package team import "errors" // 队伍相关错误定义 var ( // 队伍相关错误 ErrTeamNotFound = errors.New("队伍不存在") ErrTeamFull = errors.New("队伍已满") ErrTeamNameTooShort = errors.New("队伍名称太短") ErrTeamNameTooLong = errors.New("队伍名称太长") ErrInvalidPassword = errors.New("密码错误") // 成员相关错误 ErrMemberNotFound = errors.New("成员不存在") ErrMemberAlreadyExists = errors.New("成员已存在") ErrPlayerAlreadyInTeam = errors.New("玩家已有队伍") ErrCannotRemoveLeader = errors.New("不能移除队长") ErrLeaderMustTransfer = errors.New("队长必须先转让队长职位") // 权限相关错误 ErrInsufficientPermission = errors.New("权限不足") ErrNotTeamMember = errors.New("不是队伍成员") // 系统相关错误 ErrSystemError = errors.New("系统错误") ErrDatabaseError = errors.New("数据库错误") ) ================================================ FILE: internal/domain/social/team/events.go ================================================ package team import "time" // TeamEvent 队伍事件接口 type TeamEvent interface { GetEventType() string GetTimestamp() time.Time GetTeamID() string } // BaseTeamEvent 基础队伍事件 type BaseTeamEvent struct { EventType string Timestamp time.Time TeamID string } func (e BaseTeamEvent) GetEventType() string { return e.EventType } func (e BaseTeamEvent) GetTimestamp() time.Time { return e.Timestamp } func (e BaseTeamEvent) GetTeamID() string { return e.TeamID } // TeamCreatedEvent 队伍创建事件 type TeamCreatedEvent struct { BaseTeamEvent TeamName string LeaderID string } // NewTeamCreatedEvent 创建队伍创建事件 func NewTeamCreatedEvent(teamID, teamName, leaderID string) *TeamCreatedEvent { return &TeamCreatedEvent{ BaseTeamEvent: BaseTeamEvent{ EventType: "team.created", Timestamp: time.Now(), TeamID: teamID, }, TeamName: teamName, LeaderID: leaderID, } } // MemberJoinedTeamEvent 成员加入队伍事件 type MemberJoinedTeamEvent struct { BaseTeamEvent PlayerID string Nickname string } // NewMemberJoinedTeamEvent 创建成员加入队伍事件 func NewMemberJoinedTeamEvent(teamID, playerID, nickname string) *MemberJoinedTeamEvent { return &MemberJoinedTeamEvent{ BaseTeamEvent: BaseTeamEvent{ EventType: "team.member.joined", Timestamp: time.Now(), TeamID: teamID, }, PlayerID: playerID, Nickname: nickname, } } // MemberLeftTeamEvent 成员离开队伍事件 type MemberLeftTeamEvent struct { BaseTeamEvent PlayerID string } // NewMemberLeftTeamEvent 创建成员离开队伍事件 func NewMemberLeftTeamEvent(teamID, playerID string) *MemberLeftTeamEvent { return &MemberLeftTeamEvent{ BaseTeamEvent: BaseTeamEvent{ EventType: "team.member.left", Timestamp: time.Now(), TeamID: teamID, }, PlayerID: playerID, } } // TeamLeaderChangedEvent 队长变更事件 type TeamLeaderChangedEvent struct { BaseTeamEvent OldLeaderID string NewLeaderID string } // NewTeamLeaderChangedEvent 创建队长变更事件 func NewTeamLeaderChangedEvent(teamID, oldLeaderID, newLeaderID string) *TeamLeaderChangedEvent { return &TeamLeaderChangedEvent{ BaseTeamEvent: BaseTeamEvent{ EventType: "team.leader.changed", Timestamp: time.Now(), TeamID: teamID, }, OldLeaderID: oldLeaderID, NewLeaderID: newLeaderID, } } ================================================ FILE: internal/domain/social/team/repository.go ================================================ package team import "context" // TeamRepository 队伍仓储接口 type TeamRepository interface { // 队伍相关 SaveTeam(ctx context.Context, team *Team) error GetTeamByID(ctx context.Context, teamID string) (*Team, error) DeleteTeam(ctx context.Context, teamID string) error GetPublicTeams(ctx context.Context, limit int) ([]*Team, error) // 成员相关 GetTeamByPlayerID(ctx context.Context, playerID string) (*Team, error) PlayerHasTeam(ctx context.Context, playerID string) (bool, error) GetTeamMembers(ctx context.Context, teamID string) ([]*TeamMember, error) UpdateMember(ctx context.Context, teamID string, member *TeamMember) error // 查询相关 SearchTeamsByName(ctx context.Context, keyword string, limit int) ([]*Team, error) GetTeamsByLevelRange(ctx context.Context, minLevel, maxLevel int, limit int) ([]*Team, error) } ================================================ FILE: internal/domain/social/team/team.go ================================================ package team import ( "time" ) // Team 队伍聚合根 type Team struct { ID string Name string LeaderID string members map[string]*TeamMember MaxMembers int IsPublic bool Password string CreatedAt time.Time UpdatedAt time.Time Version int64 } // NewTeam 创建新队伍 func NewTeam(id, name, leaderID string, maxMembers int, isPublic bool) *Team { return &Team{ ID: id, Name: name, LeaderID: leaderID, members: make(map[string]*TeamMember), MaxMembers: maxMembers, IsPublic: isPublic, CreatedAt: time.Now(), UpdatedAt: time.Now(), Version: 1, } } // AddMember 添加成员 func (t *Team) AddMember(member *TeamMember) error { if len(t.members) >= t.MaxMembers { return ErrTeamFull } if _, exists := t.members[member.PlayerID]; exists { return ErrMemberAlreadyExists } t.members[member.PlayerID] = member t.UpdatedAt = time.Now() t.Version++ return nil } // RemoveMember 移除成员 func (t *Team) RemoveMember(playerID string) error { if playerID == t.LeaderID { return ErrCannotRemoveLeader } if _, exists := t.members[playerID]; !exists { return ErrMemberNotFound } delete(t.members, playerID) t.UpdatedAt = time.Now() t.Version++ return nil } // TransferLeadership 转让队长 func (t *Team) TransferLeadership(newLeaderID string) error { newLeader, exists := t.members[newLeaderID] if !exists { return ErrMemberNotFound } // 将原队长降为普通成员 if oldLeader, exists := t.members[t.LeaderID]; exists { oldLeader.Role = TeamRoleMember } // 设置新队长 t.LeaderID = newLeaderID newLeader.Role = TeamRoleLeader t.UpdatedAt = time.Now() t.Version++ return nil } // SetPassword 设置密码 func (t *Team) SetPassword(password string) { t.Password = password t.IsPublic = password == "" t.UpdatedAt = time.Now() t.Version++ } // GetMembers 获取所有成员 func (t *Team) GetMembers() []*TeamMember { members := make([]*TeamMember, 0, len(t.members)) for _, member := range t.members { members = append(members, member) } return members } // GetMemberCount 获取成员数量 func (t *Team) GetMemberCount() int { return len(t.members) } // IsFull 检查是否已满 func (t *Team) IsFull() bool { return len(t.members) >= t.MaxMembers } // IsMember 检查是否为成员 func (t *Team) IsMember(playerID string) bool { _, exists := t.members[playerID] return exists } ================================================ FILE: internal/domain/social/team/team_member.go ================================================ package team import "time" // TeamMember 队伍成员实体 type TeamMember struct { PlayerID string Nickname string Role TeamRole JoinedAt time.Time LastActive time.Time Level int IsReady bool } // TeamRole 队伍角色 type TeamRole int const ( TeamRoleMember TeamRole = iota // 普通成员 TeamRoleLeader // 队长 ) // NewTeamMember 创建新的队伍成员 func NewTeamMember(playerID, nickname string, level int) *TeamMember { return &TeamMember{ PlayerID: playerID, Nickname: nickname, Role: TeamRoleMember, JoinedAt: time.Now(), LastActive: time.Now(), Level: level, IsReady: false, } } // SetReady 设置准备状态 func (m *TeamMember) SetReady(ready bool) { m.IsReady = ready } // UpdateLastActive 更新最后活跃时间 func (m *TeamMember) UpdateLastActive() { m.LastActive = time.Now() } // IsLeader 是否为队长 func (m *TeamMember) IsLeader() bool { return m.Role == TeamRoleLeader } // GetMembershipDuration 获取加入时长 func (m *TeamMember) GetMembershipDuration() time.Duration { return time.Since(m.JoinedAt) } ================================================ FILE: internal/domain/social/team/team_service.go ================================================ package team import ( "context" "fmt" ) // TeamService 队伍领域服务 type TeamService struct { teamRepo TeamRepository } // NewTeamService 创建队伍服务 func NewTeamService(teamRepo TeamRepository) *TeamService { return &TeamService{ teamRepo: teamRepo, } } // CreateTeam 创建队伍 func (s *TeamService) CreateTeam(ctx context.Context, name, leaderID string, maxMembers int, isPublic bool, password string) (*Team, error) { // 验证队伍名称 if err := s.validateTeamName(name); err != nil { return nil, err } // 检查玩家是否已有队伍 hasTeam, err := s.teamRepo.PlayerHasTeam(ctx, leaderID) if err != nil { return nil, fmt.Errorf("检查玩家队伍状态失败: %w", err) } if hasTeam { return nil, ErrPlayerAlreadyInTeam } // 创建队伍 teamID := generateTeamID() team := NewTeam(teamID, name, leaderID, maxMembers, isPublic) if password != "" { team.SetPassword(password) } // 添加队长为成员 leader := NewTeamMember(leaderID, "", 1) leader.Role = TeamRoleLeader if err := team.AddMember(leader); err != nil { return nil, fmt.Errorf("添加队长失败: %w", err) } // 保存队伍 if err := s.teamRepo.SaveTeam(ctx, team); err != nil { return nil, fmt.Errorf("保存队伍失败: %w", err) } return team, nil } // JoinTeam 加入队伍 func (s *TeamService) JoinTeam(ctx context.Context, teamID, playerID, nickname string, level int, password string) error { // 获取队伍 team, err := s.teamRepo.GetTeamByID(ctx, teamID) if err != nil { return fmt.Errorf("获取队伍失败: %w", err) } if team == nil { return ErrTeamNotFound } // 检查密码 if !team.IsPublic && team.Password != password { return ErrInvalidPassword } // 检查队伍是否已满 if team.IsFull() { return ErrTeamFull } // 检查玩家是否已有队伍 hasTeam, err := s.teamRepo.PlayerHasTeam(ctx, playerID) if err != nil { return fmt.Errorf("检查玩家队伍状态失败: %w", err) } if hasTeam { return ErrPlayerAlreadyInTeam } // 创建新成员 member := NewTeamMember(playerID, nickname, level) // 添加成员到队伍 if err := team.AddMember(member); err != nil { return err } // 保存队伍 if err := s.teamRepo.SaveTeam(ctx, team); err != nil { return fmt.Errorf("保存队伍失败: %w", err) } return nil } // LeaveTeam 离开队伍 func (s *TeamService) LeaveTeam(ctx context.Context, teamID, playerID string) error { // 获取队伍 team, err := s.teamRepo.GetTeamByID(ctx, teamID) if err != nil { return fmt.Errorf("获取队伍失败: %w", err) } if team == nil { return ErrTeamNotFound } // 如果是队长离开且队伍还有其他成员,需要先转让队长 if team.LeaderID == playerID && team.GetMemberCount() > 1 { return ErrLeaderMustTransfer } // 移除成员 if err := team.RemoveMember(playerID); err != nil { return err } // 如果队伍为空,删除队伍 if team.GetMemberCount() == 0 { if err := s.teamRepo.DeleteTeam(ctx, teamID); err != nil { return fmt.Errorf("删除队伍失败: %w", err) } } else { // 保存队伍 if err := s.teamRepo.SaveTeam(ctx, team); err != nil { return fmt.Errorf("保存队伍失败: %w", err) } } return nil } // validateTeamName 验证队伍名称 func (s *TeamService) validateTeamName(name string) error { if len(name) < 2 { return ErrTeamNameTooShort } if len(name) > 20 { return ErrTeamNameTooLong } return nil } // generateTeamID 生成队伍ID func generateTeamID() string { return "team_" + randomString(16) } // randomString 生成随机字符串 func randomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[i%len(charset)] } return string(b) } ================================================ FILE: internal/errors/domain_errors.go ================================================ // Package errors 提供统一的错误处理机制 package errors import ( "fmt" "net/http" ) // ErrorCode 错误代码类型 type ErrorCode string const ( // 通用错误 ErrCodeInternal ErrorCode = "INTERNAL_ERROR" ErrCodeInvalidInput ErrorCode = "INVALID_INPUT" ErrCodeNotFound ErrorCode = "NOT_FOUND" ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED" ErrCodeForbidden ErrorCode = "FORBIDDEN" ErrCodeConflict ErrorCode = "CONFLICT" ErrCodeTimeout ErrorCode = "TIMEOUT" // 领域特定错误 ErrCodePlayerNotFound ErrorCode = "PLAYER_NOT_FOUND" ErrCodePlayerOffline ErrorCode = "PLAYER_OFFLINE" ErrCodePlayerAlreadyExists ErrorCode = "PLAYER_ALREADY_EXISTS" ErrCodeInvalidPlayerName ErrorCode = "INVALID_PLAYER_NAME" ErrCodeInvalidPosition ErrorCode = "INVALID_POSITION" ErrCodeVersionMismatch ErrorCode = "VERSION_MISMATCH" ErrCodeBattleNotFound ErrorCode = "BATTLE_NOT_FOUND" ErrCodeBattleAlreadyStarted ErrorCode = "BATTLE_ALREADY_STARTED" ErrCodeBattleNotInProgress ErrorCode = "BATTLE_NOT_IN_PROGRESS" ErrCodePlayerNotInBattle ErrorCode = "PLAYER_NOT_IN_BATTLE" ErrCodePlayerAlreadyInBattle ErrorCode = "PLAYER_ALREADY_IN_BATTLE" ErrCodeInsufficientParticipants ErrorCode = "INSUFFICIENT_PARTICIPANTS" ErrCodeInsufficientMana ErrorCode = "INSUFFICIENT_MANA" ErrCodeInvalidTarget ErrorCode = "INVALID_TARGET" ErrCodeBattleFinished ErrorCode = "BATTLE_FINISHED" ) // DomainError 领域错误结构 type DomainError struct { Code ErrorCode `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` HTTPStatus int `json:"-"` Cause error `json:"-"` } // Error 实现 error 接口 func (e *DomainError) Error() string { if e.Details != "" { return fmt.Sprintf("[%s] %s: %s", e.Code, e.Message, e.Details) } return fmt.Sprintf("[%s] %s", e.Code, e.Message) } // Unwrap 支持错误链 func (e *DomainError) Unwrap() error { return e.Cause } // NewDomainError 创建领域错误 func NewDomainError(code ErrorCode, message string) *DomainError { return &DomainError{ Code: code, Message: message, HTTPStatus: getHTTPStatus(code), } } // NewDomainErrorWithDetails 创建带详情的领域错误 func NewDomainErrorWithDetails(code ErrorCode, message, details string) *DomainError { return &DomainError{ Code: code, Message: message, Details: details, HTTPStatus: getHTTPStatus(code), } } // NewDomainErrorWithCause 创建带原因的领域错误 func NewDomainErrorWithCause(code ErrorCode, message string, cause error) *DomainError { return &DomainError{ Code: code, Message: message, HTTPStatus: getHTTPStatus(code), Cause: cause, } } // getHTTPStatus 根据错误代码获取HTTP状态码 func getHTTPStatus(code ErrorCode) int { switch code { case ErrCodeNotFound, ErrCodePlayerNotFound, ErrCodeBattleNotFound: return http.StatusNotFound case ErrCodeUnauthorized: return http.StatusUnauthorized case ErrCodeForbidden: return http.StatusForbidden case ErrCodeConflict, ErrCodePlayerAlreadyExists, ErrCodePlayerAlreadyInBattle: return http.StatusConflict case ErrCodeInvalidInput, ErrCodeInvalidPlayerName, ErrCodeInvalidPosition, ErrCodeInvalidTarget: return http.StatusBadRequest case ErrCodeTimeout: return http.StatusRequestTimeout default: return http.StatusInternalServerError } } // 预定义的领域错误 var ( // 玩家相关错误 ErrPlayerNotFound = NewDomainError(ErrCodePlayerNotFound, "玩家不存在") ErrPlayerOffline = NewDomainError(ErrCodePlayerOffline, "玩家已离线") ErrPlayerAlreadyExists = NewDomainError(ErrCodePlayerAlreadyExists, "玩家已存在") ErrInvalidPlayerName = NewDomainError(ErrCodeInvalidPlayerName, "无效的玩家名称") ErrInvalidPosition = NewDomainError(ErrCodeInvalidPosition, "无效的位置") ErrVersionMismatch = NewDomainError(ErrCodeVersionMismatch, "版本不匹配") // 战斗相关错误 ErrBattleNotFound = NewDomainError(ErrCodeBattleNotFound, "战斗不存在") ErrBattleAlreadyStarted = NewDomainError(ErrCodeBattleAlreadyStarted, "战斗已开始") ErrBattleNotInProgress = NewDomainError(ErrCodeBattleNotInProgress, "战斗未进行中") ErrPlayerNotInBattle = NewDomainError(ErrCodePlayerNotInBattle, "玩家不在战斗中") ErrPlayerAlreadyInBattle = NewDomainError(ErrCodePlayerAlreadyInBattle, "玩家已在战斗中") ErrInsufficientParticipants = NewDomainError(ErrCodeInsufficientParticipants, "参与者不足") ErrInsufficientMana = NewDomainError(ErrCodeInsufficientMana, "魔法值不足") ErrInvalidTarget = NewDomainError(ErrCodeInvalidTarget, "无效的目标") ErrBattleFinished = NewDomainError(ErrCodeBattleFinished, "战斗已结束") // 通用错误 ErrInternal = NewDomainError(ErrCodeInternal, "内部错误") ErrInvalidInput = NewDomainError(ErrCodeInvalidInput, "无效输入") ErrNotFound = NewDomainError(ErrCodeNotFound, "资源不存在") ErrUnauthorized = NewDomainError(ErrCodeUnauthorized, "未授权") ErrForbidden = NewDomainError(ErrCodeForbidden, "禁止访问") ErrConflict = NewDomainError(ErrCodeConflict, "冲突") ErrTimeout = NewDomainError(ErrCodeTimeout, "超时") ) // IsDomainError 检查是否为领域错误 func IsDomainError(err error) bool { _, ok := err.(*DomainError) return ok } // GetDomainError 获取领域错误 func GetDomainError(err error) (*DomainError, bool) { domainErr, ok := err.(*DomainError) return domainErr, ok } // WrapError 包装错误为领域错误 func WrapError(err error, code ErrorCode, message string) *DomainError { return NewDomainErrorWithCause(code, message, err) } ================================================ FILE: internal/events/eventbus.go ================================================ // Package events 事件系统和消息队列 // Author: MMO Server Team // Created: 2024 package events import ( "context" "encoding/json" "fmt" "reflect" "sync" "time" "greatestworks/internal/infrastructure/logging" "github.com/nats-io/nats.go" // "github.com/phuhao00/netcore-go/core" // 暂时注释掉缺失的包 ) // Logger 简单的日志接口 type Logger interface { Info(msg string, args ...interface{}) Error(msg string, args ...interface{}) Debug(msg string, args ...interface{}) } // Event 事件接口 type Event interface { GetID() string GetEventType() string GetAggregateID() string GetVersion() int64 GetOccurredAt() time.Time GetData() interface{} GetPlayerID() string } // Handler 事件处理器 type Handler func(ctx context.Context, event Event) error // EventBus 事件总线 type EventBus struct { handlers map[string][]Handler mutex sync.RWMutex logger Logger nats *nats.Conn } // BaseEvent 基础事件结构 type BaseEvent struct { ID string `json:"id"` Type string `json:"type"` AggregateID string `json:"aggregate_id"` Version int64 `json:"version"` Data interface{} `json:"data"` Timestamp time.Time `json:"timestamp"` UserID string `json:"user_id,omitempty"` SessionID string `json:"session_id,omitempty"` } // GetEventType 获取事件类型 func (e *BaseEvent) GetEventType() string { return e.Type } // GetAggregateID 获取聚合根ID func (e *BaseEvent) GetAggregateID() string { return e.AggregateID } // GetVersion 获取版本号 func (e *BaseEvent) GetVersion() int64 { return e.Version } // GetOccurredAt 获取发生时间 func (e *BaseEvent) GetOccurredAt() time.Time { return e.Timestamp } // GetData 获取事件数据 func (e *BaseEvent) GetData() interface{} { return e.Data } // GetPlayerID 获取玩家ID func (e *BaseEvent) GetPlayerID() string { return e.UserID } // GetID 获取事件ID func (e *BaseEvent) GetID() string { return e.ID } // NewEventBus 创建事件总线 func NewEventBus(logger Logger) *EventBus { return &EventBus{ handlers: make(map[string][]Handler), logger: logger, } } // ConnectNATS 连接到NATS func (eb *EventBus) ConnectNATS(url string) error { nc, err := nats.Connect(url) if err != nil { return fmt.Errorf("failed to connect to NATS: %w", err) } eb.nats = nc eb.logger.Info("Connected to NATS", logging.Fields{ "url": url, }) return nil } // Subscribe 订阅事件 func (eb *EventBus) Subscribe(eventType string, handler Handler) { eb.mutex.Lock() defer eb.mutex.Unlock() eb.handlers[eventType] = append(eb.handlers[eventType], handler) eb.logger.Debug("Event handler subscribed", logging.Fields{ "type": eventType, }) } // Unsubscribe 取消订阅 func (eb *EventBus) Unsubscribe(eventType string, handler Handler) { eb.mutex.Lock() defer eb.mutex.Unlock() handlers := eb.handlers[eventType] for i, h := range handlers { if reflect.ValueOf(h).Pointer() == reflect.ValueOf(handler).Pointer() { eb.handlers[eventType] = append(handlers[:i], handlers[i+1:]...) break } } eb.logger.Debug("Event handler unsubscribed", logging.Fields{ "type": eventType, }) } // Publish 发布事件 func (eb *EventBus) Publish(ctx context.Context, event Event) error { // 本地处理 if err := eb.publishLocal(ctx, event); err != nil { eb.logger.Error("Local event publish failed", err, logging.Fields{}) } // 远程发布(通过NATS) if eb.nats != nil { if err := eb.publishRemote(event); err != nil { eb.logger.Error("Remote event publish failed", err, logging.Fields{}) return err } } return nil } // publishLocal 本地发布事件 func (eb *EventBus) publishLocal(ctx context.Context, event Event) error { eb.mutex.RLock() handlers := eb.handlers[event.GetEventType()] eb.mutex.RUnlock() for _, handler := range handlers { go func(h Handler) { if err := h(ctx, event); err != nil { eb.logger.Error("Event handler error", err, logging.Fields{ "type": event.GetEventType(), }) } }(handler) } eb.logger.Debug("Event published locally", logging.Fields{ "type": event.GetEventType(), }) return nil } // publishRemote 远程发布事件 func (eb *EventBus) publishRemote(event Event) error { data, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal event: %w", err) } subject := fmt.Sprintf("events.%s", event.GetEventType()) if err := eb.nats.Publish(subject, data); err != nil { return fmt.Errorf("failed to publish event to NATS: %w", err) } eb.logger.Debug("Event published remotely", logging.Fields{ "type": event.GetEventType(), "subject": subject, }) return nil } // SubscribeRemote 订阅远程事件 func (eb *EventBus) SubscribeRemote(eventType string, handler Handler) error { if eb.nats == nil { return fmt.Errorf("NATS connection not established") } subject := fmt.Sprintf("events.%s", eventType) _, err := eb.nats.Subscribe(subject, func(msg *nats.Msg) { var event BaseEvent if err := json.Unmarshal(msg.Data, &event); err != nil { eb.logger.Error("Failed to unmarshal remote event", err, logging.Fields{}) return } ctx := context.Background() if err := handler(ctx, &event); err != nil { eb.logger.Error("Remote event handler error", err, logging.Fields{ "type": eventType, }) } }) if err != nil { return fmt.Errorf("failed to subscribe to remote events: %w", err) } eb.logger.Info("Subscribed to remote events", logging.Fields{ "type": eventType, "subject": subject, }) return nil } // Close 关闭事件总线 func (eb *EventBus) Close() { if eb.nats != nil { eb.nats.Close() eb.logger.Info("NATS connection closed", logging.Fields{}) } } // GameEvents 游戏事件类型常量 const ( EventPlayerLogin = "player.login" EventPlayerLogout = "player.logout" EventPlayerMove = "player.move" EventPlayerChat = "player.chat" EventBattleStart = "battle.start" EventBattleEnd = "battle.end" EventSceneEnter = "scene.enter" EventSceneLeave = "scene.leave" EventActivityJoin = "activity.join" EventActivityLeave = "activity.leave" ) // PlayerLoginEvent 玩家登录事件 type PlayerLoginEvent struct { *BaseEvent PlayerID string `json:"player_id"` IP string `json:"ip"` } // NewPlayerLoginEvent 创建玩家登录事件 func NewPlayerLoginEvent(playerID, ip string) *PlayerLoginEvent { return &PlayerLoginEvent{ BaseEvent: &BaseEvent{ Type: EventPlayerLogin, Timestamp: time.Now(), UserID: playerID, }, PlayerID: playerID, IP: ip, } } // PlayerMoveEvent 玩家移动事件 type PlayerMoveEvent struct { *BaseEvent PlayerID string `json:"player_id"` SceneID string `json:"scene_id"` X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // NewPlayerMoveEvent 创建玩家移动事件 func NewPlayerMoveEvent(playerID, sceneID string, x, y, z float64) *PlayerMoveEvent { return &PlayerMoveEvent{ BaseEvent: &BaseEvent{ Type: EventPlayerMove, Timestamp: time.Now(), UserID: playerID, }, PlayerID: playerID, SceneID: sceneID, X: x, Y: y, Z: z, } } // ChatEvent 聊天事件 type ChatEvent struct { *BaseEvent PlayerID string `json:"player_id"` Channel string `json:"channel"` Message string `json:"message"` } // NewChatEvent 创建聊天事件 func NewChatEvent(playerID, channel, message string) *ChatEvent { return &ChatEvent{ BaseEvent: &BaseEvent{ Type: EventPlayerChat, Timestamp: time.Now(), UserID: playerID, }, PlayerID: playerID, Channel: channel, Message: message, } } // BattleStartEvent 战斗开始事件 type BattleStartEvent struct { *BaseEvent BattleID string `json:"battle_id"` Players []string `json:"players"` } // NewBattleStartEvent 创建战斗开始事件 func NewBattleStartEvent(battleID string, players []string) *BattleStartEvent { return &BattleStartEvent{ BaseEvent: &BaseEvent{ Type: EventBattleStart, Timestamp: time.Now(), }, BattleID: battleID, Players: players, } } // EventManager 事件管理器 type EventManager struct { eventBus *EventBus logger Logger } // NewEventManager 创建事件管理器 func NewEventManager(logger Logger) *EventManager { return &EventManager{ eventBus: NewEventBus(logger), logger: logger, } } // Initialize 初始化事件管理器 func (em *EventManager) Initialize(natsURL string) error { if err := em.eventBus.ConnectNATS(natsURL); err != nil { return fmt.Errorf("failed to connect to NATS: %w", err) } // 注册默认事件处理器 em.registerDefaultHandlers() em.logger.Info("Event manager initialized") return nil } // registerDefaultHandlers 注册默认事件处理器 func (em *EventManager) registerDefaultHandlers() { // 玩家登录事件处理 em.eventBus.Subscribe(EventPlayerLogin, func(ctx context.Context, event Event) error { em.logger.Info("Player login event", "event", event.GetData()) return nil }) // 玩家移动事件处理 em.eventBus.Subscribe(EventPlayerMove, func(ctx context.Context, event Event) error { em.logger.Debug("Player move event", "event", event.GetData()) return nil }) // 聊天事件处理 em.eventBus.Subscribe(EventPlayerChat, func(ctx context.Context, event Event) error { em.logger.Info("Chat event", "event", event.GetData()) return nil }) } // GetEventBus 获取事件总线 func (em *EventManager) GetEventBus() *EventBus { return em.eventBus } // Close 关闭事件管理器 func (em *EventManager) Close() { em.eventBus.Close() em.logger.Info("Event manager closed") } ================================================ FILE: internal/events/metrics.go ================================================ package events import ( "sync" "time" ) // EventType 事件类型 type EventType string const ( EventTypePlayerLogin EventType = "player_login" EventTypePlayerLogout EventType = "player_logout" EventTypePlayerMove EventType = "player_move" EventTypePlayerAction EventType = "player_action" EventTypePlayerChat EventType = "player_chat" EventTypePlayerMail EventType = "player_mail" EventTypeGameBattle EventType = "game_battle" EventTypeGameShop EventType = "game_shop" EventTypeGameBag EventType = "game_bag" EventTypeGamePet EventType = "game_pet" EventTypeGameBuilding EventType = "game_building" EventTypeSystemError EventType = "system_error" EventTypeSystemWarning EventType = "system_warning" EventTypeSystemInfo EventType = "system_info" EventTypeSystemStart EventType = "system_start" EventTypeSystemStop EventType = "system_stop" EventTypeSystemHealth EventType = "system_health" ) // EventMetrics 事件指标 type EventMetrics struct { eventCounts map[EventType]uint64 successCounts map[EventType]uint64 errorCounts map[EventType]uint64 droppedCounts map[EventType]uint64 processingTimes map[EventType][]time.Duration mu sync.RWMutex } // NewEventMetrics 创建事件指标 func NewEventMetrics() *EventMetrics { return &EventMetrics{ eventCounts: make(map[EventType]uint64), successCounts: make(map[EventType]uint64), errorCounts: make(map[EventType]uint64), droppedCounts: make(map[EventType]uint64), processingTimes: make(map[EventType][]time.Duration), } } // IncrementEventCount 增加事件计数 func (em *EventMetrics) IncrementEventCount(eventType EventType) { em.mu.Lock() defer em.mu.Unlock() em.eventCounts[eventType]++ } // IncrementSuccessCount 增加成功计数 func (em *EventMetrics) IncrementSuccessCount(eventType EventType) { em.mu.Lock() defer em.mu.Unlock() em.successCounts[eventType]++ } // IncrementErrorCount 增加错误计数 func (em *EventMetrics) IncrementErrorCount(eventType EventType) { em.mu.Lock() defer em.mu.Unlock() em.errorCounts[eventType]++ } // IncrementDroppedCount 增加丢弃计数 func (em *EventMetrics) IncrementDroppedCount(eventType EventType) { em.mu.Lock() defer em.mu.Unlock() em.droppedCounts[eventType]++ } // RecordProcessingTime 记录处理时间 func (em *EventMetrics) RecordProcessingTime(eventType EventType, duration time.Duration) { em.mu.Lock() defer em.mu.Unlock() if _, exists := em.processingTimes[eventType]; !exists { em.processingTimes[eventType] = make([]time.Duration, 0) } // 保留最近100个处理时间记录 if len(em.processingTimes[eventType]) >= 100 { em.processingTimes[eventType] = em.processingTimes[eventType][1:] } em.processingTimes[eventType] = append(em.processingTimes[eventType], duration) } // GetEventCount 获取事件计数 func (em *EventMetrics) GetEventCount(eventType EventType) uint64 { em.mu.RLock() defer em.mu.RUnlock() return em.eventCounts[eventType] } // GetSuccessCount 获取成功计数 func (em *EventMetrics) GetSuccessCount(eventType EventType) uint64 { em.mu.RLock() defer em.mu.RUnlock() return em.successCounts[eventType] } // GetErrorCount 获取错误计数 func (em *EventMetrics) GetErrorCount(eventType EventType) uint64 { em.mu.RLock() defer em.mu.RUnlock() return em.errorCounts[eventType] } // GetDroppedCount 获取丢弃计数 func (em *EventMetrics) GetDroppedCount(eventType EventType) uint64 { em.mu.RLock() defer em.mu.RUnlock() return em.droppedCounts[eventType] } // GetAverageProcessingTime 获取平均处理时间 func (em *EventMetrics) GetAverageProcessingTime(eventType EventType) time.Duration { em.mu.RLock() defer em.mu.RUnlock() times, exists := em.processingTimes[eventType] if !exists || len(times) == 0 { return 0 } var total time.Duration for _, t := range times { total += t } return total / time.Duration(len(times)) } // GetSuccessRate 获取成功率 func (em *EventMetrics) GetSuccessRate(eventType EventType) float64 { em.mu.RLock() defer em.mu.RUnlock() total := em.eventCounts[eventType] if total == 0 { return 0 } success := em.successCounts[eventType] return float64(success) / float64(total) } // GetAllMetrics 获取所有指标 func (em *EventMetrics) GetAllMetrics() map[string]interface{} { em.mu.RLock() defer em.mu.RUnlock() metrics := map[string]interface{}{ "event_counts": make(map[string]uint64), "success_counts": make(map[string]uint64), "error_counts": make(map[string]uint64), "dropped_counts": make(map[string]uint64), "success_rates": make(map[string]float64), "avg_times": make(map[string]string), } // 收集所有事件类型 allEventTypes := make(map[EventType]bool) for eventType := range em.eventCounts { allEventTypes[eventType] = true } for eventType := range em.successCounts { allEventTypes[eventType] = true } for eventType := range em.errorCounts { allEventTypes[eventType] = true } for eventType := range em.droppedCounts { allEventTypes[eventType] = true } // 为每个事件类型生成指标 for eventType := range allEventTypes { eventTypeStr := string(eventType) metrics["event_counts"].(map[string]uint64)[eventTypeStr] = em.eventCounts[eventType] metrics["success_counts"].(map[string]uint64)[eventTypeStr] = em.successCounts[eventType] metrics["error_counts"].(map[string]uint64)[eventTypeStr] = em.errorCounts[eventType] metrics["dropped_counts"].(map[string]uint64)[eventTypeStr] = em.droppedCounts[eventType] // 计算成功率 total := em.eventCounts[eventType] if total > 0 { success := em.successCounts[eventType] metrics["success_rates"].(map[string]float64)[eventTypeStr] = float64(success) / float64(total) } else { metrics["success_rates"].(map[string]float64)[eventTypeStr] = 0 } // 计算平均处理时间 times, exists := em.processingTimes[eventType] if exists && len(times) > 0 { var total time.Duration for _, t := range times { total += t } avg := total / time.Duration(len(times)) metrics["avg_times"].(map[string]string)[eventTypeStr] = avg.String() } else { metrics["avg_times"].(map[string]string)[eventTypeStr] = "0s" } } return metrics } // Reset 重置指标 func (em *EventMetrics) Reset() { em.mu.Lock() defer em.mu.Unlock() em.eventCounts = make(map[EventType]uint64) em.successCounts = make(map[EventType]uint64) em.errorCounts = make(map[EventType]uint64) em.droppedCounts = make(map[EventType]uint64) em.processingTimes = make(map[EventType][]time.Duration) } ================================================ FILE: internal/events/middleware.go ================================================ package events import ( "context" "fmt" "log" "time" ) // LoggingMiddleware 日志中间件 type LoggingMiddleware struct { logger *log.Logger } // NewLoggingMiddleware 创建日志中间件 func NewLoggingMiddleware() *LoggingMiddleware { return &LoggingMiddleware{ logger: log.New(log.Writer(), "[EventLogging] ", log.LstdFlags), } } // Process 处理事件 func (lm *LoggingMiddleware) Process(ctx context.Context, event Event, next func(context.Context, Event) error) error { start := time.Now() lm.logger.Printf("Processing event: %s (type: %s, player: %s)", event.GetID(), event.GetEventType(), event.GetPlayerID()) err := next(ctx, event) duration := time.Since(start) if err != nil { lm.logger.Printf("Event processing failed: %s, duration: %v, error: %v", event.GetID(), duration, err) } else { lm.logger.Printf("Event processing completed: %s, duration: %v", event.GetID(), duration) } return err } // ValidationMiddleware 验证中间件 type ValidationMiddleware struct { logger *log.Logger } // NewValidationMiddleware 创建验证中间件 func NewValidationMiddleware() *ValidationMiddleware { return &ValidationMiddleware{ logger: log.New(log.Writer(), "[EventValidation] ", log.LstdFlags), } } // Process 处理事件 func (vm *ValidationMiddleware) Process(ctx context.Context, event Event, next func(context.Context, Event) error) error { // 验证事件基本信息 if err := vm.validateEvent(event); err != nil { vm.logger.Printf("Event validation failed: %s, error: %v", event.GetID(), err) return fmt.Errorf("event validation failed: %w", err) } return next(ctx, event) } // validateEvent 验证事件 func (vm *ValidationMiddleware) validateEvent(event Event) error { if event.GetID() == "" { return fmt.Errorf("event ID is required") } if event.GetEventType() == "" { return fmt.Errorf("event type is required") } if event.GetOccurredAt().IsZero() { return fmt.Errorf("event timestamp is required") } // 检查时间戳是否合理(不能是未来时间,不能太久以前) now := time.Now() if event.GetOccurredAt().After(now.Add(5 * time.Minute)) { return fmt.Errorf("event timestamp is in the future") } if event.GetOccurredAt().Before(now.Add(-24 * time.Hour)) { return fmt.Errorf("event timestamp is too old") } return nil } // RateLimitMiddleware 限流中间件 type RateLimitMiddleware struct { limiter map[string]*TokenBucket logger *log.Logger } // TokenBucket 令牌桶 type TokenBucket struct { capacity int tokens int refillRate int // 每秒补充的令牌数 lastRefill time.Time } // NewRateLimitMiddleware 创建限流中间件 func NewRateLimitMiddleware() *RateLimitMiddleware { return &RateLimitMiddleware{ limiter: make(map[string]*TokenBucket), logger: log.New(log.Writer(), "[EventRateLimit] ", log.LstdFlags), } } // Process 处理事件 func (rlm *RateLimitMiddleware) Process(ctx context.Context, event Event, next func(context.Context, Event) error) error { // 基于玩家ID进行限流 playerID := event.GetPlayerID() if playerID == "" { // 系统事件不限流 return next(ctx, event) } if !rlm.allowRequest(playerID) { rlm.logger.Printf("Rate limit exceeded for player: %s, event: %s", playerID, event.GetID()) return fmt.Errorf("rate limit exceeded for player: %s", playerID) } return next(ctx, event) } // allowRequest 检查是否允许请求 func (rlm *RateLimitMiddleware) allowRequest(playerID string) bool { bucket, exists := rlm.limiter[playerID] if !exists { // 为新玩家创建令牌桶 bucket = &TokenBucket{ capacity: 10, // 容量10个令牌 tokens: 10, // 初始10个令牌 refillRate: 5, // 每秒补充5个令牌 lastRefill: time.Now(), } rlm.limiter[playerID] = bucket } // 补充令牌 now := time.Now() elapsed := now.Sub(bucket.lastRefill).Seconds() if elapsed > 0 { newTokens := int(elapsed * float64(bucket.refillRate)) bucket.tokens += newTokens if bucket.tokens > bucket.capacity { bucket.tokens = bucket.capacity } bucket.lastRefill = now } // 检查是否有可用令牌 if bucket.tokens > 0 { bucket.tokens-- return true } return false } // AuthenticationMiddleware 认证中间件 type AuthenticationMiddleware struct { logger *log.Logger } // NewAuthenticationMiddleware 创建认证中间件 func NewAuthenticationMiddleware() *AuthenticationMiddleware { return &AuthenticationMiddleware{ logger: log.New(log.Writer(), "[EventAuth] ", log.LstdFlags), } } // Process 处理事件 func (am *AuthenticationMiddleware) Process(ctx context.Context, event Event, next func(context.Context, Event) error) error { // 检查事件是否需要认证 if am.requiresAuthentication(EventType(event.GetEventType())) { if err := am.authenticateEvent(ctx, event); err != nil { am.logger.Printf("Event authentication failed: %s, error: %v", event.GetID(), err) return fmt.Errorf("event authentication failed: %w", err) } } return next(ctx, event) } // requiresAuthentication 检查事件类型是否需要认证 func (am *AuthenticationMiddleware) requiresAuthentication(eventType EventType) bool { // 系统事件不需要认证 if eventType == EventTypeSystemStart || eventType == EventTypeSystemStop || eventType == EventTypeSystemHealth { return false } // 其他事件需要认证 return true } // authenticateEvent 认证事件 func (am *AuthenticationMiddleware) authenticateEvent(ctx context.Context, event Event) error { playerID := event.GetPlayerID() if playerID == "" { return fmt.Errorf("player ID is required for authenticated events") } // 这里可以添加更复杂的认证逻辑 // 例如:验证JWT token、检查玩家状态等 return nil } // MetricsMiddleware 指标中间件 type MetricsMiddleware struct { metrics *EventMetrics logger *log.Logger } // NewMetricsMiddleware 创建指标中间件 func NewMetricsMiddleware(metrics *EventMetrics) *MetricsMiddleware { return &MetricsMiddleware{ metrics: metrics, logger: log.New(log.Writer(), "[EventMetrics] ", log.LstdFlags), } } // Process 处理事件 func (mm *MetricsMiddleware) Process(ctx context.Context, event Event, next func(context.Context, Event) error) error { start := time.Now() err := next(ctx, event) duration := time.Since(start) mm.metrics.RecordProcessingTime(EventType(event.GetEventType()), duration) if err != nil { mm.metrics.IncrementErrorCount(EventType(event.GetEventType())) } else { mm.metrics.IncrementSuccessCount(EventType(event.GetEventType())) } return err } // CircuitBreakerMiddleware 熔断器中间件 type CircuitBreakerMiddleware struct { breakers map[EventType]*CircuitBreaker logger *log.Logger } // CircuitBreaker 熔断器 type CircuitBreaker struct { failureCount int failureThreshold int timeout time.Duration lastFailureTime time.Time state CircuitBreakerState } // CircuitBreakerState 熔断器状态 type CircuitBreakerState int const ( Closed CircuitBreakerState = iota Open HalfOpen ) // NewCircuitBreakerMiddleware 创建熔断器中间件 func NewCircuitBreakerMiddleware() *CircuitBreakerMiddleware { return &CircuitBreakerMiddleware{ breakers: make(map[EventType]*CircuitBreaker), logger: log.New(log.Writer(), "[EventCircuitBreaker] ", log.LstdFlags), } } // Process 处理事件 func (cbm *CircuitBreakerMiddleware) Process(ctx context.Context, event Event, next func(context.Context, Event) error) error { breaker := cbm.getBreaker(EventType(event.GetEventType())) if !breaker.allowRequest() { cbm.logger.Printf("Circuit breaker is open for event type: %s", event.GetEventType()) return fmt.Errorf("circuit breaker is open for event type: %s", event.GetEventType()) } err := next(ctx, event) if err != nil { breaker.recordFailure() } else { breaker.recordSuccess() } return err } // getBreaker 获取熔断器 func (cbm *CircuitBreakerMiddleware) getBreaker(eventType EventType) *CircuitBreaker { breaker, exists := cbm.breakers[eventType] if !exists { breaker = &CircuitBreaker{ failureThreshold: 5, // 失败阈值 timeout: 30 * time.Second, // 超时时间 state: Closed, } cbm.breakers[eventType] = breaker } return breaker } // allowRequest 检查是否允许请求 func (cb *CircuitBreaker) allowRequest() bool { now := time.Now() switch cb.state { case Closed: return true case Open: if now.Sub(cb.lastFailureTime) > cb.timeout { cb.state = HalfOpen return true } return false case HalfOpen: return true default: return false } } // recordFailure 记录失败 func (cb *CircuitBreaker) recordFailure() { cb.failureCount++ cb.lastFailureTime = time.Now() if cb.state == HalfOpen { cb.state = Open } else if cb.failureCount >= cb.failureThreshold { cb.state = Open } } // recordSuccess 记录成功 func (cb *CircuitBreaker) recordSuccess() { if cb.state == HalfOpen { cb.state = Closed cb.failureCount = 0 } } ================================================ FILE: internal/events/worker.go ================================================ package events import ( "context" "fmt" "log" "sync" "time" ) // EventDispatcher 事件分发器 type EventDispatcher struct { handlers map[EventType][]EventHandler mu sync.RWMutex } // EventHandler 事件处理器接口 type EventHandler interface { Handle(ctx context.Context, event Event) error GetEventTypes() []string GetHandlerName() string } // NewEventDispatcher 创建事件分发器 func NewEventDispatcher() *EventDispatcher { return &EventDispatcher{ handlers: make(map[EventType][]EventHandler), } } // RegisterHandler 注册事件处理器 func (d *EventDispatcher) RegisterHandler(eventType EventType, handler EventHandler) { d.mu.Lock() defer d.mu.Unlock() d.handlers[eventType] = append(d.handlers[eventType], handler) } // Dispatch 分发事件 func (d *EventDispatcher) Dispatch(ctx context.Context, event Event) error { d.mu.RLock() handlers, exists := d.handlers[EventType(event.GetEventType())] d.mu.RUnlock() if !exists { return fmt.Errorf("no handlers for event type: %s", event.GetEventType()) } for _, handler := range handlers { if err := handler.Handle(ctx, event); err != nil { return fmt.Errorf("handler error: %w", err) } } return nil } // EventTask 事件任务 type EventTask struct { Event Event Context context.Context Dispatcher *EventDispatcher } // WorkerPool 工作池 type WorkerPool struct { workerCount int TaskQueue chan *EventTask workers []*Worker logger *log.Logger mu sync.RWMutex running bool wg sync.WaitGroup } // Worker 工作者 type Worker struct { id int taskChan chan *EventTask quit chan bool logger *log.Logger metrics *WorkerMetrics } // WorkerMetrics 工作者指标 type WorkerMetrics struct { TasksProcessed uint64 TasksFailed uint64 TotalTime time.Duration mu sync.RWMutex } // NewWorkerPool 创建工作池 func NewWorkerPool(workerCount, queueSize int) *WorkerPool { return &WorkerPool{ workerCount: workerCount, TaskQueue: make(chan *EventTask, queueSize), workers: make([]*Worker, workerCount), logger: log.New(log.Writer(), "[WorkerPool] ", log.LstdFlags), } } // Start 启动工作池 func (wp *WorkerPool) Start() { wp.mu.Lock() defer wp.mu.Unlock() if wp.running { return } wp.logger.Printf("Starting worker pool with %d workers", wp.workerCount) // 创建并启动工作者 for i := 0; i < wp.workerCount; i++ { worker := &Worker{ id: i + 1, taskChan: wp.TaskQueue, quit: make(chan bool), logger: log.New(log.Writer(), fmt.Sprintf("[Worker-%d] ", i+1), log.LstdFlags), metrics: &WorkerMetrics{}, } wp.workers[i] = worker wp.wg.Add(1) go worker.start(&wp.wg) } wp.running = true wp.logger.Println("Worker pool started") } // Stop 停止工作池 func (wp *WorkerPool) Stop() { wp.mu.Lock() defer wp.mu.Unlock() if !wp.running { return } wp.logger.Println("Stopping worker pool...") // 停止所有工作者 for _, worker := range wp.workers { worker.stop() } // 等待所有工作者完成 wp.wg.Wait() // 关闭任务队列 close(wp.TaskQueue) wp.running = false wp.logger.Println("Worker pool stopped") } // GetMetrics 获取工作池指标 func (wp *WorkerPool) GetMetrics() map[string]interface{} { wp.mu.RLock() defer wp.mu.RUnlock() metrics := map[string]interface{}{ "worker_count": wp.workerCount, "queue_size": cap(wp.TaskQueue), "queue_length": len(wp.TaskQueue), "running": wp.running, "worker_metrics": make([]map[string]interface{}, len(wp.workers)), } for i, worker := range wp.workers { if worker != nil { metrics["worker_metrics"].([]map[string]interface{})[i] = worker.getMetrics() } } return metrics } // start 启动工作者 func (w *Worker) start(wg *sync.WaitGroup) { defer wg.Done() w.logger.Printf("Worker %d started", w.id) for { select { case task := <-w.taskChan: if task != nil { w.processTask(task) } case <-w.quit: w.logger.Printf("Worker %d stopping", w.id) return } } } // stop 停止工作者 func (w *Worker) stop() { close(w.quit) } // processTask 处理任务 func (w *Worker) processTask(task *EventTask) { start := time.Now() defer func() { w.metrics.mu.Lock() w.metrics.TotalTime += time.Since(start) w.metrics.mu.Unlock() }() w.logger.Printf("Processing event: %s (type: %s)", task.Event.GetID(), task.Event.GetEventType()) // 处理事件 if err := task.Dispatcher.Dispatch(task.Context, task.Event); err != nil { w.logger.Printf("Failed to process event %s: %v", task.Event.GetID(), err) w.metrics.mu.Lock() w.metrics.TasksFailed++ w.metrics.mu.Unlock() } else { w.logger.Printf("Successfully processed event: %s", task.Event.GetID()) } w.metrics.mu.Lock() w.metrics.TasksProcessed++ w.metrics.mu.Unlock() } // getMetrics 获取工作者指标 func (w *Worker) getMetrics() map[string]interface{} { w.metrics.mu.RLock() defer w.metrics.mu.RUnlock() return map[string]interface{}{ "id": w.id, "tasks_processed": w.metrics.TasksProcessed, "tasks_failed": w.metrics.TasksFailed, "total_time": w.metrics.TotalTime.String(), "avg_time": w.getAverageProcessingTime(), } } // getAverageProcessingTime 获取平均处理时间 func (w *Worker) getAverageProcessingTime() string { if w.metrics.TasksProcessed == 0 { return "0s" } avg := w.metrics.TotalTime / time.Duration(w.metrics.TasksProcessed) return avg.String() } ================================================ FILE: internal/game/player.go ================================================ // Package game 游戏核心逻辑 // Author: MMO Server Team // Created: 2024 package game import ( "context" "fmt" "sync" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" ) // Player 玩家数据结构 type Player struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` UserID string `bson:"user_id" json:"user_id"` Username string `bson:"username" json:"username"` Level int32 `bson:"level" json:"level"` Experience int64 `bson:"experience" json:"experience"` Gold int64 `bson:"gold" json:"gold"` Diamond int64 `bson:"diamond" json:"diamond"` Position Position `bson:"position" json:"position"` Attributes PlayerAttributes `bson:"attributes" json:"attributes"` Inventory []Item `bson:"inventory" json:"inventory"` Equipment Equipment `bson:"equipment" json:"equipment"` Skills []Skill `bson:"skills" json:"skills"` Quests []Quest `bson:"quests" json:"quests"` GuildID string `bson:"guild_id,omitempty" json:"guild_id,omitempty"` LastLogin time.Time `bson:"last_login" json:"last_login"` CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` Online bool `bson:"online" json:"online"` ServerID string `bson:"server_id" json:"server_id"` } // Position 位置信息 type Position struct { X float64 `bson:"x" json:"x"` Y float64 `bson:"y" json:"y"` Z float64 `bson:"z" json:"z"` MapID string `bson:"map_id" json:"map_id"` } // PlayerAttributes 玩家属性 type PlayerAttributes struct { HP int32 `bson:"hp" json:"hp"` MaxHP int32 `bson:"max_hp" json:"max_hp"` MP int32 `bson:"mp" json:"mp"` MaxMP int32 `bson:"max_mp" json:"max_mp"` Attack int32 `bson:"attack" json:"attack"` Defense int32 `bson:"defense" json:"defense"` Speed int32 `bson:"speed" json:"speed"` CriticalRate int32 `bson:"critical_rate" json:"critical_rate"` DodgeRate int32 `bson:"dodge_rate" json:"dodge_rate"` } // Item 物品 type Item struct { ID string `bson:"id" json:"id"` ItemID string `bson:"item_id" json:"item_id"` Quantity int32 `bson:"quantity" json:"quantity"` Slot int32 `bson:"slot" json:"slot"` } // Equipment 装备 type Equipment struct { Weapon *Item `bson:"weapon,omitempty" json:"weapon,omitempty"` Armor *Item `bson:"armor,omitempty" json:"armor,omitempty"` Helmet *Item `bson:"helmet,omitempty" json:"helmet,omitempty"` Boots *Item `bson:"boots,omitempty" json:"boots,omitempty"` Gloves *Item `bson:"gloves,omitempty" json:"gloves,omitempty"` Accessory1 *Item `bson:"accessory1,omitempty" json:"accessory1,omitempty"` Accessory2 *Item `bson:"accessory2,omitempty" json:"accessory2,omitempty"` } // Skill 技能 type Skill struct { SkillID string `bson:"skill_id" json:"skill_id"` Level int32 `bson:"level" json:"level"` Exp int64 `bson:"exp" json:"exp"` } // Quest 任务 type Quest struct { QuestID string `bson:"quest_id" json:"quest_id"` Status string `bson:"status" json:"status"` // pending, active, completed, failed Progress map[string]int32 `bson:"progress" json:"progress"` StartTime time.Time `bson:"start_time" json:"start_time"` EndTime *time.Time `bson:"end_time,omitempty" json:"end_time,omitempty"` } // PlayerManager 玩家管理器 type PlayerManager struct { collection *mongo.Collection mu sync.RWMutex onlinePlayers map[string]*Player } // NewPlayerManager 创建玩家管理器 func NewPlayerManager(collection *mongo.Collection) *PlayerManager { return &PlayerManager{ collection: collection, onlinePlayers: make(map[string]*Player), } } // CreatePlayer 创建新玩家 func (pm *PlayerManager) CreatePlayer(ctx context.Context, userID, username string) (*Player, error) { player := &Player{ UserID: userID, Username: username, Level: 1, Gold: 1000, Diamond: 0, Position: Position{ X: 0, Y: 0, Z: 0, MapID: "starter_map", }, Attributes: PlayerAttributes{ HP: 100, MaxHP: 100, MP: 50, MaxMP: 50, Attack: 10, Defense: 5, Speed: 10, CriticalRate: 5, DodgeRate: 5, }, Inventory: make([]Item, 0), Skills: make([]Skill, 0), Quests: make([]Quest, 0), CreatedAt: time.Now(), UpdatedAt: time.Now(), Online: false, } result, err := pm.collection.InsertOne(ctx, player) if err != nil { return nil, fmt.Errorf("failed to create player: %w", err) } player.ID = result.InsertedID.(primitive.ObjectID) return player, nil } // GetPlayer 获取玩家信息 func (pm *PlayerManager) GetPlayer(ctx context.Context, userID string) (*Player, error) { var player Player err := pm.collection.FindOne(ctx, bson.M{"user_id": userID}).Decode(&player) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("player not found") } return nil, fmt.Errorf("failed to get player: %w", err) } return &player, nil } // UpdatePlayer 更新玩家信息 func (pm *PlayerManager) UpdatePlayer(ctx context.Context, player *Player) error { player.UpdatedAt = time.Now() _, err := pm.collection.UpdateOne( ctx, bson.M{"user_id": player.UserID}, bson.M{"$set": player}, ) if err != nil { return fmt.Errorf("failed to update player: %w", err) } return nil } // SetPlayerOnline 设置玩家在线状态 func (pm *PlayerManager) SetPlayerOnline(ctx context.Context, userID string, online bool, serverID string) error { update := bson.M{ "online": online, "updated_at": time.Now(), } if online { update["last_login"] = time.Now() update["server_id"] = serverID } else { update["server_id"] = "" } _, err := pm.collection.UpdateOne( ctx, bson.M{"user_id": userID}, bson.M{"$set": update}, ) if err != nil { return fmt.Errorf("failed to set player online status: %w", err) } // 更新内存中的在线玩家列表 pm.mu.Lock() defer pm.mu.Unlock() if online { player, err := pm.GetPlayer(ctx, userID) if err == nil { pm.onlinePlayers[userID] = player } } else { delete(pm.onlinePlayers, userID) } return nil } // GetOnlinePlayers 获取在线玩家列表 func (pm *PlayerManager) GetOnlinePlayers() map[string]*Player { pm.mu.RLock() defer pm.mu.RUnlock() result := make(map[string]*Player) for k, v := range pm.onlinePlayers { result[k] = v } return result } ================================================ FILE: internal/icharacter.go ================================================ package internal type Character interface { GetName() string } ================================================ FILE: internal/imodule.go ================================================ package internal // "greatestworks/internal/note/event" // TODO: 实现事件系统 // Manager 管理器接口定义 type Manager interface { OnStart() AfterStart() OnStop() AfterStop() } // Metrics 指标接口 type Metrics interface { GetName() string Description() string SetName(str string) } // DBAction 数据库操作接口 type DBAction interface { Load() Save() } // ConfigManagerAction 配置管理操作接口 type ConfigManagerAction interface { Load() Get(id uint32) interface{} } // Module 模块接口 type Module interface { OnEvent(c Character, event interface{}) // TODO: 实现event系统 SetEventCategoryActive(eventCategory int) RegisterHandler() Manager } ================================================ FILE: internal/infrastructure/auth/jwt.go ================================================ package auth import ( "crypto/rand" "encoding/hex" "errors" "fmt" "time" "github.com/golang-jwt/jwt/v5" "greatestworks/internal/infrastructure/logging" ) // JWTConfig JWT configuration type JWTConfig struct { Secret string Issuer string Audience string AccessTokenTTL time.Duration RefreshTokenTTL time.Duration Algorithm string SigningMethod jwt.SigningMethod } // JWTService JWT service type JWTService struct { config *JWTConfig logger logging.Logger } // Claims JWT claims type Claims struct { UserID string `json:"user_id"` Username string `json:"username"` Role string `json:"role"` ExpiresAt int64 `json:"exp"` IssuedAt int64 `json:"iat"` Issuer string `json:"iss"` Audience string `json:"aud"` jwt.RegisteredClaims } // NewJWTService creates a new JWT service func NewJWTService(config *JWTConfig, logger logging.Logger) *JWTService { if config == nil { config = &JWTConfig{ Secret: "default-secret", Issuer: "greatestworks", Audience: "greatestworks-users", AccessTokenTTL: 24 * time.Hour, RefreshTokenTTL: 7 * 24 * time.Hour, Algorithm: "HS256", SigningMethod: jwt.SigningMethodHS256, } } return &JWTService{ config: config, logger: logger, } } // GenerateToken generates a new JWT token func (j *JWTService) GenerateToken(userID, username, role string) (string, time.Time, error) { now := time.Now() expiresAt := now.Add(j.config.AccessTokenTTL) claims := &Claims{ UserID: userID, Username: username, Role: role, ExpiresAt: expiresAt.Unix(), IssuedAt: now.Unix(), Issuer: j.config.Issuer, Audience: j.config.Audience, RegisteredClaims: jwt.RegisteredClaims{ Issuer: j.config.Issuer, Audience: []string{j.config.Audience}, Subject: userID, ExpiresAt: jwt.NewNumericDate(expiresAt), NotBefore: jwt.NewNumericDate(now), IssuedAt: jwt.NewNumericDate(now), }, } token := jwt.NewWithClaims(j.config.SigningMethod, claims) tokenString, err := token.SignedString([]byte(j.config.Secret)) if err != nil { j.logger.Error("Failed to sign token", err, logging.Fields{ "user_id": userID, }) return "", time.Time{}, err } j.logger.Debug("Token generated", logging.Fields{ "user_id": userID, "username": username, "expires_at": expiresAt, }) return tokenString, expiresAt, nil } // GenerateRefreshToken generates a new refresh token func (j *JWTService) GenerateRefreshToken(userID string) (string, time.Time, error) { // Generate a random refresh token bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { return "", time.Time{}, err } refreshToken := hex.EncodeToString(bytes) expiresAt := time.Now().Add(j.config.RefreshTokenTTL) j.logger.Debug("Refresh token generated", logging.Fields{ "user_id": userID, "expires_at": expiresAt, }) return refreshToken, expiresAt, nil } // ValidateToken validates a JWT token func (j *JWTService) ValidateToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { // Verify signing method if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(j.config.Secret), nil }) if err != nil { j.logger.Warn("Token validation failed", logging.Fields{ "error": err, }) return nil, err } if claims, ok := token.Claims.(*Claims); ok && token.Valid { // Verify issuer and audience if claims.Issuer != j.config.Issuer { return nil, errors.New("invalid issuer") } if claims.Audience != j.config.Audience { return nil, errors.New("invalid audience") } j.logger.Debug("Token validated successfully", logging.Fields{ "user_id": claims.UserID, "username": claims.Username, }) return claims, nil } return nil, errors.New("invalid token") } // RefreshToken refreshes an access token using a refresh token func (j *JWTService) RefreshToken(refreshToken, userID string) (string, time.Time, error) { // In a real implementation, you would validate the refresh token against a database // For now, we'll just generate a new token return j.GenerateToken(userID, "", "") } // RevokeToken revokes a token (adds it to a blacklist) func (j *JWTService) RevokeToken(tokenString string) error { // In a real implementation, you would add the token to a blacklist // For now, we'll just log the revocation j.logger.Info("Token revoked", logging.Fields{ "token": tokenString, }) return nil } // IsTokenValid checks if a token is valid without parsing it func (j *JWTService) IsTokenValid(tokenString string) bool { _, err := j.ValidateToken(tokenString) return err == nil } // GetTokenExpiration gets the expiration time of a token func (j *JWTService) GetTokenExpiration(tokenString string) (time.Time, error) { claims, err := j.ValidateToken(tokenString) if err != nil { return time.Time{}, err } return time.Unix(claims.ExpiresAt, 0), nil } // GetTokenClaims gets the claims from a token without validation func (j *JWTService) GetTokenClaims(tokenString string) (*Claims, error) { token, _, err := new(jwt.Parser).ParseUnverified(tokenString, &Claims{}) if err != nil { return nil, err } if claims, ok := token.Claims.(*Claims); ok { return claims, nil } return nil, errors.New("invalid token claims") } // GenerateTokenPair generates both access and refresh tokens func (j *JWTService) GenerateTokenPair(userID, username, role string) (accessToken, refreshToken string, accessExpires, refreshExpires time.Time, err error) { // Generate access token accessToken, accessExpires, err = j.GenerateToken(userID, username, role) if err != nil { return "", "", time.Time{}, time.Time{}, err } // Generate refresh token refreshToken, refreshExpires, err = j.GenerateRefreshToken(userID) if err != nil { return "", "", time.Time{}, time.Time{}, err } j.logger.Info("Token pair generated", logging.Fields{ "user_id": userID, "username": username, "access_expires": accessExpires, "refresh_expires": refreshExpires, }) return accessToken, refreshToken, accessExpires, refreshExpires, nil } // ValidateTokenPair validates both access and refresh tokens func (j *JWTService) ValidateTokenPair(accessToken, refreshToken string) (*Claims, error) { // Validate access token claims, err := j.ValidateToken(accessToken) if err != nil { return nil, err } // In a real implementation, you would also validate the refresh token // For now, we'll just return the access token claims return claims, nil } // GetConfig returns the JWT configuration func (j *JWTService) GetConfig() *JWTConfig { return j.config } // UpdateConfig updates the JWT configuration func (j *JWTService) UpdateConfig(config *JWTConfig) { j.config = config j.logger.Info("JWT configuration updated") } ================================================ FILE: internal/infrastructure/auth/middleware.go ================================================ package auth import ( "net/http" "time" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // AuthMiddleware 认证中间件 type AuthMiddleware struct { jwtService *JWTService logger logging.Logger } // NewAuthMiddleware 创建认证中间件 func NewAuthMiddleware(jwtService *JWTService, logger logging.Logger) *AuthMiddleware { return &AuthMiddleware{ jwtService: jwtService, logger: logger, } } // RequireAuth 需要认证的中间件 func (m *AuthMiddleware) RequireAuth() gin.HandlerFunc { return func(c *gin.Context) { // 从请求头获取token token := c.GetHeader("Authorization") if token == "" { m.logger.Warn("Missing authorization header") c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authorization header"}) c.Abort() return } // 移除Bearer前缀 if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] } // 验证token claims, err := m.jwtService.ValidateToken(token) if err != nil { m.logger.Warn("Invalid token", logging.Fields{ "error": err, }) c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) c.Abort() return } // 将用户信息存储到上下文中 c.Set("user_id", claims.UserID) c.Set("username", claims.Username) c.Set("expires_at", claims.ExpiresAt) m.logger.Debug("User authenticated", logging.Fields{ "user_id": claims.UserID, "username": claims.Username, }) c.Next() } } // OptionalAuth 可选认证的中间件 func (m *AuthMiddleware) OptionalAuth() gin.HandlerFunc { return func(c *gin.Context) { // 从请求头获取token token := c.GetHeader("Authorization") if token == "" { c.Next() return } // 移除Bearer前缀 if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] } // 验证token claims, err := m.jwtService.ValidateToken(token) if err != nil { m.logger.Debug("Invalid token in optional auth", logging.Fields{ "error": err, }) c.Next() return } // 将用户信息存储到上下文中 c.Set("user_id", claims.UserID) c.Set("username", claims.Username) c.Set("expires_at", claims.ExpiresAt) m.logger.Debug("User authenticated (optional)", logging.Fields{ "user_id": claims.UserID, "username": claims.Username, }) c.Next() } } // RequireRole 需要特定角色的中间件 func (m *AuthMiddleware) RequireRole(role string) gin.HandlerFunc { return func(c *gin.Context) { // 首先检查是否已认证 userID, exists := c.Get("user_id") if !exists { m.logger.Warn("User not authenticated for role check") c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) c.Abort() return } // 检查用户角色 userRole, err := m.getUserRole(userID.(string)) if err != nil { m.logger.Error("Failed to get user role", err, logging.Fields{ "user_id": userID, }) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check user role"}) c.Abort() return } if userRole != role { m.logger.Warn("Insufficient permissions", logging.Fields{ "user_id": userID, "required_role": role, "user_role": userRole, }) c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) c.Abort() return } m.logger.Debug("Role check passed", logging.Fields{ "user_id": userID, "role": role, }) c.Next() } } // RequireAnyRole 需要任意一个角色的中间件 func (m *AuthMiddleware) RequireAnyRole(roles ...string) gin.HandlerFunc { return func(c *gin.Context) { // 首先检查是否已认证 userID, exists := c.Get("user_id") if !exists { m.logger.Warn("User not authenticated for role check") c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) c.Abort() return } // 检查用户角色 userRole, err := m.getUserRole(userID.(string)) if err != nil { m.logger.Error("Failed to get user role", err, logging.Fields{ "user_id": userID, }) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check user role"}) c.Abort() return } // 检查用户是否有任意一个所需角色 hasRole := false for _, role := range roles { if userRole == role { hasRole = true break } } if !hasRole { m.logger.Warn("Insufficient permissions", logging.Fields{ "user_id": userID, "required_roles": roles, "user_role": userRole, }) c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) c.Abort() return } m.logger.Debug("Role check passed", logging.Fields{ "user_id": userID, "roles": roles, }) c.Next() } } // RateLimit 限流中间件 func (m *AuthMiddleware) RateLimit(requests int, window time.Duration) gin.HandlerFunc { // 这里应该实现一个简单的内存限流器 // 实际项目中应该使用Redis等外部存储 return func(c *gin.Context) { // 获取客户端IP clientIP := c.ClientIP() // 这里应该检查限流逻辑 // 简化实现,直接通过 m.logger.Debug("Rate limit check", logging.Fields{ "client_ip": clientIP, "requests": requests, "window": window, }) c.Next() } } // CORS 跨域中间件 func (m *AuthMiddleware) CORS() gin.HandlerFunc { return func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization") c.Header("Access-Control-Allow-Credentials", "true") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } // RequestLogger 请求日志中间件 func (m *AuthMiddleware) RequestLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path raw := c.Request.URL.RawQuery // 处理请求 c.Next() // 记录日志 latency := time.Since(start) clientIP := c.ClientIP() method := c.Request.Method statusCode := c.Writer.Status() bodySize := c.Writer.Size() if raw != "" { path = path + "?" + raw } m.logger.Info("HTTP Request", logging.Fields{ "status": statusCode, "latency": latency, "client_ip": clientIP, "method": method, "path": path, "body_size": bodySize, }) } } // 私有方法 // getUserRole 获取用户角色 func (m *AuthMiddleware) getUserRole(userID string) (string, error) { // 这里应该从数据库或缓存中获取用户角色 // 简化实现,返回默认角色 return "user", nil } ================================================ FILE: internal/infrastructure/cache/cache_manager.go ================================================ package cache import ( "context" "fmt" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // Cache 缓存接口 type Cache interface { Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error Get(ctx context.Context, key string, dest interface{}) error Delete(ctx context.Context, key string) error Exists(ctx context.Context, key string) (bool, error) Clear(ctx context.Context) error } // CacheManager 缓存管理器 type CacheManager struct { primary Cache secondary Cache logger logging.Logger config *CacheManagerConfig mu sync.RWMutex stats *ManagerStats } // CacheManagerConfig 缓存管理器配置 type CacheManagerConfig struct { UseFallback bool `json:"use_fallback"` FallbackOnError bool `json:"fallback_on_error"` SyncToSecondary bool `json:"sync_to_secondary"` SyncInterval time.Duration `json:"sync_interval"` MaxRetries int `json:"max_retries"` RetryDelay time.Duration `json:"retry_delay"` CircuitBreakerThreshold int `json:"circuit_breaker_threshold"` } // ManagerStats 管理器统计 type ManagerStats struct { TotalRequests int64 `json:"total_requests"` SuccessCount int64 `json:"success_count"` ErrorCount int64 `json:"error_count"` FallbackCount int64 `json:"fallback_count"` SyncCount int64 `json:"sync_count"` } // NewCacheManager 创建缓存管理器 func NewCacheManager(primary, secondary Cache, config *CacheManagerConfig, logger logging.Logger) *CacheManager { if config == nil { config = &CacheManagerConfig{ UseFallback: true, FallbackOnError: true, SyncToSecondary: false, SyncInterval: 5 * time.Minute, MaxRetries: 3, RetryDelay: time.Second, CircuitBreakerThreshold: 10, } } return &CacheManager{ primary: primary, secondary: secondary, logger: logger, config: config, stats: &ManagerStats{}, } } // Set 设置值 func (m *CacheManager) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error { m.mu.Lock() defer m.mu.Unlock() m.stats.TotalRequests++ // 尝试设置主缓存 err := m.primary.Set(ctx, key, value, ttl) if err != nil { m.stats.ErrorCount++ m.logger.Error("Primary cache set failed", err, logging.Fields{ "key": key, }) // 如果启用备用缓存,尝试设置备用缓存 if m.config.UseFallback && m.secondary != nil { if err := m.secondary.Set(ctx, key, value, ttl); err != nil { m.logger.Error("Secondary cache set failed", err, logging.Fields{ "key": key, }) return fmt.Errorf("主缓存和备用缓存都设置失败: %w", err) } m.stats.FallbackCount++ m.logger.Info("Using secondary cache", logging.Fields{ "key": key, }) } else { return err } } else { m.stats.SuccessCount++ // 如果启用同步到备用缓存 if m.config.SyncToSecondary && m.secondary != nil { go func() { if err := m.secondary.Set(context.Background(), key, value, ttl); err != nil { m.logger.Error("Failed to sync to secondary cache", err, logging.Fields{ "key": key, }) } else { m.stats.SyncCount++ } }() } } return nil } // Get 获取值 func (m *CacheManager) Get(ctx context.Context, key string, dest interface{}) error { m.mu.Lock() defer m.mu.Unlock() m.stats.TotalRequests++ // 尝试从主缓存获取 err := m.primary.Get(ctx, key, dest) if err != nil { m.stats.ErrorCount++ m.logger.Error("Primary cache get failed", err, logging.Fields{ "key": key, }) // 如果启用备用缓存,尝试从备用缓存获取 if m.config.UseFallback && m.secondary != nil { if err := m.secondary.Get(ctx, key, dest); err != nil { m.logger.Error("Secondary cache get failed", err, logging.Fields{ "key": key, }) return fmt.Errorf("主缓存和备用缓存都获取失败: %w", err) } m.stats.FallbackCount++ m.logger.Info("Got from secondary cache", logging.Fields{ "key": key, }) } else { return err } } else { m.stats.SuccessCount++ } return nil } // Delete 删除值 func (m *CacheManager) Delete(ctx context.Context, key string) error { m.mu.Lock() defer m.mu.Unlock() m.stats.TotalRequests++ // 删除主缓存 err := m.primary.Delete(ctx, key) if err != nil { m.stats.ErrorCount++ m.logger.Error("Primary cache delete failed", err, logging.Fields{ "key": key, }) } // 删除备用缓存 if m.secondary != nil { if err := m.secondary.Delete(ctx, key); err != nil { m.logger.Error("Secondary cache delete failed", err, logging.Fields{ "key": key, }) } } if err != nil { return err } m.stats.SuccessCount++ return nil } // Exists 检查键是否存在 func (m *CacheManager) Exists(ctx context.Context, key string) (bool, error) { m.mu.RLock() defer m.mu.RUnlock() // 检查主缓存 exists, err := m.primary.Exists(ctx, key) if err != nil { m.logger.Error("Primary cache exists check failed", err, logging.Fields{ "key": key, }) // 如果启用备用缓存,检查备用缓存 if m.config.UseFallback && m.secondary != nil { exists, err = m.secondary.Exists(ctx, key) if err != nil { m.logger.Error("Secondary cache exists check failed", err, logging.Fields{ "key": key, }) return false, err } } else { return false, err } } return exists, nil } // Clear 清空缓存 func (m *CacheManager) Clear(ctx context.Context) error { m.mu.Lock() defer m.mu.Unlock() // 清空主缓存 err := m.primary.Clear(ctx) if err != nil { m.logger.Error("Primary cache clear failed", err) } // 清空备用缓存 if m.secondary != nil { if err := m.secondary.Clear(ctx); err != nil { m.logger.Error("Secondary cache clear failed", err) } } return err } // GetStats 获取统计信息 func (m *CacheManager) GetStats() *ManagerStats { m.mu.RLock() defer m.mu.RUnlock() // 返回统计信息的副本 return &ManagerStats{ TotalRequests: m.stats.TotalRequests, SuccessCount: m.stats.SuccessCount, ErrorCount: m.stats.ErrorCount, FallbackCount: m.stats.FallbackCount, SyncCount: m.stats.SyncCount, } } // ResetStats 重置统计信息 func (m *CacheManager) ResetStats() { m.mu.Lock() defer m.mu.Unlock() m.stats = &ManagerStats{} } // GetConfig 获取配置 func (m *CacheManager) GetConfig() *CacheManagerConfig { return m.config } // UpdateConfig 更新配置 func (m *CacheManager) UpdateConfig(config *CacheManagerConfig) { m.mu.Lock() defer m.mu.Unlock() m.config = config } ================================================ FILE: internal/infrastructure/cache/memory_cache.go ================================================ package cache import ( "context" "fmt" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // MemoryCache 内存缓存实现 type MemoryCache struct { data map[string]*cacheItem mutex sync.RWMutex logger logging.Logger maxSize int64 cleanupInterval time.Duration } // cacheItem 缓存项 type cacheItem struct { value interface{} expiresAt time.Time createdAt time.Time } // NewMemoryCache 创建内存缓存 func NewMemoryCache(logger logging.Logger, maxSize int64, cleanupInterval time.Duration) *MemoryCache { cache := &MemoryCache{ data: make(map[string]*cacheItem), logger: logger, maxSize: maxSize, cleanupInterval: cleanupInterval, } // 启动清理例程 go cache.startCleanupRoutine() return cache } // Get 获取值 func (c *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) { c.mutex.RLock() defer c.mutex.RUnlock() item, exists := c.data[key] if !exists { return nil, fmt.Errorf("key not found: %s", key) } // 检查是否过期 if time.Now().After(item.expiresAt) { delete(c.data, key) return nil, fmt.Errorf("key expired: %s", key) } c.logger.Info("缓存命中", map[string]interface{}{ "key": key, }) return item.value, nil } // Set 设置值 func (c *MemoryCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error { c.mutex.Lock() defer c.mutex.Unlock() // 检查缓存大小 if int64(len(c.data)) >= c.maxSize { c.evictOldest() } c.data[key] = &cacheItem{ value: value, expiresAt: time.Now().Add(ttl), createdAt: time.Now(), } c.logger.Info("缓存设置", map[string]interface{}{ "key": key, "ttl": ttl, }) return nil } // Delete 删除值 func (c *MemoryCache) Delete(ctx context.Context, key string) error { c.mutex.Lock() defer c.mutex.Unlock() delete(c.data, key) c.logger.Info("缓存删除", map[string]interface{}{ "key": key, }) return nil } // Exists 检查键是否存在 func (c *MemoryCache) Exists(ctx context.Context, key string) (bool, error) { c.mutex.RLock() defer c.mutex.RUnlock() item, exists := c.data[key] if !exists { return false, nil } // 检查是否过期 if time.Now().After(item.expiresAt) { delete(c.data, key) return false, nil } return true, nil } // Clear 清空缓存 func (c *MemoryCache) Clear(ctx context.Context) error { c.mutex.Lock() defer c.mutex.Unlock() c.data = make(map[string]*cacheItem) c.logger.Info("缓存清空") return nil } // Size 获取缓存大小 func (c *MemoryCache) Size() int64 { c.mutex.RLock() defer c.mutex.RUnlock() return int64(len(c.data)) } // Keys 获取所有键 func (c *MemoryCache) Keys() []string { c.mutex.RLock() defer c.mutex.RUnlock() keys := make([]string, 0, len(c.data)) for key := range c.data { keys = append(keys, key) } return keys } // evictOldest 驱逐最旧的项 func (c *MemoryCache) evictOldest() { var oldestKey string var oldestTime time.Time for key, item := range c.data { if oldestKey == "" || item.createdAt.Before(oldestTime) { oldestKey = key oldestTime = item.createdAt } } if oldestKey != "" { delete(c.data, oldestKey) c.logger.Info("驱逐最旧缓存项", map[string]interface{}{ "key": oldestKey, }) } } // startCleanupRoutine 启动清理例程 func (c *MemoryCache) startCleanupRoutine() { ticker := time.NewTicker(c.cleanupInterval) defer ticker.Stop() for range ticker.C { c.cleanupExpired() } } // cleanupExpired 清理过期项 func (c *MemoryCache) cleanupExpired() { c.mutex.Lock() defer c.mutex.Unlock() now := time.Now() expiredCount := 0 for key, item := range c.data { if now.After(item.expiresAt) { delete(c.data, key) expiredCount++ } } if expiredCount > 0 { c.logger.Info("清理过期缓存项", map[string]interface{}{ "expired_count": expiredCount, }) } } ================================================ FILE: internal/infrastructure/cache/redis_cache.go ================================================ package cache import ( "context" "encoding/json" "fmt" "time" "greatestworks/internal/infrastructure/logging" "github.com/redis/go-redis/v9" ) // RedisCache Redis缓存实现 type RedisCache struct { client *redis.Client logger logging.Logger } // NewRedisCache 创建Redis缓存 func NewRedisCache(client *redis.Client, logger logging.Logger) *RedisCache { return &RedisCache{ client: client, logger: logger, } } // Set 设置值 func (rc *RedisCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("序列化失败: %w", err) } err = rc.client.Set(ctx, key, data, ttl).Err() if err != nil { return fmt.Errorf("设置缓存失败: %w", err) } rc.logger.Info("缓存设置成功", map[string]interface{}{ "key": key, "ttl": ttl, }) return nil } // Get 获取值 func (rc *RedisCache) Get(ctx context.Context, key string, dest interface{}) error { data, err := rc.client.Get(ctx, key).Result() if err != nil { if err == redis.Nil { return fmt.Errorf("key not found: %s", key) } return fmt.Errorf("获取缓存失败: %w", err) } err = json.Unmarshal([]byte(data), dest) if err != nil { return fmt.Errorf("反序列化失败: %w", err) } rc.logger.Info("缓存获取成功", map[string]interface{}{ "key": key, }) return nil } // Delete 删除值 func (rc *RedisCache) Delete(ctx context.Context, key string) error { err := rc.client.Del(ctx, key).Err() if err != nil { return fmt.Errorf("删除缓存失败: %w", err) } rc.logger.Info("缓存删除成功", map[string]interface{}{ "key": key, }) return nil } // Exists 检查键是否存在 func (rc *RedisCache) Exists(ctx context.Context, key string) (bool, error) { count, err := rc.client.Exists(ctx, key).Result() if err != nil { return false, fmt.Errorf("检查缓存存在性失败: %w", err) } return count > 0, nil } // SetBatch 批量设置 func (rc *RedisCache) SetBatch(ctx context.Context, items map[string]interface{}, ttl time.Duration) error { pipe := rc.client.Pipeline() for key, value := range items { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("序列化失败: %w", err) } pipe.Set(ctx, key, data, ttl) } _, err := pipe.Exec(ctx) if err != nil { return fmt.Errorf("批量设置缓存失败: %w", err) } rc.logger.Info("批量缓存设置成功", map[string]interface{}{ "count": len(items), "ttl": ttl, }) return nil } // GetBatch 批量获取 func (rc *RedisCache) GetBatch(ctx context.Context, keys []string) (map[string]interface{}, error) { pipe := rc.client.Pipeline() cmds := make([]*redis.StringCmd, len(keys)) for i, key := range keys { cmds[i] = pipe.Get(ctx, key) } _, err := pipe.Exec(ctx) if err != nil && err != redis.Nil { return nil, fmt.Errorf("批量获取缓存失败: %w", err) } result := make(map[string]interface{}) for i, cmd := range cmds { if cmd.Err() == nil { var value interface{} err := json.Unmarshal([]byte(cmd.Val()), &value) if err == nil { result[keys[i]] = value } } } rc.logger.Info("批量缓存获取成功", map[string]interface{}{ "requested": len(keys), "found": len(result), }) return result, nil } // DeleteBatch 批量删除 func (rc *RedisCache) DeleteBatch(ctx context.Context, keys []string) error { err := rc.client.Del(ctx, keys...).Err() if err != nil { return fmt.Errorf("批量删除缓存失败: %w", err) } rc.logger.Info("批量缓存删除成功", map[string]interface{}{ "count": len(keys), }) return nil } // SetTTL 设置TTL func (rc *RedisCache) SetTTL(ctx context.Context, key string, ttl time.Duration) error { err := rc.client.Expire(ctx, key, ttl).Err() if err != nil { return fmt.Errorf("设置TTL失败: %w", err) } rc.logger.Info("TTL设置成功", map[string]interface{}{ "key": key, "ttl": ttl, }) return nil } // GetTTL 获取TTL func (rc *RedisCache) GetTTL(ctx context.Context, key string) (time.Duration, error) { ttl, err := rc.client.TTL(ctx, key).Result() if err != nil { return 0, fmt.Errorf("获取TTL失败: %w", err) } return ttl, nil } // Clear 清空缓存 func (rc *RedisCache) Clear(ctx context.Context) error { err := rc.client.FlushDB(ctx).Err() if err != nil { return fmt.Errorf("清空缓存失败: %w", err) } rc.logger.Info("缓存清空成功") return nil } // Size 获取缓存大小 func (rc *RedisCache) Size() int64 { // Redis没有直接的方法获取数据库大小 // 这里返回0,实际使用中可以通过INFO命令获取 return 0 } // Keys 获取所有键 func (rc *RedisCache) Keys() []string { // 注意:在生产环境中,KEYS命令可能会阻塞Redis // 建议使用SCAN命令进行迭代 keys, err := rc.client.Keys(context.Background(), "*").Result() if err != nil { rc.logger.Error("Failed to get keys", err) return []string{} } return keys } ================================================ FILE: internal/infrastructure/config/environments/config.dev.yaml ================================================ # 开发环境配置 # Author: MMO Server Team # Created: 2024 # 服务器配置 server: gateway: port: 8080 host: "0.0.0.0" max_connections: 1000 read_timeout: 30s write_timeout: 30s heartbeat_time: 30s idle_timeout: 300s tls_enabled: false cert_file: "" key_file: "" scene: port: 8081 host: "0.0.0.0" max_players: 500 tick_rate: 20 sync_interval: 100ms view_distance: 100.0 battle: port: 8082 host: "0.0.0.0" max_battles: 50 tick_rate: 30 battle_time: 10m match_timeout: 30s activity: port: 8083 host: "0.0.0.0" max_activities: 20 update_interval: 1m cache_timeout: 5m # 数据库配置 database: mongodb: uri: "${MONGODB_URI:-mongodb://localhost:27017}" database: "${MONGODB_DATABASE:-mmo_game_dev}" max_pool_size: 50 min_pool_size: 5 max_idle_time: 10m connect_timeout: 10s socket_timeout: 30s retry_writes: true read_preference: "primary" redis: addr: "${REDIS_ADDR:-localhost:6379}" password: "${REDIS_PASSWORD:-}" db: 0 pool_size: 10 min_idle_conns: 2 max_idle_conns: 5 conn_max_age: 30m dial_timeout: 5s read_timeout: 3s write_timeout: 3s cluster: enabled: false addresses: [] # 缓存配置 cache: default_ttl: 1h cleanup_time: 10m max_size: 104857600 # 100MB eviction_policy: "lru" # 安全配置 security: jwt: secret_key: "${JWT_SECRET_KEY:-dev-secret-key-change-in-production-32chars-minimum}" token_duration: 24h refresh_time: 168h # 7 days issuer: "mmo-server-dev" audience: "mmo-client" encryption: algorithm: "AES-256-GCM" key_size: 32 salt: "${ENCRYPTION_SALT:-dev-salt-change-in-production}" rate_limit: enabled: false # 开发环境关闭限流 rps: 100 burst: 200 window_size: 1m # 日志配置 logging: level: "debug" format: "console" output: "stdout" dir: "./logs" max_size: 100 max_backups: 7 max_age: 30 compress: false prefix: "mmo-dev" # 游戏配置 game: max_level: 100 exp_multiplier: 2.0 # 开发环境加速升级 gold_multiplier: 2.0 # 开发环境加速金币获取 drop_rate: 0.5 # 开发环境提高掉落率 pk_enabled: true guild_enabled: true trade_enabled: true maintenance_mode: false # 网络配置 network: protocol: "tcp" buffer_size: 4096 max_packet_size: 65536 compression_type: "none" # 开发环境关闭压缩 encryption_type: "none" # 开发环境关闭加密 keep_alive: true no_delay: true # 消息队列配置 messaging: nsq: enabled: false # 开发环境可选 nsqd_address: "${NSQD_ADDRESS:-localhost:4150}" lookupd_http: - "${NSQD_LOOKUPD:-localhost:4161}" max_in_flight: 200 rabbitmq: enabled: false # 开发环境可选 url: "${RABBITMQ_URL:-amqp://guest:guest@localhost:5672/}" exchange: "mmo_exchange_dev" queue: "mmo_queue_dev" # 监控配置 monitoring: enabled: true port: 9090 path: "/metrics" prometheus: enabled: true namespace: "mmo_dev" subsystem: "server" # Excel配置 excel: path: "./excel" activity: "activity.xlsx" battle_pass: "battlepass.xlsx" pet: "pet.xlsx" npc: "npc.xlsx" plant: "plant.xlsx" shop: "shop.xlsx" task: "task.xlsx" skill: "skill.xlsx" vip: "vip.xlsx" building: "building.xlsx" condition: "condition.xlsx" synthetise: "synthetise.xlsx" mini_game: "minigame.xlsx" email: "email.xlsx" ================================================ FILE: internal/infrastructure/config/environments/config.prod.yaml ================================================ # 生产环境配置 # Author: MMO Server Team # Created: 2024 # 服务器配置 server: gateway: port: 8080 host: "0.0.0.0" max_connections: 10000 read_timeout: 30s write_timeout: 30s heartbeat_time: 30s idle_timeout: 300s tls_enabled: true cert_file: "${TLS_CERT_FILE:/etc/ssl/certs/server.crt}" key_file: "${TLS_KEY_FILE:/etc/ssl/private/server.key}" scene: port: 8081 host: "0.0.0.0" max_players: 2000 tick_rate: 20 sync_interval: 50ms view_distance: 150.0 battle: port: 8082 host: "0.0.0.0" max_battles: 200 tick_rate: 30 battle_time: 10m match_timeout: 30s activity: port: 8083 host: "0.0.0.0" max_activities: 100 update_interval: 30s cache_timeout: 2m # 数据库配置 database: mongodb: uri: "${MONGODB_URI}" database: "${MONGODB_DATABASE}" max_pool_size: 200 min_pool_size: 20 max_idle_time: 5m connect_timeout: 10s socket_timeout: 30s retry_writes: true read_preference: "primaryPreferred" redis: addr: "${REDIS_ADDR}" password: "${REDIS_PASSWORD}" db: 0 pool_size: 50 min_idle_conns: 10 max_idle_conns: 20 conn_max_age: 10m dial_timeout: 5s read_timeout: 3s write_timeout: 3s cluster: enabled: ${REDIS_CLUSTER_ENABLED:false} addresses: - "${REDIS_CLUSTER_ADDR1}" - "${REDIS_CLUSTER_ADDR2}" - "${REDIS_CLUSTER_ADDR3}" # 缓存配置 cache: default_ttl: 30m cleanup_time: 5m max_size: 1073741824 # 1GB eviction_policy: "lru" # 安全配置 security: jwt: secret_key: "${JWT_SECRET_KEY}" token_duration: 2h refresh_time: 24h issuer: "mmo-server-prod" audience: "mmo-client" encryption: algorithm: "AES-256-GCM" key_size: 32 salt: "${ENCRYPTION_SALT}" rate_limit: enabled: true rps: 1000 burst: 2000 window_size: 1m # 日志配置 logging: level: "info" format: "json" output: "file" dir: "${LOG_DIR:/var/log/mmo}" max_size: 500 max_backups: 30 max_age: 90 compress: true prefix: "mmo-prod" # 游戏配置 game: max_level: 100 exp_multiplier: 1.0 gold_multiplier: 1.0 drop_rate: 0.1 pk_enabled: true guild_enabled: true trade_enabled: true maintenance_mode: ${MAINTENANCE_MODE:false} # 网络配置 network: protocol: "tcp" buffer_size: 8192 max_packet_size: 65536 compression_type: "gzip" encryption_type: "aes" keep_alive: true no_delay: true # 消息队列配置 messaging: nsq: enabled: ${NSQ_ENABLED:true} nsqd_address: "${NSQD_ADDRESS}" lookupd_http: - "${NSQD_LOOKUPD1}" - "${NSQD_LOOKUPD2}" max_in_flight: 500 rabbitmq: enabled: ${RABBITMQ_ENABLED:false} url: "${RABBITMQ_URL}" exchange: "mmo_exchange_prod" queue: "mmo_queue_prod" # 监控配置 monitoring: enabled: true port: 9090 path: "/metrics" prometheus: enabled: true namespace: "mmo_prod" subsystem: "server" # Excel配置 excel: path: "${EXCEL_PATH:/opt/mmo/excel}" activity: "activity.xlsx" battle_pass: "battlepass.xlsx" pet: "pet.xlsx" npc: "npc.xlsx" plant: "plant.xlsx" shop: "shop.xlsx" task: "task.xlsx" skill: "skill.xlsx" vip: "vip.xlsx" building: "building.xlsx" condition: "condition.xlsx" synthetise: "synthetise.xlsx" mini_game: "minigame.xlsx" email: "email.xlsx" ================================================ FILE: internal/infrastructure/config/environments/config.test.yaml ================================================ # 测试环境配置 # Author: MMO Server Team # Created: 2024 # 服务器配置 server: gateway: port: 18080 host: "127.0.0.1" max_connections: 100 read_timeout: 10s write_timeout: 10s heartbeat_time: 10s idle_timeout: 60s tls_enabled: false cert_file: "" key_file: "" scene: port: 18081 host: "127.0.0.1" max_players: 50 tick_rate: 10 sync_interval: 200ms view_distance: 50.0 battle: port: 18082 host: "127.0.0.1" max_battles: 10 tick_rate: 10 battle_time: 1m match_timeout: 5s activity: port: 18083 host: "127.0.0.1" max_activities: 5 update_interval: 10s cache_timeout: 30s # 数据库配置 database: mongodb: uri: "${MONGODB_URI:-mongodb://localhost:27017}" database: "${MONGODB_DATABASE:-mmo_game_test}" max_pool_size: 10 min_pool_size: 1 max_idle_time: 1m connect_timeout: 5s socket_timeout: 10s retry_writes: false read_preference: "primary" redis: addr: "${REDIS_ADDR:-localhost:6379}" password: "${REDIS_PASSWORD:-}" db: 1 # 使用不同的数据库 pool_size: 5 min_idle_conns: 1 max_idle_conns: 2 conn_max_age: 5m dial_timeout: 2s read_timeout: 1s write_timeout: 1s cluster: enabled: false addresses: [] # 缓存配置 cache: default_ttl: 5m cleanup_time: 1m max_size: 10485760 # 10MB eviction_policy: "lru" # 安全配置 security: jwt: secret_key: "${JWT_SECRET_KEY:-test-secret-key-for-testing-only-32chars}" token_duration: 1h refresh_time: 2h issuer: "mmo-server-test" audience: "mmo-client-test" encryption: algorithm: "AES-256-GCM" key_size: 32 salt: "${ENCRYPTION_SALT:-test-salt-for-testing-only}" rate_limit: enabled: false # 测试环境关闭限流 rps: 1000 burst: 2000 window_size: 1m # 日志配置 logging: level: "debug" format: "console" output: "stdout" dir: "./test_logs" max_size: 10 max_backups: 3 max_age: 7 compress: false prefix: "mmo-test" # 游戏配置 game: max_level: 10 # 测试环境降低等级上限 exp_multiplier: 10.0 # 测试环境快速升级 gold_multiplier: 10.0 # 测试环境快速获得金币 drop_rate: 1.0 # 测试环境100%掉落 pk_enabled: true guild_enabled: true trade_enabled: true maintenance_mode: false # 网络配置 network: protocol: "tcp" buffer_size: 1024 max_packet_size: 8192 compression_type: "none" encryption_type: "none" keep_alive: false no_delay: true # 消息队列配置 messaging: nsq: enabled: false # 测试环境关闭消息队列 nsqd_address: "localhost:4150" lookupd_http: - "localhost:4161" max_in_flight: 10 rabbitmq: enabled: false # 测试环境关闭消息队列 url: "amqp://guest:guest@localhost:5672/" exchange: "mmo_exchange_test" queue: "mmo_queue_test" # 监控配置 monitoring: enabled: false # 测试环境关闭监控 port: 19090 path: "/metrics" prometheus: enabled: false namespace: "mmo_test" subsystem: "server" # Excel配置 excel: path: "./test_excel" activity: "test_activity.xlsx" battle_pass: "test_battlepass.xlsx" pet: "test_pet.xlsx" npc: "test_npc.xlsx" plant: "test_plant.xlsx" shop: "test_shop.xlsx" task: "test_task.xlsx" skill: "test_skill.xlsx" vip: "test_vip.xlsx" building: "test_building.xlsx" condition: "test_condition.xlsx" synthetise: "test_synthetise.xlsx" mini_game: "test_minigame.xlsx" email: "test_email.xlsx" ================================================ FILE: internal/infrastructure/config/file_watcher.go ================================================ package config import ( "context" "fmt" "os" "path/filepath" "sync" "time" "greatestworks/internal/infrastructure/logging" "github.com/fsnotify/fsnotify" ) // FileWatcher 文件监听器 type FileWatcher struct { filePath string callback func(string) logger logging.Logger watcher *fsnotify.Watcher ctx context.Context cancel context.CancelFunc mu sync.RWMutex isRunning bool lastModTime time.Time debounce time.Duration } // WatcherConfig 监听器配置 type WatcherConfig struct { DebounceInterval time.Duration `json:"debounce_interval" yaml:"debounce_interval"` WatchDirectory bool `json:"watch_directory" yaml:"watch_directory"` Recursive bool `json:"recursive" yaml:"recursive"` IgnorePatterns []string `json:"ignore_patterns" yaml:"ignore_patterns"` } // Watcher 文件监听器接口 type Watcher interface { // Start 启动监听 Start() error // Stop 停止监听 Stop() error // IsRunning 检查是否正在运行 IsRunning() bool // AddPath 添加监听路径 AddPath(path string) error // RemovePath 移除监听路径 RemovePath(path string) error // SetCallback 设置回调函数 SetCallback(callback func(string)) } // NewFileWatcher 创建文件监听器 func NewFileWatcher(filePath string, callback func(string), logger logging.Logger) (*FileWatcher, error) { // 检查文件是否存在 if _, err := os.Stat(filePath); os.IsNotExist(err) { return nil, fmt.Errorf("file does not exist: %s", filePath) } // 创建fsnotify监听器 watcher, err := fsnotify.NewWatcher() if err != nil { return nil, fmt.Errorf("failed to create fsnotify watcher: %w", err) } ctx, cancel := context.WithCancel(context.Background()) // 获取文件修改时间 fileInfo, err := os.Stat(filePath) if err != nil { return nil, fmt.Errorf("failed to get file info: %w", err) } fw := &FileWatcher{ filePath: filePath, callback: callback, logger: logger, watcher: watcher, ctx: ctx, cancel: cancel, isRunning: false, lastModTime: fileInfo.ModTime(), debounce: 100 * time.Millisecond, // 默认防抖时间 } logger.Debug("File watcher created", logging.Fields{ "file": filePath, }) return fw, nil } // Start 启动监听 func (fw *FileWatcher) Start() error { fw.mu.Lock() defer fw.mu.Unlock() if fw.isRunning { return fmt.Errorf("file watcher is already running") } // 添加文件到监听列表 // 对于文件,我们监听其所在的目录 dir := filepath.Dir(fw.filePath) if err := fw.watcher.Add(dir); err != nil { return fmt.Errorf("failed to add directory to watcher: %w", err) } fw.isRunning = true // 启动监听协程 go fw.watchLoop() fw.logger.Info("File watcher started", logging.Fields{ "file": fw.filePath, }) return nil } // Stop 停止监听 func (fw *FileWatcher) Stop() error { fw.mu.Lock() defer fw.mu.Unlock() if !fw.isRunning { return nil } // 取消上下文 fw.cancel() // 关闭fsnotify监听器 if err := fw.watcher.Close(); err != nil { fw.logger.Error("Failed to close fsnotify watcher", err) } fw.isRunning = false fw.logger.Info("File watcher stopped", logging.Fields{ "file": fw.filePath, }) return nil } // IsRunning 检查是否正在运行 func (fw *FileWatcher) IsRunning() bool { fw.mu.RLock() defer fw.mu.RUnlock() return fw.isRunning } // AddPath 添加监听路径 func (fw *FileWatcher) AddPath(path string) error { fw.mu.Lock() defer fw.mu.Unlock() if !fw.isRunning { return fmt.Errorf("file watcher is not running") } if err := fw.watcher.Add(path); err != nil { return fmt.Errorf("failed to add path to watcher: %w", err) } fw.logger.Debug("Path added to watcher", logging.Fields{ "path": path, }) return nil } // RemovePath 移除监听路径 func (fw *FileWatcher) RemovePath(path string) error { fw.mu.Lock() defer fw.mu.Unlock() if !fw.isRunning { return fmt.Errorf("file watcher is not running") } if err := fw.watcher.Remove(path); err != nil { return fmt.Errorf("failed to remove path from watcher: %w", err) } fw.logger.Debug("Path removed from watcher", logging.Fields{ "path": path, }) return nil } // SetCallback 设置回调函数 func (fw *FileWatcher) SetCallback(callback func(string)) { fw.mu.Lock() defer fw.mu.Unlock() fw.callback = callback fw.logger.Debug("Callback function updated") } // 私有方法 // watchLoop 监听循环 func (fw *FileWatcher) watchLoop() { fw.logger.Debug("File watcher loop started") for { select { case event, ok := <-fw.watcher.Events: if !ok { fw.logger.Debug("File watcher events channel closed") return } fw.handleEvent(event) case err, ok := <-fw.watcher.Errors: if !ok { fw.logger.Debug("File watcher errors channel closed") return } fw.logger.Error("File watcher error", err) case <-fw.ctx.Done(): fw.logger.Debug("File watcher context cancelled") return } } } // handleEvent 处理文件事件 func (fw *FileWatcher) handleEvent(event fsnotify.Event) { // 只处理我们关心的文件 if event.Name != fw.filePath { return } fw.logger.Debug("File event received", logging.Fields{ "event": event.Op.String(), "file": event.Name, }) // 检查事件类型 if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { // 文件被写入或创建 fw.handleFileChange(event.Name) } else if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename { // 文件被删除或重命名 fw.handleFileRemove(event.Name) } } // handleFileChange 处理文件变化 func (fw *FileWatcher) handleFileChange(filePath string) { // 获取文件信息 fileInfo, err := os.Stat(filePath) if err != nil { fw.logger.Error("Failed to get file info after change", err, logging.Fields{ "file": filePath, }) return } // 检查修改时间,实现防抖 fw.mu.Lock() lastModTime := fw.lastModTime fw.lastModTime = fileInfo.ModTime() fw.mu.Unlock() if fileInfo.ModTime().Sub(lastModTime) < fw.debounce { fw.logger.Debug("File change ignored due to debounce", logging.Fields{ "file": filePath, }) return } // 延迟一小段时间,确保文件写入完成 time.Sleep(fw.debounce) // 再次检查文件是否存在(可能在写入过程中被删除) if _, err := os.Stat(filePath); os.IsNotExist(err) { fw.logger.Debug("File no longer exists after change", logging.Fields{ "file": filePath, }) return } fw.logger.Info("File changed", logging.Fields{ "file": filePath, }) // 调用回调函数 fw.mu.RLock() callback := fw.callback fw.mu.RUnlock() if callback != nil { go func() { defer func() { if r := recover(); r != nil { fw.logger.Error("Panic in file change callback", fmt.Errorf("panic: %v", r), logging.Fields{ "file": filePath, }) } }() callback(filePath) }() } } // handleFileRemove 处理文件删除 func (fw *FileWatcher) handleFileRemove(filePath string) { fw.logger.Warn("File removed or renamed", logging.Fields{ "file": filePath, }) // 可以选择停止监听或等待文件重新创建 // 这里我们选择继续监听,等待文件重新创建 } ================================================ FILE: internal/infrastructure/config/unified.json ================================================ { "environment": "development", "server": { "host": "0.0.0.0", "port": 8080, "read_timeout": "60s", "write_timeout": "60s", "max_conns": 10000 }, "database": { "host": "localhost", "port": 27017, "username": "dev_user", "password": "dev_password", "database": "mmo_game_dev", "max_open_conns": 100, "max_idle_conns": 10, "max_lifetime": "3600s", "ssl": false, "timeout": "10s" }, "redis": { "host": "localhost", "port": 6379, "password": "", "db": 0, "pool_size": 10, "min_idle_conn": 5, "max_retries": 3, "dial_timeout": "5s", "read_timeout": "3s", "write_timeout": "3s" }, "nats": { "url": "nats://localhost:4222", "cluster_id": "mmo-dev-cluster", "client_id": "mmo-dev-server", "max_reconnect": 10, "reconnect_wait": "2s", "timeout": "5s" }, "gateway": { "host": "0.0.0.0", "port": 8000, "max_connections": 10000, "servers": [ "localhost:8001", "localhost:8002", "localhost:8003", "localhost:8004" ], "load_balancer": "round_robin", "health_check": { "enabled": true, "interval": "30s", "timeout": "5s", "path": "/health" } }, "scene": { "scene_id": "dev_scene_001", "host": "0.0.0.0", "port": 8001, "gateway_addr": "localhost:8000", "max_players": 1000, "tick_rate": 20, "aoi": { "enabled": true, "radius": 100.0, "update_rate": 10 } }, "battle": { "server_id": "dev_battle_001", "host": "0.0.0.0", "port": 8002, "gateway_addr": "localhost:8000", "max_battles": 100, "tick_rate": 30 }, "activity": { "server_id": "dev_activity_001", "host": "0.0.0.0", "port": 8003, "gateway_addr": "localhost:8000" }, "login": { "server_id": "dev_login_001", "host": "0.0.0.0", "port": 8004, "gateway_addr": "localhost:8000", "jwt_secret": "dev_jwt_secret_key_change_in_production", "token_expiry": "24h" }, "log": { "level": "debug", "format": "text", "output": "stdout", "max_size": 100, "max_backups": 5, "max_age": 7, "compress": true }, "game": { "max_level": 100, "exp_table": [0, 100, 250, 450, 700, 1000, 1350, 1750, 2200, 2700, 3250], "inventory_size": 50, "max_skill_points": 1000, "daily_quest_limit": 10, "weekly_quest_limit": 5, "features": { "pvp": true, "guild": true, "trade": true, "auction": true, "chat": true, "mail": true, "friend": true, "achievement": true, "pet": true, "building": true, "minigame": true, "ranking": true }, "constants": { "base_exp": 100, "exp_multiplier": 1.5, "gold_drop_rate": 0.1, "item_drop_rate": 0.05, "skill_point_per_level": 2, "max_friends": 100, "max_guild_members": 50, "trade_tax_rate": 0.05, "auction_fee_rate": 0.1 } }, "security": { "encryption": { "enabled": false, "algorithm": "AES-256", "key": "dev_encryption_key_32_characters" }, "rate_limit": { "enabled": true, "rate": 100, "burst": 200, "window": "1m" }, "anti_cheat": { "enabled": true, "speed_check_enabled": true, "max_speed": 10.0, "position_check": true, "action_validation": true }, "whitelist": [], "blacklist": [] }, "metrics": { "enabled": true, "port": 9090, "path": "/metrics", "namespace": "mmo_game", "subsystem": "server" } } ================================================ FILE: internal/infrastructure/config/unified.prod.json ================================================ { "environment": "production", "server": { "host": "0.0.0.0", "port": 8080, "read_timeout": "60s", "write_timeout": "60s", "max_conns": 50000, "tls": { "enabled": true, "cert_file": "/etc/ssl/certs/server.crt", "key_file": "/etc/ssl/private/server.key" } }, "database": { "host": "mongodb-cluster.example.com", "port": 27017, "username": "prod_user", "password": "prod_secure_password", "database": "mmo_game_prod", "max_open_conns": 500, "max_idle_conns": 50, "max_lifetime": "7200s", "ssl": true, "timeout": "10s" }, "redis": { "host": "redis-cluster.example.com", "port": 6379, "password": "redis_secure_password", "db": 0, "pool_size": 50, "min_idle_conn": 20, "max_retries": 5, "dial_timeout": "5s", "read_timeout": "3s", "write_timeout": "3s" }, "nats": { "url": "nats://nats-cluster.example.com:4222", "cluster_id": "mmo-prod-cluster", "client_id": "mmo-prod-server", "max_reconnect": 20, "reconnect_wait": "2s", "timeout": "10s" }, "gateway": { "host": "0.0.0.0", "port": 8000, "max_connections": 50000, "servers": [ "scene-server-1.example.com:8001", "scene-server-2.example.com:8001", "battle-server-1.example.com:8002", "battle-server-2.example.com:8002", "activity-server.example.com:8003", "login-server.example.com:8004" ], "load_balancer": "weighted_round_robin", "health_check": { "enabled": true, "interval": "15s", "timeout": "3s", "path": "/health" } }, "scene": { "scene_id": "prod_scene_001", "host": "0.0.0.0", "port": 8001, "gateway_addr": "gateway.example.com:8000", "max_players": 5000, "tick_rate": 30, "aoi": { "enabled": true, "radius": 150.0, "update_rate": 15 } }, "battle": { "server_id": "prod_battle_001", "host": "0.0.0.0", "port": 8002, "gateway_addr": "gateway.example.com:8000", "max_battles": 1000, "tick_rate": 60 }, "activity": { "server_id": "prod_activity_001", "host": "0.0.0.0", "port": 8003, "gateway_addr": "gateway.example.com:8000" }, "login": { "server_id": "prod_login_001", "host": "0.0.0.0", "port": 8004, "gateway_addr": "gateway.example.com:8000", "jwt_secret": "prod_jwt_ultra_secure_key_2024_change_me", "token_expiry": "12h" }, "log": { "level": "warn", "format": "json", "output": "/var/log/mmo-server/app.log", "max_size": 500, "max_backups": 10, "max_age": 30, "compress": true }, "game": { "max_level": 120, "exp_table": [0, 100, 250, 450, 700, 1000, 1350, 1750, 2200, 2700, 3250, 3850, 4500, 5200, 5950, 6750, 7600, 8500, 9450, 10450, 11500], "inventory_size": 100, "max_skill_points": 2000, "daily_quest_limit": 20, "weekly_quest_limit": 10, "features": { "pvp": true, "guild": true, "trade": true, "auction": true, "chat": true, "mail": true, "friend": true, "achievement": true, "pet": true, "building": true, "minigame": true, "ranking": true, "world_boss": true, "cross_server": true }, "constants": { "base_exp": 100, "exp_multiplier": 1.2, "gold_drop_rate": 0.08, "item_drop_rate": 0.03, "skill_point_per_level": 3, "max_friends": 200, "max_guild_members": 100, "trade_tax_rate": 0.03, "auction_fee_rate": 0.08, "world_boss_spawn_interval": 3600, "cross_server_battle_cooldown": 86400 } }, "security": { "encryption": { "enabled": true, "algorithm": "AES-256-GCM", "key": "prod_encryption_key_32_chars_here" }, "rate_limit": { "enabled": true, "rate": 200, "burst": 500, "window": "1m" }, "anti_cheat": { "enabled": true, "speed_check_enabled": true, "max_speed": 8.0, "position_check": true, "action_validation": true }, "whitelist": [ "admin.example.com", "monitoring.example.com" ], "blacklist": [] }, "metrics": { "enabled": true, "port": 9090, "path": "/metrics", "namespace": "mmo_game", "subsystem": "server" } } ================================================ FILE: internal/infrastructure/config/unified_config.go ================================================ // Package config 统一配置管理 // Author: MMO Server Team // Created: 2024 package config import ( "fmt" "os" "strings" "sync" "time" "gopkg.in/yaml.v3" ) // Config 主配置结构 type Config struct { App AppConfig `yaml:"app"` Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` Cache CacheConfig `yaml:"cache"` Security SecurityConfig `yaml:"security"` Logging LoggingConfig `yaml:"logging"` Game GameConfig `yaml:"game"` Network NetworkConfig `yaml:"network"` Messaging MessagingConfig `yaml:"messaging"` Monitoring MonitoringConfig `yaml:"monitoring"` } // ConfigLoader 配置加载器 type ConfigLoader struct { configPath string } // NewConfigLoader 创建配置加载器 func NewConfigLoader(configPath string) *ConfigLoader { return &ConfigLoader{ configPath: configPath, } } // Load 加载配置 func (cl *ConfigLoader) Load() (*Config, error) { return LoadConfig(cl.configPath) } // AppConfig 应用配置 type AppConfig struct { Name string `yaml:"name"` Version string `yaml:"version"` Environment string `yaml:"environment"` Debug bool `yaml:"debug"` } // ServerConfig 服务器配置 type ServerConfig struct { HTTP HTTPServerConfig `yaml:"http"` WebSocket WebSocketServerConfig `yaml:"websocket"` TCP TCPServerConfig `yaml:"tcp"` Metrics MetricsServerConfig `yaml:"metrics"` } // HTTPServerConfig HTTP服务器配置 type HTTPServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` MaxHeaderBytes int `yaml:"max_header_bytes"` EnableCORS bool `yaml:"enable_cors"` EnableMetrics bool `yaml:"enable_metrics"` EnableRequestID bool `yaml:"enable_request_id"` EnableLogging bool `yaml:"enable_logging"` EnableRecovery bool `yaml:"enable_recovery"` RateLimitEnabled bool `yaml:"rate_limit_enabled"` RateLimitRequests int `yaml:"rate_limit_requests"` RateLimitDuration time.Duration `yaml:"rate_limit_duration"` } // WebSocketServerConfig WebSocket服务器配置 type WebSocketServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` ReadBufferSize int `yaml:"read_buffer_size"` WriteBufferSize int `yaml:"write_buffer_size"` CheckOrigin bool `yaml:"check_origin"` } // TCPServerConfig TCP服务器配置 type TCPServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` MaxConnections int `yaml:"max_connections"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` HeartbeatInterval time.Duration `yaml:"heartbeat_interval"` KeepAliveInterval time.Duration `yaml:"keep_alive_interval"` MaxPacketSize int `yaml:"max_packet_size"` CompressionEnabled bool `yaml:"compression_enabled"` EncryptionEnabled bool `yaml:"encryption_enabled"` EnableMetrics bool `yaml:"enable_metrics"` BufferSize int `yaml:"buffer_size"` } // MetricsServerConfig 指标服务器配置 type MetricsServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Path string `yaml:"path"` } // DatabaseConfig 数据库配置 type DatabaseConfig struct { MongoDB MongoDBConfig `yaml:"mongodb"` Redis RedisConfig `yaml:"redis"` } // MongoDBConfig MongoDB配置 type MongoDBConfig struct { URI string `yaml:"uri"` Database string `yaml:"database"` Username string `yaml:"username"` Password string `yaml:"password"` AuthSource string `yaml:"auth_source"` MaxPoolSize int `yaml:"max_pool_size"` MinPoolSize int `yaml:"min_pool_size"` MaxIdleTime time.Duration `yaml:"max_idle_time"` ConnectTimeout time.Duration `yaml:"connect_timeout"` SocketTimeout time.Duration `yaml:"socket_timeout"` RetryWrites bool `yaml:"retry_writes"` ReadPreference string `yaml:"read_preference"` ReplicaSet string `yaml:"replica_set"` WriteConcern WriteConcern `yaml:"write_concern"` } // WriteConcern 写关注配置 type WriteConcern struct { W string `yaml:"w"` J bool `yaml:"j"` WTimeout time.Duration `yaml:"wtimeout"` } // RedisConfig Redis配置 type RedisConfig struct { Addr string `yaml:"addr"` Password string `yaml:"password"` DB int `yaml:"db"` PoolSize int `yaml:"pool_size"` MinIdleConns int `yaml:"min_idle_conns"` MaxIdleConns int `yaml:"max_idle_conns"` ConnMaxAge time.Duration `yaml:"conn_max_age"` DialTimeout time.Duration `yaml:"dial_timeout"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` PoolTimeout time.Duration `yaml:"pool_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` MaxRetries int `yaml:"max_retries"` Cluster ClusterConfig `yaml:"cluster"` } // ClusterConfig Redis集群配置 type ClusterConfig struct { Enabled bool `yaml:"enabled"` Addresses []string `yaml:"addresses"` } // CacheConfig 缓存配置 type CacheConfig struct { DefaultTTL time.Duration `yaml:"default_ttl"` MaxEntries int64 `yaml:"max_entries"` CleanupInterval time.Duration `yaml:"cleanup_interval"` EvictionPolicy string `yaml:"eviction_policy"` } // SecurityConfig 安全配置 type SecurityConfig struct { JWT JWTConfig `yaml:"jwt"` Encryption EncryptionConfig `yaml:"encryption"` CORS CORSConfig `yaml:"cors"` TLS TLSConfig `yaml:"tls"` } // JWTConfig JWT配置 type JWTConfig struct { Secret string `yaml:"secret"` Issuer string `yaml:"issuer"` Audience string `yaml:"audience"` AccessTokenTTL time.Duration `yaml:"access_token_ttl"` RefreshTokenTTL time.Duration `yaml:"refresh_token_ttl"` } // EncryptionConfig 加密配置 type EncryptionConfig struct { Key string `yaml:"key"` Algorithm string `yaml:"algorithm"` } // CORSConfig CORS配置 type CORSConfig struct { AllowedOrigins []string `yaml:"allowed_origins"` AllowedMethods []string `yaml:"allowed_methods"` AllowedHeaders []string `yaml:"allowed_headers"` ExposeHeaders []string `yaml:"expose_headers"` AllowCredentials bool `yaml:"allow_credentials"` MaxAge int `yaml:"max_age"` } // TLSConfig TLS配置 type TLSConfig struct { Enabled bool `yaml:"enabled"` CertFile string `yaml:"cert_file"` KeyFile string `yaml:"key_file"` MinVersion string `yaml:"min_version"` } // LoggingConfig 日志配置 type LoggingConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` Output string `yaml:"output"` File FileLogConfig `yaml:"file"` Fields map[string]string `yaml:"fields"` Sensitive []string `yaml:"sensitive_fields"` } // FileLogConfig 文件日志配置 type FileLogConfig struct { Path string `yaml:"path"` MaxSize int `yaml:"max_size"` MaxBackups int `yaml:"max_backups"` MaxAge int `yaml:"max_age"` Compress bool `yaml:"compress"` } // GameConfig 游戏配置 type GameConfig struct { Player PlayerConfig `yaml:"player"` Battle BattleConfig `yaml:"battle"` Experience ExperienceConfig `yaml:"experience"` Chat ChatConfig `yaml:"chat"` } // PlayerConfig 玩家配置 type PlayerConfig struct { MaxLevel int `yaml:"max_level"` InitialGold int64 `yaml:"initial_gold"` InitialExperience int64 `yaml:"initial_experience"` MaxInventorySlots int `yaml:"max_inventory_slots"` } // BattleConfig 战斗配置 type BattleConfig struct { MaxBattleTime time.Duration `yaml:"max_battle_time"` DamageVariance float64 `yaml:"damage_variance"` CriticalRateBase float64 `yaml:"critical_rate_base"` CriticalDamageBase float64 `yaml:"critical_damage_base"` } // ExperienceConfig 经验配置 type ExperienceConfig struct { BaseExpPerLevel int `yaml:"base_exp_per_level"` ExpMultiplier float64 `yaml:"exp_multiplier"` MaxExpBonus float64 `yaml:"max_exp_bonus"` } // ChatConfig 聊天配置 type ChatConfig struct { MaxMessageLength int `yaml:"max_message_length"` RateLimit int `yaml:"rate_limit"` BannedWords []string `yaml:"banned_words"` ProfanityFilter bool `yaml:"profanity_filter"` } // NetworkConfig 网络配置 type NetworkConfig struct { Protocol string `yaml:"protocol"` BufferSize int `yaml:"buffer_size"` MaxPacketSize int `yaml:"max_packet_size"` CompressionType string `yaml:"compression_type"` EncryptionType string `yaml:"encryption_type"` KeepAlive bool `yaml:"keep_alive"` NoDelay bool `yaml:"no_delay"` } // MessagingConfig 消息队列配置 type MessagingConfig struct { NATS NATSConfig `yaml:"nats"` } // NATSConfig NATS配置 type NATSConfig struct { URL string `yaml:"url"` ClusterID string `yaml:"cluster_id"` ClientID string `yaml:"client_id"` MaxReconnect int `yaml:"max_reconnect"` ReconnectWait time.Duration `yaml:"reconnect_wait"` Timeout time.Duration `yaml:"timeout"` TLS TLSConfig `yaml:"tls"` JetStream JetStreamConfig `yaml:"jetstream"` Subjects SubjectsConfig `yaml:"subjects"` } // JetStreamConfig JetStream配置 type JetStreamConfig struct { Enabled bool `yaml:"enabled"` Domain string `yaml:"domain"` } // SubjectsConfig 主题配置 type SubjectsConfig struct { PlayerEvents string `yaml:"player_events"` GameEvents string `yaml:"game_events"` SystemEvents string `yaml:"system_events"` } // MonitoringConfig 监控配置 type MonitoringConfig struct { Health HealthConfig `yaml:"health"` Metrics MetricsConfig `yaml:"metrics"` Tracing TracingConfig `yaml:"tracing"` Profiling ProfilingConfig `yaml:"profiling"` Alerting AlertingConfig `yaml:"alerting"` Audit AuditConfig `yaml:"audit"` } // HealthConfig 健康检查配置 type HealthConfig struct { Enabled bool `yaml:"enabled"` Path string `yaml:"path"` } // MetricsConfig 指标配置 type MetricsConfig struct { Enabled bool `yaml:"enabled"` Namespace string `yaml:"namespace"` } // TracingConfig 链路追踪配置 type TracingConfig struct { Enabled bool `yaml:"enabled"` JaegerEndpoint string `yaml:"jaeger_endpoint"` SampleRate float64 `yaml:"sample_rate"` } // ProfilingConfig 性能分析配置 type ProfilingConfig struct { Enabled bool `yaml:"enabled"` Host string `yaml:"host"` Port int `yaml:"port"` } // AlertingConfig 告警配置 type AlertingConfig struct { Enabled bool `yaml:"enabled"` WebhookURL string `yaml:"webhook_url"` } // AuditConfig 审计配置 type AuditConfig struct { Enabled bool `yaml:"enabled"` LogFile string `yaml:"log_file"` RetentionDays int `yaml:"retention_days"` } // 全局配置实例 var ( globalConfig *Config configMutex sync.RWMutex configOnce sync.Once ) // LoadConfig 加载配置文件 func LoadConfig(configPath string) (*Config, error) { // 确定配置文件路径 if configPath == "" { env := os.Getenv("APP_ENV") if env == "" { env = "development" } configPath = fmt.Sprintf("configs/config.%s.yaml", env) } // 读取配置文件 data, err := os.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("failed to read config file %s: %w", configPath, err) } // 环境变量替换 configContent := os.ExpandEnv(string(data)) // 解析YAML var config Config if err := yaml.Unmarshal([]byte(configContent), &config); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } // 设置默认值 setDefaults(&config) // 验证配置 if err := validateConfig(&config); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } return &config, nil } // InitConfig 初始化全局配置 func InitConfig(configPath string) error { var err error configOnce.Do(func() { globalConfig, err = LoadConfig(configPath) }) return err } // GetConfig 获取全局配置 func GetConfig() *Config { configMutex.RLock() defer configMutex.RUnlock() return globalConfig } // ReloadConfig 重新加载配置 func ReloadConfig(configPath string) error { newConfig, err := LoadConfig(configPath) if err != nil { return err } configMutex.Lock() globalConfig = newConfig configMutex.Unlock() return nil } // setDefaults 设置默认值 func setDefaults(config *Config) { // 应用默认配置 if config.App.Name == "" { config.App.Name = "GreatestWorks MMO Server" } if config.App.Version == "" { config.App.Version = "1.0.0" } if config.App.Environment == "" { config.App.Environment = "development" } // HTTP服务器默认配置 if config.Server.HTTP.Host == "" { config.Server.HTTP.Host = "0.0.0.0" } if config.Server.HTTP.Port == 0 { config.Server.HTTP.Port = 8080 } if config.Server.HTTP.ReadTimeout == 0 { config.Server.HTTP.ReadTimeout = 30 * time.Second } if config.Server.HTTP.WriteTimeout == 0 { config.Server.HTTP.WriteTimeout = 30 * time.Second } if config.Server.HTTP.IdleTimeout == 0 { config.Server.HTTP.IdleTimeout = 60 * time.Second } // TCP服务器默认配置 if config.Server.TCP.Host == "" { config.Server.TCP.Host = "0.0.0.0" } if config.Server.TCP.Port == 0 { config.Server.TCP.Port = 8082 } if config.Server.TCP.MaxConnections == 0 { config.Server.TCP.MaxConnections = 10000 } // MongoDB默认配置 if config.Database.MongoDB.URI == "" { config.Database.MongoDB.URI = "mongodb://localhost:27017" } if config.Database.MongoDB.Database == "" { config.Database.MongoDB.Database = "mmo_game" } if config.Database.MongoDB.MaxPoolSize == 0 { config.Database.MongoDB.MaxPoolSize = 100 } if config.Database.MongoDB.MinPoolSize == 0 { config.Database.MongoDB.MinPoolSize = 10 } // Redis默认配置 if config.Database.Redis.Addr == "" { config.Database.Redis.Addr = "localhost:6379" } if config.Database.Redis.PoolSize == 0 { config.Database.Redis.PoolSize = 100 } if config.Database.Redis.MinIdleConns == 0 { config.Database.Redis.MinIdleConns = 10 } // 日志默认配置 if config.Logging.Level == "" { config.Logging.Level = "info" } if config.Logging.Format == "" { config.Logging.Format = "json" } if config.Logging.Output == "" { config.Logging.Output = "stdout" } // 游戏默认配置 if config.Game.Player.MaxLevel == 0 { config.Game.Player.MaxLevel = 100 } if config.Game.Player.InitialGold == 0 { config.Game.Player.InitialGold = 1000 } } // validateConfig 验证配置 func validateConfig(config *Config) error { // 验证必需的配置项 if config.Database.MongoDB.URI == "" { return fmt.Errorf("MongoDB URI is required") } if config.Database.MongoDB.Database == "" { return fmt.Errorf("MongoDB database name is required") } if config.Database.Redis.Addr == "" { return fmt.Errorf("Redis address is required") } if config.Security.JWT.Secret == "" { return fmt.Errorf("JWT secret is required") } // 验证端口范围 if config.Server.HTTP.Port < 1 || config.Server.HTTP.Port > 65535 { return fmt.Errorf("invalid HTTP server port: %d", config.Server.HTTP.Port) } if config.Server.TCP.Port < 1 || config.Server.TCP.Port > 65535 { return fmt.Errorf("invalid TCP server port: %d", config.Server.TCP.Port) } // 验证游戏配置 if config.Game.Player.MaxLevel < 1 { return fmt.Errorf("player max level must be greater than 0") } return nil } // GetEnvironment 获取当前环境 func GetEnvironment() string { env := os.Getenv("APP_ENV") if env == "" { env = "development" } return strings.ToLower(env) } // IsProduction 是否为生产环境 func IsProduction() bool { return GetEnvironment() == "production" } // IsDevelopment 是否为开发环境 func IsDevelopment() bool { return GetEnvironment() == "development" } ================================================ FILE: internal/infrastructure/container/container.go ================================================ // Package container provides dependency injection container functionality package container import ( "context" "fmt" "reflect" "sync" ) // Container represents a dependency injection container type Container struct { mu sync.RWMutex services map[string]*ServiceDescriptor instances map[string]interface{} scopes map[string]*Scope parent *Container } // ServiceDescriptor describes how to create a service type ServiceDescriptor struct { Name string ServiceType reflect.Type Lifetime Lifetime Factory FactoryFunc Instance interface{} Dependencies []string } // FactoryFunc is a function that creates a service instance type FactoryFunc func(container *Container) (interface{}, error) // Lifetime defines the lifetime of a service type Lifetime int const ( // Transient creates a new instance every time Transient Lifetime = iota // Singleton creates a single instance for the container lifetime Singleton // Scoped creates a single instance per scope Scoped ) // Scope represents a service scope type Scope struct { mu sync.RWMutex instances map[string]interface{} parent *Container closed bool } // ServiceProvider defines the interface for service providers type ServiceProvider interface { RegisterServices(container *Container) error } // Lifecycle defines the interface for services with lifecycle management type Lifecycle interface { Start(ctx context.Context) error Stop(ctx context.Context) error } // NewContainer creates a new dependency injection container func NewContainer() *Container { return &Container{ services: make(map[string]*ServiceDescriptor), instances: make(map[string]interface{}), scopes: make(map[string]*Scope), } } // NewChildContainer creates a child container func (c *Container) NewChildContainer() *Container { return &Container{ services: make(map[string]*ServiceDescriptor), instances: make(map[string]interface{}), scopes: make(map[string]*Scope), parent: c, } } // RegisterTransient registers a transient service func (c *Container) RegisterTransient(name string, factory FactoryFunc, dependencies ...string) { c.register(name, &ServiceDescriptor{ Name: name, Lifetime: Transient, Factory: factory, Dependencies: dependencies, }) } // RegisterSingleton registers a singleton service func (c *Container) RegisterSingleton(name string, factory FactoryFunc, dependencies ...string) { c.register(name, &ServiceDescriptor{ Name: name, Lifetime: Singleton, Factory: factory, Dependencies: dependencies, }) } // RegisterScoped registers a scoped service func (c *Container) RegisterScoped(name string, factory FactoryFunc, dependencies ...string) { c.register(name, &ServiceDescriptor{ Name: name, Lifetime: Scoped, Factory: factory, Dependencies: dependencies, }) } // RegisterInstance registers a service instance func (c *Container) RegisterInstance(name string, instance interface{}) { c.register(name, &ServiceDescriptor{ Name: name, ServiceType: reflect.TypeOf(instance), Lifetime: Singleton, Instance: instance, }) } // RegisterProvider registers a service provider func (c *Container) RegisterProvider(provider ServiceProvider) error { return provider.RegisterServices(c) } // register registers a service descriptor func (c *Container) register(name string, descriptor *ServiceDescriptor) { c.mu.Lock() defer c.mu.Unlock() c.services[name] = descriptor } // Resolve resolves a service by name func (c *Container) Resolve(name string) (interface{}, error) { return c.ResolveWithScope(name, nil) } // ResolveWithScope resolves a service with a specific scope func (c *Container) ResolveWithScope(name string, scope *Scope) (interface{}, error) { c.mu.RLock() descriptor, exists := c.services[name] c.mu.RUnlock() if !exists { // Try parent container if c.parent != nil { return c.parent.ResolveWithScope(name, scope) } return nil, fmt.Errorf("service '%s' not registered", name) } return c.createInstance(descriptor, scope) } // createInstance creates a service instance based on its descriptor func (c *Container) createInstance(descriptor *ServiceDescriptor, scope *Scope) (interface{}, error) { switch descriptor.Lifetime { case Singleton: return c.getSingletonInstance(descriptor) case Scoped: if scope == nil { return nil, fmt.Errorf("scoped service '%s' requires a scope", descriptor.Name) } return c.getScopedInstance(descriptor, scope) case Transient: return c.createTransientInstance(descriptor, scope) default: return nil, fmt.Errorf("unknown lifetime for service '%s'", descriptor.Name) } } // getSingletonInstance gets or creates a singleton instance func (c *Container) getSingletonInstance(descriptor *ServiceDescriptor) (interface{}, error) { c.mu.Lock() defer c.mu.Unlock() if descriptor.Instance != nil { return descriptor.Instance, nil } if instance, exists := c.instances[descriptor.Name]; exists { return instance, nil } instance, err := c.createInstanceInternal(descriptor, nil) if err != nil { return nil, err } c.instances[descriptor.Name] = instance return instance, nil } // getScopedInstance gets or creates a scoped instance func (c *Container) getScopedInstance(descriptor *ServiceDescriptor, scope *Scope) (interface{}, error) { scope.mu.Lock() defer scope.mu.Unlock() if scope.closed { return nil, fmt.Errorf("scope is closed") } if instance, exists := scope.instances[descriptor.Name]; exists { return instance, nil } instance, err := c.createInstanceInternal(descriptor, scope) if err != nil { return nil, err } scope.instances[descriptor.Name] = instance return instance, nil } // createTransientInstance creates a new transient instance func (c *Container) createTransientInstance(descriptor *ServiceDescriptor, scope *Scope) (interface{}, error) { return c.createInstanceInternal(descriptor, scope) } // createInstanceInternal creates an instance using the factory function func (c *Container) createInstanceInternal(descriptor *ServiceDescriptor, scope *Scope) (interface{}, error) { if descriptor.Factory == nil { return nil, fmt.Errorf("no factory function for service '%s'", descriptor.Name) } // Resolve dependencies first for _, dep := range descriptor.Dependencies { _, err := c.ResolveWithScope(dep, scope) if err != nil { return nil, fmt.Errorf("failed to resolve dependency '%s' for service '%s': %w", dep, descriptor.Name, err) } } return descriptor.Factory(c) } // CreateScope creates a new service scope func (c *Container) CreateScope() *Scope { return &Scope{ instances: make(map[string]interface{}), parent: c, closed: false, } } // Close closes the scope and disposes scoped services func (s *Scope) Close() error { s.mu.Lock() defer s.mu.Unlock() if s.closed { return nil } // Dispose services that implement Lifecycle for _, instance := range s.instances { if lifecycle, ok := instance.(Lifecycle); ok { if err := lifecycle.Stop(context.Background()); err != nil { // Log error but continue disposing other services continue } } } s.instances = nil s.closed = true return nil } // IsRegistered checks if a service is registered func (c *Container) IsRegistered(name string) bool { c.mu.RLock() defer c.mu.RUnlock() _, exists := c.services[name] if !exists && c.parent != nil { return c.parent.IsRegistered(name) } return exists } // GetServiceNames returns all registered service names func (c *Container) GetServiceNames() []string { c.mu.RLock() defer c.mu.RUnlock() names := make([]string, 0, len(c.services)) for name := range c.services { names = append(names, name) } return names } // StartServices starts all services that implement Lifecycle func (c *Container) StartServices(ctx context.Context) error { c.mu.RLock() services := make([]*ServiceDescriptor, 0, len(c.services)) for _, service := range c.services { services = append(services, service) } c.mu.RUnlock() for _, service := range services { instance, err := c.Resolve(service.Name) if err != nil { return fmt.Errorf("failed to resolve service '%s': %w", service.Name, err) } if lifecycle, ok := instance.(Lifecycle); ok { if err := lifecycle.Start(ctx); err != nil { return fmt.Errorf("failed to start service '%s': %w", service.Name, err) } } } return nil } // StopServices stops all services that implement Lifecycle func (c *Container) StopServices(ctx context.Context) error { c.mu.RLock() instances := make([]interface{}, 0, len(c.instances)) for _, instance := range c.instances { instances = append(instances, instance) } c.mu.RUnlock() // Stop services in reverse order for i := len(instances) - 1; i >= 0; i-- { if lifecycle, ok := instances[i].(Lifecycle); ok { if err := lifecycle.Stop(ctx); err != nil { // Log error but continue stopping other services continue } } } return nil } // Dispose disposes the container and all its services func (c *Container) Dispose() error { ctx := context.Background() return c.StopServices(ctx) } ================================================ FILE: internal/infrastructure/container/providers.go ================================================ // Package container provides service providers for dependency injection package container import ( "fmt" "greatestworks/internal/config" ) // ConfigProvider provides configuration services type ConfigProvider struct { configPath string } // NewConfigProvider creates a new config provider func NewConfigProvider(configPath string) *ConfigProvider { return &ConfigProvider{ configPath: configPath, } } // RegisterServices registers configuration services func (cp *ConfigProvider) RegisterServices(container *Container) error { // Register config loader container.RegisterSingleton("config.loader", func(c *Container) (interface{}, error) { options := []config.Option{} if cp.configPath != "" { options = append(options, config.WithExplicitFiles(cp.configPath)) } loader := config.NewLoader(options...) return loader, nil }) // Register main configuration container.RegisterSingleton("config", func(c *Container) (interface{}, error) { resolved, err := c.Resolve("config.loader") if err != nil { return nil, err } loader := resolved.(*config.Loader) cfg, _, err := loader.Load() if err != nil { return nil, err } return cfg, nil }, "config.loader") return nil } // LoggingProvider provides logging services type LoggingProvider struct{} // NewLoggingProvider creates a new logging provider func NewLoggingProvider() *LoggingProvider { return &LoggingProvider{} } // RegisterServices registers logging services func (lp *LoggingProvider) RegisterServices(container *Container) error { // Register logger container.RegisterSingleton("logger", func(c *Container) (interface{}, error) { // TODO: 实现日志配置 return nil, fmt.Errorf("logging not implemented") }) return nil } // MonitoringProvider provides monitoring services type MonitoringProvider struct{} // NewMonitoringProvider creates a new monitoring provider func NewMonitoringProvider() *MonitoringProvider { return &MonitoringProvider{} } // RegisterServices registers monitoring services func (mp *MonitoringProvider) RegisterServices(container *Container) error { // TODO: 实现监控配置 return nil } // PersistenceProvider provides persistence services type PersistenceProvider struct{} // NewPersistenceProvider creates a new persistence provider func NewPersistenceProvider() *PersistenceProvider { return &PersistenceProvider{} } // RegisterServices registers persistence services func (pp *PersistenceProvider) RegisterServices(container *Container) error { // TODO: 实现持久化配置 return nil } // ProtocolProvider provides protocol services type ProtocolProvider struct{} // NewProtocolProvider creates a new protocol provider func NewProtocolProvider() *ProtocolProvider { return &ProtocolProvider{} } // RegisterServices registers protocol services func (pp *ProtocolProvider) RegisterServices(container *Container) error { // TODO: 实现协议配置 return nil } // WeaveProvider provides Service Weaver integration services type WeaveProvider struct{} // NewWeaveProvider creates a new weave provider func NewWeaveProvider() *WeaveProvider { return &WeaveProvider{} } // RegisterServices registers weave services func (wp *WeaveProvider) RegisterServices(container *Container) error { // TODO: 实现Weave配置 return nil } // AllProvidersProvider combines all service providers type AllProvidersProvider struct { configPath string } // NewAllProvidersProvider creates a provider that registers all services func NewAllProvidersProvider(configPath string) *AllProvidersProvider { return &AllProvidersProvider{ configPath: configPath, } } // RegisterServices registers all services from all providers func (app *AllProvidersProvider) RegisterServices(container *Container) error { providers := []ServiceProvider{ NewConfigProvider(app.configPath), NewLoggingProvider(), NewMonitoringProvider(), NewPersistenceProvider(), NewProtocolProvider(), NewWeaveProvider(), } for _, provider := range providers { if err := container.RegisterProvider(provider); err != nil { return err } } return nil } ================================================ FILE: internal/infrastructure/container/simple_container.go ================================================ // Package container 提供简化的依赖注入容器 package container import ( "context" "fmt" "reflect" "sync" ) // SimpleContainer 简化的依赖注入容器 type SimpleContainer struct { mu sync.RWMutex services map[string]interface{} factories map[string]func() (interface{}, error) } // NewSimpleContainer 创建新的简化容器 func NewSimpleContainer() *SimpleContainer { return &SimpleContainer{ services: make(map[string]interface{}), factories: make(map[string]func() (interface{}, error)), } } // RegisterSingleton 注册单例服务 func (c *SimpleContainer) RegisterSingleton(name string, factory func() (interface{}, error)) { c.mu.Lock() defer c.mu.Unlock() c.factories[name] = factory } // RegisterInstance 注册服务实例 func (c *SimpleContainer) RegisterInstance(name string, instance interface{}) { c.mu.Lock() defer c.mu.Unlock() c.services[name] = instance } // RegisterTransient 注册瞬态服务 func (c *SimpleContainer) RegisterTransient(name string, factory func() (interface{}, error)) { c.mu.Lock() defer c.mu.Unlock() // 对于瞬态服务,每次都调用工厂函数 c.factories[name] = factory } // Resolve 解析服务 func (c *SimpleContainer) Resolve(name string) (interface{}, error) { c.mu.RLock() instance, exists := c.services[name] c.mu.RUnlock() if exists { return instance, nil } c.mu.RLock() factory, exists := c.factories[name] c.mu.RUnlock() if !exists { return nil, fmt.Errorf("service '%s' not registered", name) } // 创建实例 instance, err := factory() if err != nil { return nil, fmt.Errorf("failed to create service '%s': %w", name, err) } // 检查是否为单例 c.mu.Lock() if _, isSingleton := c.services[name]; !isSingleton { // 如果是单例,缓存实例 c.services[name] = instance } c.mu.Unlock() return instance, nil } // ResolveTyped 解析类型化服务 func ResolveTyped[T any](c *SimpleContainer, name string) (T, error) { instance, err := c.Resolve(name) if err != nil { var zero T return zero, err } typed, ok := instance.(T) if !ok { var zero T return zero, fmt.Errorf("service '%s' is not of type %T", name, zero) } return typed, nil } // AutoRegister 自动注册服务(通过反射) func (c *SimpleContainer) AutoRegister(service interface{}) error { serviceType := reflect.TypeOf(service) if serviceType.Kind() == reflect.Ptr { serviceType = serviceType.Elem() } name := serviceType.Name() factory := func() (interface{}, error) { return service, nil } c.RegisterSingleton(name, factory) return nil } // RegisterWithDependencies 注册带依赖的服务 func (c *SimpleContainer) RegisterWithDependencies(name string, factory func(container *SimpleContainer) (interface{}, error)) { c.RegisterSingleton(name, func() (interface{}, error) { return factory(c) }) } // IsRegistered 检查服务是否已注册 func (c *SimpleContainer) IsRegistered(name string) bool { c.mu.RLock() defer c.mu.RUnlock() _, exists := c.services[name] if !exists { _, exists = c.factories[name] } return exists } // GetServiceNames 获取所有已注册的服务名称 func (c *SimpleContainer) GetServiceNames() []string { c.mu.RLock() defer c.mu.RUnlock() names := make([]string, 0, len(c.services)+len(c.factories)) for name := range c.services { names = append(names, name) } for name := range c.factories { if _, exists := c.services[name]; !exists { names = append(names, name) } } return names } // Clear 清空容器 func (c *SimpleContainer) Clear() { c.mu.Lock() defer c.mu.Unlock() c.services = make(map[string]interface{}) c.factories = make(map[string]func() (interface{}, error)) } // StartServices 启动所有实现Lifecycle接口的服务 func (c *SimpleContainer) StartServices(ctx context.Context) error { c.mu.RLock() services := make([]interface{}, 0, len(c.services)) for _, service := range c.services { services = append(services, service) } c.mu.RUnlock() for _, service := range services { if lifecycle, ok := service.(Lifecycle); ok { if err := lifecycle.Start(ctx); err != nil { return fmt.Errorf("failed to start service: %w", err) } } } return nil } // StopServices 停止所有实现Lifecycle接口的服务 func (c *SimpleContainer) StopServices(ctx context.Context) error { c.mu.RLock() services := make([]interface{}, 0, len(c.services)) for _, service := range c.services { services = append(services, service) } c.mu.RUnlock() // 逆序停止服务 for i := len(services) - 1; i >= 0; i-- { if lifecycle, ok := services[i].(Lifecycle); ok { if err := lifecycle.Stop(ctx); err != nil { // 记录错误但继续停止其他服务 continue } } } return nil } ================================================ FILE: internal/infrastructure/datamanager/data_manager.go ================================================ package datamanager import ( "encoding/json" "fmt" "os" "sync" ) // UnitDefine 单位定义 type UnitDefine struct { ID int32 `json:"id"` Name string `json:"name"` Type int32 `json:"type"` // 1=玩家 2=怪物 3=NPC Level int32 `json:"level"` MaxHP int32 `json:"max_hp"` MaxMP int32 `json:"max_mp"` STR int32 `json:"str"` INT int32 `json:"int"` AGI int32 `json:"agi"` VIT int32 `json:"vit"` SPR int32 `json:"spr"` AD int32 `json:"ad"` AP int32 `json:"ap"` DEF int32 `json:"def"` RES int32 `json:"res"` SPD int32 `json:"spd"` MoveSpeed float32 `json:"move_speed"` Skills []int32 `json:"skills"` AIType int32 `json:"ai_type"` NPCType int32 `json:"npc_type"` } // SkillDefine 技能定义 type SkillDefine struct { ID int32 `json:"id"` Name string `json:"name"` Type int32 `json:"type"` // 技能类型 BaseDamage int32 `json:"base_damage"` ScaleAD float32 `json:"scale_ad"` ScaleAP float32 `json:"scale_ap"` DamageType int32 `json:"damage_type"` // 1=物理 2=魔法 3=真实 Cooldown float32 `json:"cooldown"` CastTime float32 `json:"cast_time"` Range float32 `json:"range"` MPCost int32 `json:"mp_cost"` TargetType int32 `json:"target_type"` BuffID int32 `json:"buff_id"` } // ItemDefine 物品定义 type ItemDefine struct { ID int32 `json:"id"` Name string `json:"name"` Type int32 `json:"type"` // 1=消耗品 2=装备 3=材料 Quality int32 `json:"quality"` MaxStack int32 `json:"max_stack"` Price int32 `json:"price"` SellPrice int32 `json:"sell_price"` EquipSlot int32 `json:"equip_slot"` Description string `json:"description"` } // MapDefine 地图定义 type MapDefine struct { ID int32 `json:"id"` Name string `json:"name"` Width int32 `json:"width"` Height int32 `json:"height"` } // QuestDefine 任务定义 type QuestDefine struct { ID int32 `json:"id"` Name string `json:"name"` Description string `json:"description"` Level int32 `json:"level"` Objectives []QuestObjective `json:"objectives"` } // QuestObjective 任务目标 type QuestObjective struct { Type int32 `json:"type"` TargetID int32 `json:"target_id"` Required int32 `json:"required"` } // DataManager 数据管理器 type DataManager struct { mu sync.RWMutex unitDefines map[int32]*UnitDefine skillDefines map[int32]*SkillDefine itemDefines map[int32]*ItemDefine mapDefines map[int32]*MapDefine questDefines map[int32]*QuestDefine } var instance *DataManager var once sync.Once // GetInstance 获取单例实例 func GetInstance() *DataManager { once.Do(func() { instance = &DataManager{ unitDefines: make(map[int32]*UnitDefine), skillDefines: make(map[int32]*SkillDefine), itemDefines: make(map[int32]*ItemDefine), mapDefines: make(map[int32]*MapDefine), questDefines: make(map[int32]*QuestDefine), } }) return instance } // LoadAll 加载所有配置 func (dm *DataManager) LoadAll(configPath string) error { if err := dm.LoadUnits(configPath + "/units.json"); err != nil { return fmt.Errorf("load units failed: %w", err) } if err := dm.LoadSkills(configPath + "/skills.json"); err != nil { return fmt.Errorf("load skills failed: %w", err) } if err := dm.LoadItems(configPath + "/items.json"); err != nil { return fmt.Errorf("load items failed: %w", err) } if err := dm.LoadMaps(configPath + "/maps.json"); err != nil { return fmt.Errorf("load maps failed: %w", err) } if err := dm.LoadQuests(configPath + "/quests.json"); err != nil { return fmt.Errorf("load quests failed: %w", err) } return nil } // LoadUnits 加载单位配置 func (dm *DataManager) LoadUnits(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return err } var units []*UnitDefine if err := json.Unmarshal(data, &units); err != nil { return err } dm.mu.Lock() defer dm.mu.Unlock() for _, unit := range units { dm.unitDefines[unit.ID] = unit } return nil } // LoadSkills 加载技能配置 func (dm *DataManager) LoadSkills(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return err } var skills []*SkillDefine if err := json.Unmarshal(data, &skills); err != nil { return err } dm.mu.Lock() defer dm.mu.Unlock() for _, skill := range skills { dm.skillDefines[skill.ID] = skill } return nil } // LoadItems 加载物品配置 func (dm *DataManager) LoadItems(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return err } var items []*ItemDefine if err := json.Unmarshal(data, &items); err != nil { return err } dm.mu.Lock() defer dm.mu.Unlock() for _, item := range items { dm.itemDefines[item.ID] = item } return nil } // LoadMaps 加载地图配置 func (dm *DataManager) LoadMaps(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return err } var maps []*MapDefine if err := json.Unmarshal(data, &maps); err != nil { return err } dm.mu.Lock() defer dm.mu.Unlock() for _, m := range maps { dm.mapDefines[m.ID] = m } return nil } // LoadQuests 加载任务配置 func (dm *DataManager) LoadQuests(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return err } var quests []*QuestDefine if err := json.Unmarshal(data, &quests); err != nil { return err } dm.mu.Lock() defer dm.mu.Unlock() for _, quest := range quests { dm.questDefines[quest.ID] = quest } return nil } // GetUnitDefine 获取单位定义 func (dm *DataManager) GetUnitDefine(id int32) *UnitDefine { dm.mu.RLock() defer dm.mu.RUnlock() return dm.unitDefines[id] } // GetSkillDefine 获取技能定义 func (dm *DataManager) GetSkillDefine(id int32) *SkillDefine { dm.mu.RLock() defer dm.mu.RUnlock() return dm.skillDefines[id] } // GetItemDefine 获取物品定义 func (dm *DataManager) GetItemDefine(id int32) *ItemDefine { dm.mu.RLock() defer dm.mu.RUnlock() return dm.itemDefines[id] } // GetMapDefine 获取地图定义 func (dm *DataManager) GetMapDefine(id int32) *MapDefine { dm.mu.RLock() defer dm.mu.RUnlock() return dm.mapDefines[id] } // GetQuestDefine 获取任务定义 func (dm *DataManager) GetQuestDefine(id int32) *QuestDefine { dm.mu.RLock() defer dm.mu.RUnlock() return dm.questDefines[id] } // GetUnit 获取单位定义(简短别名) func (dm *DataManager) GetUnit(id int32) *UnitDefine { return dm.GetUnitDefine(id) } // GetSkill 获取技能定义(简短别名) func (dm *DataManager) GetSkill(id int32) *SkillDefine { return dm.GetSkillDefine(id) } // GetItem 获取物品定义(简短别名) func (dm *DataManager) GetItem(id int32) *ItemDefine { return dm.GetItemDefine(id) } // GetMap 获取地图定义(简短别名) func (dm *DataManager) GetMap(id int32) *MapDefine { return dm.GetMapDefine(id) } // GetQuest 获取任务定义(简短别名) func (dm *DataManager) GetQuest(id int32) *QuestDefine { return dm.GetQuestDefine(id) } ================================================ FILE: internal/infrastructure/errors/errors.go ================================================ package errors import ( "fmt" "net/http" "runtime" "time" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // ErrorCode 错误码类型 type ErrorCode int // 预定义错误码 const ( // 通用错误码(1000-1999) ErrUnknown ErrorCode = 1000 + iota ErrInternal ErrInvalidInput ErrNotFound ErrUnauthorized ErrForbidden ErrConflict ErrTimeout ErrRateLimit ErrServiceUnavailable // 认证相关错误码(2000-2999) ErrAuthTokenMissing ErrAuthTokenInvalid ErrAuthTokenExpired ErrAuthUserNotFound ErrAuthPasswordIncorrect ErrAuthUserAlreadyExists ErrAuthUserDisabled // 玩家相关错误码(3000-3999) ErrPlayerNotFound ErrPlayerOffline ErrPlayerAlreadyExists ErrPlayerInvalidName ErrPlayerInvalidLevel ErrPlayerInsufficientExp ErrPlayerDead ErrPlayerInvalidPosition ErrPlayerVersionMismatch // 战斗相关错误码(4000-4999) ErrBattleNotFound ErrBattleAlreadyStarted ErrBattleNotInProgress ErrPlayerNotInBattle ErrPlayerAlreadyInBattle ErrInsufficientParticipants ErrPlayerDeadInBattle ErrInvalidAction ErrActionOnCooldown ErrInsufficientMana ErrInvalidTarget ErrBattleFinished ErrBattleAlreadyFinished ErrBattleNotFinished // 数据库相关错误码(5000-5999) ErrDatabaseConnection ErrDatabaseQuery ErrDatabaseTransaction ErrDatabaseConstraint ErrDatabaseTimeout // 网络相关错误码(6000-6999) ErrNetworkConnection ErrNetworkTimeout ErrNetworkUnreachable ErrNetworkInvalidResponse ) // Error 自定义错误结构 type Error struct { Code ErrorCode `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` Timestamp time.Time `json:"timestamp"` File string `json:"file,omitempty"` Line int `json:"line,omitempty"` Stack string `json:"stack,omitempty"` } // Error 实现error接口 func (e *Error) Error() string { if e.Details != "" { return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Details) } return fmt.Sprintf("[%d] %s", e.Code, e.Message) } // NewError 创建新错误 func NewError(code ErrorCode, message string) *Error { _, file, line, _ := runtime.Caller(1) return &Error{ Code: code, Message: message, Timestamp: time.Now(), File: file, Line: line, } } // NewErrorWithDetails 创建带详情的错误 func NewErrorWithDetails(code ErrorCode, message, details string) *Error { _, file, line, _ := runtime.Caller(1) return &Error{ Code: code, Message: message, Details: details, Timestamp: time.Now(), File: file, Line: line, } } // NewErrorWithStack 创建带堆栈的错误 func NewErrorWithStack(code ErrorCode, message string) *Error { _, file, line, _ := runtime.Caller(1) stack := make([]byte, 1024) length := runtime.Stack(stack, false) return &Error{ Code: code, Message: message, Timestamp: time.Now(), File: file, Line: line, Stack: string(stack[:length]), } } // GetHTTPStatus 获取HTTP状态码 func (e *Error) GetHTTPStatus() int { switch e.Code { case ErrNotFound, ErrPlayerNotFound, ErrBattleNotFound: return http.StatusNotFound case ErrUnauthorized, ErrAuthTokenMissing, ErrAuthTokenInvalid, ErrAuthTokenExpired: return http.StatusUnauthorized case ErrForbidden, ErrAuthUserDisabled: return http.StatusForbidden case ErrConflict, ErrPlayerAlreadyExists, ErrPlayerAlreadyInBattle: return http.StatusConflict case ErrInvalidInput, ErrPlayerInvalidName, ErrPlayerInvalidLevel, ErrPlayerInvalidPosition: return http.StatusBadRequest case ErrTimeout, ErrDatabaseTimeout, ErrNetworkTimeout: return http.StatusRequestTimeout case ErrRateLimit: return http.StatusTooManyRequests case ErrServiceUnavailable, ErrDatabaseConnection, ErrNetworkConnection: return http.StatusServiceUnavailable default: return http.StatusInternalServerError } } // 预定义错误 var ( // 通用错误 ErrUnknownError = NewError(ErrUnknown, "未知错误") ErrInternalError = NewError(ErrInternal, "内部错误") ErrInvalidInputError = NewError(ErrInvalidInput, "无效输入") ErrNotFoundError = NewError(ErrNotFound, "资源不存在") ErrUnauthorizedError = NewError(ErrUnauthorized, "未授权") ErrForbiddenError = NewError(ErrForbidden, "禁止访问") ErrConflictError = NewError(ErrConflict, "冲突") ErrTimeoutError = NewError(ErrTimeout, "超时") ErrRateLimitError = NewError(ErrRateLimit, "请求过于频繁") ErrServiceUnavailableError = NewError(ErrServiceUnavailable, "服务不可用") // 认证相关错误 ErrAuthTokenMissingError = NewError(ErrAuthTokenMissing, "缺少认证令牌") ErrAuthTokenInvalidError = NewError(ErrAuthTokenInvalid, "无效的认证令牌") ErrAuthTokenExpiredError = NewError(ErrAuthTokenExpired, "认证令牌已过期") ErrAuthUserNotFoundError = NewError(ErrAuthUserNotFound, "用户不存在") ErrAuthPasswordIncorrectError = NewError(ErrAuthPasswordIncorrect, "密码错误") ErrAuthUserAlreadyExistsError = NewError(ErrAuthUserAlreadyExists, "用户已存在") ErrAuthUserDisabledError = NewError(ErrAuthUserDisabled, "用户已被禁用") // 玩家相关错误 ErrPlayerNotFoundError = NewError(ErrPlayerNotFound, "玩家不存在") ErrPlayerOfflineError = NewError(ErrPlayerOffline, "玩家已离线") ErrPlayerAlreadyExistsError = NewError(ErrPlayerAlreadyExists, "玩家已存在") ErrPlayerInvalidNameError = NewError(ErrPlayerInvalidName, "无效的玩家名称") ErrPlayerInvalidLevelError = NewError(ErrPlayerInvalidLevel, "无效的等级") ErrPlayerInsufficientExpError = NewError(ErrPlayerInsufficientExp, "经验值不足") ErrPlayerDeadError = NewError(ErrPlayerDead, "玩家已死亡") ErrPlayerInvalidPositionError = NewError(ErrPlayerInvalidPosition, "无效的位置") ErrPlayerVersionMismatchError = NewError(ErrPlayerVersionMismatch, "版本不匹配") // 战斗相关错误 ErrBattleNotFoundError = NewError(ErrBattleNotFound, "战斗不存在") ErrBattleAlreadyStartedError = NewError(ErrBattleAlreadyStarted, "战斗已开始") ErrBattleNotInProgressError = NewError(ErrBattleNotInProgress, "战斗未进行中") ErrPlayerNotInBattleError = NewError(ErrPlayerNotInBattle, "玩家不在战斗中") ErrPlayerAlreadyInBattleError = NewError(ErrPlayerAlreadyInBattle, "玩家已在战斗中") ErrInsufficientParticipantsError = NewError(ErrInsufficientParticipants, "参与者不足") ErrInvalidActionError = NewError(ErrInvalidAction, "无效的行动") ErrActionOnCooldownError = NewError(ErrActionOnCooldown, "行动冷却中") ErrInsufficientManaError = NewError(ErrInsufficientMana, "魔法值不足") ErrInvalidTargetError = NewError(ErrInvalidTarget, "无效的目标") ErrBattleFinishedError = NewError(ErrBattleFinished, "战斗已结束") ErrBattleAlreadyFinishedError = NewError(ErrBattleAlreadyFinished, "战斗已结束") ErrBattleNotFinishedError = NewError(ErrBattleNotFinished, "战斗未结束") // 数据库相关错误 ErrDatabaseConnectionError = NewError(ErrDatabaseConnection, "数据库连接失败") ErrDatabaseQueryError = NewError(ErrDatabaseQuery, "数据库查询失败") ErrDatabaseTransactionError = NewError(ErrDatabaseTransaction, "数据库事务失败") ErrDatabaseConstraintError = NewError(ErrDatabaseConstraint, "数据库约束违反") ErrDatabaseTimeoutError = NewError(ErrDatabaseTimeout, "数据库操作超时") // 网络相关错误 ErrNetworkConnectionError = NewError(ErrNetworkConnection, "网络连接失败") ErrNetworkTimeoutError = NewError(ErrNetworkTimeout, "网络超时") ErrNetworkUnreachableError = NewError(ErrNetworkUnreachable, "网络不可达") ErrNetworkInvalidResponseError = NewError(ErrNetworkInvalidResponse, "无效的网络响应") ) // ErrorHandler 错误处理器 type ErrorHandler struct { logger logging.Logger } // NewErrorHandler 创建错误处理器 func NewErrorHandler(logger logging.Logger) *ErrorHandler { return &ErrorHandler{ logger: logger, } } // HandleError 处理错误 func (h *ErrorHandler) HandleError(c *gin.Context, err error) { // 记录错误日志 h.logger.Error("Request error", err, logging.Fields{ "path": c.Request.URL.Path, "method": c.Request.Method, }) // 检查是否为自定义错误 if customErr, ok := err.(*Error); ok { c.JSON(customErr.GetHTTPStatus(), gin.H{ "error": gin.H{ "code": customErr.Code, "message": customErr.Message, "details": customErr.Details, "timestamp": customErr.Timestamp, }, }) return } // 处理其他错误 c.JSON(http.StatusInternalServerError, gin.H{ "error": gin.H{ "code": ErrInternal, "message": "内部错误", "timestamp": time.Now(), }, }) } // HandlePanic 处理panic func (h *ErrorHandler) HandlePanic(c *gin.Context, recovered interface{}) { // 记录panic日志 h.logger.Error("Panic recovered", fmt.Errorf("panic: %v", recovered), logging.Fields{ "path": c.Request.URL.Path, "method": c.Request.Method, }) c.JSON(http.StatusInternalServerError, gin.H{ "error": gin.H{ "code": ErrInternal, "message": "内部错误", "timestamp": time.Now(), }, }) } // IsError 检查是否为特定错误 func IsError(err error, code ErrorCode) bool { if customErr, ok := err.(*Error); ok { return customErr.Code == code } return false } // GetErrorCode 获取错误码 func GetErrorCode(err error) ErrorCode { if customErr, ok := err.(*Error); ok { return customErr.Code } return ErrUnknown } ================================================ FILE: internal/infrastructure/logging/logger.go ================================================ // Package logging 提供统一的日志记录接口 package logging import ( "context" "time" ) // Level 日志级别 type Level int const ( DebugLevel Level = iota InfoLevel WarnLevel ErrorLevel FatalLevel ) // String 返回日志级别的字符串表示 func (l Level) String() string { switch l { case DebugLevel: return "DEBUG" case InfoLevel: return "INFO" case WarnLevel: return "WARN" case ErrorLevel: return "ERROR" case FatalLevel: return "FATAL" default: return "UNKNOWN" } } // Fields 日志字段类型 type Fields map[string]interface{} // Logger 日志记录器接口 type Logger interface { // 基础日志方法 Debug(msg string, fields ...Fields) Info(msg string, fields ...Fields) Warn(msg string, fields ...Fields) Error(msg string, err error, fields ...Fields) Fatal(msg string, err error, fields ...Fields) // 带上下文的日志方法 DebugWithContext(ctx context.Context, msg string, fields ...Fields) InfoWithContext(ctx context.Context, msg string, fields ...Fields) WarnWithContext(ctx context.Context, msg string, fields ...Fields) ErrorWithContext(ctx context.Context, msg string, err error, fields ...Fields) FatalWithContext(ctx context.Context, msg string, err error, fields ...Fields) // 结构化日志方法 WithFields(fields Fields) Logger WithField(key string, value interface{}) Logger WithError(err error) Logger WithContext(ctx context.Context) Logger // 设置日志级别 SetLevel(level Level) GetLevel() Level // 关闭日志记录器 Close() error } // LogEntry 日志条目 type LogEntry struct { Level Level `json:"level"` Timestamp time.Time `json:"timestamp"` Message string `json:"message"` Fields map[string]interface{} `json:"fields,omitempty"` Error string `json:"error,omitempty"` TraceID string `json:"trace_id,omitempty"` SpanID string `json:"span_id,omitempty"` } // Formatter 日志格式化器接口 type Formatter interface { Format(entry *LogEntry) ([]byte, error) } // Writer 日志写入器接口 type Writer interface { Write(entry *LogEntry) error Close() error } // Config 日志配置 type Config struct { Level Level `json:"level"` Format string `json:"format"` // json, text Output string `json:"output"` // stdout, stderr, file FilePath string `json:"file_path"` // 文件路径(当output为file时) MaxSize int `json:"max_size"` // 文件最大大小(MB) MaxBackups int `json:"max_backups"` // 最大备份文件数 MaxAge int `json:"max_age"` // 文件最大保存天数 Compress bool `json:"compress"` // 是否压缩备份文件 } // NewLogger 创建新的日志记录器 func NewLogger(config *Config) (Logger, error) { // 这里应该根据配置创建具体的日志记录器实现 // 为了简化,这里返回一个默认实现 return &defaultLogger{ level: config.Level, fields: make(Fields), }, nil } // defaultLogger 默认日志记录器实现 type defaultLogger struct { level Level fields Fields } func (l *defaultLogger) Debug(msg string, fields ...Fields) { l.log(DebugLevel, msg, nil, fields...) } func (l *defaultLogger) Info(msg string, fields ...Fields) { l.log(InfoLevel, msg, nil, fields...) } func (l *defaultLogger) Warn(msg string, fields ...Fields) { l.log(WarnLevel, msg, nil, fields...) } func (l *defaultLogger) Error(msg string, err error, fields ...Fields) { l.log(ErrorLevel, msg, err, fields...) } func (l *defaultLogger) Fatal(msg string, err error, fields ...Fields) { l.log(FatalLevel, msg, err, fields...) } func (l *defaultLogger) DebugWithContext(ctx context.Context, msg string, fields ...Fields) { l.logWithContext(ctx, DebugLevel, msg, nil, fields...) } func (l *defaultLogger) InfoWithContext(ctx context.Context, msg string, fields ...Fields) { l.logWithContext(ctx, InfoLevel, msg, nil, fields...) } func (l *defaultLogger) WarnWithContext(ctx context.Context, msg string, fields ...Fields) { l.logWithContext(ctx, WarnLevel, msg, nil, fields...) } func (l *defaultLogger) ErrorWithContext(ctx context.Context, msg string, err error, fields ...Fields) { l.logWithContext(ctx, ErrorLevel, msg, err, fields...) } func (l *defaultLogger) FatalWithContext(ctx context.Context, msg string, err error, fields ...Fields) { l.logWithContext(ctx, FatalLevel, msg, err, fields...) } func (l *defaultLogger) WithFields(fields Fields) Logger { newFields := make(Fields) for k, v := range l.fields { newFields[k] = v } for k, v := range fields { newFields[k] = v } return &defaultLogger{ level: l.level, fields: newFields, } } func (l *defaultLogger) WithField(key string, value interface{}) Logger { return l.WithFields(Fields{key: value}) } func (l *defaultLogger) WithError(err error) Logger { return l.WithField("error", err.Error()) } func (l *defaultLogger) WithContext(ctx context.Context) Logger { // 这里可以从上下文中提取跟踪信息等 return l } func (l *defaultLogger) SetLevel(level Level) { l.level = level } func (l *defaultLogger) GetLevel() Level { return l.level } func (l *defaultLogger) Close() error { return nil } func (l *defaultLogger) log(level Level, msg string, err error, fields ...Fields) { if level < l.level { return } entry := &LogEntry{ Level: level, Timestamp: time.Now(), Message: msg, Fields: make(map[string]interface{}), } // 合并字段 for k, v := range l.fields { entry.Fields[k] = v } for _, f := range fields { for k, v := range f { entry.Fields[k] = v } } if err != nil { entry.Error = err.Error() } // 这里应该使用实际的格式化器和写入器 // 为了简化,这里只是打印到控制台 println(entry.String()) } func (l *defaultLogger) logWithContext(ctx context.Context, level Level, msg string, err error, fields ...Fields) { // 从上下文中提取跟踪信息等 // 这里简化处理 l.log(level, msg, err, fields...) } func (e *LogEntry) String() string { // 简化的字符串表示 return e.Timestamp.Format(time.RFC3339) + " [" + e.Level.String() + "] " + e.Message } // NewBaseLogger creates a new base logger func NewBaseLogger(level Level) Logger { return &defaultLogger{ level: level, fields: make(map[string]interface{}), } } ================================================ FILE: internal/infrastructure/managers/entity_manager.go ================================================ package managers import ( "context" "fmt" "greatestworks/internal/domain/character" "sync" "sync/atomic" ) // EntityManager 实体管理器 type EntityManager struct { mu sync.RWMutex entities map[character.EntityID]*character.Entity nextEntityID atomic.Int64 } var entityManagerInstance *EntityManager var entityManagerOnce sync.Once // GetEntityManager 获取实体管理器单例 func GetEntityManager() *EntityManager { entityManagerOnce.Do(func() { entityManagerInstance = &EntityManager{ entities: make(map[character.EntityID]*character.Entity), } entityManagerInstance.nextEntityID.Store(1000) }) return entityManagerInstance } // Register 注册实体 func (em *EntityManager) Register(entity *character.Entity) error { if entity == nil { return fmt.Errorf("entity is nil") } em.mu.Lock() defer em.mu.Unlock() entityID := entity.ID() if _, exists := em.entities[entityID]; exists { return fmt.Errorf("entity already registered: %d", entityID) } em.entities[entityID] = entity return nil } // Unregister 注销实体 func (em *EntityManager) Unregister(entityID character.EntityID) { em.mu.Lock() defer em.mu.Unlock() delete(em.entities, entityID) } // Get 获取实体 func (em *EntityManager) Get(entityID character.EntityID) *character.Entity { em.mu.RLock() defer em.mu.RUnlock() return em.entities[entityID] } // GetAll 获取所有实体 func (em *EntityManager) GetAll() []*character.Entity { em.mu.RLock() defer em.mu.RUnlock() entities := make([]*character.Entity, 0, len(em.entities)) for _, entity := range em.entities { entities = append(entities, entity) } return entities } // AllocateEntityID 分配实体ID func (em *EntityManager) AllocateEntityID() character.EntityID { return character.EntityID(em.nextEntityID.Add(1)) } // Count 获取实体数量 func (em *EntityManager) Count() int { em.mu.RLock() defer em.mu.RUnlock() return len(em.entities) } // SpawnManager 生成管理器 type SpawnManager struct { mu sync.RWMutex spawnPoints map[int32]*SpawnPoint // 刷新点ID -> 刷新点 } // SpawnPoint 刷新点 type SpawnPoint struct { ID int32 // 刷新点ID MapID int32 // 地图ID UnitDefineID int32 // 单位定义ID Position character.Vector3 RespawnTime float32 // 重生时间 MaxCount int32 // 最大数量 currentCount int32 // 当前数量 respawnTimer float32 // 重生计时器 spawnedEntities []character.EntityID // 已生成的实体ID } var spawnManagerInstance *SpawnManager var spawnManagerOnce sync.Once // GetSpawnManager 获取生成管理器单例 func GetSpawnManager() *SpawnManager { spawnManagerOnce.Do(func() { spawnManagerInstance = &SpawnManager{ spawnPoints: make(map[int32]*SpawnPoint), } }) return spawnManagerInstance } // AddSpawnPoint 添加刷新点 func (sm *SpawnManager) AddSpawnPoint(sp *SpawnPoint) { sm.mu.Lock() defer sm.mu.Unlock() sp.spawnedEntities = make([]character.EntityID, 0) sm.spawnPoints[sp.ID] = sp } // Update 更新刷新点 func (sm *SpawnManager) Update(ctx context.Context, deltaTime float32) { sm.mu.Lock() defer sm.mu.Unlock() for _, sp := range sm.spawnPoints { if sp.currentCount < sp.MaxCount { sp.respawnTimer += deltaTime if sp.respawnTimer >= sp.RespawnTime { sm.spawnEntity(ctx, sp) sp.respawnTimer = 0 } } } } // spawnEntity 生成实体 func (sm *SpawnManager) spawnEntity(ctx context.Context, sp *SpawnPoint) { // TODO: 根据UnitDefineID创建实体 // 这里需要EntityFactory sp.currentCount++ } // OnEntityDestroyed 实体销毁回调 func (sm *SpawnManager) OnEntityDestroyed(entityID character.EntityID) { sm.mu.Lock() defer sm.mu.Unlock() // 查找并更新对应的刷新点 for _, sp := range sm.spawnPoints { for i, id := range sp.spawnedEntities { if id == entityID { sp.spawnedEntities = append(sp.spawnedEntities[:i], sp.spawnedEntities[i+1:]...) sp.currentCount-- return } } } } ================================================ FILE: internal/infrastructure/managers/update_manager.go ================================================ package managers import ( "context" "sync" "time" ) // UpdateManager 更新管理器(单线程游戏主循环) type UpdateManager struct { mu sync.Mutex running bool deltaTime float32 tickRate int // 每秒更新次数 tickInterval time.Duration // 更新间隔 // 更新回调列表 updateCallbacks []UpdateCallback // 定时器 timers []*Timer // 任务队列 taskQueue chan func() } // UpdateCallback 更新回调函数 type UpdateCallback func(ctx context.Context, deltaTime float32) error // Timer 定时器 type Timer struct { ID int64 Interval float32 // 间隔时间 Elapsed float32 // 已过时间 Repeat bool // 是否重复 Callback func() Active bool } var updateManagerInstance *UpdateManager var updateManagerOnce sync.Once // GetUpdateManager 获取更新管理器单例 func GetUpdateManager() *UpdateManager { updateManagerOnce.Do(func() { updateManagerInstance = &UpdateManager{ tickRate: 30, // 默认30帧/秒 updateCallbacks: make([]UpdateCallback, 0), timers: make([]*Timer, 0), taskQueue: make(chan func(), 1000), } updateManagerInstance.tickInterval = time.Second / time.Duration(updateManagerInstance.tickRate) }) return updateManagerInstance } // RegisterUpdateCallback 注册更新回调 func (um *UpdateManager) RegisterUpdateCallback(callback UpdateCallback) { um.mu.Lock() defer um.mu.Unlock() um.updateCallbacks = append(um.updateCallbacks, callback) } // SetTickRate 设置更新频率 func (um *UpdateManager) SetTickRate(tickRate int) { um.mu.Lock() defer um.mu.Unlock() um.tickRate = tickRate um.tickInterval = time.Second / time.Duration(tickRate) } // AddTimer 添加定时器 func (um *UpdateManager) AddTimer(interval float32, repeat bool, callback func()) int64 { um.mu.Lock() defer um.mu.Unlock() timerID := int64(len(um.timers) + 1) timer := &Timer{ ID: timerID, Interval: interval, Elapsed: 0, Repeat: repeat, Callback: callback, Active: true, } um.timers = append(um.timers, timer) return timerID } // RemoveTimer 移除定时器 func (um *UpdateManager) RemoveTimer(timerID int64) { um.mu.Lock() defer um.mu.Unlock() for i, timer := range um.timers { if timer.ID == timerID { timer.Active = false um.timers = append(um.timers[:i], um.timers[i+1:]...) return } } } // PostTask 投递任务到主线程 func (um *UpdateManager) PostTask(task func()) { select { case um.taskQueue <- task: default: // 队列满,丢弃任务或者阻塞等待 } } // Start 启动主循环 func (um *UpdateManager) Start(ctx context.Context) { um.mu.Lock() if um.running { um.mu.Unlock() return } um.running = true um.mu.Unlock() ticker := time.NewTicker(um.tickInterval) defer ticker.Stop() lastTime := time.Now() for { select { case <-ctx.Done(): um.Stop() return case <-ticker.C: now := time.Now() deltaTime := float32(now.Sub(lastTime).Seconds()) lastTime = now um.deltaTime = deltaTime um.tick(ctx, deltaTime) case task := <-um.taskQueue: // 执行投递的任务 if task != nil { task() } } } } // tick 单次更新 func (um *UpdateManager) tick(ctx context.Context, deltaTime float32) { um.mu.Lock() callbacks := make([]UpdateCallback, len(um.updateCallbacks)) copy(callbacks, um.updateCallbacks) um.mu.Unlock() // 执行所有更新回调 for _, callback := range callbacks { if err := callback(ctx, deltaTime); err != nil { // TODO: 记录错误日志 } } // 更新定时器 um.updateTimers(deltaTime) } // updateTimers 更新定时器 func (um *UpdateManager) updateTimers(deltaTime float32) { um.mu.Lock() defer um.mu.Unlock() toRemove := make([]int, 0) for i, timer := range um.timers { if !timer.Active { toRemove = append(toRemove, i) continue } timer.Elapsed += deltaTime if timer.Elapsed >= timer.Interval { // 触发回调 if timer.Callback != nil { timer.Callback() } if timer.Repeat { timer.Elapsed -= timer.Interval } else { timer.Active = false toRemove = append(toRemove, i) } } } // 移除失效的定时器 for i := len(toRemove) - 1; i >= 0; i-- { idx := toRemove[i] um.timers = append(um.timers[:idx], um.timers[idx+1:]...) } } // Stop 停止主循环 func (um *UpdateManager) Stop() { um.mu.Lock() defer um.mu.Unlock() um.running = false } // IsRunning 是否正在运行 func (um *UpdateManager) IsRunning() bool { um.mu.Lock() defer um.mu.Unlock() return um.running } // GetDeltaTime 获取帧间隔时间 func (um *UpdateManager) GetDeltaTime() float32 { um.mu.Lock() defer um.mu.Unlock() return um.deltaTime } ================================================ FILE: internal/infrastructure/messaging/event_bus.go ================================================ package messaging import ( "context" "fmt" "sync" "time" "greatestworks/internal/events" ) // DomainEvent 领域事件接口 type DomainEvent interface { EventID() string EventType() string AggregateID() string OccurredAt() time.Time Version() int GetEventType() string GetEventID() string GetAggregateType() string GetTimestamp() time.Time GetAggregateID() string GetVersion() int GetMetadata() map[string]interface{} } // BaseDomainEvent 基础领域事件 type BaseDomainEvent struct { eventID string eventType string aggregateID string occurredAt time.Time version int } // EventID 获取事件ID func (e *BaseDomainEvent) EventID() string { return e.eventID } // EventType 获取事件类型 func (e *BaseDomainEvent) EventType() string { return e.eventType } // AggregateID 获取聚合根ID func (e *BaseDomainEvent) AggregateID() string { return e.aggregateID } // OccurredAt 获取发生时间 func (e *BaseDomainEvent) OccurredAt() time.Time { return e.occurredAt } // Version 获取版本 func (e *BaseDomainEvent) Version() int { return e.version } // GetEventType 获取事件类型 func (e *BaseDomainEvent) GetEventType() string { return e.eventType } // GetEventID 获取事件ID func (e *BaseDomainEvent) GetEventID() string { return e.eventID } // GetAggregateType 获取聚合根类型 func (e *BaseDomainEvent) GetAggregateType() string { return e.aggregateID } // GetTimestamp 获取时间戳 func (e *BaseDomainEvent) GetTimestamp() time.Time { return e.occurredAt } // GetAggregateID 获取聚合根ID func (e *BaseDomainEvent) GetAggregateID() string { return e.aggregateID } // GetVersion 获取版本 func (e *BaseDomainEvent) GetVersion() int { return e.version } // GetMetadata 获取元数据 func (e *BaseDomainEvent) GetMetadata() map[string]interface{} { return make(map[string]interface{}) } // EventBus 事件总线 type EventBus struct { handlers map[string][]events.EventHandler mu sync.RWMutex stats *EventBusStats } // EventBusStats 事件总线统计 type EventBusStats struct { TotalPublished int64 `json:"total_published"` TotalHandled int64 `json:"total_handled"` TotalFailed int64 `json:"total_failed"` ByEventType map[string]int64 `json:"by_event_type"` StartTime time.Time `json:"start_time"` } // NewEventBus 创建事件总线 func NewEventBus() *EventBus { return &EventBus{ handlers: make(map[string][]events.EventHandler), stats: &EventBusStats{ ByEventType: make(map[string]int64), StartTime: time.Now(), }, } } // Subscribe 订阅事件 func (bus *EventBus) Subscribe(handler events.EventHandler) error { bus.mu.Lock() defer bus.mu.Unlock() for _, eventType := range handler.GetEventTypes() { // 检查处理器是否已存在 for _, existingHandler := range bus.handlers[eventType] { if existingHandler.GetHandlerName() == handler.GetHandlerName() { return fmt.Errorf("handler %s already subscribed to event %s", handler.GetHandlerName(), eventType) } } bus.handlers[eventType] = append(bus.handlers[eventType], handler) } return nil } // Unsubscribe 取消订阅事件 func (bus *EventBus) Unsubscribe(handlerName string, eventType string) error { bus.mu.Lock() defer bus.mu.Unlock() handlers := bus.handlers[eventType] for i, handler := range handlers { if handler.GetHandlerName() == handlerName { bus.handlers[eventType] = append(handlers[:i], handlers[i+1:]...) return nil } } return fmt.Errorf("handler %s not found for event %s", handlerName, eventType) } // Publish 发布事件 func (bus *EventBus) Publish(ctx context.Context, event DomainEvent) error { bus.mu.RLock() handlers := bus.handlers[event.EventType()] bus.mu.RUnlock() bus.stats.TotalPublished++ bus.stats.ByEventType[event.EventType()]++ if len(handlers) == 0 { // 没有处理器,直接返回 return nil } // 并发处理所有处理器 var wg sync.WaitGroup errorChan := make(chan error, len(handlers)) for _, handler := range handlers { wg.Add(1) go func(h events.EventHandler) { defer wg.Done() // 将DomainEvent转换为events.Event eventWrapper := &events.BaseEvent{ ID: event.GetEventID(), Type: event.GetEventType(), Data: event, Timestamp: event.GetTimestamp(), UserID: event.GetAggregateID(), } if err := h.Handle(ctx, eventWrapper); err != nil { errorChan <- fmt.Errorf("handler %s failed: %w", h.GetHandlerName(), err) bus.stats.TotalFailed++ } else { bus.stats.TotalHandled++ } }(handler) } wg.Wait() close(errorChan) // 收集错误 var errors []error for err := range errorChan { errors = append(errors, err) } if len(errors) > 0 { return fmt.Errorf("event handling failed: %v", errors) } return nil } // PublishAsync 异步发布事件 func (bus *EventBus) PublishAsync(ctx context.Context, event DomainEvent) { go func() { if err := bus.Publish(ctx, event); err != nil { // 这里应该记录日志 fmt.Printf("Async event handling failed: %v\n", err) } }() } // GetStats 获取统计信息 func (bus *EventBus) GetStats() *EventBusStats { bus.mu.RLock() defer bus.mu.RUnlock() stats := &EventBusStats{ TotalPublished: bus.stats.TotalPublished, TotalHandled: bus.stats.TotalHandled, TotalFailed: bus.stats.TotalFailed, ByEventType: make(map[string]int64), StartTime: bus.stats.StartTime, } for eventType, count := range bus.stats.ByEventType { stats.ByEventType[eventType] = count } return stats } // PlayerEventHandler 玩家事件处理器基类 type PlayerEventHandler struct { name string } // NewPlayerEventHandler 创建玩家事件处理器 func NewPlayerEventHandler(name string) *PlayerEventHandler { return &PlayerEventHandler{name: name} } // GetHandlerName 获取处理器名称 func (h *PlayerEventHandler) GetHandlerName() string { return h.name } // GetEventTypes 获取处理的事件类型 func (h *PlayerEventHandler) GetEventTypes() []string { return []string{ "PlayerCreated", "PlayerLevelUp", "PlayerOnline", "PlayerOffline", "PlayerMoved", "PlayerDied", } } // Handle 处理事件 func (h *PlayerEventHandler) Handle(ctx context.Context, event DomainEvent) error { switch event.EventType() { case "PlayerCreated": return h.handlePlayerCreated(ctx, event) case "PlayerLevelUp": return h.handlePlayerLevelUp(ctx, event) case "PlayerOnline": return h.handlePlayerOnline(ctx, event) case "PlayerOffline": return h.handlePlayerOffline(ctx, event) case "PlayerMoved": return h.handlePlayerMoved(ctx, event) case "PlayerDied": return h.handlePlayerDied(ctx, event) default: return fmt.Errorf("unknown event type: %s", event.EventType()) } } // handlePlayerCreated 处理玩家创建事件 func (h *PlayerEventHandler) handlePlayerCreated(ctx context.Context, event DomainEvent) error { // 实现玩家创建后的逻辑,比如发送欢迎消息、初始化数据等 fmt.Printf("Player created: %s\n", event.AggregateID()) return nil } // handlePlayerLevelUp 处理玩家升级事件 func (h *PlayerEventHandler) handlePlayerLevelUp(ctx context.Context, event DomainEvent) error { // 实现玩家升级后的逻辑,比如发送奖励、更新排行榜等 fmt.Printf("Player leveled up: %s\n", event.AggregateID()) return nil } // handlePlayerOnline 处理玩家上线事件 func (h *PlayerEventHandler) handlePlayerOnline(ctx context.Context, event DomainEvent) error { // 实现玩家上线后的逻辑,比如通知好友、更新在线状态等 fmt.Printf("Player online: %s\n", event.AggregateID()) return nil } // handlePlayerOffline 处理玩家下线事件 func (h *PlayerEventHandler) handlePlayerOffline(ctx context.Context, event DomainEvent) error { // 实现玩家下线后的逻辑,比如保存数据、通知好友等 fmt.Printf("Player offline: %s\n", event.AggregateID()) return nil } // handlePlayerMoved 处理玩家移动事件 func (h *PlayerEventHandler) handlePlayerMoved(ctx context.Context, event DomainEvent) error { // 实现玩家移动后的逻辑,比如更新位置、检查触发器等 fmt.Printf("Player moved: %s\n", event.AggregateID()) return nil } // handlePlayerDied 处理玩家死亡事件 func (h *PlayerEventHandler) handlePlayerDied(ctx context.Context, event DomainEvent) error { // 实现玩家死亡后的逻辑,比如掉落物品、复活处理等 fmt.Printf("Player died: %s\n", event.AggregateID()) return nil } // BattleEventHandler 战斗事件处理器 type BattleEventHandler struct { name string } // NewBattleEventHandler 创建战斗事件处理器 func NewBattleEventHandler(name string) *BattleEventHandler { return &BattleEventHandler{name: name} } // GetHandlerName 获取处理器名称 func (h *BattleEventHandler) GetHandlerName() string { return h.name } // GetEventTypes 获取处理的事件类型 func (h *BattleEventHandler) GetEventTypes() []string { return []string{ "BattleStarted", "BattleEnded", "PlayerJoinedBattle", "PlayerLeftBattle", "BattleActionExecuted", } } // Handle 处理事件 func (h *BattleEventHandler) Handle(ctx context.Context, event DomainEvent) error { switch event.EventType() { case "BattleStarted": return h.handleBattleStarted(ctx, event) case "BattleEnded": return h.handleBattleEnded(ctx, event) case "PlayerJoinedBattle": return h.handlePlayerJoinedBattle(ctx, event) case "PlayerLeftBattle": return h.handlePlayerLeftBattle(ctx, event) case "BattleActionExecuted": return h.handleBattleActionExecuted(ctx, event) default: return fmt.Errorf("unknown battle event type: %s", event.EventType()) } } // handleBattleStarted 处理战斗开始事件 func (h *BattleEventHandler) handleBattleStarted(ctx context.Context, event DomainEvent) error { fmt.Printf("Battle started: %s\n", event.AggregateID()) return nil } // handleBattleEnded 处理战斗结束事件 func (h *BattleEventHandler) handleBattleEnded(ctx context.Context, event DomainEvent) error { fmt.Printf("Battle ended: %s\n", event.AggregateID()) return nil } // handlePlayerJoinedBattle 处理玩家加入战斗事件 func (h *BattleEventHandler) handlePlayerJoinedBattle(ctx context.Context, event DomainEvent) error { fmt.Printf("Player joined battle: %s\n", event.AggregateID()) return nil } // handlePlayerLeftBattle 处理玩家离开战斗事件 func (h *BattleEventHandler) handlePlayerLeftBattle(ctx context.Context, event DomainEvent) error { fmt.Printf("Player left battle: %s\n", event.AggregateID()) return nil } // handleBattleActionExecuted 处理战斗行动执行事件 func (h *BattleEventHandler) handleBattleActionExecuted(ctx context.Context, event DomainEvent) error { fmt.Printf("Battle action executed: %s\n", event.AggregateID()) return nil } ================================================ FILE: internal/infrastructure/messaging/event_dispatcher.go ================================================ package messaging import ( "context" "fmt" "sync" "time" "greatestworks/internal/events" "greatestworks/internal/infrastructure/logging" ) // EventDispatcher 事件分发器 type EventDispatcher struct { publisher Publisher subscriber Subscriber logger logging.Logger config *DispatcherConfig handlers map[string][]events.EventHandler mu sync.RWMutex ctx context.Context cancel context.CancelFunc wg sync.WaitGroup } // Publisher 发布者接口 type Publisher interface { Publish(ctx context.Context, topic string, event events.Event) error } // Subscriber 订阅者接口 type Subscriber interface { Subscribe(ctx context.Context, topic string, handler events.EventHandler) error Unsubscribe(ctx context.Context, topic string, handler events.EventHandler) error } // DispatcherConfig 分发器配置 type DispatcherConfig struct { MaxRetries int `json:"max_retries" yaml:"max_retries"` RetryDelay time.Duration `json:"retry_delay" yaml:"retry_delay"` MaxConcurrency int `json:"max_concurrency" yaml:"max_concurrency"` BufferSize int `json:"buffer_size" yaml:"buffer_size"` EnableMetrics bool `json:"enable_metrics" yaml:"enable_metrics"` EnableTracing bool `json:"enable_tracing" yaml:"enable_tracing"` } // NewEventDispatcher 创建事件分发器 func NewEventDispatcher(publisher Publisher, subscriber Subscriber, config *DispatcherConfig, logger logging.Logger) *EventDispatcher { if config == nil { config = &DispatcherConfig{ MaxRetries: 3, RetryDelay: time.Second, MaxConcurrency: 10, BufferSize: 1000, EnableMetrics: true, EnableTracing: false, } } ctx, cancel := context.WithCancel(context.Background()) return &EventDispatcher{ publisher: publisher, subscriber: subscriber, logger: logger, config: config, handlers: make(map[string][]events.EventHandler), ctx: ctx, cancel: cancel, } } // Start 启动事件分发器 func (d *EventDispatcher) Start() error { d.logger.Info("Event dispatcher starting") // 启动订阅处理 go d.subscribeLoop() d.logger.Info("Event dispatcher started") return nil } // Stop 停止事件分发器 func (d *EventDispatcher) Stop() error { d.logger.Info("Event dispatcher stopping") // 取消上下文 d.cancel() // 等待所有goroutine完成 d.wg.Wait() d.logger.Info("Event dispatcher stopped") return nil } // Publish 发布事件 func (d *EventDispatcher) Publish(ctx context.Context, event events.Event) error { if d.publisher == nil { return fmt.Errorf("publisher not configured") } // 获取事件类型作为主题 topic := event.GetEventType() // 发布事件 if err := d.publisher.Publish(ctx, topic, event); err != nil { d.logger.Error("Failed to publish event", err, logging.Fields{ "event_type": event.GetEventType(), }) return fmt.Errorf("failed to publish event: %w", err) } d.logger.Debug("Event published", logging.Fields{ "event_type": event.GetEventType(), "aggregate_id": event.GetAggregateID(), }) return nil } // Subscribe 订阅事件 func (d *EventDispatcher) Subscribe(eventType string, handler events.EventHandler) error { d.mu.Lock() defer d.mu.Unlock() // 添加到本地处理器列表 d.handlers[eventType] = append(d.handlers[eventType], handler) // 如果配置了订阅者,也添加到远程订阅 if d.subscriber != nil { if err := d.subscriber.Subscribe(d.ctx, eventType, handler); err != nil { d.logger.Error("Failed to subscribe to remote event", err, logging.Fields{ "event_type": eventType, }) return fmt.Errorf("failed to subscribe to remote event: %w", err) } } d.logger.Debug("Event handler subscribed", logging.Fields{ "event_type": eventType, }) return nil } // Unsubscribe 取消订阅事件 func (d *EventDispatcher) Unsubscribe(eventType string, handler events.EventHandler) error { d.mu.Lock() defer d.mu.Unlock() // 从本地处理器列表移除 if handlers, exists := d.handlers[eventType]; exists { for i, h := range handlers { if h == handler { d.handlers[eventType] = append(handlers[:i], handlers[i+1:]...) break } } } // 如果配置了订阅者,也从远程订阅移除 if d.subscriber != nil { if err := d.subscriber.Unsubscribe(d.ctx, eventType, handler); err != nil { d.logger.Error("Failed to unsubscribe from remote event", err, logging.Fields{ "event_type": eventType, }) return fmt.Errorf("failed to unsubscribe from remote event: %w", err) } } d.logger.Debug("Event handler unsubscribed", logging.Fields{ "event_type": eventType, }) return nil } // Dispatch 分发事件到本地处理器 func (d *EventDispatcher) Dispatch(ctx context.Context, event events.Event) error { eventType := event.GetEventType() d.mu.RLock() handlers, exists := d.handlers[eventType] d.mu.RUnlock() if !exists || len(handlers) == 0 { d.logger.Debug("No handlers found for event", logging.Fields{ "event_type": eventType, }) return nil } // 并发处理所有处理器 var wg sync.WaitGroup for _, handler := range handlers { wg.Add(1) go func(h events.EventHandler) { defer wg.Done() d.handleEvent(ctx, h, event) }(handler) } wg.Wait() return nil } // 私有方法 // subscribeLoop 订阅循环 func (d *EventDispatcher) subscribeLoop() { d.wg.Add(1) defer d.wg.Done() d.logger.Debug("Event dispatcher subscribe loop started") for { select { case <-d.ctx.Done(): d.logger.Debug("Event dispatcher subscribe loop stopped") return default: // 这里应该处理来自订阅者的消息 // 简化实现,实际项目中应该从消息队列接收事件 time.Sleep(100 * time.Millisecond) } } } // handleEvent 处理事件 func (d *EventDispatcher) handleEvent(ctx context.Context, handler events.EventHandler, event events.Event) { // 实现重试逻辑 for attempt := 0; attempt <= d.config.MaxRetries; attempt++ { if err := handler.Handle(ctx, event); err != nil { d.logger.Error("Event handler failed", err, logging.Fields{ "event_type": event.GetEventType(), "attempt": attempt + 1, }) if attempt < d.config.MaxRetries { // 等待重试延迟 time.Sleep(d.config.RetryDelay) continue } // 达到最大重试次数,记录错误 d.logger.Error("Event handler failed after max retries", err, logging.Fields{ "event_type": event.GetEventType(), }) return } // 处理成功 d.logger.Debug("Event handled successfully", logging.Fields{ "event_type": event.GetEventType(), }) return } } // GetStats 获取统计信息 func (d *EventDispatcher) GetStats() map[string]interface{} { d.mu.RLock() defer d.mu.RUnlock() stats := make(map[string]interface{}) stats["total_event_types"] = len(d.handlers) totalHandlers := 0 for _, handlers := range d.handlers { totalHandlers += len(handlers) } stats["total_handlers"] = totalHandlers return stats } ================================================ FILE: internal/infrastructure/messaging/logger_adapter.go ================================================ package messaging import ( "greatestworks/internal/infrastructure/logging" ) // EventLoggerAdapter adapts infrastructure logging.Logger to events.Logger type EventLoggerAdapter struct{ logger logging.Logger } func NewEventLoggerAdapter(logger logging.Logger) *EventLoggerAdapter { return &EventLoggerAdapter{logger: logger} } func (a *EventLoggerAdapter) Info(msg string, args ...interface{}) { a.logger.Info(msg, logging.Fields{"data": args}) } func (a *EventLoggerAdapter) Debug(msg string, args ...interface{}) { a.logger.Debug(msg, logging.Fields{"data": args}) } func (a *EventLoggerAdapter) Error(msg string, args ...interface{}) { if len(args) > 0 { if err, ok := args[0].(error); ok { a.logger.Error(msg, err, logging.Fields{"data": args[1:]}) return } } a.logger.Error(msg, nil, logging.Fields{"data": args}) } ================================================ FILE: internal/infrastructure/messaging/nats_publisher.go ================================================ package messaging import ( "context" "encoding/json" "fmt" "time" "greatestworks/internal/infrastructure/logging" "github.com/nats-io/nats.go" ) // NATSPublisher NATS消息发布器 type NATSPublisher struct { conn *nats.Conn logger logging.Logger config *PublisherConfig } // PublisherConfig 发布器配置 type PublisherConfig struct { URL string `json:"url" yaml:"url"` ClusterID string `json:"cluster_id" yaml:"cluster_id"` ClientID string `json:"client_id" yaml:"client_id"` Timeout time.Duration `json:"timeout" yaml:"timeout"` MaxReconn int `json:"max_reconn" yaml:"max_reconn"` ReconnWait time.Duration `json:"reconn_wait" yaml:"reconn_wait"` PingInterval time.Duration `json:"ping_interval" yaml:"ping_interval"` MaxPingsOut int `json:"max_pings_out" yaml:"max_pings_out"` } // NewNATSPublisher 创建NATS发布器 func NewNATSPublisher(config *PublisherConfig, logger logging.Logger) (*NATSPublisher, error) { if config == nil { config = &PublisherConfig{ URL: "nats://localhost:4222", ClusterID: "test-cluster", ClientID: "publisher", Timeout: 30 * time.Second, MaxReconn: 10, ReconnWait: 2 * time.Second, PingInterval: 2 * time.Minute, MaxPingsOut: 2, } } // 连接NATS服务器 conn, err := nats.Connect(config.URL, nats.Name(config.ClientID)) if err != nil { return nil, fmt.Errorf("failed to connect to NATS: %w", err) } publisher := &NATSPublisher{ conn: conn, logger: logger, config: config, } logger.Info("NATS publisher created", logging.Fields{ "url": config.URL, "client_id": config.ClientID, }) return publisher, nil } // Publish 发布消息 func (p *NATSPublisher) Publish(ctx context.Context, subject string, data interface{}) error { // 序列化数据 payload, err := json.Marshal(data) if err != nil { return fmt.Errorf("failed to marshal data: %w", err) } // 发布消息 if err := p.conn.Publish(subject, payload); err != nil { p.logger.Error("Failed to publish message", err, logging.Fields{ "subject": subject, }) return fmt.Errorf("failed to publish message: %w", err) } p.logger.Debug("Message published", logging.Fields{ "subject": subject, "size": len(payload), }) return nil } // PublishAsync 异步发布消息 func (p *NATSPublisher) PublishAsync(ctx context.Context, subject string, data interface{}, callback func(error)) error { // 序列化数据 payload, err := json.Marshal(data) if err != nil { if callback != nil { callback(err) } return fmt.Errorf("failed to marshal data: %w", err) } // 异步发布消息 if err := p.conn.Publish(subject, payload); err != nil { p.logger.Error("Failed to publish message async", err, logging.Fields{ "subject": subject, }) return fmt.Errorf("failed to publish message async: %w", err) } p.logger.Debug("Message published async", logging.Fields{ "subject": subject, "size": len(payload), }) return nil } // PublishWithReply 发布带回复的消息 func (p *NATSPublisher) PublishWithReply(ctx context.Context, subject string, data interface{}, replySubject string) error { // 序列化数据 payload, err := json.Marshal(data) if err != nil { return fmt.Errorf("failed to marshal data: %w", err) } // 发布消息 if err := p.conn.PublishRequest(subject, replySubject, payload); err != nil { p.logger.Error("Failed to publish message with reply", err, logging.Fields{ "subject": subject, "reply": replySubject, }) return fmt.Errorf("failed to publish message with reply: %w", err) } p.logger.Debug("Message published with reply", logging.Fields{ "subject": subject, "reply": replySubject, "size": len(payload), }) return nil } // Request 发送请求并等待回复 func (p *NATSPublisher) Request(ctx context.Context, subject string, data interface{}, timeout time.Duration) ([]byte, error) { // 序列化数据 payload, err := json.Marshal(data) if err != nil { return nil, fmt.Errorf("failed to marshal data: %w", err) } // 发送请求 msg, err := p.conn.Request(subject, payload, timeout) if err != nil { p.logger.Error("Failed to send request", err, logging.Fields{ "subject": subject, }) return nil, fmt.Errorf("failed to send request: %w", err) } p.logger.Debug("Request sent and reply received", logging.Fields{ "subject": subject, "reply_size": len(msg.Data), }) return msg.Data, nil } // Close 关闭连接 func (p *NATSPublisher) Close() error { if p.conn != nil { p.conn.Close() p.logger.Info("NATS publisher connection closed") } return nil } // IsConnected 检查连接状态 func (p *NATSPublisher) IsConnected() bool { return p.conn != nil && p.conn.IsConnected() } // GetStats 获取统计信息 func (p *NATSPublisher) GetStats() map[string]interface{} { stats := make(map[string]interface{}) if p.conn != nil { stats["connected"] = p.conn.IsConnected() stats["server_url"] = p.conn.ConnectedUrl() stats["server_id"] = p.conn.ConnectedServerId() stats["server_version"] = p.conn.ConnectedServerVersion() } return stats } ================================================ FILE: internal/infrastructure/messaging/nats_subscriber.go ================================================ package messaging import ( "context" "encoding/json" "fmt" "sync" "time" "greatestworks/internal/events" "greatestworks/internal/infrastructure/logging" "github.com/nats-io/nats.go" ) // NATSSubscriber NATS消息订阅器 type NATSSubscriber struct { conn *nats.Conn logger logging.Logger config *SubscriberConfig subscriptions map[string]*nats.Subscription mu sync.RWMutex ctx context.Context cancel context.CancelFunc } // SubscriberConfig 订阅器配置 type SubscriberConfig struct { URL string `json:"url" yaml:"url"` ClusterID string `json:"cluster_id" yaml:"cluster_id"` ClientID string `json:"client_id" yaml:"client_id"` Timeout time.Duration `json:"timeout" yaml:"timeout"` MaxReconn int `json:"max_reconn" yaml:"max_reconn"` ReconnWait time.Duration `json:"reconn_wait" yaml:"reconn_wait"` PingInterval time.Duration `json:"ping_interval" yaml:"ping_interval"` MaxPingsOut int `json:"max_pings_out" yaml:"max_pings_out"` } // NewNATSSubscriber 创建NATS订阅器 func NewNATSSubscriber(config *SubscriberConfig, logger logging.Logger) (*NATSSubscriber, error) { if config == nil { config = &SubscriberConfig{ URL: "nats://localhost:4222", ClusterID: "test-cluster", ClientID: "subscriber", Timeout: 30 * time.Second, MaxReconn: 10, ReconnWait: 2 * time.Second, PingInterval: 2 * time.Minute, MaxPingsOut: 2, } } // 连接NATS服务器 conn, err := nats.Connect(config.URL, nats.Name(config.ClientID)) if err != nil { return nil, fmt.Errorf("failed to connect to NATS: %w", err) } ctx, cancel := context.WithCancel(context.Background()) subscriber := &NATSSubscriber{ conn: conn, logger: logger, config: config, subscriptions: make(map[string]*nats.Subscription), ctx: ctx, cancel: cancel, } logger.Info("NATS subscriber created", logging.Fields{ "url": config.URL, "client_id": config.ClientID, }) return subscriber, nil } // Subscribe 订阅消息 func (s *NATSSubscriber) Subscribe(subject string, handler events.EventHandler) error { s.mu.Lock() defer s.mu.Unlock() // 检查是否已经订阅 if _, exists := s.subscriptions[subject]; exists { return fmt.Errorf("already subscribed to subject: %s", subject) } // 创建订阅 subscription, err := s.conn.Subscribe(subject, func(msg *nats.Msg) { s.handleMessage(subject, msg, handler) }) if err != nil { return fmt.Errorf("failed to subscribe to subject %s: %w", subject, err) } // 存储订阅 s.subscriptions[subject] = subscription s.logger.Info("Subscribed to subject", logging.Fields{ "subject": subject, }) return nil } // SubscribeWithQueue 使用队列组订阅消息 func (s *NATSSubscriber) SubscribeWithQueue(subject, queue string, handler events.EventHandler) error { s.mu.Lock() defer s.mu.Unlock() // 检查是否已经订阅 key := fmt.Sprintf("%s:%s", subject, queue) if _, exists := s.subscriptions[key]; exists { return fmt.Errorf("already subscribed to subject with queue: %s", key) } // 创建队列订阅 subscription, err := s.conn.QueueSubscribe(subject, queue, func(msg *nats.Msg) { s.handleMessage(subject, msg, handler) }) if err != nil { return fmt.Errorf("failed to subscribe to subject %s with queue %s: %w", subject, queue, err) } // 存储订阅 s.subscriptions[key] = subscription s.logger.Info("Subscribed to subject with queue", logging.Fields{ "subject": subject, "queue": queue, }) return nil } // Unsubscribe 取消订阅 func (s *NATSSubscriber) Unsubscribe(subject string) error { s.mu.Lock() defer s.mu.Unlock() subscription, exists := s.subscriptions[subject] if !exists { return fmt.Errorf("not subscribed to subject: %s", subject) } // 取消订阅 if err := subscription.Unsubscribe(); err != nil { return fmt.Errorf("failed to unsubscribe from subject %s: %w", subject, err) } // 从订阅列表中移除 delete(s.subscriptions, subject) s.logger.Info("Unsubscribed from subject", logging.Fields{ "subject": subject, }) return nil } // Close 关闭订阅器 func (s *NATSSubscriber) Close() error { s.mu.Lock() defer s.mu.Unlock() // 取消所有订阅 for subject, subscription := range s.subscriptions { if err := subscription.Unsubscribe(); err != nil { s.logger.Error("Failed to unsubscribe", err, logging.Fields{ "subject": subject, }) } } // 清空订阅列表 s.subscriptions = make(map[string]*nats.Subscription) // 关闭连接 if s.conn != nil { s.conn.Close() s.logger.Info("NATS subscriber connection closed") } return nil } // IsConnected 检查连接状态 func (s *NATSSubscriber) IsConnected() bool { return s.conn != nil && s.conn.IsConnected() } // GetSubscriptions 获取订阅列表 func (s *NATSSubscriber) GetSubscriptions() []string { s.mu.RLock() defer s.mu.RUnlock() subjects := make([]string, 0, len(s.subscriptions)) for subject := range s.subscriptions { subjects = append(subjects, subject) } return subjects } // GetStats 获取统计信息 func (s *NATSSubscriber) GetStats() map[string]interface{} { stats := make(map[string]interface{}) if s.conn != nil { stats["connected"] = s.conn.IsConnected() stats["server_url"] = s.conn.ConnectedUrl() stats["server_id"] = s.conn.ConnectedServerId() stats["server_version"] = s.conn.ConnectedServerVersion() } s.mu.RLock() stats["subscriptions"] = len(s.subscriptions) s.mu.RUnlock() return stats } // 私有方法 // handleMessage 处理接收到的消息 func (s *NATSSubscriber) handleMessage(subject string, msg *nats.Msg, handler events.EventHandler) { s.logger.Debug("Message received", logging.Fields{ "subject": subject, "size": len(msg.Data), }) // 解析消息 var event events.Event if err := json.Unmarshal(msg.Data, &event); err != nil { s.logger.Error("Failed to unmarshal message", err, logging.Fields{ "subject": subject, }) return } // 处理事件 if err := handler.Handle(s.ctx, event); err != nil { s.logger.Error("Failed to handle event", err, logging.Fields{ "subject": subject, "event_type": event.GetEventType(), }) return } s.logger.Debug("Event handled successfully", logging.Fields{ "subject": subject, "event_type": event.GetEventType(), }) } ================================================ FILE: internal/infrastructure/messaging/publisher.go ================================================ package messaging import ( "context" "reflect" "time" "greatestworks/internal/events" ) // EventBusPublisher publishes application events to the EventBus. // If the event doesn't implement events.Event, it is wrapped into BaseEvent. type EventBusPublisher struct{ bus *events.EventBus } func NewEventBusPublisher(bus *events.EventBus) *EventBusPublisher { return &EventBusPublisher{bus: bus} } func (p *EventBusPublisher) Publish(ctx context.Context, event interface{}) error { if e, ok := event.(events.Event); ok { return p.bus.Publish(ctx, e) } wrapper := &events.BaseEvent{ Type: reflect.TypeOf(event).String(), Timestamp: time.Now(), Data: event, } return p.bus.Publish(ctx, wrapper) } ================================================ FILE: internal/infrastructure/messaging/worker_pool.go ================================================ package messaging import ( "context" "fmt" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // WorkerPool 工作池 type WorkerPool struct { workerCount int workQueue chan interface{} workers []*Worker processor WorkerProcessor logger logging.Logger ctx context.Context cancel context.CancelFunc wg sync.WaitGroup mu sync.RWMutex running bool } // Worker 工作者 type Worker struct { id int workQueue chan interface{} processor WorkerProcessor logger logging.Logger ctx context.Context wg *sync.WaitGroup } // WorkerProcessor 工作处理器接口 type WorkerProcessor interface { Process(ctx context.Context, work interface{}) error } // NewWorkerPool 创建工作者池 func NewWorkerPool(workerCount int, processor WorkerProcessor, logger logging.Logger) *WorkerPool { ctx, cancel := context.WithCancel(context.Background()) return &WorkerPool{ workerCount: workerCount, workQueue: make(chan interface{}, workerCount*2), // 缓冲区大小为工作者数量的2倍 processor: processor, logger: logger, ctx: ctx, cancel: cancel, workers: make([]*Worker, 0, workerCount), } } // Start 启动工作者池 func (wp *WorkerPool) Start() error { wp.mu.Lock() defer wp.mu.Unlock() if wp.running { return fmt.Errorf("worker pool is already running") } // 创建工作线程 for i := 0; i < wp.workerCount; i++ { worker := &Worker{ id: i, workQueue: wp.workQueue, processor: wp.processor, logger: wp.logger, ctx: wp.ctx, wg: &wp.wg, } wp.workers = append(wp.workers, worker) wp.wg.Add(1) go worker.start() } wp.running = true wp.logger.Info("Worker pool started", logging.Fields{ "worker_count": wp.workerCount, }) return nil } // Stop 停止工作者池 func (wp *WorkerPool) Stop() error { wp.mu.Lock() defer wp.mu.Unlock() if !wp.running { return nil } // 取消上下文 wp.cancel() // 关闭工作队列 close(wp.workQueue) // 等待所有工作者完成 wp.wg.Wait() wp.running = false wp.logger.Info("Worker pool stopped") return nil } // Submit 提交工作 func (wp *WorkerPool) Submit(work interface{}) error { wp.mu.RLock() defer wp.mu.RUnlock() if !wp.running { return fmt.Errorf("worker pool is not running") } select { case wp.workQueue <- work: wp.logger.Debug("Work submitted", logging.Fields{ "work": work, }) return nil case <-wp.ctx.Done(): return fmt.Errorf("worker pool is stopping") default: return fmt.Errorf("work queue is full") } } // SubmitWithTimeout 带超时的提交工作 func (wp *WorkerPool) SubmitWithTimeout(work interface{}, timeout time.Duration) error { wp.mu.RLock() defer wp.mu.RUnlock() if !wp.running { return fmt.Errorf("worker pool is not running") } select { case wp.workQueue <- work: wp.logger.Debug("Work submitted with timeout", logging.Fields{ "work": work, "timeout": timeout, }) return nil case <-time.After(timeout): return fmt.Errorf("work submission timeout") case <-wp.ctx.Done(): return fmt.Errorf("worker pool is stopping") } } // IsRunning 检查是否正在运行 func (wp *WorkerPool) IsRunning() bool { wp.mu.RLock() defer wp.mu.RUnlock() return wp.running } // GetWorkerCount 获取工作者数量 func (wp *WorkerPool) GetWorkerCount() int { return wp.workerCount } // GetQueueSize 获取队列大小 func (wp *WorkerPool) GetQueueSize() int { return len(wp.workQueue) } // GetStats 获取统计信息 func (wp *WorkerPool) GetStats() map[string]interface{} { wp.mu.RLock() defer wp.mu.RUnlock() stats := make(map[string]interface{}) stats["running"] = wp.running stats["worker_count"] = wp.workerCount stats["queue_size"] = len(wp.workQueue) stats["queue_capacity"] = cap(wp.workQueue) return stats } // 工作者方法 // start 启动工作者 func (w *Worker) start() { defer w.wg.Done() w.logger.Debug("Worker started", logging.Fields{ "worker_id": w.id, }) for { select { case work, ok := <-w.workQueue: if !ok { w.logger.Debug("Work queue closed, worker stopping", logging.Fields{ "worker_id": w.id, }) return } w.processWork(work) case <-w.ctx.Done(): w.logger.Debug("Worker context cancelled, stopping", logging.Fields{ "worker_id": w.id, }) return } } } // processWork 处理工作 func (w *Worker) processWork(work interface{}) { start := time.Now() w.logger.Debug("Worker processing work", logging.Fields{ "worker_id": w.id, "work": work, }) // 处理工作 if err := w.processor.Process(w.ctx, work); err != nil { w.logger.Error("Worker failed to process work", err, logging.Fields{ "worker_id": w.id, "work": work, }) return } duration := time.Since(start) w.logger.Debug("Worker completed work", logging.Fields{ "worker_id": w.id, "work": work, "duration": duration, }) } ================================================ FILE: internal/infrastructure/monitoring/metrics.go ================================================ package monitoring import ( "context" "fmt" "net/http" "net/http/pprof" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // Profiler wraps a standalone HTTP server exposing Go pprof diagnostics. type Profiler struct { server *http.Server logger logging.Logger once sync.Once } // NewProfiler creates a profiler instance using the provided logger. func NewProfiler(logger logging.Logger) *Profiler { return &Profiler{logger: logger} } // RegisterHandlers attaches standard pprof handlers to the supplied mux. func RegisterHandlers(mux *http.ServeMux) { mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) mux.Handle("/debug/pprof/heap", pprof.Handler("heap")) mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs")) mux.Handle("/debug/pprof/block", pprof.Handler("block")) mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex")) mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) } // Start launches a dedicated pprof HTTP server on the configured host/port. // The server runs asynchronously and logs lifecycle events through the logger. func (p *Profiler) Start(host string, port int) error { if p == nil { return fmt.Errorf("profiler is nil") } addr := fmt.Sprintf("%s:%d", host, port) mux := http.NewServeMux() RegisterHandlers(mux) server := &http.Server{ Addr: addr, Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, IdleTimeout: 60 * time.Second, } p.once.Do(func() { p.server = server go func() { p.logger.Info("pprof server starting", logging.Fields{"addr": addr}) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { p.logger.Error("pprof server failed", err, logging.Fields{"addr": addr}) } }() }) return nil } // Stop gracefully shuts down the pprof HTTP server. func (p *Profiler) Stop(ctx context.Context) error { if p == nil || p.server == nil { return nil } shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() p.logger.Info("stopping pprof server", logging.Fields{"addr": p.server.Addr}) if err := p.server.Shutdown(shutdownCtx); err != nil { p.logger.Error("pprof server shutdown failed", err, logging.Fields{"addr": p.server.Addr}) return err } p.logger.Info("pprof server stopped", logging.Fields{"addr": p.server.Addr}) return nil } ================================================ FILE: internal/infrastructure/network/connection_manager.go ================================================ package network import ( "context" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // ConnectionManager 连接管理器 type ConnectionManager struct { connections map[string]*Connection mutex sync.RWMutex logger logging.Logger } // Connection 连接信息 type Connection struct { ID string Address string CreatedAt time.Time LastSeen time.Time Status string } // NewConnectionManager 创建连接管理器 func NewConnectionManager(logger logging.Logger) *ConnectionManager { return &ConnectionManager{ connections: make(map[string]*Connection), logger: logger, } } // AddConnection 添加连接 func (cm *ConnectionManager) AddConnection(id, address string) { cm.mutex.Lock() defer cm.mutex.Unlock() cm.connections[id] = &Connection{ ID: id, Address: address, CreatedAt: time.Now(), LastSeen: time.Now(), Status: "active", } cm.logger.Info("连接已添加", map[string]interface{}{ "connection_id": id, "address": address, }) } // RemoveConnection 移除连接 func (cm *ConnectionManager) RemoveConnection(id string) { cm.mutex.Lock() defer cm.mutex.Unlock() if conn, exists := cm.connections[id]; exists { delete(cm.connections, id) cm.logger.Info("连接已移除", map[string]interface{}{ "connection_id": id, "address": conn.Address, }) } } // GetConnection 获取连接 func (cm *ConnectionManager) GetConnection(id string) (*Connection, bool) { cm.mutex.RLock() defer cm.mutex.RUnlock() conn, exists := cm.connections[id] return conn, exists } // GetAllConnections 获取所有连接 func (cm *ConnectionManager) GetAllConnections() map[string]*Connection { cm.mutex.RLock() defer cm.mutex.RUnlock() // 返回副本 connections := make(map[string]*Connection) for id, conn := range cm.connections { connections[id] = conn } return connections } // UpdateLastSeen 更新最后活跃时间 func (cm *ConnectionManager) UpdateLastSeen(id string) { cm.mutex.Lock() defer cm.mutex.Unlock() if conn, exists := cm.connections[id]; exists { conn.LastSeen = time.Now() } } // CleanupInactiveConnections 清理非活跃连接 func (cm *ConnectionManager) CleanupInactiveConnections(timeout time.Duration) { cm.mutex.Lock() defer cm.mutex.Unlock() now := time.Now() for id, conn := range cm.connections { if now.Sub(conn.LastSeen) > timeout { delete(cm.connections, id) cm.logger.Info("清理非活跃连接", map[string]interface{}{ "connection_id": id, "last_seen": conn.LastSeen, }) } } } // GetConnectionCount 获取连接数量 func (cm *ConnectionManager) GetConnectionCount() int { cm.mutex.RLock() defer cm.mutex.RUnlock() return len(cm.connections) } // StartCleanupRoutine 启动清理例程 func (cm *ConnectionManager) StartCleanupRoutine(ctx context.Context, interval, timeout time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): cm.logger.Info("连接清理例程已停止") return case <-ticker.C: cm.CleanupInactiveConnections(timeout) } } } ================================================ FILE: internal/infrastructure/network/netcore_client.go ================================================ package network import ( "fmt" "net" "time" "greatestworks/internal/infrastructure/logging" ) // NetCoreClient 网络核心客户端 type NetCoreClient struct { conn net.Conn logger logging.Logger } // NewNetCoreClient 创建网络核心客户端 func NewNetCoreClient(conn net.Conn, logger logging.Logger) *NetCoreClient { return &NetCoreClient{ conn: conn, logger: logger, } } // Connect 连接到服务器 func (c *NetCoreClient) Connect(address string) error { conn, err := net.Dial("tcp", address) if err != nil { return fmt.Errorf("连接失败: %w", err) } c.conn = conn c.logger.Info("已连接到服务器", map[string]interface{}{ "address": address, }) return nil } // Send 发送数据 func (c *NetCoreClient) Send(data []byte) error { if c.conn == nil { return fmt.Errorf("连接未建立") } _, err := c.conn.Write(data) if err != nil { return fmt.Errorf("发送数据失败: %w", err) } c.logger.Info("数据已发送", map[string]interface{}{ "data_length": len(data), }) return nil } // Receive 接收数据 func (c *NetCoreClient) Receive() ([]byte, error) { if c.conn == nil { return nil, fmt.Errorf("连接未建立") } buffer := make([]byte, 4096) n, err := c.conn.Read(buffer) if err != nil { return nil, fmt.Errorf("接收数据失败: %w", err) } c.logger.Info("数据已接收", map[string]interface{}{ "data_length": n, }) return buffer[:n], nil } // Close 关闭连接 func (c *NetCoreClient) Close() error { if c.conn == nil { return nil } err := c.conn.Close() if err != nil { return fmt.Errorf("关闭连接失败: %w", err) } c.logger.Info("连接已关闭") return nil } // SetReadTimeout 设置读取超时 func (c *NetCoreClient) SetReadTimeout(timeout time.Duration) error { if c.conn == nil { return fmt.Errorf("连接未建立") } return c.conn.SetReadDeadline(time.Now().Add(timeout)) } // SetWriteTimeout 设置写入超时 func (c *NetCoreClient) SetWriteTimeout(timeout time.Duration) error { if c.conn == nil { return fmt.Errorf("连接未建立") } return c.conn.SetWriteDeadline(time.Now().Add(timeout)) } // IsConnected 检查是否已连接 func (c *NetCoreClient) IsConnected() bool { return c.conn != nil } // GetRemoteAddr 获取远程地址 func (c *NetCoreClient) GetRemoteAddr() string { if c.conn == nil { return "" } return c.conn.RemoteAddr().String() } // GetLocalAddr 获取本地地址 func (c *NetCoreClient) GetLocalAddr() string { if c.conn == nil { return "" } return c.conn.LocalAddr().String() } ================================================ FILE: internal/infrastructure/network/netcore_server.go ================================================ package network import ( "fmt" "net" "sync" "greatestworks/internal/infrastructure/logging" ) // NetCoreServer 网络核心服务器 type NetCoreServer struct { listener net.Listener clients map[string]*NetCoreClient mutex sync.RWMutex logger logging.Logger port int host string onConnect func(*NetCoreClient) onDisconnect func(*NetCoreClient) onMessage func(*NetCoreClient, []byte) } // NewNetCoreServer 创建网络核心服务器 func NewNetCoreServer(host string, port int, logger logging.Logger) *NetCoreServer { return &NetCoreServer{ clients: make(map[string]*NetCoreClient), logger: logger, port: port, host: host, } } // SetOnConnect 设置连接回调 func (s *NetCoreServer) SetOnConnect(callback func(*NetCoreClient)) { s.onConnect = callback } // SetOnDisconnect 设置断开连接回调 func (s *NetCoreServer) SetOnDisconnect(callback func(*NetCoreClient)) { s.onDisconnect = callback } // SetOnMessage 设置消息回调 func (s *NetCoreServer) SetOnMessage(callback func(*NetCoreClient, []byte)) { s.onMessage = callback } // Start 启动服务器 func (s *NetCoreServer) Start() error { addr := fmt.Sprintf("%s:%d", s.host, s.port) listener, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("启动服务器失败: %w", err) } s.listener = listener s.logger.Info("Network server started", logging.Fields{ "address": addr, }) // 启动接受连接循环 go s.acceptConnections() return nil } // Stop 停止服务器 func (s *NetCoreServer) Stop() error { if s.listener == nil { return nil } s.logger.Info("Network server stopped") // 关闭所有客户端连接 s.mutex.Lock() for _, client := range s.clients { client.Close() } s.clients = make(map[string]*NetCoreClient) s.mutex.Unlock() return s.listener.Close() } // acceptConnections 接受连接 func (s *NetCoreServer) acceptConnections() { for { conn, err := s.listener.Accept() if err != nil { s.logger.Error("Failed to accept connection", err) continue } // 创建客户端 client := NewNetCoreClient(conn, s.logger) clientID := conn.RemoteAddr().String() // 添加到客户端列表 s.mutex.Lock() s.clients[clientID] = client s.mutex.Unlock() s.logger.Info("新客户端连接", logging.Fields{ "client_id": clientID, "remote_addr": conn.RemoteAddr().String(), }) // 调用连接回调 if s.onConnect != nil { s.onConnect(client) } // 启动客户端处理循环 go s.handleClient(client) } } // handleClient 处理客户端 func (s *NetCoreServer) handleClient(client *NetCoreClient) { defer func() { // 从客户端列表移除 s.mutex.Lock() delete(s.clients, client.GetRemoteAddr()) s.mutex.Unlock() // 调用断开连接回调 if s.onDisconnect != nil { s.onDisconnect(client) } // 关闭客户端连接 client.Close() }() for { // 接收消息 data, err := client.Receive() if err != nil { s.logger.Error("Failed to receive message", err, logging.Fields{ "client_id": client.GetRemoteAddr(), }) break } // 调用消息回调 if s.onMessage != nil { s.onMessage(client, data) } } } // GetClientCount 获取客户端数量 func (s *NetCoreServer) GetClientCount() int { s.mutex.RLock() defer s.mutex.RUnlock() return len(s.clients) } // GetClients 获取所有客户端 func (s *NetCoreServer) GetClients() map[string]*NetCoreClient { s.mutex.RLock() defer s.mutex.RUnlock() clients := make(map[string]*NetCoreClient) for id, client := range s.clients { clients[id] = client } return clients } // Broadcast 广播消息 func (s *NetCoreServer) Broadcast(message []byte) { s.mutex.RLock() defer s.mutex.RUnlock() for _, client := range s.clients { if err := client.Send(message); err != nil { s.logger.Error("Failed to broadcast message", err, logging.Fields{ "client_id": client.GetRemoteAddr(), }) } } } // SendToClient 发送消息给指定客户端 func (s *NetCoreServer) SendToClient(clientID string, message []byte) error { s.mutex.RLock() client, exists := s.clients[clientID] s.mutex.RUnlock() if !exists { return fmt.Errorf("客户端不存在: %s", clientID) } return client.Send(message) } // GetClient 获取客户端 func (s *NetCoreServer) GetClient(clientID string) (*NetCoreClient, bool) { s.mutex.RLock() defer s.mutex.RUnlock() client, exists := s.clients[clientID] return client, exists } ================================================ FILE: internal/infrastructure/persistence/base_repository.go ================================================ // Package persistence 统一仓储基类 // Author: MMO Server Team // Created: 2024 package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/infrastructure/cache" "greatestworks/internal/infrastructure/logging" ) // BaseRepository 基础仓储实现 type BaseRepository struct { db *mongo.Database cache cache.Cache logger logging.Logger collection *mongo.Collection collectionName string } // NewBaseRepository 创建基础仓储 func NewBaseRepository(db *mongo.Database, cache cache.Cache, logger logging.Logger, collectionName string) *BaseRepository { return &BaseRepository{ db: db, cache: cache, logger: logger, collection: db.Collection(collectionName), collectionName: collectionName, } } // GetCollection 获取集合 func (r *BaseRepository) GetCollection() *mongo.Collection { return r.collection } // GetDB 获取数据库 func (r *BaseRepository) GetDB() *mongo.Database { return r.db } // GetCache 获取缓存 func (r *BaseRepository) GetCache() cache.Cache { return r.cache } // GetLogger 获取日志器 func (r *BaseRepository) GetLogger() logging.Logger { return r.logger } // Save 保存文档 func (r *BaseRepository) Save(ctx context.Context, id string, document interface{}) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("invalid object ID: %w", err) } opts := options.Replace().SetUpsert(true) _, err = r.collection.ReplaceOne(ctx, bson.M{"_id": objectID}, document, opts) if err != nil { r.logger.Error("Failed to save document", err, logging.Fields{ "collection": r.collectionName, "id": id, }) return fmt.Errorf("failed to save document: %w", err) } // 清除缓存 if r.cache != nil { cacheKey := r.buildCacheKey(id) r.cache.Delete(ctx, cacheKey) } r.logger.Debug("Document saved successfully", logging.Fields{ "collection": r.collectionName, "id": id, }) return nil } // FindByID 根据ID查找文档 func (r *BaseRepository) FindByID(ctx context.Context, id string, result interface{}) error { // 先尝试从缓存获取 if r.cache != nil { cacheKey := r.buildCacheKey(id) if err := r.cache.Get(ctx, cacheKey, result); err == nil { r.logger.Debug("Document found in cache", logging.Fields{ "collection": r.collectionName, "id": id, }) return nil } } // 从数据库获取 objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("invalid object ID: %w", err) } err = r.collection.FindOne(ctx, bson.M{"_id": objectID}).Decode(result) if err != nil { if err == mongo.ErrNoDocuments { return fmt.Errorf("document not found") } r.logger.Error("Failed to find document", err, logging.Fields{ "collection": r.collectionName, "id": id, }) return fmt.Errorf("failed to find document: %w", err) } // 缓存结果 if r.cache != nil { cacheKey := r.buildCacheKey(id) r.cache.Set(ctx, cacheKey, result, time.Hour) } r.logger.Debug("Document found in database", logging.Fields{ "collection": r.collectionName, "id": id, }) return nil } // FindOne 查找单个文档 func (r *BaseRepository) FindOne(ctx context.Context, filter bson.M, result interface{}) error { err := r.collection.FindOne(ctx, filter).Decode(result) if err != nil { if err == mongo.ErrNoDocuments { return fmt.Errorf("document not found") } r.logger.Error("Failed to find document", err, logging.Fields{ "collection": r.collectionName, "filter": filter, "error": err, }) return fmt.Errorf("failed to find document: %w", err) } return nil } // FindMany 查找多个文档 func (r *BaseRepository) FindMany(ctx context.Context, filter bson.M, results interface{}, opts ...*options.FindOptions) error { cursor, err := r.collection.Find(ctx, filter, opts...) if err != nil { r.logger.Error("Failed to find documents", err, logging.Fields{ "collection": r.collectionName, "filter": filter, }) return fmt.Errorf("failed to find documents: %w", err) } defer cursor.Close(ctx) err = cursor.All(ctx, results) if err != nil { r.logger.Error("Failed to decode documents", err, logging.Fields{ "collection": r.collectionName, }) return fmt.Errorf("failed to decode documents: %w", err) } return nil } // Delete 删除文档 func (r *BaseRepository) Delete(ctx context.Context, id string) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("invalid object ID: %w", err) } result, err := r.collection.DeleteOne(ctx, bson.M{"_id": objectID}) if err != nil { r.logger.Error("Failed to delete document", err, logging.Fields{ "collection": r.collectionName, "id": id, "error": err, }) return fmt.Errorf("failed to delete document: %w", err) } if result.DeletedCount == 0 { return fmt.Errorf("document not found") } // 清除缓存 if r.cache != nil { cacheKey := r.buildCacheKey(id) r.cache.Delete(ctx, cacheKey) } r.logger.Debug("Document deleted successfully", logging.Fields{ "collection": r.collectionName, "id": id, }) return nil } // Count 统计文档数量 func (r *BaseRepository) Count(ctx context.Context, filter bson.M) (int64, error) { count, err := r.collection.CountDocuments(ctx, filter) if err != nil { r.logger.Error("Failed to count documents", err, logging.Fields{ "collection": r.collectionName, "filter": filter, }) return 0, fmt.Errorf("failed to count documents: %w", err) } return count, nil } // Exists 检查文档是否存在 func (r *BaseRepository) Exists(ctx context.Context, filter bson.M) (bool, error) { count, err := r.Count(ctx, filter) if err != nil { return false, err } return count > 0, nil } // CreateIndex 创建索引 func (r *BaseRepository) CreateIndex(ctx context.Context, index mongo.IndexModel) error { _, err := r.collection.Indexes().CreateOne(ctx, index) if err != nil { r.logger.Error("Failed to create index", err, logging.Fields{ "collection": r.collectionName, "error": err, }) return fmt.Errorf("failed to create index: %w", err) } r.logger.Debug("Index created successfully", logging.Fields{ "collection": r.collectionName, }) return nil } // CreateIndexes 创建多个索引 func (r *BaseRepository) CreateIndexes(ctx context.Context, indexes []mongo.IndexModel) error { _, err := r.collection.Indexes().CreateMany(ctx, indexes) if err != nil { r.logger.Error("Failed to create indexes", err, logging.Fields{ "collection": r.collectionName, }) return fmt.Errorf("failed to create indexes: %w", err) } r.logger.Debug("Indexes created successfully", logging.Fields{ "collection": r.collectionName, "count": len(indexes), }) return nil } // Aggregate 聚合查询 func (r *BaseRepository) Aggregate(ctx context.Context, pipeline mongo.Pipeline, results interface{}) error { cursor, err := r.collection.Aggregate(ctx, pipeline) if err != nil { r.logger.Error("Failed to execute aggregation", err, logging.Fields{ "collection": r.collectionName, }) return fmt.Errorf("failed to execute aggregation: %w", err) } defer cursor.Close(ctx) err = cursor.All(ctx, results) if err != nil { r.logger.Error("Failed to decode aggregation results", err, logging.Fields{ "collection": r.collectionName, }) return fmt.Errorf("failed to decode aggregation results: %w", err) } return nil } // UpdateOne 更新单个文档 func (r *BaseRepository) UpdateOne(ctx context.Context, filter bson.M, update bson.M) error { result, err := r.collection.UpdateOne(ctx, filter, update) if err != nil { r.logger.Error("Failed to update document", err, logging.Fields{ "collection": r.collectionName, "filter": filter, "error": err, }) return fmt.Errorf("failed to update document: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("document not found") } r.logger.Debug("Document updated successfully", logging.Fields{ "collection": r.collectionName, "matched": result.MatchedCount, "modified": result.ModifiedCount, }) return nil } // UpdateMany 更新多个文档 func (r *BaseRepository) UpdateMany(ctx context.Context, filter bson.M, update bson.M) error { result, err := r.collection.UpdateMany(ctx, filter, update) if err != nil { r.logger.Error("Failed to update documents", err, logging.Fields{ "collection": r.collectionName, "filter": filter, }) return fmt.Errorf("failed to update documents: %w", err) } r.logger.Debug("Documents updated successfully", logging.Fields{ "collection": r.collectionName, "matched": result.MatchedCount, "modified": result.ModifiedCount, }) return nil } // buildCacheKey 构建缓存键 func (r *BaseRepository) buildCacheKey(id string) string { return fmt.Sprintf("%s:%s", r.collectionName, id) } // WithTransaction 执行事务 func (r *BaseRepository) WithTransaction(ctx context.Context, fn func(mongo.SessionContext) (interface{}, error)) (interface{}, error) { session, err := r.db.Client().StartSession() if err != nil { return nil, fmt.Errorf("failed to start session: %w", err) } defer session.EndSession(ctx) return session.WithTransaction(ctx, fn) } // GetStats 获取仓储统计信息 func (r *BaseRepository) GetStats(ctx context.Context) (map[string]interface{}, error) { stats := make(map[string]interface{}) // 获取集合统计信息 count, err := r.Count(ctx, bson.M{}) if err != nil { return nil, fmt.Errorf("failed to get document count: %w", err) } stats["collection"] = r.collectionName stats["document_count"] = count // 获取索引信息 indexes, err := r.collection.Indexes().List(ctx) if err != nil { return nil, fmt.Errorf("failed to list indexes: %w", err) } defer indexes.Close(ctx) var indexList []bson.M if err := indexes.All(ctx, &indexList); err != nil { return nil, fmt.Errorf("failed to decode indexes: %w", err) } stats["index_count"] = len(indexList) return stats, nil } ================================================ FILE: internal/infrastructure/persistence/building_repository.go ================================================ package persistence import ( "context" "errors" "greatestworks/internal/domain/building" ) // MongoBuildingRepository MongoDB建筑仓储实现 type MongoBuildingRepository struct { // TODO: 添加MongoDB连接 } // NewMongoBuildingRepository 创建新的MongoDB建筑仓储 func NewMongoBuildingRepository() building.BuildingRepository { return &MongoBuildingRepository{} } // Save 保存建筑 func (r *MongoBuildingRepository) Save(ctx context.Context, building *building.BuildingAggregate) error { // TODO: 实现保存逻辑 return errors.New("not implemented") } // FindByID 根据ID查找建筑 func (r *MongoBuildingRepository) FindByID(ctx context.Context, id string) (*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByIDs 根据ID列表查找建筑 func (r *MongoBuildingRepository) FindByIDs(ctx context.Context, ids []string) ([]*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // Delete 删除建筑 func (r *MongoBuildingRepository) Delete(ctx context.Context, id string) error { // TODO: 实现删除逻辑 return errors.New("not implemented") } // Exists 检查建筑是否存在 func (r *MongoBuildingRepository) Exists(ctx context.Context, id string) (bool, error) { // TODO: 实现存在检查逻辑 return false, errors.New("not implemented") } // FindByOwner 根据拥有者查找建筑 func (r *MongoBuildingRepository) FindByOwner(ctx context.Context, ownerID uint64) ([]*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByType 根据类型查找建筑 func (r *MongoBuildingRepository) FindByType(ctx context.Context, buildingType building.BuildingType) ([]*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByCategory 根据分类查找建筑 func (r *MongoBuildingRepository) FindByCategory(ctx context.Context, category building.BuildingCategory) ([]*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByStatus 根据状态查找建筑 func (r *MongoBuildingRepository) FindByStatus(ctx context.Context, status building.BuildingStatus) ([]*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByPosition 根据位置查找建筑 func (r *MongoBuildingRepository) FindByPosition(ctx context.Context, position *building.Position) (*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByPlayerAndPosition 根据玩家和位置查找建筑 func (r *MongoBuildingRepository) FindByPlayerAndPosition(ctx context.Context, playerID uint64, position *building.Position) (*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByArea 根据区域查找建筑 func (r *MongoBuildingRepository) FindByArea(ctx context.Context, area *building.Area) ([]*building.BuildingAggregate, error) { // TODO: 实现查找逻辑 return nil, errors.New("not implemented") } // FindByQuery 根据查询条件查找建筑 func (r *MongoBuildingRepository) FindByQuery(ctx context.Context, query *building.BuildingQuery) ([]*building.BuildingAggregate, int64, error) { // TODO: 实现查询逻辑 return nil, 0, errors.New("not implemented") } // Count 统计建筑总数 func (r *MongoBuildingRepository) Count(ctx context.Context) (int64, error) { // TODO: 实现统计逻辑 return 0, errors.New("not implemented") } // CountByOwner 根据拥有者统计建筑数量 func (r *MongoBuildingRepository) CountByOwner(ctx context.Context, ownerID uint64) (int64, error) { // TODO: 实现统计逻辑 return 0, errors.New("not implemented") } // CountByType 根据类型统计建筑数量 func (r *MongoBuildingRepository) CountByType(ctx context.Context, buildingType building.BuildingType) (int64, error) { // TODO: 实现统计逻辑 return 0, errors.New("not implemented") } // CountByCategory 根据分类统计建筑数量 func (r *MongoBuildingRepository) CountByCategory(ctx context.Context, category building.BuildingCategory) (int64, error) { // TODO: 实现统计逻辑 return 0, errors.New("not implemented") } // CountByStatus 根据状态统计建筑数量 func (r *MongoBuildingRepository) CountByStatus(ctx context.Context, status building.BuildingStatus) (int64, error) { // TODO: 实现统计逻辑 return 0, errors.New("not implemented") } // GetStatistics 获取建筑统计信息 func (r *MongoBuildingRepository) GetStatistics(ctx context.Context, ownerID uint64) (*building.BuildingStatistics, error) { // TODO: 实现统计逻辑 return nil, errors.New("not implemented") } // SaveAll 批量保存建筑 func (r *MongoBuildingRepository) SaveAll(ctx context.Context, buildings []*building.BuildingAggregate) error { // TODO: 实现批量保存逻辑 return errors.New("not implemented") } // DeleteAll 批量删除建筑 func (r *MongoBuildingRepository) DeleteAll(ctx context.Context, ids []string) error { // TODO: 实现批量删除逻辑 return errors.New("not implemented") } // UpdateStatus 更新建筑状态 func (r *MongoBuildingRepository) UpdateStatus(ctx context.Context, ids []string, status building.BuildingStatus) error { // TODO: 实现状态更新逻辑 return errors.New("not implemented") } // UpdateHealth 更新建筑健康度 func (r *MongoBuildingRepository) UpdateHealth(ctx context.Context, id string, health float64) error { // TODO: 实现健康度更新逻辑 return errors.New("not implemented") } // UpdateLevel 更新建筑等级 func (r *MongoBuildingRepository) UpdateLevel(ctx context.Context, id string, level int32) error { // TODO: 实现等级更新逻辑 return errors.New("not implemented") } ================================================ FILE: internal/infrastructure/persistence/db_entities.go ================================================ package persistence import ( "time" "go.mongodb.org/mongo-driver/bson/primitive" ) // DbUser 用户数据库实体 type DbUser struct { ID primitive.ObjectID `bson:"_id,omitempty"` UserID int64 `bson:"user_id"` Username string `bson:"username"` PasswordHash string `bson:"password_hash"` Status int32 `bson:"status"` // 0:正常 1:封禁 CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` LastLoginAt time.Time `bson:"last_login_at"` } // DbCharacter 角色数据库实体 type DbCharacter struct { ID primitive.ObjectID `bson:"_id,omitempty"` CharacterID int64 `bson:"character_id"` UserID int64 `bson:"user_id"` Name string `bson:"name"` Race int32 `bson:"race"` Class int32 `bson:"class"` Level int32 `bson:"level"` Exp int64 `bson:"exp"` Gold int64 `bson:"gold"` // 位置信息 MapID int32 `bson:"map_id"` PositionX float32 `bson:"position_x"` PositionY float32 `bson:"position_y"` PositionZ float32 `bson:"position_z"` Direction float32 `bson:"direction"` // 属性 HP int32 `bson:"hp"` MP int32 `bson:"mp"` MaxHP int32 `bson:"max_hp"` MaxMP int32 `bson:"max_mp"` // 基础属性 STR int32 `bson:"str"` // 力量 INT int32 `bson:"int"` // 智力 AGI int32 `bson:"agi"` // 敏捷 VIT int32 `bson:"vit"` // 体力 SPR int32 `bson:"spr"` // 精神 // 战斗属性 AD int32 `bson:"ad"` // 物理攻击 AP int32 `bson:"ap"` // 魔法攻击 DEF int32 `bson:"def"` // 物理防御 RES int32 `bson:"res"` // 魔法抗性 SPD int32 `bson:"spd"` // 速度 CRI int32 `bson:"cri"` // 暴击率 CRID int32 `bson:"crid"` // 暴击伤害 HitRate int32 `bson:"hit_rate"` // 命中率 Dodge int32 `bson:"dodge"` // 闪避率 // 时间戳 CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` DeletedAt time.Time `bson:"deleted_at,omitempty"` } // DbItem 物品数据库实体(背包/装备/仓库) type DbItem struct { ID primitive.ObjectID `bson:"_id,omitempty"` ItemUID int64 `bson:"item_uid"` // 物品唯一ID CharacterID int64 `bson:"character_id"` // 所属角色 ItemID int32 `bson:"item_id"` // 物品配置ID Count int32 `bson:"count"` // 数量 Slot int32 `bson:"slot"` // 槽位 Location int32 `bson:"location"` // 位置(背包/装备/仓库) Bound bool `bson:"bound"` // 是否绑定 Expire int64 `bson:"expire"` // 过期时间戳 CreatedAt time.Time `bson:"created_at"` } // DbQuest 任务进度数据库实体 type DbQuest struct { ID primitive.ObjectID `bson:"_id,omitempty"` CharacterID int64 `bson:"character_id"` QuestID int32 `bson:"quest_id"` Status int32 `bson:"status"` // 0:进行中 1:已完成 2:已领取 Objectives []DbObjective `bson:"objectives"` AcceptedAt time.Time `bson:"accepted_at"` CompletedAt time.Time `bson:"completed_at,omitempty"` } // DbObjective 任务目标 type DbObjective struct { Type int32 `bson:"type"` // 目标类型 TargetID int32 `bson:"target_id"` // 目标ID Required int32 `bson:"required"` // 需要数量 Current int32 `bson:"current"` // 当前数量 } // DbMail 邮件数据库实体 type DbMail struct { ID primitive.ObjectID `bson:"_id,omitempty"` MailID int64 `bson:"mail_id"` ReceiverID int64 `bson:"receiver_id"` SenderName string `bson:"sender_name"` Title string `bson:"title"` Content string `bson:"content"` IsRead bool `bson:"is_read"` HasItems bool `bson:"has_items"` Attachments []DbAttachment `bson:"attachments"` ExpireAt time.Time `bson:"expire_at"` CreatedAt time.Time `bson:"created_at"` } // DbAttachment 邮件附件 type DbAttachment struct { ItemID int32 `bson:"item_id"` Count int32 `bson:"count"` } // DbGuild 公会数据库实体 type DbGuild struct { ID primitive.ObjectID `bson:"_id,omitempty"` GuildID int64 `bson:"guild_id"` Name string `bson:"name"` LeaderID int64 `bson:"leader_id"` Level int32 `bson:"level"` Exp int64 `bson:"exp"` Notice string `bson:"notice"` MemberCount int32 `bson:"member_count"` MaxMembers int32 `bson:"max_members"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` } // DbGuildMember 公会成员数据库实体 type DbGuildMember struct { ID primitive.ObjectID `bson:"_id,omitempty"` GuildID int64 `bson:"guild_id"` CharacterID int64 `bson:"character_id"` Rank int32 `bson:"rank"` // 0:会长 1:副会长 2:精英 3:成员 Contribution int64 `bson:"contribution"` JoinedAt time.Time `bson:"joined_at"` } ================================================ FILE: internal/infrastructure/persistence/hangup_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/infrastructure/logging" ) // HangupRepository 挂机仓储 type HangupRepository struct { collection *mongo.Collection logger logging.Logger } // NewHangupRepository 创建挂机仓储 func NewHangupRepository(db *mongo.Database, logger logging.Logger) *HangupRepository { return &HangupRepository{ collection: db.Collection("hangups"), logger: logger, } } // HangupRecord 挂机记录 type HangupRecord struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` PlayerID string `bson:"player_id" json:"player_id"` StartTime time.Time `bson:"start_time" json:"start_time"` EndTime *time.Time `bson:"end_time,omitempty" json:"end_time,omitempty"` Duration int64 `bson:"duration" json:"duration"` Experience int64 `bson:"experience" json:"experience"` Gold int64 `bson:"gold" json:"gold"` Status string `bson:"status" json:"status"` CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` } // CreateHangup 创建挂机记录 func (r *HangupRepository) CreateHangup(ctx context.Context, record *HangupRecord) error { record.CreatedAt = time.Now() record.UpdatedAt = time.Now() _, err := r.collection.InsertOne(ctx, record) if err != nil { r.logger.Error("创建挂机记录失败", err, logging.Fields{ "player_id": record.PlayerID, }) return fmt.Errorf("创建挂机记录失败: %w", err) } r.logger.Info("挂机记录创建成功", map[string]interface{}{ "player_id": record.PlayerID, "start_time": record.StartTime, }) return nil } // GetHangup 获取挂机记录 func (r *HangupRepository) GetHangup(ctx context.Context, id string) (*HangupRecord, error) { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, fmt.Errorf("无效的ID格式: %w", err) } var record HangupRecord err = r.collection.FindOne(ctx, bson.M{"_id": objectID}).Decode(&record) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("挂机记录不存在") } r.logger.Error("获取挂机记录失败", err, logging.Fields{ "id": id, }) return nil, fmt.Errorf("获取挂机记录失败: %w", err) } return &record, nil } // UpdateHangup 更新挂机记录 func (r *HangupRepository) UpdateHangup(ctx context.Context, id string, updates map[string]interface{}) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates["updated_at"] = time.Now() result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("更新挂机记录失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("更新挂机记录失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("挂机记录不存在") } r.logger.Info("挂机记录更新成功", map[string]interface{}{ "id": id, }) return nil } // DeleteHangup 删除挂机记录 func (r *HangupRepository) DeleteHangup(ctx context.Context, id string) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } result, err := r.collection.DeleteOne(ctx, bson.M{"_id": objectID}) if err != nil { r.logger.Error("删除挂机记录失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("删除挂机记录失败: %w", err) } if result.DeletedCount == 0 { return fmt.Errorf("挂机记录不存在") } r.logger.Info("挂机记录删除成功", map[string]interface{}{ "id": id, }) return nil } // GetPlayerHangups 获取玩家的挂机记录 func (r *HangupRepository) GetPlayerHangups(ctx context.Context, playerID string, limit, offset int) ([]*HangupRecord, error) { filter := bson.M{"player_id": playerID} opts := options.Find(). SetSort(bson.D{{Key: "created_at", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("获取玩家挂机记录失败", err, logging.Fields{ "player_id": playerID, }) return nil, fmt.Errorf("获取玩家挂机记录失败: %w", err) } defer cursor.Close(ctx) var records []*HangupRecord if err = cursor.All(ctx, &records); err != nil { return nil, fmt.Errorf("解析挂机记录失败: %w", err) } r.logger.Info("获取玩家挂机记录成功", map[string]interface{}{ "player_id": playerID, "count": len(records), }) return records, nil } // GetActiveHangups 获取活跃的挂机记录 func (r *HangupRepository) GetActiveHangups(ctx context.Context, playerID string) ([]*HangupRecord, error) { filter := bson.M{ "player_id": playerID, "status": "active", } cursor, err := r.collection.Find(ctx, filter) if err != nil { r.logger.Error("获取活跃挂机记录失败", err, logging.Fields{ "player_id": playerID, }) return nil, fmt.Errorf("获取活跃挂机记录失败: %w", err) } defer cursor.Close(ctx) var records []*HangupRecord if err = cursor.All(ctx, &records); err != nil { return nil, fmt.Errorf("解析挂机记录失败: %w", err) } return records, nil } // EndHangup 结束挂机 func (r *HangupRepository) EndHangup(ctx context.Context, id string, endTime time.Time, experience, gold int64) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates := bson.M{ "end_time": endTime, "duration": time.Since(endTime).Seconds(), "experience": experience, "gold": gold, "status": "completed", "updated_at": time.Now(), } result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("结束挂机失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("结束挂机失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("挂机记录不存在") } r.logger.Info("挂机结束成功", map[string]interface{}{ "id": id, "experience": experience, "gold": gold, }) return nil } ================================================ FILE: internal/infrastructure/persistence/minigame_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/domain/minigame" ) // MinigameRepository MongoDB小游戏仓储实现 type MinigameRepository struct { db *mongo.Database minigameColl *mongo.Collection sessionColl *mongo.Collection } // NewMinigameRepository 创建小游戏仓储 func NewMinigameRepository(db *mongo.Database) *MinigameRepository { return &MinigameRepository{ db: db, minigameColl: db.Collection("minigames"), sessionColl: db.Collection("game_sessions"), } } // MinigameDocument 小游戏文档结构 type MinigameDocument struct { ID primitive.ObjectID `bson:"_id,omitempty"` MinigameID string `bson:"minigame_id"` Name string `bson:"name"` Description string `bson:"description"` GameType string `bson:"game_type"` Difficulty string `bson:"difficulty"` MaxPlayers int32 `bson:"max_players"` TimeLimit int32 `bson:"time_limit"` IsActive bool `bson:"is_active"` Rules map[string]interface{} `bson:"rules"` Rewards []RewardDocument `bson:"rewards"` Settings map[string]interface{} `bson:"settings"` Statistics StatisticsDocument `bson:"statistics"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` Version int64 `bson:"version"` } // RewardDocument 奖励文档结构 type RewardDocument struct { Type string `bson:"type"` ItemID string `bson:"item_id,omitempty"` Quantity int32 `bson:"quantity"` Reason string `bson:"reason"` } // StatisticsDocument 统计文档结构 type StatisticsDocument struct { TotalPlays int64 `bson:"total_plays"` TotalPlayers int64 `bson:"total_players"` AverageScore float64 `bson:"average_score"` HighestScore int64 `bson:"highest_score"` AverageTime float64 `bson:"average_time"` CompletionRate float64 `bson:"completion_rate"` } // GameSessionDocument 游戏会话文档结构 type GameSessionDocument struct { ID primitive.ObjectID `bson:"_id,omitempty"` SessionID string `bson:"session_id"` MinigameID string `bson:"minigame_id"` PlayerID uint64 `bson:"player_id"` Status string `bson:"status"` Score int64 `bson:"score"` TimeLimit int32 `bson:"time_limit"` TimeElapsed int32 `bson:"time_elapsed"` Settings map[string]interface{} `bson:"settings"` GameData map[string]interface{} `bson:"game_data"` Rewards []RewardDocument `bson:"rewards"` StartedAt time.Time `bson:"started_at"` ExpiresAt time.Time `bson:"expires_at"` CompletedAt time.Time `bson:"completed_at,omitempty"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` } // Save 保存小游戏聚合根 func (r *MinigameRepository) Save(ctx context.Context, minigameAggregate *minigame.MinigameAggregate) error { doc := r.toMinigameDocument(minigameAggregate) filter := bson.M{"minigame_id": doc.MinigameID} update := bson.M{ "$set": doc, "$inc": bson.M{"version": 1}, } upsert := true _, err := r.minigameColl.UpdateOne(ctx, filter, update, &options.UpdateOptions{Upsert: &upsert}) if err != nil { return fmt.Errorf("failed to save minigame: %w", err) } return nil } // FindByID 根据ID查找小游戏 func (r *MinigameRepository) FindByID(ctx context.Context, minigameID string) (*minigame.MinigameAggregate, error) { filter := bson.M{"minigame_id": minigameID} var doc MinigameDocument err := r.minigameColl.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("failed to find minigame: %w", err) } return r.fromMinigameDocument(&doc), nil } // FindByType 根据游戏类型查找小游戏 func (r *MinigameRepository) FindByType(ctx context.Context, gameType minigame.GameType) ([]*minigame.MinigameAggregate, error) { filter := bson.M{ "game_type": gameType.String(), "is_active": true, } cursor, err := r.minigameColl.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("failed to find minigames by type: %w", err) } defer cursor.Close(ctx) var minigames []*minigame.MinigameAggregate for cursor.Next(ctx) { var doc MinigameDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode minigame document: %w", err) } minigames = append(minigames, r.fromMinigameDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return minigames, nil } // FindActive 查找激活的小游戏 func (r *MinigameRepository) FindActive(ctx context.Context) ([]*minigame.MinigameAggregate, error) { filter := bson.M{"is_active": true} cursor, err := r.minigameColl.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("failed to find active minigames: %w", err) } defer cursor.Close(ctx) var minigames []*minigame.MinigameAggregate for cursor.Next(ctx) { var doc MinigameDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode minigame document: %w", err) } minigames = append(minigames, r.fromMinigameDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return minigames, nil } // Delete 删除小游戏 func (r *MinigameRepository) Delete(ctx context.Context, minigameID string) error { filter := bson.M{"minigame_id": minigameID} update := bson.M{ "$set": bson.M{ "is_active": false, "updated_at": time.Now(), }, "$inc": bson.M{"version": 1}, } _, err := r.minigameColl.UpdateOne(ctx, filter, update) if err != nil { return fmt.Errorf("failed to delete minigame: %w", err) } return nil } // GameSessionRepository 游戏会话仓储实现 type GameSessionRepository struct { db *mongo.Database sessionColl *mongo.Collection } // NewGameSessionRepository 创建游戏会话仓储 func NewGameSessionRepository(db *mongo.Database) *GameSessionRepository { return &GameSessionRepository{ db: db, sessionColl: db.Collection("game_sessions"), } } // Save 保存游戏会话 func (r *GameSessionRepository) Save(ctx context.Context, session *minigame.GameSession) error { doc := r.toGameSessionDocument(session) filter := bson.M{"session_id": doc.SessionID} update := bson.M{"$set": doc} upsert := true _, err := r.sessionColl.UpdateOne(ctx, filter, update, &options.UpdateOptions{Upsert: &upsert}) if err != nil { return fmt.Errorf("failed to save game session: %w", err) } return nil } // FindByID 根据ID查找游戏会话 func (r *GameSessionRepository) FindByID(ctx context.Context, sessionID string) (*minigame.GameSession, error) { filter := bson.M{"session_id": sessionID} var doc GameSessionDocument err := r.sessionColl.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("failed to find game session: %w", err) } return r.fromGameSessionDocument(&doc), nil } // FindActiveByPlayer 根据玩家查找激活的游戏会话 func (r *GameSessionRepository) FindActiveByPlayer(ctx context.Context, playerID uint64) (*minigame.GameSession, error) { filter := bson.M{ "player_id": playerID, "status": minigame.SessionStatusActive.String(), } var doc GameSessionDocument err := r.sessionColl.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("failed to find active game session: %w", err) } return r.fromGameSessionDocument(&doc), nil } // FindByPlayer 根据玩家查找游戏会话 func (r *GameSessionRepository) FindByPlayer(ctx context.Context, playerID uint64, limit int) ([]*minigame.GameSession, error) { filter := bson.M{"player_id": playerID} opts := options.Find(). SetSort(bson.D{{Key: "created_at", Value: -1}}). SetLimit(int64(limit)) cursor, err := r.sessionColl.Find(ctx, filter, opts) if err != nil { return nil, fmt.Errorf("failed to find game sessions by player: %w", err) } defer cursor.Close(ctx) var sessions []*minigame.GameSession for cursor.Next(ctx) { var doc GameSessionDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode game session document: %w", err) } sessions = append(sessions, r.fromGameSessionDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return sessions, nil } // FindByMinigame 根据小游戏查找会话 func (r *GameSessionRepository) FindByMinigame(ctx context.Context, minigameID string, limit int) ([]*minigame.GameSession, error) { filter := bson.M{"minigame_id": minigameID} opts := options.Find(). SetSort(bson.D{{Key: "score", Value: -1}}). SetLimit(int64(limit)) cursor, err := r.sessionColl.Find(ctx, filter, opts) if err != nil { return nil, fmt.Errorf("failed to find game sessions by minigame: %w", err) } defer cursor.Close(ctx) var sessions []*minigame.GameSession for cursor.Next(ctx) { var doc GameSessionDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode game session document: %w", err) } sessions = append(sessions, r.fromGameSessionDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return sessions, nil } // FindByQuery 根据查询条件查找会话 func (r *GameSessionRepository) FindByQuery(ctx context.Context, query *minigame.GameSessionQuery) ([]*minigame.GameSession, int64, error) { filter := r.buildGameSessionFilter(query) // 计算总数 total, err := r.sessionColl.CountDocuments(ctx, filter) if err != nil { return nil, 0, fmt.Errorf("failed to count game sessions: %w", err) } // 构建查询选项 opts := options.Find() if query.GetSort() != "" { sortOrder := 1 if query.GetSortOrder() { sortOrder = -1 } opts.SetSort(bson.D{{Key: query.GetSort(), Value: sortOrder}}) } if query.GetLimit() > 0 { opts.SetLimit(int64(query.GetLimit())) } if query.GetOffset() > 0 { opts.SetSkip(int64(query.GetOffset())) } cursor, err := r.sessionColl.Find(ctx, filter, opts) if err != nil { return nil, 0, fmt.Errorf("failed to find game sessions: %w", err) } defer cursor.Close(ctx) var sessions []*minigame.GameSession for cursor.Next(ctx) { var doc GameSessionDocument if err := cursor.Decode(&doc); err != nil { return nil, 0, fmt.Errorf("failed to decode game session document: %w", err) } sessions = append(sessions, r.fromGameSessionDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, 0, fmt.Errorf("cursor error: %w", err) } return sessions, total, nil } // CleanupExpiredSessions 清理过期会话 func (r *GameSessionRepository) CleanupExpiredSessions(ctx context.Context) (int64, error) { filter := bson.M{ "status": minigame.SessionStatusActive.String(), "expires_at": bson.M{"$lt": time.Now()}, } update := bson.M{ "$set": bson.M{ "status": minigame.SessionStatusExpired.String(), "updated_at": time.Now(), }, } result, err := r.sessionColl.UpdateMany(ctx, filter, update) if err != nil { return 0, fmt.Errorf("failed to cleanup expired sessions: %w", err) } return result.ModifiedCount, nil } // 私有方法 // toMinigameDocument 转换为小游戏文档 func (r *MinigameRepository) toMinigameDocument(minigameAggregate *minigame.MinigameAggregate) *MinigameDocument { // 转换奖励 rewards := make([]RewardDocument, 0) for _, reward := range minigameAggregate.GetRewards() { rewards = append(rewards, RewardDocument{ Type: reward.GetType().String(), ItemID: reward.GetItemID(), Quantity: int32(reward.GetQuantity()), Reason: reward.GetReason(), }) } // 转换统计信息 stats := minigameAggregate.GetStatistics() statistics := StatisticsDocument{ TotalPlays: int64(stats.GetTotalPlays()), TotalPlayers: int64(stats.GetTotalPlayers()), AverageScore: stats.GetAverageScore(), HighestScore: stats.GetHighestScore(), AverageTime: float64(stats.GetAverageTime()), CompletionRate: stats.GetCompletionRate(), } return &MinigameDocument{ MinigameID: minigameAggregate.GetID(), Name: minigameAggregate.GetName(), Description: minigameAggregate.GetDescription(), GameType: minigameAggregate.GetGameType().String(), Difficulty: minigameAggregate.GetDifficulty().String(), MaxPlayers: minigameAggregate.GetMaxPlayers(), TimeLimit: minigameAggregate.GetTimeLimit(), IsActive: minigameAggregate.GetIsActive(), Rules: minigameAggregate.GetRules(), Rewards: rewards, Settings: minigameAggregate.GetSettings(), Statistics: statistics, CreatedAt: minigameAggregate.GetCreatedAt(), UpdatedAt: minigameAggregate.GetUpdatedAt(), Version: minigameAggregate.GetVersion(), } } // fromMinigameDocument 从小游戏文档转换 func (r *MinigameRepository) fromMinigameDocument(doc *MinigameDocument) *minigame.MinigameAggregate { // 解析枚举值 gameType, _ := minigame.GetGameTypeByString(doc.GameType) // 重建聚合 - 使用默认值替代缺失的字段 minigameAggregate := minigame.NewMinigameAggregate( doc.MinigameID, gameType, minigame.GameCategoryNormal, // 使用默认分类 0, // 使用默认创建者ID ) // 转换奖励 for _, rewardDoc := range doc.Rewards { rewardType, _ := minigame.GetRewardTypeByString(rewardDoc.Type) reward := minigame.NewGameReward(doc.MinigameID, 0, "", rewardType, rewardDoc.ItemID, int64(rewardDoc.Quantity)) // Note: AddReward method may need to be implemented on MinigameAggregate _ = reward // 暂时忽略以避免编译错误 } // Note: 统计信息和激活状态的设置需要根据实际的聚合根方法来调整 // 由于这些setter方法可能不存在,我们暂时注释掉 /* // 设置统计信息 stats := minigame.NewGameStatistics() stats.SetTotalPlays(doc.Statistics.TotalPlays) stats.SetTotalPlayers(doc.Statistics.TotalPlayers) stats.SetAverageScore(doc.Statistics.AverageScore) stats.SetHighestScore(doc.Statistics.HighestScore) stats.SetAverageTime(doc.Statistics.AverageTime) stats.SetCompletionRate(doc.Statistics.CompletionRate) minigameAggregate.SetStatistics(stats) if doc.IsActive { minigameAggregate.Activate() } else { minigameAggregate.Deactivate() } */ return minigameAggregate } // toGameSessionDocument 转换为游戏会话文档 func (r *GameSessionRepository) toGameSessionDocument(session *minigame.GameSession) *GameSessionDocument { // 转换奖励 rewards := make([]RewardDocument, 0) for _, reward := range session.GetRewards() { rewards = append(rewards, RewardDocument{ Type: reward.GetType().String(), ItemID: reward.GetItemID(), Quantity: int32(reward.GetQuantity()), Reason: reward.GetReason(), }) } doc := &GameSessionDocument{ SessionID: session.GetID(), MinigameID: session.GetMinigameID(), PlayerID: session.GetPlayerID(), Status: session.GetStatus().String(), Score: session.GetScore(), TimeLimit: int32(session.GetTimeLimit() / time.Second), TimeElapsed: int32(session.GetTimeElapsed() / time.Second), Settings: session.GetSettings(), GameData: make(map[string]interface{}), // 使用空的游戏数据 Rewards: rewards, StartedAt: session.GetStartedAt(), ExpiresAt: session.GetExpiresAt(), CreatedAt: session.GetCreatedAt(), UpdatedAt: session.GetUpdatedAt(), } if !session.GetCompletedAt().IsZero() { doc.CompletedAt = session.GetCompletedAt() } return doc } // fromGameSessionDocument 从游戏会话文档转换 func (r *GameSessionRepository) fromGameSessionDocument(doc *GameSessionDocument) *minigame.GameSession { // 解析状态 status := minigame.ParseSessionStatus(doc.Status) // 重建会话 session := minigame.NewGameSession( doc.SessionID, doc.PlayerID, doc.MinigameID, ) session.SetStatus(status) session.SetScore(doc.Score) session.SetTimeElapsed(time.Duration(doc.TimeElapsed) * time.Second) session.SetSettings(doc.Settings) // 设置游戏数据 - 跳过,因为方法签名不匹配 // session.SetGameData(doc.GameData) session.SetTimestamps(doc.StartedAt, doc.ExpiresAt, doc.CompletedAt) // 转换奖励 for _, rewardDoc := range doc.Rewards { rewardType, _ := minigame.GetRewardTypeByString(rewardDoc.Type) reward := minigame.NewGameReward(doc.MinigameID, doc.PlayerID, doc.SessionID, rewardType, rewardDoc.ItemID, int64(rewardDoc.Quantity)) session.AddReward(reward) } return session } // buildGameSessionFilter 构建游戏会话查询过滤器 func (r *GameSessionRepository) buildGameSessionFilter(query *minigame.GameSessionQuery) bson.M { filter := bson.M{} if query.GetPlayerID() > 0 { filter["player_id"] = query.GetPlayerID() } if query.GetMinigameID() != "" { filter["minigame_id"] = query.GetMinigameID() } if query.GetStatus() != nil { filter["status"] = query.GetStatus().String() } // 注释掉不存在的方法调用 // if query.GetMinScore() > 0 { // filter["score"] = bson.M{"$gte": query.GetMinScore()} // } // if query.GetMaxScore() > 0 { // if scoreFilter, exists := filter["score"]; exists { // scoreFilter.(bson.M)["$lte"] = query.GetMaxScore() // } else { // filter["score"] = bson.M{"$lte": query.GetMaxScore()} // } // } // 注释掉不存在的时间方法调用 // if !query.GetStartTime().IsZero() { // filter["started_at"] = bson.M{"$gte": query.GetStartTime()} // } // if !query.GetEndTime().IsZero() { // if timeFilter, exists := filter["started_at"]; exists { // timeFilter.(bson.M)["$lte"] = query.GetEndTime() // } else { // filter["started_at"] = bson.M{"$lte": query.GetEndTime()} // } // } return filter } // CreateIndexes 创建索引 func (r *MinigameRepository) CreateIndexes(ctx context.Context) error { // 小游戏索引 minigameIndexes := []mongo.IndexModel{ { Keys: bson.D{{Key: "minigame_id", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "game_type", Value: 1}, {Key: "is_active", Value: 1}}, }, { Keys: bson.D{{Key: "difficulty", Value: 1}}, }, { Keys: bson.D{{Key: "is_active", Value: 1}}, }, } if _, err := r.minigameColl.Indexes().CreateMany(ctx, minigameIndexes); err != nil { return fmt.Errorf("failed to create minigame indexes: %w", err) } return nil } // CreateIndexes 创建游戏会话索引 func (r *GameSessionRepository) CreateIndexes(ctx context.Context) error { // 游戏会话索引 sessionIndexes := []mongo.IndexModel{ { Keys: bson.D{{Key: "session_id", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "player_id", Value: 1}, {Key: "status", Value: 1}}, }, { Keys: bson.D{{Key: "minigame_id", Value: 1}, {Key: "score", Value: -1}}, }, { Keys: bson.D{{Key: "status", Value: 1}, {Key: "expires_at", Value: 1}}, }, { Keys: bson.D{{Key: "started_at", Value: -1}}, }, { Keys: bson.D{{Key: "completed_at", Value: -1}}, }, } if _, err := r.sessionColl.Indexes().CreateMany(ctx, sessionIndexes); err != nil { return fmt.Errorf("failed to create game session indexes: %w", err) } return nil } ================================================ FILE: internal/infrastructure/persistence/mongodb.go ================================================ // Package persistence 数据持久化基础设施 package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" ) // MongoConfig MongoDB配置 type MongoConfig struct { URI string `json:"uri"` Database string `json:"database"` ConnectTimeout time.Duration `json:"connect_timeout"` MaxPoolSize uint64 `json:"max_pool_size"` MinPoolSize uint64 `json:"min_pool_size"` } // DefaultMongoConfig 默认MongoDB配置 func DefaultMongoConfig() *MongoConfig { return &MongoConfig{ URI: "mongodb://localhost:27017", Database: "greatestworks", ConnectTimeout: 10 * time.Second, MaxPoolSize: 100, MinPoolSize: 5, } } // MongoDB MongoDB客户端包装器 type MongoDB struct { client *mongo.Client database *mongo.Database config *MongoConfig } // NewMongoDB 创建新的MongoDB客户端 func NewMongoDB(config *MongoConfig) (*MongoDB, error) { ctx, cancel := context.WithTimeout(context.Background(), config.ConnectTimeout) defer cancel() // 设置客户端选项 clientOptions := options.Client(). ApplyURI(config.URI). SetMaxPoolSize(config.MaxPoolSize). SetMinPoolSize(config.MinPoolSize). SetMaxConnIdleTime(30 * time.Minute) // 连接到MongoDB client, err := mongo.Connect(ctx, clientOptions) if err != nil { return nil, fmt.Errorf("连接MongoDB失败: %w", err) } // 测试连接 if err := client.Ping(ctx, readpref.Primary()); err != nil { return nil, fmt.Errorf("MongoDB连接测试失败: %w", err) } database := client.Database(config.Database) return &MongoDB{ client: client, database: database, config: config, }, nil } // GetDatabase 获取数据库实例 func (m *MongoDB) GetDatabase() *mongo.Database { return m.database } // GetCollection 获取集合 func (m *MongoDB) GetCollection(name string) *mongo.Collection { return m.database.Collection(name) } // Close 关闭连接 func (m *MongoDB) Close(ctx context.Context) error { return m.client.Disconnect(ctx) } // Ping 测试连接 func (m *MongoDB) Ping(ctx context.Context) error { return m.client.Ping(ctx, readpref.Primary()) } // StartSession 开始会话 func (m *MongoDB) StartSession() (mongo.Session, error) { return m.client.StartSession() } // WithTransaction 执行事务 func (m *MongoDB) WithTransaction(ctx context.Context, fn func(mongo.SessionContext) (interface{}, error)) (interface{}, error) { session, err := m.StartSession() if err != nil { return nil, err } defer session.EndSession(ctx) return session.WithTransaction(ctx, fn) } // CreateIndexes 创建索引 func (m *MongoDB) CreateIndexes(ctx context.Context, collectionName string, indexes []mongo.IndexModel) error { collection := m.GetCollection(collectionName) _, err := collection.Indexes().CreateMany(ctx, indexes) return err } // DropCollection 删除集合 func (m *MongoDB) DropCollection(ctx context.Context, collectionName string) error { return m.GetCollection(collectionName).Drop(ctx) } // ListCollections 列出所有集合 func (m *MongoDB) ListCollections(ctx context.Context) ([]string, error) { cursor, err := m.database.ListCollectionNames(ctx, map[string]interface{}{}) if err != nil { return nil, err } return cursor, nil } ================================================ FILE: internal/infrastructure/persistence/npc_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/infrastructure/logging" ) // NPCRepository NPC仓储 type NPCRepository struct { collection *mongo.Collection logger logging.Logger } // NewNPCRepository 创建NPC仓储 func NewNPCRepository(db *mongo.Database, logger logging.Logger) *NPCRepository { return &NPCRepository{ collection: db.Collection("npcs"), logger: logger, } } // NPCRecord NPC记录 type NPCRecord struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Level int `bson:"level" json:"level"` Health int64 `bson:"health" json:"health"` MaxHealth int64 `bson:"max_health" json:"max_health"` Attack int64 `bson:"attack" json:"attack"` Defense int64 `bson:"defense" json:"defense"` Position Position `bson:"position" json:"position"` Status string `bson:"status" json:"status"` LastSeen time.Time `bson:"last_seen" json:"last_seen"` CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` } // Position 位置信息 type Position struct { X float64 `bson:"x" json:"x"` Y float64 `bson:"y" json:"y"` Z float64 `bson:"z" json:"z"` } // CreateNPC 创建NPC func (r *NPCRepository) CreateNPC(ctx context.Context, npc *NPCRecord) error { npc.CreatedAt = time.Now() npc.UpdatedAt = time.Now() npc.LastSeen = time.Now() _, err := r.collection.InsertOne(ctx, npc) if err != nil { r.logger.Error("创建NPC失败", err, logging.Fields{ "name": npc.Name, "type": npc.Type, }) return fmt.Errorf("创建NPC失败: %w", err) } r.logger.Info("NPC创建成功", map[string]interface{}{ "name": npc.Name, "type": npc.Type, "level": npc.Level, }) return nil } // GetNPC 获取NPC func (r *NPCRepository) GetNPC(ctx context.Context, id string) (*NPCRecord, error) { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, fmt.Errorf("无效的ID格式: %w", err) } var npc NPCRecord err = r.collection.FindOne(ctx, bson.M{"_id": objectID}).Decode(&npc) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("NPC不存在") } r.logger.Error("获取NPC失败", err, logging.Fields{ "id": id, }) return nil, fmt.Errorf("获取NPC失败: %w", err) } return &npc, nil } // UpdateNPC 更新NPC func (r *NPCRepository) UpdateNPC(ctx context.Context, id string, updates map[string]interface{}) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates["updated_at"] = time.Now() result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("更新NPC失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("更新NPC失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("NPC不存在") } r.logger.Info("NPC更新成功", map[string]interface{}{ "id": id, }) return nil } // DeleteNPC 删除NPC func (r *NPCRepository) DeleteNPC(ctx context.Context, id string) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } result, err := r.collection.DeleteOne(ctx, bson.M{"_id": objectID}) if err != nil { r.logger.Error("删除NPC失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("删除NPC失败: %w", err) } if result.DeletedCount == 0 { return fmt.Errorf("NPC不存在") } r.logger.Info("NPC删除成功", map[string]interface{}{ "id": id, }) return nil } // GetNPCsByType 根据类型获取NPC列表 func (r *NPCRepository) GetNPCsByType(ctx context.Context, npcType string, limit, offset int) ([]*NPCRecord, error) { filter := bson.M{"type": npcType} opts := options.Find(). SetSort(bson.D{{Key: "created_at", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("根据类型获取NPC失败", err, logging.Fields{ "type": npcType, }) return nil, fmt.Errorf("根据类型获取NPC失败: %w", err) } defer cursor.Close(ctx) var npcs []*NPCRecord if err = cursor.All(ctx, &npcs); err != nil { return nil, fmt.Errorf("解析NPC列表失败: %w", err) } r.logger.Info("根据类型获取NPC成功", map[string]interface{}{ "type": npcType, "count": len(npcs), }) return npcs, nil } // GetNPCsByPosition 根据位置获取NPC列表 func (r *NPCRepository) GetNPCsByPosition(ctx context.Context, x, y, z float64, radius float64) ([]*NPCRecord, error) { filter := bson.M{ "position.x": bson.M{ "$gte": x - radius, "$lte": x + radius, }, "position.y": bson.M{ "$gte": y - radius, "$lte": y + radius, }, "position.z": bson.M{ "$gte": z - radius, "$lte": z + radius, }, } cursor, err := r.collection.Find(ctx, filter) if err != nil { r.logger.Error("根据位置获取NPC失败", err, logging.Fields{ "x": x, "y": y, "z": z, "radius": radius, }) return nil, fmt.Errorf("根据位置获取NPC失败: %w", err) } defer cursor.Close(ctx) var npcs []*NPCRecord if err = cursor.All(ctx, &npcs); err != nil { return nil, fmt.Errorf("解析NPC列表失败: %w", err) } r.logger.Info("根据位置获取NPC成功", map[string]interface{}{ "x": x, "y": y, "z": z, "radius": radius, "count": len(npcs), }) return npcs, nil } // UpdateNPCPosition 更新NPC位置 func (r *NPCRepository) UpdateNPCPosition(ctx context.Context, id string, position Position) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates := bson.M{ "position": position, "last_seen": time.Now(), "updated_at": time.Now(), } result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("更新NPC位置失败", err, logging.Fields{ "id": id, "position": position, }) return fmt.Errorf("更新NPC位置失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("NPC不存在") } r.logger.Info("NPC位置更新成功", map[string]interface{}{ "id": id, "position": position, }) return nil } ================================================ FILE: internal/infrastructure/persistence/plant_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/infrastructure/logging" ) // PlantRepository 植物仓储 type PlantRepository struct { collection *mongo.Collection logger logging.Logger } // NewPlantRepository 创建植物仓储 func NewPlantRepository(db *mongo.Database, logger logging.Logger) *PlantRepository { return &PlantRepository{ collection: db.Collection("plants"), logger: logger, } } // PlantRecord 植物记录 type PlantRecord struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` PlayerID string `bson:"player_id" json:"player_id"` PlantType string `bson:"plant_type" json:"plant_type"` Position Position `bson:"position" json:"position"` Level int `bson:"level" json:"level"` Growth int `bson:"growth" json:"growth"` MaxGrowth int `bson:"max_growth" json:"max_growth"` WaterLevel int `bson:"water_level" json:"water_level"` Fertilizer int `bson:"fertilizer" json:"fertilizer"` Status string `bson:"status" json:"status"` PlantedAt time.Time `bson:"planted_at" json:"planted_at"` HarvestAt *time.Time `bson:"harvest_at,omitempty" json:"harvest_at,omitempty"` CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` } // CreatePlant 创建植物 func (r *PlantRepository) CreatePlant(ctx context.Context, plant *PlantRecord) error { plant.CreatedAt = time.Now() plant.UpdatedAt = time.Now() plant.PlantedAt = time.Now() _, err := r.collection.InsertOne(ctx, plant) if err != nil { r.logger.Error("创建植物失败", err, logging.Fields{ "player_id": plant.PlayerID, "plant_type": plant.PlantType, }) return fmt.Errorf("创建植物失败: %w", err) } r.logger.Info("植物创建成功", map[string]interface{}{ "player_id": plant.PlayerID, "plant_type": plant.PlantType, "level": plant.Level, }) return nil } // GetPlant 获取植物 func (r *PlantRepository) GetPlant(ctx context.Context, id string) (*PlantRecord, error) { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, fmt.Errorf("无效的ID格式: %w", err) } var plant PlantRecord err = r.collection.FindOne(ctx, bson.M{"_id": objectID}).Decode(&plant) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("植物不存在") } r.logger.Error("获取植物失败", err, logging.Fields{ "id": id, }) return nil, fmt.Errorf("获取植物失败: %w", err) } return &plant, nil } // UpdatePlant 更新植物 func (r *PlantRepository) UpdatePlant(ctx context.Context, id string, updates map[string]interface{}) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates["updated_at"] = time.Now() result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("更新植物失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("更新植物失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("植物不存在") } r.logger.Info("植物更新成功", map[string]interface{}{ "id": id, }) return nil } // DeletePlant 删除植物 func (r *PlantRepository) DeletePlant(ctx context.Context, id string) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } result, err := r.collection.DeleteOne(ctx, bson.M{"_id": objectID}) if err != nil { r.logger.Error("删除植物失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("删除植物失败: %w", err) } if result.DeletedCount == 0 { return fmt.Errorf("植物不存在") } r.logger.Info("植物删除成功", map[string]interface{}{ "id": id, }) return nil } // GetPlayerPlants 获取玩家的植物列表 func (r *PlantRepository) GetPlayerPlants(ctx context.Context, playerID string, limit, offset int) ([]*PlantRecord, error) { filter := bson.M{"player_id": playerID} opts := options.Find(). SetSort(bson.D{{Key: "created_at", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("获取玩家植物失败", err, logging.Fields{ "player_id": playerID, }) return nil, fmt.Errorf("获取玩家植物失败: %w", err) } defer cursor.Close(ctx) var plants []*PlantRecord if err = cursor.All(ctx, &plants); err != nil { return nil, fmt.Errorf("解析植物列表失败: %w", err) } r.logger.Info("获取玩家植物成功", map[string]interface{}{ "player_id": playerID, "count": len(plants), }) return plants, nil } // GetPlantsByType 根据类型获取植物列表 func (r *PlantRepository) GetPlantsByType(ctx context.Context, plantType string, limit, offset int) ([]*PlantRecord, error) { filter := bson.M{"plant_type": plantType} opts := options.Find(). SetSort(bson.D{{Key: "created_at", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("根据类型获取植物失败", err, logging.Fields{ "plant_type": plantType, }) return nil, fmt.Errorf("根据类型获取植物失败: %w", err) } defer cursor.Close(ctx) var plants []*PlantRecord if err = cursor.All(ctx, &plants); err != nil { return nil, fmt.Errorf("解析植物列表失败: %w", err) } r.logger.Info("根据类型获取植物成功", map[string]interface{}{ "plant_type": plantType, "count": len(plants), }) return plants, nil } // WaterPlant 浇水 func (r *PlantRepository) WaterPlant(ctx context.Context, id string, waterAmount int) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates := bson.M{ "$inc": bson.M{ "water_level": waterAmount, }, "$set": bson.M{ "updated_at": time.Now(), }, } result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, updates, ) if err != nil { r.logger.Error("植物浇水失败", err, logging.Fields{ "id": id, "water_amount": waterAmount, }) return fmt.Errorf("植物浇水失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("植物不存在") } r.logger.Info("植物浇水成功", map[string]interface{}{ "id": id, "water_amount": waterAmount, }) return nil } // FertilizePlant 施肥 func (r *PlantRepository) FertilizePlant(ctx context.Context, id string, fertilizerAmount int) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates := bson.M{ "$inc": bson.M{ "fertilizer": fertilizerAmount, }, "$set": bson.M{ "updated_at": time.Now(), }, } result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, updates, ) if err != nil { r.logger.Error("植物施肥失败", err, logging.Fields{ "id": id, "fertilizer_amount": fertilizerAmount, }) return fmt.Errorf("植物施肥失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("植物不存在") } r.logger.Info("植物施肥成功", map[string]interface{}{ "id": id, "fertilizer_amount": fertilizerAmount, }) return nil } // HarvestPlant 收获植物 func (r *PlantRepository) HarvestPlant(ctx context.Context, id string) (*PlantRecord, error) { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, fmt.Errorf("无效的ID格式: %w", err) } harvestTime := time.Now() updates := bson.M{ "harvest_at": harvestTime, "status": "harvested", "updated_at": harvestTime, } result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("收获植物失败", err, logging.Fields{ "id": id, }) return nil, fmt.Errorf("收获植物失败: %w", err) } if result.MatchedCount == 0 { return nil, fmt.Errorf("植物不存在") } // 获取更新后的植物记录 plant, err := r.GetPlant(ctx, id) if err != nil { return nil, err } r.logger.Info("植物收获成功", map[string]interface{}{ "id": id, "harvest_at": harvestTime, }) return plant, nil } ================================================ FILE: internal/infrastructure/persistence/player_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/domain/player" "greatestworks/internal/infrastructure/cache" "greatestworks/internal/infrastructure/logging" ) // MongoPlayerRepository MongoDB玩家仓储实现 type MongoPlayerRepository struct { collection *mongo.Collection cache cache.Cache logger logging.Logger } // NewMongoPlayerRepository 创建MongoDB玩家仓储 func NewMongoPlayerRepository(db *mongo.Database, cache cache.Cache, logger logging.Logger) *MongoPlayerRepository { return &MongoPlayerRepository{ collection: db.Collection("players"), cache: cache, logger: logger, } } // PlayerDocument 玩家文档结构 type PlayerDocument struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` Name string `bson:"name" json:"name"` Level int `bson:"level" json:"level"` Exp int64 `bson:"exp" json:"exp"` Status int `bson:"status" json:"status"` Position PlayerPosition `bson:"position" json:"position"` LastMapID int32 `bson:"last_map_id" json:"last_map_id"` Stats PlayerStats `bson:"stats" json:"stats"` CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` Version int64 `bson:"version" json:"version"` } // PlayerPosition 玩家位置值对象 type PlayerPosition struct { X float64 `bson:"x" json:"x"` Y float64 `bson:"y" json:"y"` Z float64 `bson:"z" json:"z"` } // PlayerStats 玩家属性值对象 type PlayerStats struct { HP int `bson:"hp" json:"hp"` MaxHP int `bson:"max_hp" json:"max_hp"` MP int `bson:"mp" json:"mp"` MaxMP int `bson:"max_mp" json:"max_mp"` Attack int `bson:"attack" json:"attack"` Defense int `bson:"defense" json:"defense"` Speed int `bson:"speed" json:"speed"` } // Save 保存玩家 func (r *MongoPlayerRepository) Save(ctx context.Context, p *player.Player) error { doc := r.toDocument(p) // 设置时间戳 now := time.Now() if doc.ID.IsZero() { doc.CreatedAt = now } doc.UpdatedAt = now // 使用Upsert操作 filter := bson.M{"name": p.Name()} opts := options.Replace().SetUpsert(true) _, err := r.collection.ReplaceOne(ctx, filter, doc, opts) if err != nil { r.logger.Error("保存玩家失败", err, logging.Fields{ "name": p.Name(), }) return fmt.Errorf("保存玩家失败: %w", err) } // 更新缓存 cacheKey := fmt.Sprintf("player:id:%s", p.ID().String()) if err := r.cache.Set(ctx, cacheKey, p, time.Hour); err != nil { r.logger.Warn("更新玩家缓存失败", map[string]interface{}{ "name": p.Name(), "error": err.Error(), }) } r.logger.Info("玩家保存成功", map[string]interface{}{ "name": p.Name(), "level": p.Level(), }) return nil } // FindByID 根据ID查找玩家 func (r *MongoPlayerRepository) FindByID(ctx context.Context, id string) (*player.Player, error) { // 先从缓存获取 cacheKey := fmt.Sprintf("player:id:%s", id) var cachedPlayer *player.Player if err := r.cache.Get(ctx, cacheKey, &cachedPlayer); err == nil && cachedPlayer != nil { return cachedPlayer, nil } // 从数据库获取 objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, fmt.Errorf("无效的ID格式: %w", err) } filter := bson.M{"_id": objectID} var doc PlayerDocument err = r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, player.ErrPlayerNotFound } r.logger.Error("查找玩家失败", err, logging.Fields{ "id": id, }) return nil, fmt.Errorf("查找玩家失败: %w", err) } // 转换为领域对象 playerAggregate, err := r.toAggregate(&doc) if err != nil { return nil, fmt.Errorf("转换玩家对象失败: %w", err) } // 更新缓存 if err := r.cache.Set(ctx, cacheKey, playerAggregate, time.Hour); err != nil { r.logger.Warn("更新玩家缓存失败", map[string]interface{}{ "id": id, "error": err.Error(), }) } return playerAggregate, nil } // FindByName 根据名称查找玩家 func (r *MongoPlayerRepository) FindByName(ctx context.Context, name string) (*player.Player, error) { // 先从缓存获取 cacheKey := fmt.Sprintf("player:name:%s", name) var cachedPlayer *player.Player if err := r.cache.Get(ctx, cacheKey, &cachedPlayer); err == nil && cachedPlayer != nil { return cachedPlayer, nil } // 从数据库获取 filter := bson.M{"name": name} var doc PlayerDocument err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, player.ErrPlayerNotFound } r.logger.Error("根据名称查找玩家失败", err, logging.Fields{ "name": name, }) return nil, fmt.Errorf("根据名称查找玩家失败: %w", err) } // 转换为领域对象 playerAggregate, err := r.toAggregate(&doc) if err != nil { return nil, fmt.Errorf("转换玩家对象失败: %w", err) } // 更新缓存 if err := r.cache.Set(ctx, cacheKey, playerAggregate, time.Hour); err != nil { r.logger.Warn("更新玩家缓存失败", map[string]interface{}{ "name": name, "error": err.Error(), }) } return playerAggregate, nil } // Delete 删除玩家 func (r *MongoPlayerRepository) Delete(ctx context.Context, id string) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } filter := bson.M{"_id": objectID} result, err := r.collection.DeleteOne(ctx, filter) if err != nil { r.logger.Error("删除玩家失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("删除玩家失败: %w", err) } if result.DeletedCount == 0 { return player.ErrPlayerNotFound } // 清除缓存 cacheKey := fmt.Sprintf("player:id:%s", id) if err := r.cache.Delete(ctx, cacheKey); err != nil { r.logger.Warn("清除玩家缓存失败", map[string]interface{}{ "id": id, "error": err.Error(), }) } r.logger.Info("玩家删除成功", map[string]interface{}{ "id": id, }) return nil } // List 获取玩家列表 func (r *MongoPlayerRepository) List(ctx context.Context, limit, offset int) ([]*player.Player, error) { opts := options.Find(). SetSort(bson.D{{Key: "created_at", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, bson.M{}, opts) if err != nil { r.logger.Error("获取玩家列表失败", err, logging.Fields{}) return nil, fmt.Errorf("获取玩家列表失败: %w", err) } defer cursor.Close(ctx) var docs []PlayerDocument if err = cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析玩家列表失败: %w", err) } players := make([]*player.Player, 0, len(docs)) for _, doc := range docs { playerAggregate, err := r.toAggregate(&doc) if err != nil { r.logger.Error("转换玩家对象失败", err, logging.Fields{ "id": doc.ID.Hex(), }) continue } players = append(players, playerAggregate) } r.logger.Info("获取玩家列表成功", map[string]interface{}{ "count": len(players), }) return players, nil } // Count 获取玩家总数 func (r *MongoPlayerRepository) Count(ctx context.Context) (int64, error) { count, err := r.collection.CountDocuments(ctx, bson.M{}) if err != nil { r.logger.Error("获取玩家总数失败", err, logging.Fields{}) return 0, fmt.Errorf("获取玩家总数失败: %w", err) } return count, nil } // toDocument 转换为文档 func (r *MongoPlayerRepository) toDocument(p *player.Player) *PlayerDocument { position := p.GetPosition() stats := p.Stats() doc := &PlayerDocument{ Name: p.Name(), Level: p.Level(), Exp: p.Exp(), Status: int(p.Status()), Position: PlayerPosition{X: position.X, Y: position.Y, Z: position.Z}, LastMapID: p.LastMapID(), Stats: PlayerStats{HP: stats.HP, MaxHP: stats.MaxHP, MP: stats.MP, MaxMP: stats.MaxMP, Attack: stats.Attack, Defense: stats.Defense, Speed: stats.Speed}, CreatedAt: p.CreatedAt(), UpdatedAt: p.UpdatedAt(), Version: p.Version(), } // 如果有ID,转换为ObjectID if p.ID().String() != "" { if objectID, err := primitive.ObjectIDFromHex(p.ID().String()); err == nil { doc.ID = objectID } } return doc } // toAggregate 转换为聚合根 func (r *MongoPlayerRepository) toAggregate(doc *PlayerDocument) (*player.Player, error) { // 使用ReconstructPlayer方法从持久化数据重建玩家聚合根 playerID := player.PlayerIDFromString(doc.ID.Hex()) status := player.PlayerStatus(doc.Status) position := player.Position{X: doc.Position.X, Y: doc.Position.Y, Z: doc.Position.Z} stats := player.PlayerStats{HP: doc.Stats.HP, MaxHP: doc.Stats.MaxHP, MP: doc.Stats.MP, MaxMP: doc.Stats.MaxMP, Attack: doc.Stats.Attack, Defense: doc.Stats.Defense, Speed: doc.Stats.Speed} p := player.ReconstructPlayer( playerID, doc.Name, doc.Level, doc.Exp, status, position, doc.LastMapID, stats, doc.CreatedAt, doc.UpdatedAt, doc.Version, ) return p, nil } ================================================ FILE: internal/infrastructure/persistence/ranking_repository.go ================================================ package persistence import ( "context" "fmt" "strconv" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/domain/ranking" ) // RankingRepository MongoDB排行榜仓储实现 type RankingRepository struct { db *mongo.Database rankingColl *mongo.Collection entryColl *mongo.Collection } // NewRankingRepository 创建排行榜仓储 func NewRankingRepository(db *mongo.Database) *RankingRepository { return &RankingRepository{ db: db, rankingColl: db.Collection("rankings"), entryColl: db.Collection("rank_entries"), } } // RankingDocument 排行榜文档结构 type RankingDocument struct { ID primitive.ObjectID `bson:"_id,omitempty"` RankingID string `bson:"ranking_id"` Name string `bson:"name"` Description string `bson:"description"` RankType string `bson:"rank_type"` PeriodType string `bson:"period_type"` MaxEntries int32 `bson:"max_entries"` IsActive bool `bson:"is_active"` Blacklist []uint64 `bson:"blacklist"` Settings map[string]interface{} `bson:"settings"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` ResetAt *time.Time `bson:"reset_at,omitempty"` Version int64 `bson:"version"` } // RankEntryDocument 排名条目文档结构 type RankEntryDocument struct { ID primitive.ObjectID `bson:"_id,omitempty"` EntryID string `bson:"entry_id"` RankingID string `bson:"ranking_id"` PlayerID uint64 `bson:"player_id"` Rank int32 `bson:"rank"` Score int64 `bson:"score"` PrevRank int32 `bson:"prev_rank"` PrevScore int64 `bson:"prev_score"` Metadata map[string]interface{} `bson:"metadata"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` } // Save 保存排行榜聚合根 func (r *RankingRepository) Save(ctx context.Context, rankingAggregate *ranking.RankingAggregate) error { doc := r.toRankingDocument(rankingAggregate) filter := bson.M{"ranking_id": doc.RankingID} update := bson.M{ "$set": doc, "$inc": bson.M{"version": 1}, } opts := options.Update().SetUpsert(true) _, err := r.rankingColl.UpdateOne(ctx, filter, update, opts) if err != nil { return fmt.Errorf("failed to save ranking: %w", err) } return nil } // FindByID 根据ID查找排行榜 func (r *RankingRepository) FindByID(ctx context.Context, rankingID string) (*ranking.RankingAggregate, error) { filter := bson.M{"ranking_id": rankingID} var doc RankingDocument err := r.rankingColl.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("failed to find ranking: %w", err) } return r.fromRankingDocument(&doc) } // FindByType 根据类型查找排行榜 func (r *RankingRepository) FindByType(ctx context.Context, rankType ranking.RankType) ([]*ranking.RankingAggregate, error) { filter := bson.M{ "rank_type": rankType.String(), "is_active": true, } cursor, err := r.rankingColl.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("failed to find rankings by type: %w", err) } defer cursor.Close(ctx) var rankings []*ranking.RankingAggregate for cursor.Next(ctx) { var doc RankingDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode ranking document: %w", err) } if ranking, err := r.fromRankingDocument(&doc); err == nil { rankings = append(rankings, ranking) } } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return rankings, nil } // FindActive 查找激活的排行榜 func (r *RankingRepository) FindActive(ctx context.Context) ([]*ranking.RankingAggregate, error) { filter := bson.M{"is_active": true} cursor, err := r.rankingColl.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("failed to find active rankings: %w", err) } defer cursor.Close(ctx) var rankings []*ranking.RankingAggregate for cursor.Next(ctx) { var doc RankingDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode ranking document: %w", err) } if ranking, err := r.fromRankingDocument(&doc); err == nil { rankings = append(rankings, ranking) } } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return rankings, nil } // Delete 删除排行榜 func (r *RankingRepository) Delete(ctx context.Context, rankingID string) error { filter := bson.M{"ranking_id": rankingID} update := bson.M{ "$set": bson.M{ "is_active": false, "updated_at": time.Now(), }, "$inc": bson.M{"version": 1}, } _, err := r.rankingColl.UpdateOne(ctx, filter, update) if err != nil { return fmt.Errorf("failed to delete ranking: %w", err) } return nil } // RankEntryRepository 排名条目仓储实现 type RankEntryRepository struct { db *mongo.Database entryColl *mongo.Collection } // NewRankEntryRepository 创建排名条目仓储 func NewRankEntryRepository(db *mongo.Database) *RankEntryRepository { return &RankEntryRepository{ db: db, entryColl: db.Collection("rank_entries"), } } // Save 保存排名条目 func (r *RankEntryRepository) Save(ctx context.Context, entry *ranking.RankEntry) error { doc := r.toRankEntryDocument(entry) filter := bson.M{"entry_id": doc.EntryID} update := bson.M{"$set": doc} opts := options.Update().SetUpsert(true) _, err := r.entryColl.UpdateOne(ctx, filter, update, opts) if err != nil { return fmt.Errorf("failed to save rank entry: %w", err) } return nil } // FindByID 根据ID查找排名条目 func (r *RankEntryRepository) FindByID(ctx context.Context, entryID string) (*ranking.RankEntry, error) { filter := bson.M{"entry_id": entryID} var doc RankEntryDocument err := r.entryColl.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("failed to find rank entry: %w", err) } return r.fromRankEntryDocument(&doc), nil } // FindByRankingAndPlayer 根据排行榜和玩家查找条目 func (r *RankEntryRepository) FindByRankingAndPlayer(ctx context.Context, rankingID string, playerID uint64) (*ranking.RankEntry, error) { filter := bson.M{ "ranking_id": rankingID, "player_id": playerID, } var doc RankEntryDocument err := r.entryColl.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("failed to find rank entry: %w", err) } return r.fromRankEntryDocument(&doc), nil } // FindByRanking 根据排行榜查找条目 func (r *RankEntryRepository) FindByRanking(ctx context.Context, rankingID string, limit int) ([]*ranking.RankEntry, error) { filter := bson.M{"ranking_id": rankingID} opts := options.Find(). SetSort(bson.D{{Key: "rank", Value: 1}}). SetLimit(int64(limit)) cursor, err := r.entryColl.Find(ctx, filter, opts) if err != nil { return nil, fmt.Errorf("failed to find rank entries: %w", err) } defer cursor.Close(ctx) var entries []*ranking.RankEntry for cursor.Next(ctx) { var doc RankEntryDocument if err := cursor.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to decode rank entry document: %w", err) } entries = append(entries, r.fromRankEntryDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, fmt.Errorf("cursor error: %w", err) } return entries, nil } // FindByQuery 根据查询条件查找条目 func (r *RankEntryRepository) FindByQuery(ctx context.Context, query *ranking.RankEntryQuery) ([]*ranking.RankEntry, int64, error) { filter := r.buildRankEntryFilter(query) // 计算总数 total, err := r.entryColl.CountDocuments(ctx, filter) if err != nil { return nil, 0, fmt.Errorf("failed to count rank entries: %w", err) } // 构建查询选项 opts := options.Find() if query.GetSort() != "" { sortOrder := 1 if query.GetSortOrder() { sortOrder = -1 } opts.SetSort(bson.D{{Key: query.GetSort(), Value: sortOrder}}) } if query.GetLimit() > 0 { opts.SetLimit(int64(query.GetLimit())) } if query.GetOffset() > 0 { opts.SetSkip(int64(query.GetOffset())) } cursor, err := r.entryColl.Find(ctx, filter, opts) if err != nil { return nil, 0, fmt.Errorf("failed to find rank entries: %w", err) } defer cursor.Close(ctx) var entries []*ranking.RankEntry for cursor.Next(ctx) { var doc RankEntryDocument if err := cursor.Decode(&doc); err != nil { return nil, 0, fmt.Errorf("failed to decode rank entry document: %w", err) } entries = append(entries, r.fromRankEntryDocument(&doc)) } if err := cursor.Err(); err != nil { return nil, 0, fmt.Errorf("cursor error: %w", err) } return entries, total, nil } // DeleteByRanking 删除排行榜的所有条目 func (r *RankEntryRepository) DeleteByRanking(ctx context.Context, rankingID string) (int64, error) { filter := bson.M{"ranking_id": rankingID} result, err := r.entryColl.DeleteMany(ctx, filter) if err != nil { return 0, fmt.Errorf("failed to delete rank entries: %w", err) } return result.DeletedCount, nil } // UpdateRanks 批量更新排名 func (r *RankEntryRepository) UpdateRanks(ctx context.Context, rankingID string) error { // 使用聚合管道重新计算排名 pipeline := []bson.M{ {"$match": bson.M{"ranking_id": rankingID}}, {"$sort": bson.M{"score": -1, "updated_at": 1}}, {"$group": bson.M{ "_id": "$ranking_id", "entries": bson.M{"$push": "$$ROOT"}, }}, {"$unwind": bson.M{ "path": "$entries", "includeArrayIndex": "rank", }}, {"$addFields": bson.M{ "entries.prev_rank": "$entries.rank", "entries.rank": bson.M{"$add": []interface{}{"$rank", 1}}, "entries.updated_at": time.Now(), }}, {"$replaceRoot": bson.M{"newRoot": "$entries"}}, {"$merge": bson.M{ "into": "rank_entries", "on": "_id", "whenMatched": "replace", }}, } _, err := r.entryColl.Aggregate(ctx, pipeline) if err != nil { return fmt.Errorf("failed to update ranks: %w", err) } return nil } // 私有方法 // toRankingDocument 转换为排行榜文档 func (r *RankingRepository) toRankingDocument(rankingAggregate *ranking.RankingAggregate) *RankingDocument { doc := &RankingDocument{ RankingID: rankingAggregate.GetID(), Name: rankingAggregate.GetName(), Description: rankingAggregate.GetDescription(), RankType: rankingAggregate.GetRankType().String(), PeriodType: rankingAggregate.GetPeriodType().String(), MaxEntries: int32(rankingAggregate.GetMaxEntries()), IsActive: rankingAggregate.IsActive, Blacklist: rankingAggregate.GetBlacklist(), Settings: rankingAggregate.GetSettings(), CreatedAt: rankingAggregate.GetCreatedAt(), UpdatedAt: rankingAggregate.GetUpdatedAt(), Version: rankingAggregate.GetVersion(), } if !rankingAggregate.GetResetAt().IsZero() { resetAt := rankingAggregate.GetResetAt() doc.ResetAt = &resetAt } return doc } // fromRankingDocument 从排行榜文档转换 func (r *RankingRepository) fromRankingDocument(doc *RankingDocument) (*ranking.RankingAggregate, error) { rankType := ranking.ParseRankType(doc.RankType) _ = ranking.ParsePeriodType(doc.PeriodType) // 暂时忽略periodType,避免未使用变量错误 // 创建排行榜聚合 - 需要提供rankID, name, rankType, category // 从RankingID解析出rankID rankID := uint32(0) // 这里需要从doc.RankingID解析出实际的rankID rankingAggregate := ranking.NewRankingAggregate( rankID, doc.Name, rankType, ranking.RankCategoryPlayer, // 默认分类 ) // 设置其他属性 rankingAggregate.SetID(doc.RankingID) rankingAggregate.SetDescription(doc.Description) rankingAggregate.SetMaxEntries(int64(doc.MaxEntries)) rankingAggregate.SetBlacklist(doc.Blacklist) rankingAggregate.SetSettings(doc.Settings) rankingAggregate.SetVersion(doc.Version) if doc.ResetAt != nil { rankingAggregate.SetResetAt(*doc.ResetAt) } if !doc.IsActive { rankingAggregate.Deactivate() } else { rankingAggregate.Activate() } return rankingAggregate, nil } // toRankEntryDocument 转换为排名条目文档 func (r *RankEntryRepository) toRankEntryDocument(entry *ranking.RankEntry) *RankEntryDocument { doc := &RankEntryDocument{ EntryID: entry.GetID(), RankingID: fmt.Sprintf("%d", entry.GetRankingID()), PlayerID: entry.GetPlayerID(), Rank: int32(entry.GetRank()), Score: entry.GetScore(), PrevRank: func() int32 { if prevRank := entry.GetPrevRank(); prevRank != nil { return int32(*prevRank) } return 0 }(), PrevScore: entry.GetPrevScore(), Metadata: entry.GetMetadata(), CreatedAt: entry.GetCreatedAt(), UpdatedAt: entry.GetUpdatedAt(), } return doc } // fromRankEntryDocument 从排名条目文档转换 func (r *RankEntryRepository) fromRankEntryDocument(doc *RankEntryDocument) *ranking.RankEntry { // 将string类型的RankingID转换为uint32 rankingID, err := strconv.ParseUint(doc.RankingID, 10, 32) if err != nil { // 如果转换失败,使用默认值0 rankingID = 0 } entry := ranking.NewRankEntryFromRepository( doc.EntryID, uint32(rankingID), doc.PlayerID, doc.Score, ) // 类型转换:int32 -> int64 entry.SetRank(int64(doc.Rank)) // 类型转换:int32 -> *int64 prevRank := int64(doc.PrevRank) entry.SetPrevious(&prevRank, doc.PrevScore) entry.SetMetadata(doc.Metadata) return entry } // buildRankEntryFilter 构建排名条目查询过滤器 func (r *RankEntryRepository) buildRankEntryFilter(query *ranking.RankEntryQuery) bson.M { filter := bson.M{} if query.GetRankingID() != "" { filter["ranking_id"] = query.GetRankingID() } if query.GetPlayerID() > 0 { filter["player_id"] = query.GetPlayerID() } if query.GetMinRank() != nil && *query.GetMinRank() > 0 { filter["rank"] = bson.M{"$gte": *query.GetMinRank()} } if query.GetMaxRank() != nil && *query.GetMaxRank() > 0 { if rankFilter, exists := filter["rank"]; exists { rankFilter.(bson.M)["$lte"] = *query.GetMaxRank() } else { filter["rank"] = bson.M{"$lte": *query.GetMaxRank()} } } if query.GetMinScore() != nil && *query.GetMinScore() > 0 { filter["score"] = bson.M{"$gte": *query.GetMinScore()} } if query.GetMaxScore() != nil && *query.GetMaxScore() > 0 { if scoreFilter, exists := filter["score"]; exists { scoreFilter.(bson.M)["$lte"] = *query.GetMaxScore() } else { filter["score"] = bson.M{"$lte": *query.GetMaxScore()} } } return filter } // CreateIndexes 创建索引 func (r *RankingRepository) CreateIndexes(ctx context.Context) error { // 排行榜索引 rankingIndexes := []mongo.IndexModel{ { Keys: bson.D{{Key: "ranking_id", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "rank_type", Value: 1}, {Key: "is_active", Value: 1}}, }, { Keys: bson.D{{Key: "period_type", Value: 1}}, }, { Keys: bson.D{{Key: "is_active", Value: 1}}, }, } if _, err := r.rankingColl.Indexes().CreateMany(ctx, rankingIndexes); err != nil { return fmt.Errorf("failed to create ranking indexes: %w", err) } return nil } // CreateIndexes 创建排名条目索引 func (r *RankEntryRepository) CreateIndexes(ctx context.Context) error { // 排名条目索引 entryIndexes := []mongo.IndexModel{ { Keys: bson.D{{Key: "entry_id", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "ranking_id", Value: 1}, {Key: "player_id", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "ranking_id", Value: 1}, {Key: "rank", Value: 1}}, }, { Keys: bson.D{{Key: "ranking_id", Value: 1}, {Key: "score", Value: -1}}, }, { Keys: bson.D{{Key: "player_id", Value: 1}}, }, } if _, err := r.entryColl.Indexes().CreateMany(ctx, entryIndexes); err != nil { return fmt.Errorf("failed to create rank entry indexes: %w", err) } return nil } ================================================ FILE: internal/infrastructure/persistence/replication_repository.go ================================================ // Package persistence 持久化层实现 package persistence import ( "context" "encoding/json" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/domain/replication" "greatestworks/internal/infrastructure/cache" "greatestworks/internal/infrastructure/logging" ) // MongoReplicationRepository MongoDB副本仓储实现 type MongoReplicationRepository struct { collection *mongo.Collection cache cache.Cache logger logging.Logger } // ReplicationInstanceDocument MongoDB文档结构 type ReplicationInstanceDocument struct { InstanceID string `bson:"instance_id"` TemplateID string `bson:"template_id"` InstanceType int `bson:"instance_type"` SceneID string `bson:"scene_id"` Players []ReplicationPlayerDocument `bson:"players"` MaxPlayers int `bson:"max_players"` MinPlayers int `bson:"min_players"` // Updated to include default value OwnerPlayerID string `bson:"owner_player_id"` Status int `bson:"status"` Difficulty int `bson:"difficulty"` CreatedAt time.Time `bson:"created_at"` StartedAt time.Time `bson:"started_at,omitempty"` ExpireAt time.Time `bson:"expire_at"` ClosedAt time.Time `bson:"closed_at,omitempty"` Lifetime int64 `bson:"lifetime"` // 毫秒 Progress int `bson:"progress"` CompletedTasks []string `bson:"completed_tasks"` Metadata map[string]string `bson:"metadata"` ScoreMultiplier float64 `bson:"score_multiplier"` UpdatedAt time.Time `bson:"updated_at"` } // ReplicationPlayerDocument 实例中的玩家文档结构 type ReplicationPlayerDocument struct { PlayerID string `bson:"player_id"` PlayerName string `bson:"player_name"` Level int `bson:"level"` JoinedAt time.Time `bson:"joined_at"` IsReady bool `bson:"is_ready"` Role string `bson:"role"` } // NewMongoReplicationRepository 创建MongoDB副本仓储 func NewMongoReplicationRepository( db *mongo.Database, cache cache.Cache, logger logging.Logger, ) *MongoReplicationRepository { return &MongoReplicationRepository{ collection: db.Collection("replication_instances"), cache: cache, logger: logger, } } // Save 保存实例 func (r *MongoReplicationRepository) Save(ctx context.Context, instance *replication.Instance) error { doc := r.toDocument(instance) filter := bson.M{"instance_id": instance.ID()} update := bson.M{"$set": doc} opts := options.Update().SetUpsert(true) _, err := r.collection.UpdateOne(ctx, filter, update, opts) if err != nil { return fmt.Errorf("保存实例失败: %w", err) } // 清除缓存 cacheKey := fmt.Sprintf("instance:%s", instance.ID()) _ = r.cache.Delete(ctx, cacheKey) return nil } // FindByID 根据ID查找实例 func (r *MongoReplicationRepository) FindByID(ctx context.Context, instanceID string) (*replication.Instance, error) { // 先查缓存 cacheKey := fmt.Sprintf("instance:%s", instanceID) var cachedData string if err := r.cache.Get(ctx, cacheKey, &cachedData); err == nil { var doc ReplicationInstanceDocument if err := json.Unmarshal([]byte(cachedData), &doc); err == nil { return r.toDomain(&doc), nil } } // 查数据库 var doc ReplicationInstanceDocument filter := bson.M{"instance_id": instanceID} err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("查找实例失败: %w", err) } // 写入缓存 if data, err := json.Marshal(doc); err == nil { _ = r.cache.Set(ctx, cacheKey, string(data), 5*time.Minute) } return r.toDomain(&doc), nil } // FindByTemplateID 根据模板ID查找实例列表 func (r *MongoReplicationRepository) FindByTemplateID(ctx context.Context, templateID string) ([]*replication.Instance, error) { filter := bson.M{"template_id": templateID} cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找实例失败: %w", err) } defer cursor.Close(ctx) var instances []*replication.Instance for cursor.Next(ctx) { var doc ReplicationInstanceDocument if err := cursor.Decode(&doc); err != nil { r.logger.Error("解码实例文档失败", err, logging.Fields{}) continue } instances = append(instances, r.toDomain(&doc)) } return instances, nil } // FindActiveInstances 查找所有活跃实例 func (r *MongoReplicationRepository) FindActiveInstances(ctx context.Context) ([]*replication.Instance, error) { filter := bson.M{ "status": bson.M{"$in": []int{ int(replication.InstanceStatusPending), int(replication.InstanceStatusCreating), int(replication.InstanceStatusActive), int(replication.InstanceStatusFull), }}, } cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找活跃实例失败: %w", err) } defer cursor.Close(ctx) var instances []*replication.Instance for cursor.Next(ctx) { var doc ReplicationInstanceDocument if err := cursor.Decode(&doc); err != nil { r.logger.Error("解码实例文档失败", err, logging.Fields{}) continue } instances = append(instances, r.toDomain(&doc)) } return instances, nil } // FindByPlayerID 根据玩家ID查找实例 func (r *MongoReplicationRepository) FindByPlayerID(ctx context.Context, playerID string) (*replication.Instance, error) { filter := bson.M{ "players.player_id": playerID, "status": bson.M{"$in": []int{ int(replication.InstanceStatusActive), int(replication.InstanceStatusFull), }}, } var doc ReplicationInstanceDocument err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("查找玩家实例失败: %w", err) } return r.toDomain(&doc), nil } // Delete 删除实例 func (r *MongoReplicationRepository) Delete(ctx context.Context, instanceID string) error { filter := bson.M{"instance_id": instanceID} _, err := r.collection.DeleteOne(ctx, filter) if err != nil { return fmt.Errorf("删除实例失败: %w", err) } // 清除缓存 cacheKey := fmt.Sprintf("instance:%s", instanceID) _ = r.cache.Delete(ctx, cacheKey) return nil } // UpdateStatus 更新实例状态 func (r *MongoReplicationRepository) UpdateStatus(ctx context.Context, instanceID string, status replication.InstanceStatus) error { filter := bson.M{"instance_id": instanceID} update := bson.M{ "$set": bson.M{ "status": int(status), "updated_at": time.Now(), }, } _, err := r.collection.UpdateOne(ctx, filter, update) if err != nil { return fmt.Errorf("更新实例状态失败: %w", err) } // 清除缓存 cacheKey := fmt.Sprintf("instance:%s", instanceID) _ = r.cache.Delete(ctx, cacheKey) return nil } // FindExpiredInstances 查找过期实例 func (r *MongoReplicationRepository) FindExpiredInstances(ctx context.Context) ([]*replication.Instance, error) { filter := bson.M{ "expire_at": bson.M{"$lt": time.Now()}, "status": bson.M{"$ne": int(replication.InstanceStatusClosed)}, } cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找过期实例失败: %w", err) } defer cursor.Close(ctx) var instances []*replication.Instance for cursor.Next(ctx) { var doc ReplicationInstanceDocument if err := cursor.Decode(&doc); err != nil { r.logger.Error("解码实例文档失败", err, logging.Fields{}) continue } instances = append(instances, r.toDomain(&doc)) } return instances, nil } // toDocument 转换为文档 func (r *MongoReplicationRepository) toDocument(instance *replication.Instance) *ReplicationInstanceDocument { players := instance.GetPlayers() playerDocs := make([]ReplicationPlayerDocument, 0, len(players)) for _, p := range players { playerDocs = append(playerDocs, ReplicationPlayerDocument{ PlayerID: p.PlayerID, PlayerName: p.PlayerName, Level: p.Level, JoinedAt: p.JoinedAt, IsReady: p.IsReady, Role: p.Role, }) } return &ReplicationInstanceDocument{ InstanceID: instance.ID(), TemplateID: instance.TemplateID(), InstanceType: int(instance.Type()), SceneID: instance.SceneID(), Players: playerDocs, MaxPlayers: instance.MaxPlayers(), Status: int(instance.Status()), Progress: instance.Progress(), CreatedAt: instance.CreatedAt(), Difficulty: instance.Difficulty(), UpdatedAt: time.Now(), } } // toDomain 转换为领域对象 func (r *MongoReplicationRepository) toDomain(doc *ReplicationInstanceDocument) *replication.Instance { // 通过快照重建领域对象 players := make([]replication.PlayerInfo, 0, len(doc.Players)) for _, p := range doc.Players { players = append(players, replication.PlayerInfo{ PlayerID: p.PlayerID, PlayerName: p.PlayerName, Level: p.Level, JoinedAt: p.JoinedAt, IsReady: p.IsReady, Role: p.Role, }) } snap := replication.InstanceSnapshot{ InstanceID: doc.InstanceID, TemplateID: doc.TemplateID, SceneID: doc.SceneID, OwnerPlayerID: doc.OwnerPlayerID, InstanceType: replication.InstanceType(doc.InstanceType), Status: replication.InstanceStatus(doc.Status), MaxPlayers: doc.MaxPlayers, MinPlayers: doc.MinPlayers, Difficulty: doc.Difficulty, CreatedAt: doc.CreatedAt, StartedAt: doc.StartedAt, ExpireAt: doc.ExpireAt, ClosedAt: doc.ClosedAt, Lifetime: time.Duration(doc.Lifetime) * time.Millisecond, Progress: doc.Progress, Completed: append([]string(nil), doc.CompletedTasks...), Metadata: doc.Metadata, Players: players, } return replication.NewInstanceFromSnapshot(snap) } ================================================ FILE: internal/infrastructure/persistence/repositories.go ================================================ package persistence import ( "context" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // UserRepository 用户仓储 type UserRepository struct { collection *mongo.Collection } // NewUserRepository 创建用户仓储 func NewUserRepository(db *mongo.Database) *UserRepository { return &UserRepository{ collection: db.Collection("users"), } } // Create 创建用户 func (r *UserRepository) Create(ctx context.Context, user *DbUser) error { user.CreatedAt = time.Now() user.UpdatedAt = time.Now() result, err := r.collection.InsertOne(ctx, user) if err != nil { return err } user.ID = result.InsertedID.(primitive.ObjectID) return nil } // FindByID 根据ID查找用户 func (r *UserRepository) FindByID(ctx context.Context, userID int64) (*DbUser, error) { var user DbUser err := r.collection.FindOne(ctx, bson.M{"user_id": userID}).Decode(&user) if err != nil { return nil, err } return &user, nil } // FindByUsername 根据用户名查找 func (r *UserRepository) FindByUsername(ctx context.Context, username string) (*DbUser, error) { var user DbUser err := r.collection.FindOne(ctx, bson.M{"username": username}).Decode(&user) if err != nil { return nil, err } return &user, nil } // Update 更新用户 func (r *UserRepository) Update(ctx context.Context, user *DbUser) error { user.UpdatedAt = time.Now() _, err := r.collection.UpdateOne( ctx, bson.M{"user_id": user.UserID}, bson.M{"$set": user}, ) return err } // UpdateLastLogin 更新最后登录时间 func (r *UserRepository) UpdateLastLogin(ctx context.Context, userID int64) error { _, err := r.collection.UpdateOne( ctx, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"last_login_at": time.Now()}}, ) return err } // CharacterRepository 角色仓储 type CharacterRepository struct { collection *mongo.Collection } // NewCharacterRepository 创建角色仓储 func NewCharacterRepository(db *mongo.Database) *CharacterRepository { return &CharacterRepository{ collection: db.Collection("characters"), } } // Create 创建角色 func (r *CharacterRepository) Create(ctx context.Context, character *DbCharacter) error { character.CreatedAt = time.Now() character.UpdatedAt = time.Now() result, err := r.collection.InsertOne(ctx, character) if err != nil { return err } character.ID = result.InsertedID.(primitive.ObjectID) return nil } // FindByID 根据ID查找角色 func (r *CharacterRepository) FindByID(ctx context.Context, characterID int64) (*DbCharacter, error) { var character DbCharacter err := r.collection.FindOne(ctx, bson.M{ "character_id": characterID, "deleted_at": bson.M{"$exists": false}, }).Decode(&character) if err != nil { return nil, err } return &character, nil } // FindByUserID 根据用户ID查找所有角色 func (r *CharacterRepository) FindByUserID(ctx context.Context, userID int64) ([]*DbCharacter, error) { cursor, err := r.collection.Find(ctx, bson.M{ "user_id": userID, "deleted_at": bson.M{"$exists": false}, }) if err != nil { return nil, err } defer cursor.Close(ctx) var characters []*DbCharacter if err := cursor.All(ctx, &characters); err != nil { return nil, err } return characters, nil } // Update 更新角色 func (r *CharacterRepository) Update(ctx context.Context, character *DbCharacter) error { character.UpdatedAt = time.Now() _, err := r.collection.UpdateOne( ctx, bson.M{"character_id": character.CharacterID}, bson.M{"$set": character}, ) return err } // Delete 软删除角色 func (r *CharacterRepository) Delete(ctx context.Context, characterID int64) error { _, err := r.collection.UpdateOne( ctx, bson.M{"character_id": characterID}, bson.M{"$set": bson.M{ "deleted_at": time.Now(), }}, ) return err } // UpdatePosition 更新角色位置 func (r *CharacterRepository) UpdatePosition(ctx context.Context, characterID int64, mapID int32, x, y, z, dir float32) error { _, err := r.collection.UpdateOne( ctx, bson.M{"character_id": characterID}, bson.M{"$set": bson.M{ "map_id": mapID, "position_x": x, "position_y": y, "position_z": z, "direction": dir, "updated_at": time.Now(), }}, ) return err } // ItemRepository 物品仓储 type ItemRepository struct { collection *mongo.Collection } // NewItemRepository 创建物品仓储 func NewItemRepository(db *mongo.Database) *ItemRepository { return &ItemRepository{ collection: db.Collection("items"), } } // Create 创建物品 func (r *ItemRepository) Create(ctx context.Context, item *DbItem) error { item.CreatedAt = time.Now() result, err := r.collection.InsertOne(ctx, item) if err != nil { return err } item.ID = result.InsertedID.(primitive.ObjectID) return nil } // FindByCharacterID 查找角色的所有物品 func (r *ItemRepository) FindByCharacterID(ctx context.Context, characterID int64) ([]*DbItem, error) { cursor, err := r.collection.Find(ctx, bson.M{"character_id": characterID}) if err != nil { return nil, err } defer cursor.Close(ctx) var items []*DbItem if err := cursor.All(ctx, &items); err != nil { return nil, err } return items, nil } // FindByUID 根据唯一ID查找物品 func (r *ItemRepository) FindByUID(ctx context.Context, itemUID int64) (*DbItem, error) { var item DbItem err := r.collection.FindOne(ctx, bson.M{"item_uid": itemUID}).Decode(&item) if err != nil { return nil, err } return &item, nil } // Update 更新物品 func (r *ItemRepository) Update(ctx context.Context, item *DbItem) error { _, err := r.collection.UpdateOne( ctx, bson.M{"item_uid": item.ItemUID}, bson.M{"$set": item}, ) return err } // Delete 删除物品 func (r *ItemRepository) Delete(ctx context.Context, itemUID int64) error { _, err := r.collection.DeleteOne(ctx, bson.M{"item_uid": itemUID}) return err } // QuestRepository 任务仓储 type QuestRepository struct { collection *mongo.Collection } // NewQuestRepository 创建任务仓储 func NewQuestRepository(db *mongo.Database) *QuestRepository { return &QuestRepository{ collection: db.Collection("quests"), } } // Create 创建任务进度 func (r *QuestRepository) Create(ctx context.Context, quest *DbQuest) error { quest.AcceptedAt = time.Now() result, err := r.collection.InsertOne(ctx, quest) if err != nil { return err } quest.ID = result.InsertedID.(primitive.ObjectID) return nil } // FindByCharacterID 查找角色的所有任务 func (r *QuestRepository) FindByCharacterID(ctx context.Context, characterID int64) ([]*DbQuest, error) { cursor, err := r.collection.Find(ctx, bson.M{"character_id": characterID}) if err != nil { return nil, err } defer cursor.Close(ctx) var quests []*DbQuest if err := cursor.All(ctx, &quests); err != nil { return nil, err } return quests, nil } // Update 更新任务进度 func (r *QuestRepository) Update(ctx context.Context, quest *DbQuest) error { _, err := r.collection.UpdateOne( ctx, bson.M{ "character_id": quest.CharacterID, "quest_id": quest.QuestID, }, bson.M{"$set": quest}, ) return err } // MailRepository 邮件仓储 type MailRepository struct { collection *mongo.Collection } // NewMailRepository 创建邮件仓储 func NewMailRepository(db *mongo.Database) *MailRepository { return &MailRepository{ collection: db.Collection("mails"), } } // Create 创建邮件 func (r *MailRepository) Create(ctx context.Context, mail *DbMail) error { mail.CreatedAt = time.Now() result, err := r.collection.InsertOne(ctx, mail) if err != nil { return err } mail.ID = result.InsertedID.(primitive.ObjectID) return nil } // FindByReceiverID 查找收件人的邮件 func (r *MailRepository) FindByReceiverID(ctx context.Context, receiverID int64, limit int) ([]*DbMail, error) { opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}).SetLimit(int64(limit)) cursor, err := r.collection.Find(ctx, bson.M{"receiver_id": receiverID}, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var mails []*DbMail if err := cursor.All(ctx, &mails); err != nil { return nil, err } return mails, nil } // MarkAsRead 标记为已读 func (r *MailRepository) MarkAsRead(ctx context.Context, mailID int64) error { _, err := r.collection.UpdateOne( ctx, bson.M{"mail_id": mailID}, bson.M{"$set": bson.M{"is_read": true}}, ) return err } // Delete 删除邮件 func (r *MailRepository) Delete(ctx context.Context, mailID int64) error { _, err := r.collection.DeleteOne(ctx, bson.M{"mail_id": mailID}) return err } // DeleteExpired 删除过期邮件 func (r *MailRepository) DeleteExpired(ctx context.Context) error { _, err := r.collection.DeleteMany(ctx, bson.M{ "expire_at": bson.M{"$lt": time.Now()}, }) return err } ================================================ FILE: internal/infrastructure/persistence/scene_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/domain/scene" "greatestworks/internal/infrastructure/cache" "greatestworks/internal/infrastructure/logging" ) // MongoSceneRepository MongoDB场景仓储实现 type MongoSceneRepository struct { collection *mongo.Collection cache cache.Cache logger logging.Logger } // NewMongoSceneRepository 创建MongoDB场景仓储 func NewMongoSceneRepository(db *mongo.Database, cache cache.Cache, logger logging.Logger) *MongoSceneRepository { return &MongoSceneRepository{ collection: db.Collection("scenes"), cache: cache, logger: logger, } } // SceneDocument 场景文档结构 type SceneDocument struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` SceneID string `bson:"scene_id" json:"scene_id"` Name string `bson:"name" json:"name"` SceneType int `bson:"scene_type" json:"scene_type"` Status int `bson:"status" json:"status"` Width float64 `bson:"width" json:"width"` Height float64 `bson:"height" json:"height"` MaxPlayers int `bson:"max_players" json:"max_players"` CurrentPlayers int `bson:"current_players" json:"current_players"` Players []string `bson:"players" json:"players"` // 玩家ID列表 CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` Version int64 `bson:"version" json:"version"` } // Save 保存场景 func (r *MongoSceneRepository) Save(ctx context.Context, s *scene.Scene) error { doc := r.toDocument(s) now := time.Now() if doc.ID.IsZero() { doc.CreatedAt = now } doc.UpdatedAt = now filter := bson.M{"scene_id": s.ID()} opts := options.Replace().SetUpsert(true) _, err := r.collection.ReplaceOne(ctx, filter, doc, opts) if err != nil { return fmt.Errorf("保存场景到MongoDB失败: %w", err) } // 清理缓存 if r.cache != nil { cacheKey := fmt.Sprintf("scene:%s", s.ID()) _ = r.cache.Delete(ctx, cacheKey) } return nil } // FindByID 根据ID查找场景 func (r *MongoSceneRepository) FindByID(ctx context.Context, sceneID string) (*scene.Scene, error) { // 先尝试从缓存获取 if r.cache != nil { cacheKey := fmt.Sprintf("scene:%s", sceneID) var doc SceneDocument if err := r.cache.Get(ctx, cacheKey, &doc); err == nil { return r.toDomain(&doc), nil } } // 从MongoDB获取 filter := bson.M{"scene_id": sceneID} var doc SceneDocument err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("从MongoDB查找场景失败: %w", err) } // 写入缓存 if r.cache != nil { cacheKey := fmt.Sprintf("scene:%s", sceneID) _ = r.cache.Set(ctx, cacheKey, &doc, 5*time.Minute) } return r.toDomain(&doc), nil } // Delete 删除场景 func (r *MongoSceneRepository) Delete(ctx context.Context, sceneID string) error { filter := bson.M{"scene_id": sceneID} _, err := r.collection.DeleteOne(ctx, filter) if err != nil { return fmt.Errorf("从MongoDB删除场景失败: %w", err) } // 清理缓存 if r.cache != nil { cacheKey := fmt.Sprintf("scene:%s", sceneID) _ = r.cache.Delete(ctx, cacheKey) } return nil } // Exists 检查场景是否存在 func (r *MongoSceneRepository) Exists(ctx context.Context, sceneID string) (bool, error) { filter := bson.M{"scene_id": sceneID} count, err := r.collection.CountDocuments(ctx, filter) if err != nil { return false, fmt.Errorf("检查场景是否存在失败: %w", err) } return count > 0, nil } // SaveBatch 批量保存场景 func (r *MongoSceneRepository) SaveBatch(ctx context.Context, scenes []*scene.Scene) error { if len(scenes) == 0 { return nil } models := make([]mongo.WriteModel, 0, len(scenes)) for _, s := range scenes { doc := r.toDocument(s) now := time.Now() if doc.ID.IsZero() { doc.CreatedAt = now } doc.UpdatedAt = now filter := bson.M{"scene_id": s.ID()} update := bson.M{"$set": doc} model := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true) models = append(models, model) } _, err := r.collection.BulkWrite(ctx, models) if err != nil { return fmt.Errorf("批量保存场景失败: %w", err) } return nil } // FindByIDs 根据ID列表查找场景 func (r *MongoSceneRepository) FindByIDs(ctx context.Context, sceneIDs []string) ([]*scene.Scene, error) { filter := bson.M{"scene_id": bson.M{"$in": sceneIDs}} cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找场景列表失败: %w", err) } defer cursor.Close(ctx) var docs []SceneDocument if err := cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析场景列表失败: %w", err) } scenes := make([]*scene.Scene, 0, len(docs)) for _, doc := range docs { scenes = append(scenes, r.toDomain(&doc)) } return scenes, nil } // FindAll 查找所有场景 func (r *MongoSceneRepository) FindAll(ctx context.Context) ([]*scene.Scene, error) { cursor, err := r.collection.Find(ctx, bson.M{}) if err != nil { return nil, fmt.Errorf("查找所有场景失败: %w", err) } defer cursor.Close(ctx) var docs []SceneDocument if err := cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析场景列表失败: %w", err) } scenes := make([]*scene.Scene, 0, len(docs)) for _, doc := range docs { scenes = append(scenes, r.toDomain(&doc)) } return scenes, nil } // FindByType 根据类型查找场景 func (r *MongoSceneRepository) FindByType(ctx context.Context, sceneType scene.SceneType) ([]*scene.Scene, error) { filter := bson.M{"scene_type": int(sceneType)} cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找场景失败: %w", err) } defer cursor.Close(ctx) var docs []SceneDocument if err := cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析场景列表失败: %w", err) } scenes := make([]*scene.Scene, 0, len(docs)) for _, doc := range docs { scenes = append(scenes, r.toDomain(&doc)) } return scenes, nil } // FindByStatus 根据状态查找场景 func (r *MongoSceneRepository) FindByStatus(ctx context.Context, status scene.SceneStatus) ([]*scene.Scene, error) { filter := bson.M{"status": int(status)} cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找场景失败: %w", err) } defer cursor.Close(ctx) var docs []SceneDocument if err := cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析场景列表失败: %w", err) } scenes := make([]*scene.Scene, 0, len(docs)) for _, doc := range docs { scenes = append(scenes, r.toDomain(&doc)) } return scenes, nil } // FindAvailableScenes 查找可用场景 func (r *MongoSceneRepository) FindAvailableScenes(ctx context.Context) ([]*scene.Scene, error) { filter := bson.M{ "status": int(scene.SceneStatusActive), "$expr": bson.M{ "$lt": []interface{}{"$current_players", "$max_players"}, }, } cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找可用场景失败: %w", err) } defer cursor.Close(ctx) var docs []SceneDocument if err := cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析场景列表失败: %w", err) } scenes := make([]*scene.Scene, 0, len(docs)) for _, doc := range docs { scenes = append(scenes, r.toDomain(&doc)) } return scenes, nil } // FindScenesWithSpace 查找有空位的场景 func (r *MongoSceneRepository) FindScenesWithSpace(ctx context.Context, minSpace int) ([]*scene.Scene, error) { filter := bson.M{ "$expr": bson.M{ "$gte": []interface{}{ bson.M{"$subtract": []interface{}{"$max_players", "$current_players"}}, minSpace, }, }, } cursor, err := r.collection.Find(ctx, filter) if err != nil { return nil, fmt.Errorf("查找有空位的场景失败: %w", err) } defer cursor.Close(ctx) var docs []SceneDocument if err := cursor.All(ctx, &docs); err != nil { return nil, fmt.Errorf("解析场景列表失败: %w", err) } scenes := make([]*scene.Scene, 0, len(docs)) for _, doc := range docs { scenes = append(scenes, r.toDomain(&doc)) } return scenes, nil } // SaveEntity 保存实体(简化实现,实际应该独立存储) func (r *MongoSceneRepository) SaveEntity(ctx context.Context, sceneID string, entity scene.Entity) error { // TODO: 实现实体持久化逻辑 return nil } // RemoveEntity 移除实体 func (r *MongoSceneRepository) RemoveEntity(ctx context.Context, sceneID string, entityID string) error { // TODO: 实现实体移除逻辑 return nil } // FindEntitiesByType 根据类型查找实体 func (r *MongoSceneRepository) FindEntitiesByType(ctx context.Context, sceneID string, entityType scene.EntityType) ([]scene.Entity, error) { // TODO: 实现实体查询逻辑 return nil, nil } // FindEntitiesInRadius 查找半径内的实体 func (r *MongoSceneRepository) FindEntitiesInRadius(ctx context.Context, sceneID string, center *scene.Position, radius float64) ([]scene.Entity, error) { // TODO: 实现空间查询逻辑 return nil, nil } // AddPlayerToScene 添加玩家到场景 func (r *MongoSceneRepository) AddPlayerToScene(ctx context.Context, sceneID string, playerID string) error { filter := bson.M{"scene_id": sceneID} update := bson.M{ "$addToSet": bson.M{"players": playerID}, "$inc": bson.M{"current_players": 1}, } _, err := r.collection.UpdateOne(ctx, filter, update) return err } // RemovePlayerFromScene 从场景移除玩家 func (r *MongoSceneRepository) RemovePlayerFromScene(ctx context.Context, sceneID string, playerID string) error { filter := bson.M{"scene_id": sceneID} update := bson.M{ "$pull": bson.M{"players": playerID}, "$inc": bson.M{"current_players": -1}, } _, err := r.collection.UpdateOne(ctx, filter, update) return err } // FindPlayerScene 查找玩家所在场景 func (r *MongoSceneRepository) FindPlayerScene(ctx context.Context, playerID string) (*scene.Scene, error) { filter := bson.M{"players": playerID} var doc SceneDocument err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil } return nil, fmt.Errorf("查找玩家场景失败: %w", err) } return r.toDomain(&doc), nil } // GetScenePlayerCount 获取场景玩家数 func (r *MongoSceneRepository) GetScenePlayerCount(ctx context.Context, sceneID string) (int, error) { filter := bson.M{"scene_id": sceneID} var doc SceneDocument err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { return 0, err } return doc.CurrentPlayers, nil } // GetScenePlayers 获取场景玩家列表 func (r *MongoSceneRepository) GetScenePlayers(ctx context.Context, sceneID string) ([]string, error) { filter := bson.M{"scene_id": sceneID} var doc SceneDocument err := r.collection.FindOne(ctx, filter).Decode(&doc) if err != nil { return nil, err } return doc.Players, nil } // GetSceneStats 获取场景统计信息 func (r *MongoSceneRepository) GetSceneStats(ctx context.Context, sceneID string) (*scene.SceneStats, error) { // TODO: 实现场景统计信息 return nil, nil } // GetSceneHistory 获取场景历史记录 func (r *MongoSceneRepository) GetSceneHistory(ctx context.Context, sceneID string, limit int) ([]*scene.SceneHistoryRecord, error) { // TODO: 实现场景历史记录 return nil, nil } // GetPopularScenes 获取热门场景 func (r *MongoSceneRepository) GetPopularScenes(ctx context.Context, limit int) ([]*scene.ScenePopularity, error) { // TODO: 实现热门场景查询 return nil, nil } // GetSceneConfig 获取场景配置 func (r *MongoSceneRepository) GetSceneConfig(ctx context.Context, sceneID string) (*scene.SceneConfig, error) { // TODO: 实现场景配置查询 return nil, nil } // SaveSceneConfig 保存场景配置 func (r *MongoSceneRepository) SaveSceneConfig(ctx context.Context, config *scene.SceneConfig) error { // TODO: 实现场景配置保存 return nil } // GetAllSceneConfigs 获取所有场景配置 func (r *MongoSceneRepository) GetAllSceneConfigs(ctx context.Context) ([]*scene.SceneConfig, error) { // TODO: 实现场景配置列表查询 return nil, nil } // toDocument 转换为文档 func (r *MongoSceneRepository) toDocument(s *scene.Scene) *SceneDocument { return &SceneDocument{ SceneID: s.ID(), Name: s.Name(), SceneType: int(s.Type()), Status: int(s.Status()), Width: s.GetWidth(), Height: s.GetHeight(), MaxPlayers: s.GetMaxPlayers(), CurrentPlayers: s.PlayerCount(), Players: []string{}, // TODO: 从场景中提取玩家ID列表 } } // toDomain 转换为领域对象 func (r *MongoSceneRepository) toDomain(doc *SceneDocument) *scene.Scene { return scene.NewScene( doc.SceneID, doc.Name, scene.SceneType(doc.SceneType), doc.Width, doc.Height, doc.MaxPlayers, ) } ================================================ FILE: internal/infrastructure/persistence/weather_repository.go ================================================ package persistence import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "greatestworks/internal/infrastructure/logging" ) // WeatherRepository 天气仓储 type WeatherRepository struct { collection *mongo.Collection logger logging.Logger } // NewWeatherRepository 创建天气仓储 func NewWeatherRepository(db *mongo.Database, logger logging.Logger) *WeatherRepository { return &WeatherRepository{ collection: db.Collection("weather"), logger: logger, } } // WeatherRecord 天气记录 type WeatherRecord struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` Region string `bson:"region" json:"region"` WeatherType string `bson:"weather_type" json:"weather_type"` Temperature int `bson:"temperature" json:"temperature"` Humidity int `bson:"humidity" json:"humidity"` WindSpeed int `bson:"wind_speed" json:"wind_speed"` WindDirection string `bson:"wind_direction" json:"wind_direction"` Pressure int `bson:"pressure" json:"pressure"` Visibility int `bson:"visibility" json:"visibility"` Description string `bson:"description" json:"description"` StartTime time.Time `bson:"start_time" json:"start_time"` EndTime time.Time `bson:"end_time" json:"end_time"` CreatedAt time.Time `bson:"created_at" json:"created_at"` UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` } // CreateWeather 创建天气记录 func (r *WeatherRepository) CreateWeather(ctx context.Context, weather *WeatherRecord) error { weather.CreatedAt = time.Now() weather.UpdatedAt = time.Now() _, err := r.collection.InsertOne(ctx, weather) if err != nil { r.logger.Error("创建天气记录失败", err, logging.Fields{ "region": weather.Region, "weather_type": weather.WeatherType, }) return fmt.Errorf("创建天气记录失败: %w", err) } r.logger.Info("天气记录创建成功", map[string]interface{}{ "region": weather.Region, "weather_type": weather.WeatherType, "temperature": weather.Temperature, }) return nil } // GetWeather 获取天气记录 func (r *WeatherRepository) GetWeather(ctx context.Context, id string) (*WeatherRecord, error) { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, fmt.Errorf("无效的ID格式: %w", err) } var weather WeatherRecord err = r.collection.FindOne(ctx, bson.M{"_id": objectID}).Decode(&weather) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("天气记录不存在") } r.logger.Error("获取天气记录失败", err, logging.Fields{ "id": id, }) return nil, fmt.Errorf("获取天气记录失败: %w", err) } return &weather, nil } // UpdateWeather 更新天气记录 func (r *WeatherRepository) UpdateWeather(ctx context.Context, id string, updates map[string]interface{}) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } updates["updated_at"] = time.Now() result, err := r.collection.UpdateOne( ctx, bson.M{"_id": objectID}, bson.M{"$set": updates}, ) if err != nil { r.logger.Error("更新天气记录失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("更新天气记录失败: %w", err) } if result.MatchedCount == 0 { return fmt.Errorf("天气记录不存在") } r.logger.Info("天气记录更新成功", map[string]interface{}{ "id": id, }) return nil } // DeleteWeather 删除天气记录 func (r *WeatherRepository) DeleteWeather(ctx context.Context, id string) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return fmt.Errorf("无效的ID格式: %w", err) } result, err := r.collection.DeleteOne(ctx, bson.M{"_id": objectID}) if err != nil { r.logger.Error("删除天气记录失败", err, logging.Fields{ "id": id, }) return fmt.Errorf("删除天气记录失败: %w", err) } if result.DeletedCount == 0 { return fmt.Errorf("天气记录不存在") } r.logger.Info("天气记录删除成功", map[string]interface{}{ "id": id, }) return nil } // GetCurrentWeather 获取当前天气 func (r *WeatherRepository) GetCurrentWeather(ctx context.Context, region string) (*WeatherRecord, error) { now := time.Now() filter := bson.M{ "region": region, "start_time": bson.M{"$lte": now}, "end_time": bson.M{"$gte": now}, } opts := options.FindOne().SetSort(bson.D{{Key: "start_time", Value: -1}}) var weather WeatherRecord err := r.collection.FindOne(ctx, filter, opts).Decode(&weather) if err != nil { if err == mongo.ErrNoDocuments { return nil, fmt.Errorf("当前没有天气记录") } r.logger.Error("获取当前天气失败", err, logging.Fields{ "region": region, }) return nil, fmt.Errorf("获取当前天气失败: %w", err) } return &weather, nil } // GetWeatherHistory 获取天气历史 func (r *WeatherRepository) GetWeatherHistory(ctx context.Context, region string, startTime, endTime time.Time, limit, offset int) ([]*WeatherRecord, error) { filter := bson.M{ "region": region, "start_time": bson.M{"$gte": startTime, "$lte": endTime}, } opts := options.Find(). SetSort(bson.D{{Key: "start_time", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("获取天气历史失败", err, logging.Fields{ "region": region, "start_time": startTime, "end_time": endTime, }) return nil, fmt.Errorf("获取天气历史失败: %w", err) } defer cursor.Close(ctx) var weathers []*WeatherRecord if err = cursor.All(ctx, &weathers); err != nil { return nil, fmt.Errorf("解析天气历史失败: %w", err) } r.logger.Info("获取天气历史成功", map[string]interface{}{ "region": region, "count": len(weathers), }) return weathers, nil } // GetWeatherByType 根据天气类型获取记录 func (r *WeatherRepository) GetWeatherByType(ctx context.Context, weatherType string, limit, offset int) ([]*WeatherRecord, error) { filter := bson.M{"weather_type": weatherType} opts := options.Find(). SetSort(bson.D{{Key: "start_time", Value: -1}}). SetLimit(int64(limit)). SetSkip(int64(offset)) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("根据天气类型获取记录失败", err, logging.Fields{ "weather_type": weatherType, }) return nil, fmt.Errorf("根据天气类型获取记录失败: %w", err) } defer cursor.Close(ctx) var weathers []*WeatherRecord if err = cursor.All(ctx, &weathers); err != nil { return nil, fmt.Errorf("解析天气记录失败: %w", err) } r.logger.Info("根据天气类型获取记录成功", map[string]interface{}{ "weather_type": weatherType, "count": len(weathers), }) return weathers, nil } // GetWeatherStats 获取天气统计 func (r *WeatherRepository) GetWeatherStats(ctx context.Context, region string, startTime, endTime time.Time) (map[string]interface{}, error) { pipeline := []bson.M{ { "$match": bson.M{ "region": region, "start_time": bson.M{"$gte": startTime, "$lte": endTime}, }, }, { "$group": bson.M{ "_id": "$weather_type", "count": bson.M{"$sum": 1}, "avg_temperature": bson.M{"$avg": "$temperature"}, "avg_humidity": bson.M{"$avg": "$humidity"}, "avg_wind_speed": bson.M{"$avg": "$wind_speed"}, }, }, } cursor, err := r.collection.Aggregate(ctx, pipeline) if err != nil { r.logger.Error("获取天气统计失败", err, logging.Fields{ "region": region, "start_time": startTime, "end_time": endTime, }) return nil, fmt.Errorf("获取天气统计失败: %w", err) } defer cursor.Close(ctx) var results []bson.M if err = cursor.All(ctx, &results); err != nil { return nil, fmt.Errorf("解析天气统计失败: %w", err) } stats := make(map[string]interface{}) for _, result := range results { weatherType := result["_id"].(string) stats[weatherType] = map[string]interface{}{ "count": result["count"], "avg_temperature": result["avg_temperature"], "avg_humidity": result["avg_humidity"], "avg_wind_speed": result["avg_wind_speed"], } } r.logger.Info("获取天气统计成功", map[string]interface{}{ "region": region, "stats": stats, }) return stats, nil } // GetWeatherForecast 获取天气预报 func (r *WeatherRepository) GetWeatherForecast(ctx context.Context, region string, days int) ([]*WeatherRecord, error) { startTime := time.Now() endTime := startTime.AddDate(0, 0, days) filter := bson.M{ "region": region, "start_time": bson.M{"$gte": startTime, "$lte": endTime}, } opts := options.Find(). SetSort(bson.D{{Key: "start_time", Value: 1}}). SetLimit(int64(days * 24)) // 假设每小时一条记录 cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { r.logger.Error("获取天气预报失败", err, logging.Fields{ "region": region, "days": days, }) return nil, fmt.Errorf("获取天气预报失败: %w", err) } defer cursor.Close(ctx) var weathers []*WeatherRecord if err = cursor.All(ctx, &weathers); err != nil { return nil, fmt.Errorf("解析天气预报失败: %w", err) } r.logger.Info("获取天气预报成功", map[string]interface{}{ "region": region, "days": days, "count": len(weathers), }) return weathers, nil } ================================================ FILE: internal/infrastructure/protocol/binary_protocol.go ================================================ // Package protocol 二进制协议实现 // Author: MMO Server Team // Created: 2024 package protocol import ( "bytes" "encoding/binary" "fmt" "hash/crc32" "io" "time" ) // BinaryCodec 二进制编解码器 type BinaryCodec struct { byteOrder binary.ByteOrder } // NewBinaryCodec 创建二进制编解码器 func NewBinaryCodec() *BinaryCodec { return &BinaryCodec{ byteOrder: binary.LittleEndian, } } // GetName 获取编解码器名称 func (bc *BinaryCodec) GetName() string { return "binary" } // Encode 编码消息 func (bc *BinaryCodec) Encode(message Message) ([]byte, error) { // 序列化消息数据 msgData, err := message.Marshal() if err != nil { return nil, fmt.Errorf("failed to marshal message: %w", err) } // 创建数据包 packet := NewBasePacket(message.GetType(), msgData) // 编码数据包 return bc.EncodePacket(packet) } // Decode 解码消息 func (bc *BinaryCodec) Decode(data []byte) (Message, error) { // 解码数据包 packet, err := bc.DecodePacket(data) if err != nil { return nil, err } // 创建消息实例(这里需要消息注册表支持) message := &BinaryMessage{ BaseMessage: BaseMessage{msgType: packet.GetType()}, data: packet.GetData(), } return message, nil } // EncodePacket 编码数据包 func (bc *BinaryCodec) EncodePacket(packet Packet) ([]byte, error) { basePacket, ok := packet.(*BasePacket) if !ok { return nil, fmt.Errorf("unsupported packet type: %T", packet) } // 计算校验和 basePacket.header.Checksum = bc.calculateChecksum(basePacket.data) // 创建缓冲区 buf := new(bytes.Buffer) // 写入头部 if err := bc.writeHeader(buf, &basePacket.header); err != nil { return nil, fmt.Errorf("failed to write header: %w", err) } // 写入数据 if len(basePacket.data) > 0 { if _, err := buf.Write(basePacket.data); err != nil { return nil, fmt.Errorf("failed to write data: %w", err) } } return buf.Bytes(), nil } // DecodePacket 解码数据包 func (bc *BinaryCodec) DecodePacket(data []byte) (Packet, error) { if len(data) < HeaderSize { return nil, fmt.Errorf("packet too small: %d < %d", len(data), HeaderSize) } // 读取头部 buf := bytes.NewReader(data) header, err := bc.readHeader(buf) if err != nil { return nil, fmt.Errorf("failed to read header: %w", err) } // 验证头部 if err := bc.validateHeader(header); err != nil { return nil, fmt.Errorf("invalid header: %w", err) } // 检查数据长度 expectedSize := HeaderSize + int(header.Length) if len(data) != expectedSize { return nil, fmt.Errorf("packet size mismatch: expected %d, got %d", expectedSize, len(data)) } // 读取数据 var msgData []byte if header.Length > 0 { msgData = make([]byte, header.Length) if _, err := buf.Read(msgData); err != nil { return nil, fmt.Errorf("failed to read data: %w", err) } // 验证校验和 if header.Checksum != bc.calculateChecksum(msgData) { return nil, fmt.Errorf("checksum mismatch") } } // 创建数据包 packet := &BasePacket{ header: *header, data: msgData, } return packet, nil } // writeHeader 写入头部 func (bc *BinaryCodec) writeHeader(w io.Writer, header *PacketHeader) error { if err := binary.Write(w, bc.byteOrder, header.Magic); err != nil { return err } if err := binary.Write(w, bc.byteOrder, header.Version); err != nil { return err } if err := binary.Write(w, bc.byteOrder, header.Type); err != nil { return err } if err := binary.Write(w, bc.byteOrder, header.Length); err != nil { return err } if err := binary.Write(w, bc.byteOrder, header.Sequence); err != nil { return err } if err := binary.Write(w, bc.byteOrder, header.Timestamp); err != nil { return err } if err := binary.Write(w, bc.byteOrder, header.Checksum); err != nil { return err } return nil } // readHeader 读取头部 func (bc *BinaryCodec) readHeader(r io.Reader) (*PacketHeader, error) { header := &PacketHeader{} if err := binary.Read(r, bc.byteOrder, &header.Magic); err != nil { return nil, err } if err := binary.Read(r, bc.byteOrder, &header.Version); err != nil { return nil, err } if err := binary.Read(r, bc.byteOrder, &header.Type); err != nil { return nil, err } if err := binary.Read(r, bc.byteOrder, &header.Length); err != nil { return nil, err } if err := binary.Read(r, bc.byteOrder, &header.Sequence); err != nil { return nil, err } if err := binary.Read(r, bc.byteOrder, &header.Timestamp); err != nil { return nil, err } if err := binary.Read(r, bc.byteOrder, &header.Checksum); err != nil { return nil, err } return header, nil } // validateHeader 验证头部 func (bc *BinaryCodec) validateHeader(header *PacketHeader) error { if header.Magic != MagicNumber { return fmt.Errorf("invalid magic number: %x", header.Magic) } if header.Version != ProtocolVersion { return fmt.Errorf("unsupported protocol version: %d", header.Version) } if header.Length > DefaultMaxPacketSize { return fmt.Errorf("packet too large: %d > %d", header.Length, DefaultMaxPacketSize) } return nil } // calculateChecksum 计算校验和 func (bc *BinaryCodec) calculateChecksum(data []byte) uint32 { return crc32.ChecksumIEEE(data) } // BinaryMessage 二进制消息实现 type BinaryMessage struct { BaseMessage data []byte } // NewBinaryMessage 创建二进制消息 func NewBinaryMessage(msgType MessageType, data []byte) *BinaryMessage { return &BinaryMessage{ BaseMessage: BaseMessage{msgType: msgType}, data: data, } } // Marshal 序列化消息 func (bm *BinaryMessage) Marshal() ([]byte, error) { return bm.data, nil } // Unmarshal 反序列化消息 func (bm *BinaryMessage) Unmarshal(data []byte) error { bm.data = make([]byte, len(data)) copy(bm.data, data) return nil } // GetData 获取数据 func (bm *BinaryMessage) GetData() []byte { return bm.data } // SetData 设置数据 func (bm *BinaryMessage) SetData(data []byte) { bm.data = make([]byte, len(data)) copy(bm.data, data) } // String 字符串表示 func (bm *BinaryMessage) String() string { return fmt.Sprintf("BinaryMessage{Type: %d, Size: %d}", bm.msgType, len(bm.data)) } // BinarySerializer 二进制序列化器 type BinarySerializer struct { byteOrder binary.ByteOrder } // NewBinarySerializer 创建二进制序列化器 func NewBinarySerializer() *BinarySerializer { return &BinarySerializer{ byteOrder: binary.LittleEndian, } } // GetContentType 获取内容类型 func (bs *BinarySerializer) GetContentType() string { return "application/octet-stream" } // Serialize 序列化对象 func (bs *BinarySerializer) Serialize(obj interface{}) ([]byte, error) { buf := new(bytes.Buffer) if err := binary.Write(buf, bs.byteOrder, obj); err != nil { return nil, fmt.Errorf("failed to serialize object: %w", err) } return buf.Bytes(), nil } // Deserialize 反序列化对象 func (bs *BinarySerializer) Deserialize(data []byte, obj interface{}) error { buf := bytes.NewReader(data) if err := binary.Read(buf, bs.byteOrder, obj); err != nil { return fmt.Errorf("failed to deserialize object: %w", err) } return nil } // BinaryPacketReader 二进制数据包读取器 type BinaryPacketReader struct { reader io.Reader codec *BinaryCodec buffer []byte offset int } // NewBinaryPacketReader 创建二进制数据包读取器 func NewBinaryPacketReader(reader io.Reader) *BinaryPacketReader { return &BinaryPacketReader{ reader: reader, codec: NewBinaryCodec(), buffer: make([]byte, DefaultBufferSize), } } // ReadPacket 读取数据包 func (bpr *BinaryPacketReader) ReadPacket() (Packet, error) { // 确保有足够的数据读取头部 if err := bpr.ensureBytes(HeaderSize); err != nil { return nil, err } // 读取头部 headerData := bpr.buffer[bpr.offset : bpr.offset+HeaderSize] header, err := bpr.codec.readHeader(bytes.NewReader(headerData)) if err != nil { return nil, fmt.Errorf("failed to read header: %w", err) } // 验证头部 if err := bpr.codec.validateHeader(header); err != nil { return nil, fmt.Errorf("invalid header: %w", err) } // 计算总包大小 totalSize := HeaderSize + int(header.Length) // 确保有足够的数据读取整个数据包 if err := bpr.ensureBytes(totalSize); err != nil { return nil, err } // 读取完整数据包 packetData := bpr.buffer[bpr.offset : bpr.offset+totalSize] packet, err := bpr.codec.DecodePacket(packetData) if err != nil { return nil, err } // 更新偏移量 bpr.offset += totalSize return packet, nil } // ensureBytes 确保缓冲区有足够的字节 func (bpr *BinaryPacketReader) ensureBytes(needed int) error { available := len(bpr.buffer) - bpr.offset if available >= needed { return nil } // 移动现有数据到缓冲区开头 if bpr.offset > 0 { copy(bpr.buffer, bpr.buffer[bpr.offset:]) bpr.buffer = bpr.buffer[:available] bpr.offset = 0 } // 扩展缓冲区如果需要 if cap(bpr.buffer) < needed { newBuffer := make([]byte, needed*2) copy(newBuffer, bpr.buffer) bpr.buffer = newBuffer[:len(bpr.buffer)] } // 读取更多数据 for len(bpr.buffer) < needed { n, err := bpr.reader.Read(bpr.buffer[len(bpr.buffer):cap(bpr.buffer)]) if err != nil { return err } bpr.buffer = bpr.buffer[:len(bpr.buffer)+n] } return nil } // BinaryPacketWriter 二进制数据包写入器 type BinaryPacketWriter struct { writer io.Writer codec *BinaryCodec } // NewBinaryPacketWriter 创建二进制数据包写入器 func NewBinaryPacketWriter(writer io.Writer) *BinaryPacketWriter { return &BinaryPacketWriter{ writer: writer, codec: NewBinaryCodec(), } } // WritePacket 写入数据包 func (bpw *BinaryPacketWriter) WritePacket(packet Packet) error { // 编码数据包 data, err := bpw.codec.EncodePacket(packet) if err != nil { return fmt.Errorf("failed to encode packet: %w", err) } // 写入数据 if _, err := bpw.writer.Write(data); err != nil { return fmt.Errorf("failed to write packet: %w", err) } return nil } // BinaryPacketReadWriter 二进制数据包读写器 type BinaryPacketReadWriter struct { *BinaryPacketReader *BinaryPacketWriter closer io.Closer } // NewBinaryPacketReadWriter 创建二进制数据包读写器 func NewBinaryPacketReadWriter(rw io.ReadWriter) *BinaryPacketReadWriter { var closer io.Closer if c, ok := rw.(io.Closer); ok { closer = c } return &BinaryPacketReadWriter{ BinaryPacketReader: NewBinaryPacketReader(rw), BinaryPacketWriter: NewBinaryPacketWriter(rw), closer: closer, } } // Close 关闭读写器 func (bprw *BinaryPacketReadWriter) Close() error { if bprw.closer != nil { return bprw.closer.Close() } return nil } // 预定义的二进制消息类型 // HeartbeatMessage 心跳消息 type HeartbeatMessage struct { BaseMessage Timestamp int64 } // NewHeartbeatMessage 创建心跳消息 func NewHeartbeatMessage() *HeartbeatMessage { return &HeartbeatMessage{ BaseMessage: BaseMessage{msgType: MsgTypeHeartbeat}, Timestamp: time.Now().UnixNano(), } } // Marshal 序列化心跳消息 func (hm *HeartbeatMessage) Marshal() ([]byte, error) { buf := new(bytes.Buffer) if err := binary.Write(buf, binary.LittleEndian, hm.Timestamp); err != nil { return nil, err } return buf.Bytes(), nil } // Unmarshal 反序列化心跳消息 func (hm *HeartbeatMessage) Unmarshal(data []byte) error { buf := bytes.NewReader(data) return binary.Read(buf, binary.LittleEndian, &hm.Timestamp) } // String 字符串表示 func (hm *HeartbeatMessage) String() string { return fmt.Sprintf("HeartbeatMessage{Timestamp: %d}", hm.Timestamp) } // LoginMessage 登录消息 type LoginMessage struct { BaseMessage Username string Password string Version uint32 } // NewLoginMessage 创建登录消息 func NewLoginMessage(username, password string, version uint32) *LoginMessage { return &LoginMessage{ BaseMessage: BaseMessage{msgType: MsgTypeLogin}, Username: username, Password: password, Version: version, } } // Marshal 序列化登录消息 func (lm *LoginMessage) Marshal() ([]byte, error) { buf := new(bytes.Buffer) // 写入版本 if err := binary.Write(buf, binary.LittleEndian, lm.Version); err != nil { return nil, err } // 写入用户名长度和内容 usernameBytes := []byte(lm.Username) if err := binary.Write(buf, binary.LittleEndian, uint16(len(usernameBytes))); err != nil { return nil, err } if _, err := buf.Write(usernameBytes); err != nil { return nil, err } // 写入密码长度和内容 passwordBytes := []byte(lm.Password) if err := binary.Write(buf, binary.LittleEndian, uint16(len(passwordBytes))); err != nil { return nil, err } if _, err := buf.Write(passwordBytes); err != nil { return nil, err } return buf.Bytes(), nil } // Unmarshal 反序列化登录消息 func (lm *LoginMessage) Unmarshal(data []byte) error { buf := bytes.NewReader(data) // 读取版本 if err := binary.Read(buf, binary.LittleEndian, &lm.Version); err != nil { return err } // 读取用户名 var usernameLen uint16 if err := binary.Read(buf, binary.LittleEndian, &usernameLen); err != nil { return err } usernameBytes := make([]byte, usernameLen) if _, err := buf.Read(usernameBytes); err != nil { return err } lm.Username = string(usernameBytes) // 读取密码 var passwordLen uint16 if err := binary.Read(buf, binary.LittleEndian, &passwordLen); err != nil { return err } passwordBytes := make([]byte, passwordLen) if _, err := buf.Read(passwordBytes); err != nil { return err } lm.Password = string(passwordBytes) return nil } // Validate 验证登录消息 func (lm *LoginMessage) Validate() error { if lm.Username == "" { return fmt.Errorf("username cannot be empty") } if lm.Password == "" { return fmt.Errorf("password cannot be empty") } if len(lm.Username) > 32 { return fmt.Errorf("username too long: %d > 32", len(lm.Username)) } if len(lm.Password) > 64 { return fmt.Errorf("password too long: %d > 64", len(lm.Password)) } return nil } // String 字符串表示 func (lm *LoginMessage) String() string { return fmt.Sprintf("LoginMessage{Username: %s, Version: %d}", lm.Username, lm.Version) } // ErrorMessage 错误消息 type ErrorMessage struct { BaseMessage Code uint32 Message string } // NewErrorMessage 创建错误消息 func NewErrorMessage(code uint32, message string) *ErrorMessage { return &ErrorMessage{ BaseMessage: BaseMessage{msgType: MsgTypeError}, Code: code, Message: message, } } // Marshal 序列化错误消息 func (em *ErrorMessage) Marshal() ([]byte, error) { buf := new(bytes.Buffer) // 写入错误码 if err := binary.Write(buf, binary.LittleEndian, em.Code); err != nil { return nil, err } // 写入消息长度和内容 messageBytes := []byte(em.Message) if err := binary.Write(buf, binary.LittleEndian, uint16(len(messageBytes))); err != nil { return nil, err } if _, err := buf.Write(messageBytes); err != nil { return nil, err } return buf.Bytes(), nil } // Unmarshal 反序列化错误消息 func (em *ErrorMessage) Unmarshal(data []byte) error { buf := bytes.NewReader(data) // 读取错误码 if err := binary.Read(buf, binary.LittleEndian, &em.Code); err != nil { return err } // 读取消息 var messageLen uint16 if err := binary.Read(buf, binary.LittleEndian, &messageLen); err != nil { return err } messageBytes := make([]byte, messageLen) if _, err := buf.Read(messageBytes); err != nil { return err } em.Message = string(messageBytes) return nil } // String 字符串表示 func (em *ErrorMessage) String() string { return fmt.Sprintf("ErrorMessage{Code: %d, Message: %s}", em.Code, em.Message) } ================================================ FILE: internal/infrastructure/protocol/json_protocol.go ================================================ // Package protocol JSON协议实现 // Author: MMO Server Team // Created: 2024 package protocol import ( "encoding/json" "fmt" "time" ) // JSONCodec JSON编解码器 type JSONCodec struct { prettyPrint bool } // NewJSONCodec 创建JSON编解码器 func NewJSONCodec(prettyPrint bool) *JSONCodec { return &JSONCodec{ prettyPrint: prettyPrint, } } // GetName 获取编解码器名称 func (jc *JSONCodec) GetName() string { return "json" } // Encode 编码消息 func (jc *JSONCodec) Encode(message Message) ([]byte, error) { // 创建JSON包装器 wrapper := &JSONMessageWrapper{ Type: message.GetType(), Timestamp: time.Now().UnixNano(), Data: message, } // 序列化为JSON if jc.prettyPrint { return json.MarshalIndent(wrapper, "", " ") } return json.Marshal(wrapper) } // Decode 解码消息 func (jc *JSONCodec) Decode(data []byte) (Message, error) { // 解析JSON包装器 var wrapper JSONMessageWrapper if err := json.Unmarshal(data, &wrapper); err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) } // 创建具体的消息实例 message, err := jc.createMessage(wrapper.Type) if err != nil { return nil, err } // 反序列化消息数据 if wrapper.Data != nil { dataBytes, err := json.Marshal(wrapper.Data) if err != nil { return nil, fmt.Errorf("failed to marshal message data: %w", err) } if err := message.Unmarshal(dataBytes); err != nil { return nil, fmt.Errorf("failed to unmarshal message: %w", err) } } return message, nil } // createMessage 创建消息实例 func (jc *JSONCodec) createMessage(msgType MessageType) (Message, error) { // 根据消息类型创建对应的消息实例 switch msgType { case MsgTypeHeartbeat: return &JSONHeartbeatMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil case MsgTypeLogin: return &JSONLoginMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil case MsgTypeLogout: return &JSONLogoutMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil case MsgTypeError: return &JSONErrorMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil case MsgTypePlayerInfo: return &JSONPlayerInfoMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil case MsgTypePlayerMove: return &JSONPlayerMoveMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil case MsgTypePlayerChat: return &JSONPlayerChatMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil default: // 默认创建通用JSON消息 return &JSONGenericMessage{BaseMessage: BaseMessage{msgType: msgType}}, nil } } // JSONMessageWrapper JSON消息包装器 type JSONMessageWrapper struct { Type MessageType `json:"type"` Timestamp int64 `json:"timestamp"` Sequence uint32 `json:"sequence,omitempty"` Data interface{} `json:"data,omitempty"` } // JSONSerializer JSON序列化器 type JSONSerializer struct { prettyPrint bool } // NewJSONSerializer 创建JSON序列化器 func NewJSONSerializer(prettyPrint bool) *JSONSerializer { return &JSONSerializer{ prettyPrint: prettyPrint, } } // GetContentType 获取内容类型 func (js *JSONSerializer) GetContentType() string { return "application/json" } // Serialize 序列化对象 func (js *JSONSerializer) Serialize(obj interface{}) ([]byte, error) { if js.prettyPrint { return json.MarshalIndent(obj, "", " ") } return json.Marshal(obj) } // Deserialize 反序列化对象 func (js *JSONSerializer) Deserialize(data []byte, obj interface{}) error { return json.Unmarshal(data, obj) } // JSONGenericMessage 通用JSON消息 type JSONGenericMessage struct { BaseMessage Data map[string]interface{} `json:"data"` } // NewJSONGenericMessage 创建通用JSON消息 func NewJSONGenericMessage(msgType MessageType, data map[string]interface{}) *JSONGenericMessage { return &JSONGenericMessage{ BaseMessage: BaseMessage{msgType: msgType}, Data: data, } } // Marshal 序列化消息 func (jgm *JSONGenericMessage) Marshal() ([]byte, error) { return json.Marshal(jgm.Data) } // Unmarshal 反序列化消息 func (jgm *JSONGenericMessage) Unmarshal(data []byte) error { return json.Unmarshal(data, &jgm.Data) } // String 字符串表示 func (jgm *JSONGenericMessage) String() string { return fmt.Sprintf("JSONGenericMessage{Type: %d, Data: %v}", jgm.msgType, jgm.Data) } // JSONHeartbeatMessage JSON心跳消息 type JSONHeartbeatMessage struct { BaseMessage Timestamp int64 `json:"timestamp"` ClientID string `json:"client_id,omitempty"` } // NewJSONHeartbeatMessage 创建JSON心跳消息 func NewJSONHeartbeatMessage(clientID string) *JSONHeartbeatMessage { return &JSONHeartbeatMessage{ BaseMessage: BaseMessage{msgType: MsgTypeHeartbeat}, Timestamp: time.Now().UnixNano(), ClientID: clientID, } } // Marshal 序列化消息 func (jhm *JSONHeartbeatMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "timestamp": jhm.Timestamp, "client_id": jhm.ClientID, }) } // Unmarshal 反序列化消息 func (jhm *JSONHeartbeatMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if timestamp, ok := obj["timestamp"].(float64); ok { jhm.Timestamp = int64(timestamp) } if clientID, ok := obj["client_id"].(string); ok { jhm.ClientID = clientID } return nil } // String 字符串表示 func (jhm *JSONHeartbeatMessage) String() string { return fmt.Sprintf("JSONHeartbeatMessage{Timestamp: %d, ClientID: %s}", jhm.Timestamp, jhm.ClientID) } // JSONLoginMessage JSON登录消息 type JSONLoginMessage struct { BaseMessage Username string `json:"username"` Password string `json:"password"` Version uint32 `json:"version"` Metadata map[string]string `json:"metadata,omitempty"` } // NewJSONLoginMessage 创建JSON登录消息 func NewJSONLoginMessage(username, password string, version uint32) *JSONLoginMessage { return &JSONLoginMessage{ BaseMessage: BaseMessage{msgType: MsgTypeLogin}, Username: username, Password: password, Version: version, Metadata: make(map[string]string), } } // Marshal 序列化消息 func (jlm *JSONLoginMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "username": jlm.Username, "password": jlm.Password, "version": jlm.Version, "metadata": jlm.Metadata, }) } // Unmarshal 反序列化消息 func (jlm *JSONLoginMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if username, ok := obj["username"].(string); ok { jlm.Username = username } if password, ok := obj["password"].(string); ok { jlm.Password = password } if version, ok := obj["version"].(float64); ok { jlm.Version = uint32(version) } if metadata, ok := obj["metadata"].(map[string]interface{}); ok { jlm.Metadata = make(map[string]string) for k, v := range metadata { if str, ok := v.(string); ok { jlm.Metadata[k] = str } } } return nil } // Validate 验证登录消息 func (jlm *JSONLoginMessage) Validate() error { if jlm.Username == "" { return fmt.Errorf("username cannot be empty") } if jlm.Password == "" { return fmt.Errorf("password cannot be empty") } if len(jlm.Username) > 32 { return fmt.Errorf("username too long: %d > 32", len(jlm.Username)) } if len(jlm.Password) > 64 { return fmt.Errorf("password too long: %d > 64", len(jlm.Password)) } return nil } // String 字符串表示 func (jlm *JSONLoginMessage) String() string { return fmt.Sprintf("JSONLoginMessage{Username: %s, Version: %d}", jlm.Username, jlm.Version) } // JSONLogoutMessage JSON登出消息 type JSONLogoutMessage struct { BaseMessage Reason string `json:"reason,omitempty"` } // NewJSONLogoutMessage 创建JSON登出消息 func NewJSONLogoutMessage(reason string) *JSONLogoutMessage { return &JSONLogoutMessage{ BaseMessage: BaseMessage{msgType: MsgTypeLogout}, Reason: reason, } } // Marshal 序列化消息 func (jlom *JSONLogoutMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "reason": jlom.Reason, }) } // Unmarshal 反序列化消息 func (jlom *JSONLogoutMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if reason, ok := obj["reason"].(string); ok { jlom.Reason = reason } return nil } // String 字符串表示 func (jlom *JSONLogoutMessage) String() string { return fmt.Sprintf("JSONLogoutMessage{Reason: %s}", jlom.Reason) } // JSONErrorMessage JSON错误消息 type JSONErrorMessage struct { BaseMessage Code uint32 `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` } // NewJSONErrorMessage 创建JSON错误消息 func NewJSONErrorMessage(code uint32, message, details string) *JSONErrorMessage { return &JSONErrorMessage{ BaseMessage: BaseMessage{msgType: MsgTypeError}, Code: code, Message: message, Details: details, } } // Marshal 序列化消息 func (jem *JSONErrorMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "code": jem.Code, "message": jem.Message, "details": jem.Details, }) } // Unmarshal 反序列化消息 func (jem *JSONErrorMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if code, ok := obj["code"].(float64); ok { jem.Code = uint32(code) } if message, ok := obj["message"].(string); ok { jem.Message = message } if details, ok := obj["details"].(string); ok { jem.Details = details } return nil } // String 字符串表示 func (jem *JSONErrorMessage) String() string { return fmt.Sprintf("JSONErrorMessage{Code: %d, Message: %s}", jem.Code, jem.Message) } // JSONPlayerInfoMessage JSON玩家信息消息 type JSONPlayerInfoMessage struct { BaseMessage PlayerID string `json:"player_id"` Name string `json:"name"` Level uint32 `json:"level"` Exp uint64 `json:"exp"` Gold uint64 `json:"gold"` HP uint32 `json:"hp"` MP uint32 `json:"mp"` X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // NewJSONPlayerInfoMessage 创建JSON玩家信息消息 func NewJSONPlayerInfoMessage(playerID, name string) *JSONPlayerInfoMessage { return &JSONPlayerInfoMessage{ BaseMessage: BaseMessage{msgType: MsgTypePlayerInfo}, PlayerID: playerID, Name: name, } } // Marshal 序列化消息 func (jpim *JSONPlayerInfoMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "player_id": jpim.PlayerID, "name": jpim.Name, "level": jpim.Level, "exp": jpim.Exp, "gold": jpim.Gold, "hp": jpim.HP, "mp": jpim.MP, "x": jpim.X, "y": jpim.Y, "z": jpim.Z, }) } // Unmarshal 反序列化消息 func (jpim *JSONPlayerInfoMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if playerID, ok := obj["player_id"].(string); ok { jpim.PlayerID = playerID } if name, ok := obj["name"].(string); ok { jpim.Name = name } if level, ok := obj["level"].(float64); ok { jpim.Level = uint32(level) } if exp, ok := obj["exp"].(float64); ok { jpim.Exp = uint64(exp) } if gold, ok := obj["gold"].(float64); ok { jpim.Gold = uint64(gold) } if hp, ok := obj["hp"].(float64); ok { jpim.HP = uint32(hp) } if mp, ok := obj["mp"].(float64); ok { jpim.MP = uint32(mp) } if x, ok := obj["x"].(float64); ok { jpim.X = x } if y, ok := obj["y"].(float64); ok { jpim.Y = y } if z, ok := obj["z"].(float64); ok { jpim.Z = z } return nil } // String 字符串表示 func (jpim *JSONPlayerInfoMessage) String() string { return fmt.Sprintf("JSONPlayerInfoMessage{PlayerID: %s, Name: %s, Level: %d}", jpim.PlayerID, jpim.Name, jpim.Level) } // JSONPlayerMoveMessage JSON玩家移动消息 type JSONPlayerMoveMessage struct { BaseMessage PlayerID string `json:"player_id"` FromX float64 `json:"from_x"` FromY float64 `json:"from_y"` FromZ float64 `json:"from_z"` ToX float64 `json:"to_x"` ToY float64 `json:"to_y"` ToZ float64 `json:"to_z"` Speed float64 `json:"speed"` Timestamp int64 `json:"timestamp"` } // NewJSONPlayerMoveMessage 创建JSON玩家移动消息 func NewJSONPlayerMoveMessage(playerID string, fromX, fromY, fromZ, toX, toY, toZ, speed float64) *JSONPlayerMoveMessage { return &JSONPlayerMoveMessage{ BaseMessage: BaseMessage{msgType: MsgTypePlayerMove}, PlayerID: playerID, FromX: fromX, FromY: fromY, FromZ: fromZ, ToX: toX, ToY: toY, ToZ: toZ, Speed: speed, Timestamp: time.Now().UnixNano(), } } // Marshal 序列化消息 func (jpmm *JSONPlayerMoveMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "player_id": jpmm.PlayerID, "from_x": jpmm.FromX, "from_y": jpmm.FromY, "from_z": jpmm.FromZ, "to_x": jpmm.ToX, "to_y": jpmm.ToY, "to_z": jpmm.ToZ, "speed": jpmm.Speed, "timestamp": jpmm.Timestamp, }) } // Unmarshal 反序列化消息 func (jpmm *JSONPlayerMoveMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if playerID, ok := obj["player_id"].(string); ok { jpmm.PlayerID = playerID } if fromX, ok := obj["from_x"].(float64); ok { jpmm.FromX = fromX } if fromY, ok := obj["from_y"].(float64); ok { jpmm.FromY = fromY } if fromZ, ok := obj["from_z"].(float64); ok { jpmm.FromZ = fromZ } if toX, ok := obj["to_x"].(float64); ok { jpmm.ToX = toX } if toY, ok := obj["to_y"].(float64); ok { jpmm.ToY = toY } if toZ, ok := obj["to_z"].(float64); ok { jpmm.ToZ = toZ } if speed, ok := obj["speed"].(float64); ok { jpmm.Speed = speed } if timestamp, ok := obj["timestamp"].(float64); ok { jpmm.Timestamp = int64(timestamp) } return nil } // String 字符串表示 func (jpmm *JSONPlayerMoveMessage) String() string { return fmt.Sprintf("JSONPlayerMoveMessage{PlayerID: %s, From: (%.2f,%.2f,%.2f), To: (%.2f,%.2f,%.2f)}", jpmm.PlayerID, jpmm.FromX, jpmm.FromY, jpmm.FromZ, jpmm.ToX, jpmm.ToY, jpmm.ToZ) } // JSONPlayerChatMessage JSON玩家聊天消息 type JSONPlayerChatMessage struct { BaseMessage PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` Channel string `json:"channel"` Message string `json:"message"` Timestamp int64 `json:"timestamp"` } // NewJSONPlayerChatMessage 创建JSON玩家聊天消息 func NewJSONPlayerChatMessage(playerID, playerName, channel, message string) *JSONPlayerChatMessage { return &JSONPlayerChatMessage{ BaseMessage: BaseMessage{msgType: MsgTypePlayerChat}, PlayerID: playerID, PlayerName: playerName, Channel: channel, Message: message, Timestamp: time.Now().UnixNano(), } } // Marshal 序列化消息 func (jpcm *JSONPlayerChatMessage) Marshal() ([]byte, error) { return json.Marshal(map[string]interface{}{ "player_id": jpcm.PlayerID, "player_name": jpcm.PlayerName, "channel": jpcm.Channel, "message": jpcm.Message, "timestamp": jpcm.Timestamp, }) } // Unmarshal 反序列化消息 func (jpcm *JSONPlayerChatMessage) Unmarshal(data []byte) error { var obj map[string]interface{} if err := json.Unmarshal(data, &obj); err != nil { return err } if playerID, ok := obj["player_id"].(string); ok { jpcm.PlayerID = playerID } if playerName, ok := obj["player_name"].(string); ok { jpcm.PlayerName = playerName } if channel, ok := obj["channel"].(string); ok { jpcm.Channel = channel } if message, ok := obj["message"].(string); ok { jpcm.Message = message } if timestamp, ok := obj["timestamp"].(float64); ok { jpcm.Timestamp = int64(timestamp) } return nil } // Validate 验证聊天消息 func (jpcm *JSONPlayerChatMessage) Validate() error { if jpcm.PlayerID == "" { return fmt.Errorf("player_id cannot be empty") } if jpcm.Message == "" { return fmt.Errorf("message cannot be empty") } if len(jpcm.Message) > 500 { return fmt.Errorf("message too long: %d > 500", len(jpcm.Message)) } return nil } // String 字符串表示 func (jpcm *JSONPlayerChatMessage) String() string { return fmt.Sprintf("JSONPlayerChatMessage{PlayerID: %s, Channel: %s, Message: %s}", jpcm.PlayerID, jpcm.Channel, jpcm.Message) } ================================================ FILE: internal/infrastructure/protocol/protocol.go ================================================ // Package protocol 统一协议系统 // Author: MMO Server Team // Created: 2024 package protocol import ( "context" "fmt" // "io" "time" ) // MessageType 消息类型 type MessageType uint16 // 预定义消息类型 const ( // 系统消息 MsgTypeHeartbeat MessageType = 1000 + iota MsgTypeLogin MsgTypeLogout MsgTypeError MsgTypeNotification // 玩家消息 MsgTypePlayerInfo MessageType = 2000 + iota MsgTypePlayerMove MsgTypePlayerAction MsgTypePlayerChat MsgTypePlayerStatus // 背包消息 MsgTypeInventoryList MessageType = 3000 + iota MsgTypeInventoryAdd MsgTypeInventoryRemove MsgTypeInventoryUpdate MsgTypeInventoryUse // 战斗消息 MsgTypeBattleStart MessageType = 4000 + iota MsgTypeBattleEnd MsgTypeBattleAction MsgTypeBattleResult MsgTypeBattleStatus // 社交消息 MsgTypeFriendList MessageType = 5000 + iota MsgTypeFriendAdd MsgTypeFriendRemove MsgTypeGuildInfo MsgTypeGuildJoin // 场景消息 MsgTypeSceneEnter MessageType = 6000 + iota MsgTypeSceneLeave MsgTypeSceneUpdate MsgTypeSceneObject MsgTypeSceneNPC // 活动消息 MsgTypeActivityList MessageType = 7000 + iota MsgTypeActivityJoin MsgTypeActivityReward MsgTypeActivityStatus // 宠物消息 MsgTypePetList MessageType = 8000 + iota MsgTypePetSummon MsgTypePetDismiss MsgTypePetUpgrade MsgTypePetSkill // 建筑消息 MsgTypeBuildingList MessageType = 9000 + iota MsgTypeBuildingBuild MsgTypeBuildingUpgrade MsgTypeBuildingDestroy MsgTypeBuildingCollect ) // Packet 数据包接口 type Packet interface { // GetType 获取消息类型 GetType() MessageType // GetData 获取消息数据 GetData() []byte // SetData 设置消息数据 SetData(data []byte) // GetSize 获取数据包大小 GetSize() int // GetTimestamp 获取时间戳 GetTimestamp() time.Time // SetTimestamp 设置时间戳 SetTimestamp(timestamp time.Time) // GetSequence 获取序列号 GetSequence() uint32 // SetSequence 设置序列号 SetSequence(seq uint32) // Validate 验证数据包 Validate() error } // Message 消息接口 type Message interface { // GetType 获取消息类型 GetType() MessageType // Marshal 序列化消息 Marshal() ([]byte, error) // Unmarshal 反序列化消息 Unmarshal(data []byte) error // Validate 验证消息 Validate() error // String 字符串表示 String() string } // Codec 编解码器接口 type Codec interface { // Encode 编码消息 Encode(message Message) ([]byte, error) // Decode 解码消息 Decode(data []byte) (Message, error) // GetName 获取编解码器名称 GetName() string } // Serializer 序列化器接口 type Serializer interface { // Serialize 序列化对象 Serialize(obj interface{}) ([]byte, error) // Deserialize 反序列化对象 Deserialize(data []byte, obj interface{}) error // GetContentType 获取内容类型 GetContentType() string } // Compressor 压缩器接口 type Compressor interface { // Compress 压缩数据 Compress(data []byte) ([]byte, error) // Decompress 解压数据 Decompress(data []byte) ([]byte, error) // GetType 获取压缩类型 GetType() string } // Encryptor 加密器接口 type Encryptor interface { // Encrypt 加密数据 Encrypt(data []byte) ([]byte, error) // Decrypt 解密数据 Decrypt(data []byte) ([]byte, error) // GetType 获取加密类型 GetType() string } // Handler 消息处理器接口 type Handler interface { // Handle 处理消息 Handle(ctx context.Context, message Message) (Message, error) // GetMessageType 获取处理的消息类型 GetMessageType() MessageType } // Middleware 中间件接口 type Middleware interface { // Process 处理消息 Process(ctx context.Context, message Message, next func(context.Context, Message) (Message, error)) (Message, error) // GetName 获取中间件名称 GetName() string } // Router 路由器接口 type Router interface { // RegisterHandler 注册处理器 RegisterHandler(msgType MessageType, handler Handler) error // UnregisterHandler 注销处理器 UnregisterHandler(msgType MessageType) error // GetHandler 获取处理器 GetHandler(msgType MessageType) (Handler, bool) // Route 路由消息 Route(ctx context.Context, message Message) (Message, error) // AddMiddleware 添加中间件 AddMiddleware(middleware Middleware) // RemoveMiddleware 移除中间件 RemoveMiddleware(name string) } // Connection 连接接口 type Connection interface { // ID 获取连接ID ID() string // RemoteAddr 获取远程地址 RemoteAddr() string // LocalAddr 获取本地地址 LocalAddr() string // Send 发送消息 Send(message Message) error // SendPacket 发送数据包 SendPacket(packet Packet) error // Receive 接收消息 Receive() (Message, error) // ReceivePacket 接收数据包 ReceivePacket() (Packet, error) // Close 关闭连接 Close() error // IsClosed 是否已关闭 IsClosed() bool // GetMetadata 获取元数据 GetMetadata(key string) interface{} // SetMetadata 设置元数据 SetMetadata(key string, value interface{}) // GetLastActivity 获取最后活动时间 GetLastActivity() time.Time // UpdateActivity 更新活动时间 UpdateActivity() } // Server 协议服务器接口 type Server interface { // Start 启动服务器 Start(ctx context.Context) error // Stop 停止服务器 Stop(ctx context.Context) error // GetAddr 获取监听地址 GetAddr() string // GetConnections 获取所有连接 GetConnections() []Connection // GetConnection 获取指定连接 GetConnection(id string) (Connection, bool) // Broadcast 广播消息 Broadcast(message Message) error // BroadcastToGroup 向组广播消息 BroadcastToGroup(group string, message Message) error // SetRouter 设置路由器 SetRouter(router Router) // GetRouter 获取路由器 GetRouter() Router } // Client 协议客户端接口 type Client interface { // Connect 连接服务器 Connect(ctx context.Context, addr string) error // Disconnect 断开连接 Disconnect() error // Send 发送消息 Send(message Message) error // Receive 接收消息 Receive() (Message, error) // IsConnected 是否已连接 IsConnected() bool // GetConnection 获取连接 GetConnection() Connection // SetHandler 设置消息处理器 SetHandler(handler func(Message) error) } // Registry 协议注册表接口 type Registry interface { // RegisterMessage 注册消息类型 RegisterMessage(msgType MessageType, factory func() Message) error // UnregisterMessage 注销消息类型 UnregisterMessage(msgType MessageType) error // CreateMessage 创建消息实例 CreateMessage(msgType MessageType) (Message, error) // GetMessageTypes 获取所有消息类型 GetMessageTypes() []MessageType // IsRegistered 检查是否已注册 IsRegistered(msgType MessageType) bool } // Config 协议配置 type Config struct { // 协议类型 Protocol string `yaml:"protocol" json:"protocol"` // 监听地址 Host string `yaml:"host" json:"host"` // 监听端口 Port int `yaml:"port" json:"port"` // 缓冲区大小 BufferSize int `yaml:"buffer_size" json:"buffer_size"` // 最大数据包大小 MaxPacketSize int `yaml:"max_packet_size" json:"max_packet_size"` // 读取超时 ReadTimeout time.Duration `yaml:"read_timeout" json:"read_timeout"` // 写入超时 WriteTimeout time.Duration `yaml:"write_timeout" json:"write_timeout"` // 心跳间隔 HeartbeatInterval time.Duration `yaml:"heartbeat_interval" json:"heartbeat_interval"` // 连接超时 ConnectionTimeout time.Duration `yaml:"connection_timeout" json:"connection_timeout"` // 最大连接数 MaxConnections int `yaml:"max_connections" json:"max_connections"` // 压缩类型 CompressionType string `yaml:"compression_type" json:"compression_type"` // 加密类型 EncryptionType string `yaml:"encryption_type" json:"encryption_type"` // 序列化类型 SerializationType string `yaml:"serialization_type" json:"serialization_type"` // 是否启用压缩 EnableCompression bool `yaml:"enable_compression" json:"enable_compression"` // 是否启用加密 EnableEncryption bool `yaml:"enable_encryption" json:"enable_encryption"` // 是否启用心跳 EnableHeartbeat bool `yaml:"enable_heartbeat" json:"enable_heartbeat"` // TLS配置 TLS TLSConfig `yaml:"tls" json:"tls"` } // TLSConfig TLS配置 type TLSConfig struct { // 是否启用TLS Enabled bool `yaml:"enabled" json:"enabled"` // 证书文件 CertFile string `yaml:"cert_file" json:"cert_file"` // 私钥文件 KeyFile string `yaml:"key_file" json:"key_file"` // CA文件 CAFile string `yaml:"ca_file" json:"ca_file"` // 是否跳过验证 InsecureSkipVerify bool `yaml:"insecure_skip_verify" json:"insecure_skip_verify"` } // DefaultConfig 默认配置 func DefaultConfig() *Config { return &Config{ Protocol: "tcp", Host: "0.0.0.0", Port: 8080, BufferSize: 4096, MaxPacketSize: 65536, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, HeartbeatInterval: 30 * time.Second, ConnectionTimeout: 10 * time.Second, MaxConnections: 10000, CompressionType: "gzip", EncryptionType: "aes", SerializationType: "binary", EnableCompression: false, EnableEncryption: false, EnableHeartbeat: true, TLS: TLSConfig{ Enabled: false, InsecureSkipVerify: false, }, } } // Factory 协议工厂接口 type Factory interface { // CreateServer 创建服务器 CreateServer(config *Config) (Server, error) // CreateClient 创建客户端 CreateClient(config *Config) (Client, error) // CreateCodec 创建编解码器 CreateCodec(codecType string) (Codec, error) // CreateSerializer 创建序列化器 CreateSerializer(serializerType string) (Serializer, error) // CreateCompressor 创建压缩器 CreateCompressor(compressorType string) (Compressor, error) // CreateEncryptor 创建加密器 CreateEncryptor(encryptorType string) (Encryptor, error) } // Manager 协议管理器接口 type Manager interface { // GetRegistry 获取注册表 GetRegistry() Registry // GetFactory 获取工厂 GetFactory() Factory // RegisterProtocol 注册协议 RegisterProtocol(name string, factory Factory) error // UnregisterProtocol 注销协议 UnregisterProtocol(name string) error // GetProtocol 获取协议 GetProtocol(name string) (Factory, bool) // CreateServer 创建服务器 CreateServer(protocol string, config *Config) (Server, error) // CreateClient 创建客户端 CreateClient(protocol string, config *Config) (Client, error) } // 预定义错误 var ( ErrInvalidMessageType = fmt.Errorf("invalid message type") ErrMessageNotRegistered = fmt.Errorf("message not registered") ErrInvalidPacket = fmt.Errorf("invalid packet") ErrPacketTooLarge = fmt.Errorf("packet too large") ErrConnectionClosed = fmt.Errorf("connection closed") ErrConnectionTimeout = fmt.Errorf("connection timeout") ErrHandlerNotFound = fmt.Errorf("handler not found") ErrProtocolNotSupported = fmt.Errorf("protocol not supported") ErrSerializationFailed = fmt.Errorf("serialization failed") ErrDeserializationFailed = fmt.Errorf("deserialization failed") ErrCompressionFailed = fmt.Errorf("compression failed") ErrDecompressionFailed = fmt.Errorf("decompression failed") ErrEncryptionFailed = fmt.Errorf("encryption failed") ErrDecryptionFailed = fmt.Errorf("decryption failed") ) // 常用常量 const ( // 协议版本 ProtocolVersion = 1 // 魔数 MagicNumber = 0x12345678 // 头部大小 HeaderSize = 16 // 最小数据包大小 MinPacketSize = HeaderSize // 默认缓冲区大小 DefaultBufferSize = 4096 // 默认最大数据包大小 DefaultMaxPacketSize = 1024 * 1024 // 1MB ) // PacketHeader 数据包头部 type PacketHeader struct { Magic uint32 // 魔数 Version uint16 // 协议版本 Type MessageType // 消息类型 Length uint32 // 数据长度 Sequence uint32 // 序列号 Timestamp int64 // 时间戳 Checksum uint32 // 校验和 } // BasePacket 基础数据包实现 type BasePacket struct { header PacketHeader data []byte } // NewBasePacket 创建基础数据包 func NewBasePacket(msgType MessageType, data []byte) *BasePacket { return &BasePacket{ header: PacketHeader{ Magic: MagicNumber, Version: ProtocolVersion, Type: msgType, Length: uint32(len(data)), Timestamp: time.Now().UnixNano(), }, data: data, } } func (bp *BasePacket) GetType() MessageType { return bp.header.Type } func (bp *BasePacket) GetData() []byte { return bp.data } func (bp *BasePacket) SetData(data []byte) { bp.data = data; bp.header.Length = uint32(len(data)) } func (bp *BasePacket) GetSize() int { return HeaderSize + len(bp.data) } func (bp *BasePacket) GetTimestamp() time.Time { return time.Unix(0, bp.header.Timestamp) } func (bp *BasePacket) SetTimestamp(t time.Time) { bp.header.Timestamp = t.UnixNano() } func (bp *BasePacket) GetSequence() uint32 { return bp.header.Sequence } func (bp *BasePacket) SetSequence(seq uint32) { bp.header.Sequence = seq } func (bp *BasePacket) Validate() error { if bp.header.Magic != MagicNumber { return fmt.Errorf("invalid magic number: %x", bp.header.Magic) } if bp.header.Version != ProtocolVersion { return fmt.Errorf("unsupported protocol version: %d", bp.header.Version) } if bp.header.Length != uint32(len(bp.data)) { return fmt.Errorf("length mismatch: header=%d, actual=%d", bp.header.Length, len(bp.data)) } return nil } // BaseMessage 基础消息实现 type BaseMessage struct { msgType MessageType } func (bm *BaseMessage) GetType() MessageType { return bm.msgType } func (bm *BaseMessage) Validate() error { return nil } func (bm *BaseMessage) String() string { return fmt.Sprintf("Message{Type: %d}", bm.msgType) } // 便捷函数 // IsSystemMessage 检查是否为系统消息 func IsSystemMessage(msgType MessageType) bool { return msgType >= 1000 && msgType < 2000 } // IsPlayerMessage 检查是否为玩家消息 func IsPlayerMessage(msgType MessageType) bool { return msgType >= 2000 && msgType < 3000 } // IsInventoryMessage 检查是否为背包消息 func IsInventoryMessage(msgType MessageType) bool { return msgType >= 3000 && msgType < 4000 } // IsBattleMessage 检查是否为战斗消息 func IsBattleMessage(msgType MessageType) bool { return msgType >= 4000 && msgType < 5000 } // IsSocialMessage 检查是否为社交消息 func IsSocialMessage(msgType MessageType) bool { return msgType >= 5000 && msgType < 6000 } // IsSceneMessage 检查是否为场景消息 func IsSceneMessage(msgType MessageType) bool { return msgType >= 6000 && msgType < 7000 } // IsActivityMessage 检查是否为活动消息 func IsActivityMessage(msgType MessageType) bool { return msgType >= 7000 && msgType < 8000 } // IsPetMessage 检查是否为宠物消息 func IsPetMessage(msgType MessageType) bool { return msgType >= 8000 && msgType < 9000 } // IsBuildingMessage 检查是否为建筑消息 func IsBuildingMessage(msgType MessageType) bool { return msgType >= 9000 && msgType < 10000 } // GetMessageCategory 获取消息分类 func GetMessageCategory(msgType MessageType) string { switch { case IsSystemMessage(msgType): return "system" case IsPlayerMessage(msgType): return "player" case IsInventoryMessage(msgType): return "inventory" case IsBattleMessage(msgType): return "battle" case IsSocialMessage(msgType): return "social" case IsSceneMessage(msgType): return "scene" case IsActivityMessage(msgType): return "activity" case IsPetMessage(msgType): return "pet" case IsBuildingMessage(msgType): return "building" default: return "unknown" } } // PacketReader 数据包读取器 type PacketReader interface { // ReadPacket 读取数据包 ReadPacket() (Packet, error) } // PacketWriter 数据包写入器 type PacketWriter interface { // WritePacket 写入数据包 WritePacket(packet Packet) error } ================================================ FILE: internal/infrastructure/weave/weavelet.go ================================================ // Package weave provides Service Weaver integration functionality package weave import ( "fmt" "github.com/google/uuid" ) // WeaveletInfo represents weavelet information for Service Weaver type WeaveletInfo struct { App string `json:"app"` DeploymentId string `json:"deployment_id"` Group *ColocationGroup `json:"group"` GroupId string `json:"group_id"` Id string `json:"id"` SingleProcess bool `json:"single_process"` SingleMachine bool `json:"single_machine"` Labels map[string]string `json:"labels,omitempty"` } // ColocationGroup represents a colocation group in Service Weaver type ColocationGroup struct { Name string `json:"name"` Components []string `json:"components"` } // WeaveletConfig holds configuration for weavelet integration type WeaveletConfig struct { Enabled bool `yaml:"enabled" json:"enabled"` App string `yaml:"app" json:"app"` DeploymentId string `yaml:"deployment_id" json:"deployment_id"` GroupName string `yaml:"group_name" json:"group_name"` Components []string `yaml:"components" json:"components"` SingleProcess bool `yaml:"single_process" json:"single_process"` SingleMachine bool `yaml:"single_machine" json:"single_machine"` Labels map[string]string `yaml:"labels" json:"labels"` } // DefaultWeaveletConfig returns default weavelet configuration func DefaultWeaveletConfig() WeaveletConfig { return WeaveletConfig{ Enabled: false, App: "greatestworks", DeploymentId: uuid.New().String(), GroupName: "main", Components: []string{"player", "battle", "inventory"}, SingleProcess: true, SingleMachine: true, Labels: make(map[string]string), } } // WeaveletManager manages weavelet lifecycle and operations type WeaveletManager struct { config *WeaveletConfig info *WeaveletInfo } // NewWeaveletManager creates a new weavelet manager func NewWeaveletManager(config *WeaveletConfig) *WeaveletManager { if config == nil { defaultConfig := DefaultWeaveletConfig() config = &defaultConfig } return &WeaveletManager{ config: config, } } // Initialize initializes the weavelet manager func (wm *WeaveletManager) Initialize() error { if !wm.config.Enabled { return nil } // Create weavelet info wm.info = &WeaveletInfo{ App: wm.config.App, DeploymentId: wm.config.DeploymentId, Group: &ColocationGroup{ Name: wm.config.GroupName, Components: wm.config.Components, }, GroupId: uuid.New().String(), Id: uuid.New().String(), SingleProcess: wm.config.SingleProcess, SingleMachine: wm.config.SingleMachine, Labels: wm.config.Labels, } // Validate weavelet info if err := CheckWeaveletInfo(wm.info); err != nil { return fmt.Errorf("weavelet validation failed: %w", err) } return nil } // GetInfo returns the weavelet information func (wm *WeaveletManager) GetInfo() *WeaveletInfo { return wm.info } // IsEnabled returns whether weavelet is enabled func (wm *WeaveletManager) IsEnabled() bool { return wm.config.Enabled } // UpdateConfig updates the weavelet configuration func (wm *WeaveletManager) UpdateConfig(config *WeaveletConfig) error { if config == nil { return fmt.Errorf("config cannot be nil") } wm.config = config return wm.Initialize() } // AddLabel adds a label to the weavelet func (wm *WeaveletManager) AddLabel(key, value string) { if wm.config.Labels == nil { wm.config.Labels = make(map[string]string) } wm.config.Labels[key] = value if wm.info != nil { if wm.info.Labels == nil { wm.info.Labels = make(map[string]string) } wm.info.Labels[key] = value } } // RemoveLabel removes a label from the weavelet func (wm *WeaveletManager) RemoveLabel(key string) { if wm.config.Labels != nil { delete(wm.config.Labels, key) } if wm.info != nil && wm.info.Labels != nil { delete(wm.info.Labels, key) } } // GetLabels returns all labels func (wm *WeaveletManager) GetLabels() map[string]string { if wm.info != nil && wm.info.Labels != nil { return wm.info.Labels } return wm.config.Labels } // CheckWeaveletInfo checks that weavelet information is well-formed. func CheckWeaveletInfo(w *WeaveletInfo) error { if w == nil { return fmt.Errorf("WeaveletInfo: nil") } if w.App == "" { return fmt.Errorf("WeaveletInfo: missing app name") } if _, err := uuid.Parse(w.DeploymentId); err != nil { return fmt.Errorf("WeaveletInfo: invalid deployment id: %w", err) } if w.Group == nil { return fmt.Errorf("WeaveletInfo: nil colocation group") } if w.Group.Name == "" { return fmt.Errorf("WeaveletInfo: missing colocation group name") } if w.GroupId == "" { return fmt.Errorf("WeaveletInfo: missing colocation group replica id") } if _, err := uuid.Parse(w.Id); err != nil { return fmt.Errorf("WeaveletInfo: invalid weavelet id: %w", err) } if w.SingleProcess && !w.SingleMachine { return fmt.Errorf("WeaveletInfo: single process but not single machine") } return nil } // ValidateColocationGroup validates a colocation group func ValidateColocationGroup(group *ColocationGroup) error { if group == nil { return fmt.Errorf("colocation group cannot be nil") } if group.Name == "" { return fmt.Errorf("colocation group name cannot be empty") } if len(group.Components) == 0 { return fmt.Errorf("colocation group must have at least one component") } // Check for duplicate components componentSet := make(map[string]bool) for _, component := range group.Components { if component == "" { return fmt.Errorf("component name cannot be empty") } if componentSet[component] { return fmt.Errorf("duplicate component: %s", component) } componentSet[component] = true } return nil } // CreateWeaveletInfo creates a new WeaveletInfo with validation func CreateWeaveletInfo(config *WeaveletConfig) (*WeaveletInfo, error) { if config == nil { return nil, fmt.Errorf("config cannot be nil") } info := &WeaveletInfo{ App: config.App, DeploymentId: config.DeploymentId, Group: &ColocationGroup{ Name: config.GroupName, Components: config.Components, }, GroupId: uuid.New().String(), Id: uuid.New().String(), SingleProcess: config.SingleProcess, SingleMachine: config.SingleMachine, Labels: make(map[string]string), } // Copy labels for k, v := range config.Labels { info.Labels[k] = v } // Validate the created info if err := CheckWeaveletInfo(info); err != nil { return nil, err } return info, nil } ================================================ FILE: internal/interfaces/http/auth/login_handler.go ================================================ package auth import ( "context" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "greatestworks/internal/application/handlers" playerQueries "greatestworks/internal/application/queries/player" "greatestworks/internal/infrastructure/logging" ) // LoginHandler 登录处理器 type LoginHandler struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger jwtSecret string } // LoginRequest 登录请求 type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // LoginResponse 登录响应 type LoginResponse struct { Token string `json:"token"` ExpiresAt time.Time `json:"expires_at"` User UserInfo `json:"user"` } // UserInfo 用户信息 type UserInfo struct { ID string `json:"id"` Username string `json:"username"` Email string `json:"email"` } // NewLoginHandler 创建登录处理器 func NewLoginHandler(commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger, jwtSecret string) *LoginHandler { return &LoginHandler{ commandBus: commandBus, queryBus: queryBus, logger: logger, jwtSecret: jwtSecret, } } // Login 处理登录请求 func (h *LoginHandler) Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid login request", logging.Fields{ "error": err, }) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 验证用户凭据 user, err := h.authenticateUser(c.Request.Context(), req.Username, req.Password) if err != nil { h.logger.Warn("Authentication failed", logging.Fields{ "error": err, "username": req.Username, }) c.JSON(401, gin.H{"error": "Invalid credentials"}) return } // 生成JWT令牌 token, expiresAt, err := h.generateToken(user.ID, user.Username) if err != nil { h.logger.Error("Failed to generate token", err, logging.Fields{ "user_id": user.ID, }) c.JSON(500, gin.H{"error": "Failed to generate token"}) return } // 返回登录响应 response := LoginResponse{ Token: token, ExpiresAt: expiresAt, User: UserInfo{ ID: user.ID, Username: user.Username, Email: user.Email, }, } h.logger.Info("User logged in successfully", logging.Fields{ "user_id": user.ID, "username": user.Username, }) c.JSON(200, response) } // Logout 处理登出请求 func (h *LoginHandler) Logout(c *gin.Context) { // 获取用户ID userID, exists := c.Get("user_id") if !exists { c.JSON(401, gin.H{"error": "Not authenticated"}) return } // 这里可以实现令牌黑名单逻辑 h.logger.Info("User logged out", logging.Fields{ "user_id": userID, }) c.JSON(200, gin.H{"message": "Logged out successfully"}) } // RefreshToken 刷新令牌 func (h *LoginHandler) RefreshToken(c *gin.Context) { // 获取当前用户信息 userID, exists := c.Get("user_id") if !exists { c.JSON(401, gin.H{"error": "Not authenticated"}) return } username, _ := c.Get("username") // 生成新的令牌 token, expiresAt, err := h.generateToken(userID.(string), username.(string)) if err != nil { h.logger.Error("Failed to refresh token", err, logging.Fields{ "user_id": userID, }) c.JSON(500, gin.H{"error": "Failed to refresh token"}) return } response := LoginResponse{ Token: token, ExpiresAt: expiresAt, } h.logger.Info("Token refreshed", logging.Fields{ "user_id": userID, }) c.JSON(200, response) } // 私有方法 // authenticateUser 验证用户凭据 func (h *LoginHandler) authenticateUser(ctx context.Context, username, password string) (*UserInfo, error) { // 这里应该实现实际的用户认证逻辑 // 简化实现,实际项目中应该查询数据库 // 模拟用户查询 query := &playerQueries.GetPlayerQuery{ PlayerID: "user_123", // 使用PlayerID而不是Username } // 这里应该调用查询总线 // result, err := h.queryBus.Execute(ctx, query) // 简化实现 _ = query // 模拟用户信息 user := &UserInfo{ ID: "user_123", Username: username, Email: username + "@example.com", } return user, nil } // generateToken 生成JWT令牌 func (h *LoginHandler) generateToken(userID, username string) (string, time.Time, error) { // 设置过期时间 expiresAt := time.Now().Add(24 * time.Hour) // 创建声明 claims := jwt.MapClaims{ "user_id": userID, "username": username, "exp": expiresAt.Unix(), "iat": time.Now().Unix(), } // 创建令牌 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 签名令牌 tokenString, err := token.SignedString([]byte(h.jwtSecret)) if err != nil { return "", time.Time{}, err } return tokenString, expiresAt, nil } ================================================ FILE: internal/interfaces/http/auth/middleware.go ================================================ package auth import ( "fmt" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "greatestworks/internal/infrastructure/logging" ) // AuthMiddleware 认证中间件 type AuthMiddleware struct { jwtSecret string logger logging.Logger } // NewAuthMiddleware 创建认证中间件 func NewAuthMiddleware(jwtSecret string, logger logging.Logger) *AuthMiddleware { return &AuthMiddleware{ jwtSecret: jwtSecret, logger: logger, } } // RequireAuth 需要认证的中间件 func (m *AuthMiddleware) RequireAuth() gin.HandlerFunc { return func(c *gin.Context) { // 从请求头获取token authHeader := c.GetHeader("Authorization") if authHeader == "" { m.logger.Warn("Missing authorization header") c.JSON(401, gin.H{"error": "Missing authorization header"}) c.Abort() return } // 检查Bearer前缀 if !strings.HasPrefix(authHeader, "Bearer ") { m.logger.Warn("Invalid authorization header format") c.JSON(401, gin.H{"error": "Invalid authorization header format"}) c.Abort() return } // 提取token tokenString := strings.TrimPrefix(authHeader, "Bearer ") // 验证token claims, err := m.validateToken(tokenString) if err != nil { m.logger.Warn("Invalid token", logging.Fields{ "error": err, }) c.JSON(401, gin.H{"error": "Invalid token"}) c.Abort() return } // 检查token是否过期 if exp, ok := claims["exp"].(float64); ok { if time.Unix(int64(exp), 0).Before(time.Now()) { m.logger.Warn("Token expired") c.JSON(401, gin.H{"error": "Token expired"}) c.Abort() return } } // 将用户信息存储到上下文中 if userID, ok := claims["user_id"].(string); ok { c.Set("user_id", userID) } if username, ok := claims["username"].(string); ok { c.Set("username", username) } if exp, ok := claims["exp"].(float64); ok { c.Set("expires_at", time.Unix(int64(exp), 0)) } m.logger.Debug("User authenticated", logging.Fields{ "user_id": claims["user_id"], "username": claims["username"], }) c.Next() } } // OptionalAuth 可选认证的中间件 func (m *AuthMiddleware) OptionalAuth() gin.HandlerFunc { return func(c *gin.Context) { // 从请求头获取token authHeader := c.GetHeader("Authorization") if authHeader == "" { c.Next() return } // 检查Bearer前缀 if !strings.HasPrefix(authHeader, "Bearer ") { c.Next() return } // 提取token tokenString := strings.TrimPrefix(authHeader, "Bearer ") // 验证token claims, err := m.validateToken(tokenString) if err != nil { m.logger.Debug("Invalid token in optional auth", logging.Fields{ "error": err, }) c.Next() return } // 检查token是否过期 if exp, ok := claims["exp"].(float64); ok { if time.Unix(int64(exp), 0).Before(time.Now()) { m.logger.Debug("Token expired in optional auth") c.Next() return } } // 将用户信息存储到上下文中 if userID, ok := claims["user_id"].(string); ok { c.Set("user_id", userID) } if username, ok := claims["username"].(string); ok { c.Set("username", username) } if exp, ok := claims["exp"].(float64); ok { c.Set("expires_at", time.Unix(int64(exp), 0)) } m.logger.Debug("User authenticated (optional)", logging.Fields{ "user_id": claims["user_id"], "username": claims["username"], }) c.Next() } } // RequireRole 需要特定角色的中间件 func (m *AuthMiddleware) RequireRole(role string) gin.HandlerFunc { return func(c *gin.Context) { // 首先检查是否已认证 userID, exists := c.Get("user_id") if !exists { m.logger.Warn("User not authenticated for role check") c.JSON(401, gin.H{"error": "Authentication required"}) c.Abort() return } // 检查用户角色 userRole, err := m.getUserRole(userID.(string)) if err != nil { m.logger.Error("Failed to get user role", err, logging.Fields{ "user_id": userID, }) c.JSON(500, gin.H{"error": "Failed to check user role"}) c.Abort() return } if userRole != role { m.logger.Warn("Insufficient permissions", logging.Fields{ "user_id": userID, "required_role": role, "user_role": userRole, }) c.JSON(403, gin.H{"error": "Insufficient permissions"}) c.Abort() return } m.logger.Debug("Role check passed", logging.Fields{ "user_id": userID, "role": role, }) c.Next() } } // RequireAnyRole 需要任意一个角色的中间件 func (m *AuthMiddleware) RequireAnyRole(roles ...string) gin.HandlerFunc { return func(c *gin.Context) { // 首先检查是否已认证 userID, exists := c.Get("user_id") if !exists { m.logger.Warn("User not authenticated for role check") c.JSON(401, gin.H{"error": "Authentication required"}) c.Abort() return } // 检查用户角色 userRole, err := m.getUserRole(userID.(string)) if err != nil { m.logger.Error("Failed to get user role", err, logging.Fields{ "user_id": userID, }) c.JSON(500, gin.H{"error": "Failed to check user role"}) c.Abort() return } // 检查用户是否有任意一个所需角色 hasRole := false for _, role := range roles { if userRole == role { hasRole = true break } } if !hasRole { m.logger.Warn("Insufficient permissions", logging.Fields{ "user_id": userID, "required_roles": roles, "user_role": userRole, }) c.JSON(403, gin.H{"error": "Insufficient permissions"}) c.Abort() return } m.logger.Debug("Role check passed", logging.Fields{ "user_id": userID, "roles": roles, }) c.Next() } } // 私有方法 // validateToken 验证JWT token func (m *AuthMiddleware) validateToken(tokenString string) (jwt.MapClaims, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // 验证签名方法 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(m.jwtSecret), nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return claims, nil } return nil, fmt.Errorf("invalid token") } // getUserRole 获取用户角色 func (m *AuthMiddleware) getUserRole(userID string) (string, error) { // 这里应该从数据库或缓存中获取用户角色 // 简化实现,返回默认角色 return "user", nil } ================================================ FILE: internal/interfaces/http/auth/register_handler.go ================================================ package auth import ( "context" "fmt" "time" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" playerCommands "greatestworks/internal/application/commands/player" "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" ) // RegisterHandler 注册处理器 type RegisterHandler struct { commandBus *handlers.CommandBus logger logging.Logger } // RegisterRequest 注册请求 type RegisterRequest struct { Username string `json:"username" binding:"required,min=3,max=50"` Password string `json:"password" binding:"required,min=6,max=100"` Email string `json:"email" binding:"required,email"` PlayerName string `json:"player_name" binding:"required,min=2,max=50"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty"` } // RegisterResponse 注册响应 type RegisterResponse struct { UserID string `json:"user_id"` Username string `json:"username"` Email string `json:"email"` PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` CreatedAt time.Time `json:"created_at"` } // NewRegisterHandler 创建注册处理器 func NewRegisterHandler(commandBus *handlers.CommandBus, logger logging.Logger) *RegisterHandler { return &RegisterHandler{ commandBus: commandBus, logger: logger, } } // Register 处理用户注册 func (h *RegisterHandler) Register(c *gin.Context) { var req RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid register request", logging.Fields{ "error": err, }) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 验证用户名是否已存在 if h.isUsernameExists(c.Request.Context(), req.Username) { h.logger.Warn("Username already exists", logging.Fields{ "username": req.Username, }) c.JSON(409, gin.H{"error": "Username already exists"}) return } // 验证邮箱是否已存在 if h.isEmailExists(c.Request.Context(), req.Email) { h.logger.Warn("Email already exists", logging.Fields{ "email": req.Email, }) c.JSON(409, gin.H{"error": "Email already exists"}) return } // 验证玩家名称是否已存在 if h.isPlayerNameExists(c.Request.Context(), req.PlayerName) { h.logger.Warn("Player name already exists", logging.Fields{ "player_name": req.PlayerName, }) c.JSON(409, gin.H{"error": "Player name already exists"}) return } // 加密密码 hashedPassword, err := h.hashPassword(req.Password) if err != nil { h.logger.Error("Failed to hash password", err) c.JSON(500, gin.H{"error": "Failed to process password"}) return } // 创建用户账户 userID, err := h.createUserAccount(c.Request.Context(), req.Username, hashedPassword, req.Email) if err != nil { h.logger.Error("Failed to create user account", err, logging.Fields{ "username": req.Username, }) c.JSON(500, gin.H{"error": "Failed to create user account"}) return } // 创建玩家角色 playerID, err := h.createPlayerCharacter(c.Request.Context(), userID, req.PlayerName, req.Avatar, req.Gender) if err != nil { h.logger.Error("Failed to create player character", err, logging.Fields{ "user_id": userID, }) c.JSON(500, gin.H{"error": "Failed to create player character"}) return } // 返回注册成功响应 response := RegisterResponse{ UserID: userID, Username: req.Username, Email: req.Email, PlayerID: playerID, PlayerName: req.PlayerName, CreatedAt: time.Now(), } h.logger.Info("User registered successfully", logging.Fields{ "user_id": userID, "username": req.Username, "player_id": playerID, }) c.JSON(201, response) } // 私有方法 // isUsernameExists 检查用户名是否已存在 func (h *RegisterHandler) isUsernameExists(ctx context.Context, username string) bool { // 这里应该查询数据库检查用户名是否存在 // 简化实现,实际项目中应该调用相应的服务 return false } // isEmailExists 检查邮箱是否已存在 func (h *RegisterHandler) isEmailExists(ctx context.Context, email string) bool { // 这里应该查询数据库检查邮箱是否存在 // 简化实现,实际项目中应该调用相应的服务 return false } // isPlayerNameExists 检查玩家名称是否已存在 func (h *RegisterHandler) isPlayerNameExists(ctx context.Context, playerName string) bool { // 这里应该查询数据库检查玩家名称是否存在 // 简化实现,实际项目中应该调用相应的服务 return false } // hashPassword 加密密码 func (h *RegisterHandler) hashPassword(password string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hashedBytes), nil } // createUserAccount 创建用户账户 func (h *RegisterHandler) createUserAccount(ctx context.Context, username, hashedPassword, email string) (string, error) { // 这里应该调用用户服务创建用户账户 // 简化实现,返回模拟的用户ID return "user_" + username, nil } // createPlayerCharacter 创建玩家角色 func (h *RegisterHandler) createPlayerCharacter(ctx context.Context, userID, playerName, avatar string, gender int) (string, error) { // 创建玩家命令 cmd := &playerCommands.CreatePlayerCommand{ Name: playerName, Avatar: avatar, Gender: gender, } // 执行命令 result, err := h.commandBus.Execute(ctx, cmd) if err != nil { return "", err } // 获取玩家ID createResult, ok := result.(*playerCommands.CreatePlayerResult) if !ok { return "", fmt.Errorf("unexpected result type") } return createResult.PlayerID, nil } ================================================ FILE: internal/interfaces/http/auth/token_handler.go ================================================ package auth import ( "fmt" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "greatestworks/internal/infrastructure/logging" ) // TokenHandler Token管理处理器 type TokenHandler struct { jwtSecret string logger logging.Logger } // NewTokenHandler 创建Token处理器 func NewTokenHandler(jwtSecret string, logger logging.Logger) *TokenHandler { return &TokenHandler{ jwtSecret: jwtSecret, logger: logger, } } // GenerateToken 生成JWT令牌 func (h *TokenHandler) GenerateToken(userID, username string, expiresIn time.Duration) (string, time.Time, error) { // 计算过期时间 expiresAt := time.Now().Add(expiresIn) // 创建声明 claims := jwt.MapClaims{ "user_id": userID, "username": username, "exp": expiresAt.Unix(), "iat": time.Now().Unix(), "iss": "greatestworks", "aud": "greatestworks-users", } // 创建令牌 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 签名令牌 tokenString, err := token.SignedString([]byte(h.jwtSecret)) if err != nil { h.logger.Error("Failed to sign token", err, logging.Fields{ "user_id": userID, }) return "", time.Time{}, err } h.logger.Debug("Token generated", logging.Fields{ "user_id": userID, "username": username, "expires_at": expiresAt, }) return tokenString, expiresAt, nil } // ValidateToken 验证JWT令牌 func (h *TokenHandler) ValidateToken(tokenString string) (jwt.MapClaims, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // 验证签名方法 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(h.jwtSecret), nil }) if err != nil { h.logger.Warn("Token validation failed", logging.Fields{ "error": err, }) return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { h.logger.Debug("Token validated successfully", logging.Fields{ "user_id": claims["user_id"], }) return claims, nil } return nil, fmt.Errorf("invalid token") } // RefreshToken 刷新令牌 func (h *TokenHandler) RefreshToken(c *gin.Context) { // 获取当前用户信息 userID, exists := c.Get("user_id") if !exists { h.logger.Warn("User not authenticated for token refresh") c.JSON(401, gin.H{"error": "Not authenticated"}) return } username, _ := c.Get("username") // 生成新的令牌 token, expiresAt, err := h.GenerateToken(userID.(string), username.(string), 24*time.Hour) if err != nil { h.logger.Error("Failed to refresh token", err, logging.Fields{ "user_id": userID, }) c.JSON(500, gin.H{"error": "Failed to refresh token"}) return } response := gin.H{ "token": token, "expires_at": expiresAt, "type": "Bearer", } h.logger.Info("Token refreshed", logging.Fields{ "user_id": userID, }) c.JSON(200, response) } // RevokeToken 撤销令牌 func (h *TokenHandler) RevokeToken(c *gin.Context) { // 获取当前用户信息 userID, exists := c.Get("user_id") if !exists { h.logger.Warn("User not authenticated for token revocation") c.JSON(401, gin.H{"error": "Not authenticated"}) return } // 这里应该实现令牌黑名单逻辑 // 简化实现,实际项目中应该将令牌添加到黑名单 h.logger.Info("Token revoked", logging.Fields{ "user_id": userID, }) c.JSON(200, gin.H{"message": "Token revoked successfully"}) } // GetTokenInfo 获取令牌信息 func (h *TokenHandler) GetTokenInfo(c *gin.Context) { // 获取当前用户信息 userID, exists := c.Get("user_id") if !exists { h.logger.Warn("User not authenticated for token info") c.JSON(401, gin.H{"error": "Not authenticated"}) return } username, _ := c.Get("username") expiresAt, _ := c.Get("expires_at") response := gin.H{ "user_id": userID, "username": username, "expires_at": expiresAt, } c.JSON(200, response) } // ValidateTokenMiddleware 验证令牌中间件 func (h *TokenHandler) ValidateTokenMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 从请求头获取token authHeader := c.GetHeader("Authorization") if authHeader == "" { h.logger.Warn("Missing authorization header") c.JSON(401, gin.H{"error": "Missing authorization header"}) c.Abort() return } // 检查Bearer前缀 if !strings.HasPrefix(authHeader, "Bearer ") { h.logger.Warn("Invalid authorization header format") c.JSON(401, gin.H{"error": "Invalid authorization header format"}) c.Abort() return } // 提取token tokenString := strings.TrimPrefix(authHeader, "Bearer ") // 验证token claims, err := h.ValidateToken(tokenString) if err != nil { h.logger.Warn("Token validation failed", logging.Fields{ "error": err, }) c.JSON(401, gin.H{"error": "Invalid token"}) c.Abort() return } // 将用户信息存储到上下文中 if userID, ok := claims["user_id"].(string); ok { c.Set("user_id", userID) } if username, ok := claims["username"].(string); ok { c.Set("username", username) } if exp, ok := claims["exp"].(float64); ok { c.Set("expires_at", time.Unix(int64(exp), 0)) } h.logger.Debug("Token validated", logging.Fields{ "user_id": claims["user_id"], }) c.Next() } } ================================================ FILE: internal/interfaces/http/battle_handler.go ================================================ package http import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // BattleHandler 战斗HTTP处理器 type BattleHandler struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewBattleHandler 创建战斗处理器 func NewBattleHandler(commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger) *BattleHandler { return &BattleHandler{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // CreateBattle 创建战斗 func (h *BattleHandler) CreateBattle(c *gin.Context) { // 实现创建战斗逻辑 h.logger.Info("创建战斗请求") // TODO: 实现具体的创建战斗逻辑 c.JSON(200, gin.H{ "message": "战斗创建成功", "status": "success", }) } // GetBattle 获取战斗信息 func (h *BattleHandler) GetBattle(c *gin.Context) { // 实现获取战斗信息逻辑 h.logger.Info("获取战斗信息请求") // TODO: 实现具体的获取战斗信息逻辑 c.JSON(200, gin.H{ "message": "获取战斗信息成功", "status": "success", }) } // JoinBattle 加入战斗 func (h *BattleHandler) JoinBattle(c *gin.Context) { // 实现加入战斗逻辑 h.logger.Info("加入战斗请求") // TODO: 实现具体的加入战斗逻辑 c.JSON(200, gin.H{ "message": "加入战斗成功", "status": "success", }) } // LeaveBattle 离开战斗 func (h *BattleHandler) LeaveBattle(c *gin.Context) { // 实现离开战斗逻辑 h.logger.Info("离开战斗请求") // TODO: 实现具体的离开战斗逻辑 c.JSON(200, gin.H{ "message": "离开战斗成功", "status": "success", }) } // RegisterRoutes 注册路由 func (h *BattleHandler) RegisterRoutes(router gin.IRouter) { battle := router.Group("/battle") { battle.POST("/create", h.CreateBattle) battle.GET("/:id", h.GetBattle) battle.POST("/:id/join", h.JoinBattle) battle.POST("/:id/leave", h.LeaveBattle) } } ================================================ FILE: internal/interfaces/http/building_handler.go ================================================ package http import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // BuildingHandler 建筑HTTP处理器 type BuildingHandler struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewBuildingHandler 创建建筑处理器 func NewBuildingHandler(commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger) *BuildingHandler { return &BuildingHandler{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // CreateBuilding 创建建筑 func (h *BuildingHandler) CreateBuilding(c *gin.Context) { // 实现创建建筑逻辑 h.logger.Info("创建建筑请求") // TODO: 实现具体的创建建筑逻辑 c.JSON(200, gin.H{ "message": "建筑创建成功", "status": "success", }) } // GetBuilding 获取建筑信息 func (h *BuildingHandler) GetBuilding(c *gin.Context) { // 实现获取建筑信息逻辑 h.logger.Info("获取建筑信息请求") // TODO: 实现具体的获取建筑信息逻辑 c.JSON(200, gin.H{ "message": "获取建筑信息成功", "status": "success", }) } // UpgradeBuilding 升级建筑 func (h *BuildingHandler) UpgradeBuilding(c *gin.Context) { // 实现升级建筑逻辑 h.logger.Info("升级建筑请求") // TODO: 实现具体的升级建筑逻辑 c.JSON(200, gin.H{ "message": "建筑升级成功", "status": "success", }) } // DestroyBuilding 销毁建筑 func (h *BuildingHandler) DestroyBuilding(c *gin.Context) { // 实现销毁建筑逻辑 h.logger.Info("销毁建筑请求") // TODO: 实现具体的销毁建筑逻辑 c.JSON(200, gin.H{ "message": "建筑销毁成功", "status": "success", }) } // RegisterRoutes 注册路由 func (h *BuildingHandler) RegisterRoutes(router gin.IRouter) { building := router.Group("/building") { building.POST("/create", h.CreateBuilding) building.GET("/:id", h.GetBuilding) building.PUT("/:id/upgrade", h.UpgradeBuilding) building.DELETE("/:id", h.DestroyBuilding) } } ================================================ FILE: internal/interfaces/http/gm/player_management.go ================================================ package gm import ( "strconv" "time" "github.com/gin-gonic/gin" playerCmd "greatestworks/internal/application/commands/player" "greatestworks/internal/application/handlers" playerQuery "greatestworks/internal/application/queries/player" "greatestworks/internal/infrastructure/logging" ) // PlayerManagementHandler GM玩家管理处理器 type PlayerManagementHandler struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewPlayerManagementHandler 创建GM玩家管理处理器 func NewPlayerManagementHandler(commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger) *PlayerManagementHandler { return &PlayerManagementHandler{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // CreatePlayer 创建玩家 func (h *PlayerManagementHandler) CreatePlayer(c *gin.Context) { var req CreatePlayerRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid create player request", logging.Fields{ "error": err, }) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 创建命令 cmd := &playerCmd.CreatePlayerCommand{ Name: req.Name, Avatar: req.Avatar, Gender: req.Gender, } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to create player", err, logging.Fields{ "name": req.Name, }) c.JSON(500, gin.H{"error": "Failed to create player"}) return } h.logger.Info("Player created successfully", logging.Fields{ "player_id": result.(*playerCmd.CreatePlayerResult).PlayerID, "name": req.Name, }) c.JSON(200, result) } // GetPlayer 获取玩家信息 func (h *PlayerManagementHandler) GetPlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } // 创建查询 query := &playerQuery.GetPlayerQuery{ PlayerID: playerID, } // 执行查询 result, err := h.queryBus.Execute(c.Request.Context(), query) if err != nil { h.logger.Error("Failed to get player", err, logging.Fields{ "player_id": playerID, }) c.JSON(500, gin.H{"error": "Failed to get player"}) return } c.JSON(200, result) } // ListPlayers 获取玩家列表 func (h *PlayerManagementHandler) ListPlayers(c *gin.Context) { // 解析查询参数 page := c.DefaultQuery("page", "1") limit := c.DefaultQuery("limit", "20") search := c.Query("search") // 创建查询 pageInt, _ := strconv.Atoi(page) limitInt, _ := strconv.Atoi(limit) query := &playerQuery.ListPlayersQuery{ Page: pageInt, PageSize: limitInt, Name: search, } // 执行查询 result, err := h.queryBus.Execute(c.Request.Context(), query) if err != nil { h.logger.Error("Failed to list players", err, logging.Fields{}) c.JSON(500, gin.H{"error": "Failed to list players"}) return } c.JSON(200, result) } // UpdatePlayer 更新玩家信息 func (h *PlayerManagementHandler) UpdatePlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } var req UpdatePlayerRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid update player request", logging.Fields{ "error": err, }) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 创建命令 cmd := &playerCmd.UpdatePlayerCommand{ PlayerID: playerID, Name: req.Name, } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to update player", err, logging.Fields{ "player_id": playerID, }) c.JSON(500, gin.H{"error": "Failed to update player"}) return } h.logger.Info("Player updated successfully", logging.Fields{ "player_id": playerID, }) c.JSON(200, result) } // DeletePlayer 删除玩家 func (h *PlayerManagementHandler) DeletePlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } // 创建命令 cmd := &playerCmd.DeletePlayerCommand{ PlayerID: playerID, } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to delete player", err, logging.Fields{ "player_id": playerID, }) c.JSON(500, gin.H{"error": "Failed to delete player"}) return } h.logger.Info("Player deleted successfully", logging.Fields{ "player_id": playerID, }) c.JSON(200, result) } // BanPlayer 封禁玩家 func (h *PlayerManagementHandler) BanPlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } var req BanPlayerRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid ban player request", logging.Fields{"error": err}) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 创建命令 banType := "temporary" if req.Permanent { banType = "permanent" } cmd := &playerCmd.BanPlayerCommand{ PlayerID: playerID, BannedBy: "GM", BannedByName: "GameMaster", Reason: req.Reason, BanType: banType, BanUntil: time.Now().Add(req.Duration), } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to ban player", err, logging.Fields{"player_id": playerID}) c.JSON(500, gin.H{"error": "Failed to ban player"}) return } h.logger.Info("Player banned successfully", logging.Fields{"player_id": playerID, "reason": req.Reason}) c.JSON(200, result) } // UnbanPlayer 解封玩家 func (h *PlayerManagementHandler) UnbanPlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } // 创建命令 cmd := &playerCmd.UnbanPlayerCommand{ PlayerID: playerID, } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to unban player", err, logging.Fields{"player_id": playerID}) c.JSON(500, gin.H{"error": "Failed to unban player"}) return } h.logger.Info("Player unbanned successfully", logging.Fields{"player_id": playerID}) c.JSON(200, result) } // MovePlayer 移动玩家 func (h *PlayerManagementHandler) MovePlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } var req MovePlayerRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid move player request", logging.Fields{"error": err}) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 创建命令 cmd := &playerCmd.MovePlayerCommand{ PlayerID: playerID, Position: playerCmd.Position{X: req.X, Y: req.Y, Z: req.Z}, } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to move player", err, logging.Fields{"player_id": playerID}) c.JSON(500, gin.H{"error": "Failed to move player"}) return } h.logger.Info("Player moved successfully", logging.Fields{ "player_id": playerID, "position": req, }) c.JSON(200, result) } // LevelUpPlayer 升级玩家 func (h *PlayerManagementHandler) LevelUpPlayer(c *gin.Context) { playerID := c.Param("id") if playerID == "" { c.JSON(400, gin.H{"error": "Player ID is required"}) return } var req LevelUpPlayerRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid level up player request", logging.Fields{ "error": err, }) c.JSON(400, gin.H{"error": "Invalid request format"}) return } // 创建命令 cmd := &playerCmd.LevelUpPlayerCommand{ PlayerID: playerID, ExpGain: int64(req.Levels * 1000), // 假设每级需要1000经验 } // 执行命令 result, err := h.commandBus.Execute(c.Request.Context(), cmd) if err != nil { h.logger.Error("Failed to level up player", err, logging.Fields{ "player_id": playerID, }) c.JSON(500, gin.H{"error": "Failed to level up player"}) return } h.logger.Info("Player leveled up successfully", logging.Fields{ "player_id": playerID, "levels": req.Levels, }) c.JSON(200, result) } // 请求和响应结构体 // CreatePlayerRequest 创建玩家请求 type CreatePlayerRequest struct { Name string `json:"name" binding:"required"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty"` } // UpdatePlayerRequest 更新玩家请求 type UpdatePlayerRequest struct { Name string `json:"name,omitempty"` Level int `json:"level,omitempty"` Exp int64 `json:"exp,omitempty"` } // BanPlayerRequest 封禁玩家请求 type BanPlayerRequest struct { Reason string `json:"reason" binding:"required"` Duration time.Duration `json:"duration,omitempty"` Permanent bool `json:"permanent,omitempty"` } // MovePlayerRequest 移动玩家请求 type MovePlayerRequest struct { X float64 `json:"x" binding:"required"` Y float64 `json:"y" binding:"required"` Z float64 `json:"z" binding:"required"` } // LevelUpPlayerRequest 升级玩家请求 type LevelUpPlayerRequest struct { Levels int `json:"levels" binding:"required,min=1"` } ================================================ FILE: internal/interfaces/http/gm/server_monitor.go ================================================ package gm import ( "runtime" "strconv" "time" "github.com/gin-gonic/gin" "greatestworks/internal/application/handlers" // "greatestworks/internal/application/queries" // TODO: 实现查询系统 "greatestworks/internal/infrastructure/logging" ) // ServerMonitorHandler GM服务器监控处理器 type ServerMonitorHandler struct { queryBus *handlers.QueryBus logger logging.Logger } // NewServerMonitorHandler 创建GM服务器监控处理器 func NewServerMonitorHandler(queryBus *handlers.QueryBus, logger logging.Logger) *ServerMonitorHandler { return &ServerMonitorHandler{ queryBus: queryBus, logger: logger, } } // ServerStatusResponse 服务器状态响�? type ServerStatusResponse struct { ServerInfo ServerInfo `json:"server_info"` SystemInfo SystemInfo `json:"system_info"` PlayerStats PlayerStats `json:"player_stats"` Performance Performance `json:"performance"` Connections Connections `json:"connections"` GameStats GameStats `json:"game_stats"` Timestamp time.Time `json:"timestamp"` } // ServerInfo 服务器信�? type ServerInfo struct { Name string `json:"name"` Version string `json:"version"` Environment string `json:"environment"` StartTime time.Time `json:"start_time"` Uptime string `json:"uptime"` Region string `json:"region"` NodeID string `json:"node_id"` } // SystemInfo 系统信息 type SystemInfo struct { OS string `json:"os"` Arch string `json:"arch"` GoVersion string `json:"go_version"` CPUCores int `json:"cpu_cores"` MemoryTotal uint64 `json:"memory_total"` MemoryUsed uint64 `json:"memory_used"` MemoryUsage float64 `json:"memory_usage"` DiskTotal uint64 `json:"disk_total"` DiskUsed uint64 `json:"disk_used"` DiskUsage float64 `json:"disk_usage"` } // PlayerStats 玩家统计 type PlayerStats struct { OnlineCount int `json:"online_count"` TotalCount int `json:"total_count"` NewToday int `json:"new_today"` ActiveToday int `json:"active_today"` PeakOnline int `json:"peak_online"` PeakOnlineTime time.Time `json:"peak_online_time"` } // Performance 性能指标 type Performance struct { CPUUsage float64 `json:"cpu_usage"` MemoryUsage float64 `json:"memory_usage"` Goroutines int `json:"goroutines"` GCPauseAvg float64 `json:"gc_pause_avg"` GCPauseMax float64 `json:"gc_pause_max"` RequestsPerSec float64 `json:"requests_per_sec"` ResponseTime float64 `json:"response_time"` ErrorRate float64 `json:"error_rate"` } // Connections 连接统计 type Connections struct { HTTPConnections int `json:"http_connections"` TCPConnections int `json:"tcp_connections"` WebSocketConns int `json:"websocket_connections"` TotalConnections int `json:"total_connections"` MaxConnections int `json:"max_connections"` } // GameStats 游戏统计 type GameStats struct { ActiveBattles int `json:"active_battles"` ActiveRooms int `json:"active_rooms"` MessagesPerSec int `json:"messages_per_sec"` EventsProcessed int `json:"events_processed"` QueuedEvents int `json:"queued_events"` DatabaseQueries int `json:"database_queries"` CacheHitRate float64 `json:"cache_hit_rate"` } // MetricsHistoryRequest 指标历史请求 type MetricsHistoryRequest struct { Metric string `form:"metric" binding:"required,oneof=cpu memory connections players"` TimeRange string `form:"time_range" binding:"required,oneof=1h 6h 24h 7d 30d"` Interval string `form:"interval,omitempty"` } // MetricsHistoryResponse 指标历史响应 type MetricsHistoryResponse struct { Metric string `json:"metric"` TimeRange string `json:"time_range"` Interval string `json:"interval"` DataPoints []MetricDataPoint `json:"data_points"` } // MetricDataPoint 指标数据�? type MetricDataPoint struct { Timestamp time.Time `json:"timestamp"` Value float64 `json:"value"` Label string `json:"label,omitempty"` } // AlertsResponse 告警响应 type AlertsResponse struct { ActiveAlerts []Alert `json:"active_alerts"` RecentAlerts []Alert `json:"recent_alerts"` AlertSummary AlertSummary `json:"alert_summary"` } // Alert 告警信息 type Alert struct { ID string `json:"id"` Level string `json:"level"` Type string `json:"type"` Message string `json:"message"` Source string `json:"source"` TriggeredAt time.Time `json:"triggered_at"` ResolvedAt *time.Time `json:"resolved_at,omitempty"` Status string `json:"status"` } // AlertSummary 告警摘要 type AlertSummary struct { Critical int `json:"critical"` Warning int `json:"warning"` Info int `json:"info"` Total int `json:"total"` } // GetServerStatus 获取服务器状�? func (h *ServerMonitorHandler) GetServerStatus(c *gin.Context) { // ctx := context.Background() // 查询服务器状�? // TODO: 修复system包引�? // query := &system.GetServerStatusQuery{} // result, err := handlers.ExecuteQueryTyped[*system.GetServerStatusQuery, *system.GetServerStatusResult](ctx, h.queryBus, query) // result := &struct{}{} // TODO: 修复system.GetServerStatusResult类型 // if err != nil { // h.logger.Error("Failed to get server status", "error", err) // c.JSON(500, gin.H{"error": "Failed to get server status", "success": false}) // return // } // 获取系统运行时信�? var memStats runtime.MemStats runtime.ReadMemStats(&memStats) // 构造响�? response := &ServerStatusResponse{ ServerInfo: ServerInfo{ Name: "", // TODO: result.ServerName, Version: "", // TODO: result.Version, Environment: "", // TODO: result.Environment, StartTime: time.Now(), // TODO: result.StartTime, Uptime: "0s", // TODO: time.Since(result.StartTime).String(), Region: "", // TODO: result.Region, NodeID: "", // TODO: result.NodeID, }, SystemInfo: SystemInfo{ OS: runtime.GOOS, Arch: runtime.GOARCH, GoVersion: runtime.Version(), CPUCores: runtime.NumCPU(), MemoryTotal: 0, // TODO: result.SystemInfo.MemoryTotal, MemoryUsed: memStats.Alloc, MemoryUsage: 0, // TODO: float64(memStats.Alloc) / float64(result.SystemInfo.MemoryTotal) * 100, DiskTotal: 0, // TODO: result.SystemInfo.DiskTotal, DiskUsed: 0, // TODO: result.SystemInfo.DiskUsed, DiskUsage: 0, // TODO: float64(result.SystemInfo.DiskUsed) / float64(result.SystemInfo.DiskTotal) * 100, }, PlayerStats: PlayerStats{ OnlineCount: 0, // TODO: result.PlayerStats.OnlineCount, TotalCount: 0, // TODO: result.PlayerStats.TotalCount, NewToday: 0, // TODO: result.PlayerStats.NewToday, ActiveToday: 0, // TODO: result.PlayerStats.ActiveToday, PeakOnline: 0, // TODO: result.PlayerStats.PeakOnline, PeakOnlineTime: time.Now(), // TODO: result.PlayerStats.PeakOnlineTime, }, Performance: Performance{ CPUUsage: 0, // TODO: result.Performance.CPUUsage, MemoryUsage: 0, // TODO: float64(memStats.Alloc) / float64(result.SystemInfo.MemoryTotal) * 100, Goroutines: runtime.NumGoroutine(), GCPauseAvg: 0, // TODO: result.Performance.GCPauseAvg, GCPauseMax: 0, // TODO: result.Performance.GCPauseMax, RequestsPerSec: 0, // TODO: result.Performance.RequestsPerSec, ResponseTime: 0, // TODO: result.Performance.ResponseTime, ErrorRate: 0, // TODO: result.Performance.ErrorRate, }, Connections: Connections{ HTTPConnections: 0, // TODO: result.Connections.HTTPConnections, TCPConnections: 0, // TODO: result.Connections.TCPConnections, WebSocketConns: 0, // TODO: result.Connections.WebSocketConns, TotalConnections: 0, // TODO: result.Connections.TotalConnections, MaxConnections: 0, // TODO: result.Connections.MaxConnections, }, GameStats: GameStats{ ActiveBattles: 0, // TODO: result.GameStats.ActiveBattles, ActiveRooms: 0, // TODO: result.GameStats.ActiveRooms, MessagesPerSec: 0, // TODO: result.GameStats.MessagesPerSec, EventsProcessed: 0, // TODO: result.GameStats.EventsProcessed, QueuedEvents: 0, // TODO: result.GameStats.QueuedEvents, DatabaseQueries: 0, // TODO: result.GameStats.DatabaseQueries, CacheHitRate: 0, // TODO: result.GameStats.CacheHitRate, }, Timestamp: time.Now(), } // 记录GM操作日志 // gmUser, _ := auth.GetCurrentUser(c) h.logger.Debug("GM viewed server status", logging.Fields{ "gm_user": "admin", // 临时硬编码 }) c.JSON(200, gin.H{"data": response, "success": true}) } // GetMetricsHistory 获取指标历史数据 func (h *ServerMonitorHandler) GetMetricsHistory(c *gin.Context) { var req MetricsHistoryRequest if err := c.ShouldBindQuery(&req); err != nil { h.logger.Error("Invalid metrics history request", err, logging.Fields{}) c.JSON(400, gin.H{"error": "Invalid request parameters", "success": false}) return } // 设置默认间隔 if req.Interval == "" { switch req.TimeRange { case "1h": req.Interval = "1m" case "6h": req.Interval = "5m" case "24h": req.Interval = "15m" case "7d": req.Interval = "1h" case "30d": req.Interval = "6h" } } // ctx := context.Background() // 查询指标历史数据 // TODO: 修复system包引�? // query := &system.GetMetricsHistoryQuery{ // Metric: req.Metric, // TimeRange: req.TimeRange, // Interval: req.Interval, // } // result, err := handlers.ExecuteQueryTyped[*system.GetMetricsHistoryQuery, *system.GetMetricsHistoryResult](ctx, h.queryBus, query) // result := &struct{}{} // TODO: 修复system.GetMetricsHistoryResult类型 // if err != nil { // h.logger.Error("Failed to get metrics history", "error", err, "metric", req.Metric) // c.JSON(500, gin.H{"error": "Failed to get metrics history", "success": false}) // return // } // 构造响�? // TODO: 修复result.DataPoints // dataPoints := make([]MetricDataPoint, len(result.DataPoints)) // for i, dp := range result.DataPoints { // dataPoints[i] = MetricDataPoint{ // Timestamp: dp.Timestamp, // Value: dp.Value, // Label: dp.Label, // } // } dataPoints := []MetricDataPoint{} // TODO: 修复result.DataPoints response := &MetricsHistoryResponse{ Metric: req.Metric, TimeRange: req.TimeRange, Interval: req.Interval, DataPoints: dataPoints, } // 记录GM操作日志 // gmUser, _ := auth.GetCurrentUser(c) h.logger.Debug("GM viewed metrics history", logging.Fields{ "gm_user": "admin", // 临时硬编码 "metric": req.Metric, "time_range": req.TimeRange, }) c.JSON(200, gin.H{"data": response, "success": true}) } // GetAlerts 获取告警信息 func (h *ServerMonitorHandler) GetAlerts(c *gin.Context) { // ctx := context.Background() // 查询告警信息 // TODO: 修复system包引�? // query := &system.GetAlertsQuery{} // result, err := handlers.ExecuteQueryTyped[*system.GetAlertsQuery, *system.GetAlertsResult](ctx, h.queryBus, query) // result := &struct{}{} // TODO: 修复system.GetAlertsResult类型 // if err != nil { // h.logger.Error("Failed to get alerts", "error", err) // c.JSON(500, gin.H{"error": "Failed to get alerts", "success": false}) // return // } // 构造响�? // TODO: 修复result.ActiveAlerts // activeAlerts := make([]Alert, len(result.ActiveAlerts)) // for i, alert := range result.ActiveAlerts { activeAlerts := []Alert{} // TODO: 修复result.ActiveAlerts // for i, alert := range result.ActiveAlerts { // activeAlerts[i] = Alert{ // ID: alert.ID, // Level: alert.Level, // Type: alert.Type, // Message: alert.Message, // Source: alert.Source, // TriggeredAt: alert.TriggeredAt, // ResolvedAt: alert.ResolvedAt, // Status: alert.Status, // } // } // TODO: 修复result.RecentAlerts // recentAlerts := make([]Alert, len(result.RecentAlerts)) // for i, alert := range result.RecentAlerts { recentAlerts := []Alert{} // TODO: 修复result.RecentAlerts // for i, alert := range result.RecentAlerts { // recentAlerts[i] = Alert{ // ID: alert.ID, // Level: alert.Level, // Type: alert.Type, // Message: alert.Message, // Source: alert.Source, // TriggeredAt: alert.TriggeredAt, // ResolvedAt: alert.ResolvedAt, // Status: alert.Status, // } // } response := &AlertsResponse{ ActiveAlerts: activeAlerts, RecentAlerts: recentAlerts, AlertSummary: AlertSummary{ Critical: 0, // TODO: result.AlertSummary.Critical, Warning: 0, // TODO: result.AlertSummary.Warning, Info: 0, // TODO: result.AlertSummary.Info, Total: 0, // TODO: result.AlertSummary.Total, }, } // 记录GM操作日志 // gmUser, _ := auth.GetCurrentUser(c) h.logger.Debug("GM viewed alerts", logging.Fields{ "gm_user": "admin", // 临时硬编码 }) c.JSON(200, gin.H{"data": response, "success": true}) } // GetOnlinePlayers 获取在线玩家列表 func (h *ServerMonitorHandler) GetOnlinePlayers(c *gin.Context) { page := 1 pageSize := 50 if p := c.Query("page"); p != "" { if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 { page = parsed } } if ps := c.Query("page_size"); ps != "" { if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 100 { pageSize = parsed } } // ctx := context.Background() // 查询在线玩家 // TODO: 修复system包引�? // query := &system.GetOnlinePlayersQuery{ // Page: page, // PageSize: pageSize, // } // result, err := handlers.ExecuteQueryTyped[*system.GetOnlinePlayersQuery, *system.GetOnlinePlayersResult](ctx, h.queryBus, query) // if err != nil { // h.logger.Error("Failed to get online players", "error", err) // c.JSON(500, gin.H{"error": "Failed to get online players", "success": false}) // return // } // 构造响�? // TODO: 修复result.Players // players := make([]map[string]interface{}, len(result.Players)) // for i, player := range result.Players { players := []map[string]interface{}{} // TODO: 修复result.Players // for i, player := range result.Players { // players[i] = map[string]interface{}{ // "id": player.ID, // "username": player.Username, // "name": player.Name, // "level": player.Level, // "status": player.Status, // "login_time": player.LoginTime, // "online_duration": time.Since(player.LoginTime).String(), // "ip_address": player.IPAddress, // "location": player.Location, // } // } response := map[string]interface{}{ "players": players, "pagination": map[string]interface{}{ "page": page, "page_size": pageSize, "total": 0, // TODO: result.Total, "total_pages": 0, // TODO: (result.Total + int64(pageSize) - 1) / int64(pageSize), }, "summary": map[string]interface{}{ "total_online": 0, // TODO: result.Total, "avg_level": 0, // TODO: result.AvgLevel, "new_players": 0, // TODO: result.NewPlayersToday, }, } // 记录GM操作日志 // gmUser, _ := auth.GetCurrentUser(c) h.logger.Debug("GM viewed online players", logging.Fields{ "gm_user": "admin", // 临时硬编码 "page": page, "page_size": pageSize, }) c.JSON(200, gin.H{"data": response, "success": true}) } // RestartServer 重启服务器(仅超级管理员�? func (h *ServerMonitorHandler) RestartServer(c *gin.Context) { type RestartRequest struct { Reason string `json:"reason" binding:"required"` DelayMinutes int `json:"delay_minutes,omitempty"` NotifyUsers bool `json:"notify_users"` } var req RestartRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Error("Invalid restart server request", err, logging.Fields{}) c.JSON(400, gin.H{"error": "Invalid request format", "success": false}) return } // 获取GM用户信息 // gmUser, _ := auth.GetCurrentUser(c) // 记录重启操作日志 h.logger.Warn("Server restart initiated by GM", logging.Fields{ "gm_user": "admin", // 临时硬编码 "reason": req.Reason, "delay_minutes": req.DelayMinutes, }) // TODO: 实现服务器重启逻辑 // 1. 通知所有在线玩�? // 2. 等待延迟时间 // 3. 优雅关闭服务�? // 4. 重启服务�? response := map[string]interface{}{ "message": "Server restart scheduled", "delay_minutes": req.DelayMinutes, "restart_time": time.Now().Add(time.Duration(req.DelayMinutes) * time.Minute), "initiated_by": "admin", // 临时硬编码 } c.JSON(200, gin.H{"data": response, "success": true, "message": "Server restart scheduled successfully"}) } ================================================ FILE: internal/interfaces/http/health_handler.go ================================================ package http import ( "net/http" "time" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // HealthHandler 健康检查处理器 type HealthHandler struct { logger logging.Logger } // NewHealthHandler 创建健康检查处理器 func NewHealthHandler(logger logging.Logger) *HealthHandler { return &HealthHandler{ logger: logger, } } // HealthCheck 健康检查 func (h *HealthHandler) HealthCheck(c *gin.Context) { h.logger.Info("健康检查请求") // TODO: 实现具体的健康检查逻辑 // 1. 检查数据库连接 // 2. 检查缓存连接 // 3. 检查其他依赖服务 c.JSON(http.StatusOK, gin.H{ "status": "ok", "message": "服务运行正常", "timestamp": time.Now().Format(time.RFC3339), "version": "1.0.0", }) } // ReadinessCheck 就绪检查 func (h *HealthHandler) ReadinessCheck(c *gin.Context) { h.logger.Info("就绪检查请求") // TODO: 实现具体的就绪检查逻辑 // 1. 检查所有依赖服务是否就绪 // 2. 检查数据库连接是否正常 // 3. 检查缓存连接是否正常 c.JSON(http.StatusOK, gin.H{ "status": "ready", "message": "服务已就绪", "timestamp": time.Now().Format(time.RFC3339), }) } // LivenessCheck 存活检查 func (h *HealthHandler) LivenessCheck(c *gin.Context) { h.logger.Info("存活检查请求") // TODO: 实现具体的存活检查逻辑 // 1. 检查服务是否还在运行 // 2. 检查内存使用情况 // 3. 检查CPU使用情况 c.JSON(http.StatusOK, gin.H{ "status": "alive", "message": "服务存活", "timestamp": time.Now().Format(time.RFC3339), }) } // RegisterRoutes 注册路由 func (h *HealthHandler) RegisterRoutes(router *gin.Engine) { health := router.Group("/health") { health.GET("/", h.HealthCheck) health.GET("/ready", h.ReadinessCheck) health.GET("/live", h.LivenessCheck) } } ================================================ FILE: internal/interfaces/http/pet_handler.go ================================================ package http import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // PetHandler 宠物HTTP处理器 type PetHandler struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewPetHandler 创建宠物处理器 func NewPetHandler(commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger) *PetHandler { return &PetHandler{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // CreatePet 创建宠物 func (h *PetHandler) CreatePet(c *gin.Context) { // 实现创建宠物逻辑 h.logger.Info("创建宠物请求") // TODO: 实现具体的创建宠物逻辑 c.JSON(200, gin.H{ "message": "宠物创建成功", "status": "success", }) } // GetPet 获取宠物信息 func (h *PetHandler) GetPet(c *gin.Context) { // 实现获取宠物信息逻辑 h.logger.Info("获取宠物信息请求") // TODO: 实现具体的获取宠物信息逻辑 c.JSON(200, gin.H{ "message": "获取宠物信息成功", "status": "success", }) } // FeedPet 喂养宠物 func (h *PetHandler) FeedPet(c *gin.Context) { // 实现喂养宠物逻辑 h.logger.Info("喂养宠物请求") // TODO: 实现具体的喂养宠物逻辑 c.JSON(200, gin.H{ "message": "宠物喂养成功", "status": "success", }) } // TrainPet 训练宠物 func (h *PetHandler) TrainPet(c *gin.Context) { // 实现训练宠物逻辑 h.logger.Info("训练宠物请求") // TODO: 实现具体的训练宠物逻辑 c.JSON(200, gin.H{ "message": "宠物训练成功", "status": "success", }) } // ReleasePet 释放宠物 func (h *PetHandler) ReleasePet(c *gin.Context) { // 实现释放宠物逻辑 h.logger.Info("释放宠物请求") // TODO: 实现具体的释放宠物逻辑 c.JSON(200, gin.H{ "message": "宠物释放成功", "status": "success", }) } // RegisterRoutes 注册路由 func (h *PetHandler) RegisterRoutes(router gin.IRouter) { pet := router.Group("/pet") { pet.POST("/create", h.CreatePet) pet.GET("/:id", h.GetPet) pet.POST("/:id/feed", h.FeedPet) pet.POST("/:id/train", h.TrainPet) pet.DELETE("/:id", h.ReleasePet) } } ================================================ FILE: internal/interfaces/http/player_handler.go ================================================ package http import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" "github.com/gin-gonic/gin" ) // PlayerHandler 玩家HTTP处理器 type PlayerHandler struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewPlayerHandler 创建玩家处理器 func NewPlayerHandler(commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger) *PlayerHandler { return &PlayerHandler{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // GetPlayer 获取玩家信息 func (h *PlayerHandler) GetPlayer(c *gin.Context) { // 实现获取玩家信息逻辑 h.logger.Info("获取玩家信息请求") // TODO: 实现具体的获取玩家信息逻辑 c.JSON(200, gin.H{ "message": "获取玩家信息成功", "status": "success", }) } // UpdatePlayer 更新玩家信息 func (h *PlayerHandler) UpdatePlayer(c *gin.Context) { // 实现更新玩家信息逻辑 h.logger.Info("更新玩家信息请求") // TODO: 实现具体的更新玩家信息逻辑 c.JSON(200, gin.H{ "message": "玩家信息更新成功", "status": "success", }) } // GetPlayerStats 获取玩家统计 func (h *PlayerHandler) GetPlayerStats(c *gin.Context) { // 实现获取玩家统计逻辑 h.logger.Info("获取玩家统计请求") // TODO: 实现具体的获取玩家统计逻辑 c.JSON(200, gin.H{ "message": "获取玩家统计成功", "status": "success", }) } // LevelUpPlayer 玩家升级 func (h *PlayerHandler) LevelUpPlayer(c *gin.Context) { // 实现玩家升级逻辑 h.logger.Info("玩家升级请求") // TODO: 实现具体的玩家升级逻辑 c.JSON(200, gin.H{ "message": "玩家升级成功", "status": "success", }) } // MovePlayer 移动玩家 func (h *PlayerHandler) MovePlayer(c *gin.Context) { // 实现移动玩家逻辑 h.logger.Info("移动玩家请求") // TODO: 实现具体的移动玩家逻辑 c.JSON(200, gin.H{ "message": "玩家移动成功", "status": "success", }) } // RegisterRoutes 注册路由 func (h *PlayerHandler) RegisterRoutes(router gin.IRouter) { player := router.Group("/player") { player.GET("/:id", h.GetPlayer) player.PUT("/:id", h.UpdatePlayer) player.GET("/:id/stats", h.GetPlayerStats) player.POST("/:id/levelup", h.LevelUpPlayer) player.POST("/:id/move", h.MovePlayer) } } ================================================ FILE: internal/interfaces/http/replication_handlers.go ================================================ package http import ( "encoding/json" "net/http" "strconv" "time" "greatestworks/internal/application/services" "greatestworks/internal/infrastructure/logging" ) // ReplicationHTTPHandlers 提供副本相关的HTTP处理器 type ReplicationHTTPHandlers struct { app *services.ReplicationService logger logging.Logger } func NewReplicationHTTPHandlers(app *services.ReplicationService, logger logging.Logger) *ReplicationHTTPHandlers { return &ReplicationHTTPHandlers{app: app, logger: logger} } // RegisterReplicationRoutes 在给定服务器上注册副本相关路由 func RegisterReplicationRoutes(s *Server, h *ReplicationHTTPHandlers) { s.Handle("POST", "/instances/create", h.CreateInstance) s.Handle("POST", "/instances/join", h.JoinInstance) s.Handle("POST", "/instances/leave", h.LeaveInstance) s.Handle("GET", "/instances/info", h.GetInstanceInfo) s.Handle("GET", "/instances/active", h.ListActiveInstances) s.Handle("POST", "/instances/cleanup", h.CleanupExpiredInstances) } // CreateInstance 创建实例 func (h *ReplicationHTTPHandlers) CreateInstance(w http.ResponseWriter, r *http.Request) { var req struct { TemplateID string `json:"template_id"` InstanceType int `json:"instance_type"` OwnerPlayerID string `json:"owner_player_id"` OwnerName string `json:"owner_name"` OwnerLevel int `json:"owner_level"` MaxPlayers int `json:"max_players"` Difficulty int `json:"difficulty"` LifetimeSec int64 `json:"lifetime_sec"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } dto, err := h.app.CreateInstance(r.Context(), &services.CreateInstanceCommand{ TemplateID: req.TemplateID, InstanceType: req.InstanceType, OwnerPlayerID: req.OwnerPlayerID, OwnerName: req.OwnerName, OwnerLevel: req.OwnerLevel, MaxPlayers: req.MaxPlayers, Difficulty: req.Difficulty, Lifetime: time.Duration(req.LifetimeSec) * time.Second, }) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } writeJSON(w, dto) } // JoinInstance 加入实例 func (h *ReplicationHTTPHandlers) JoinInstance(w http.ResponseWriter, r *http.Request) { var req struct { InstanceID string `json:"instance_id"` PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` Level int `json:"level"` Role string `json:"role"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } if err := h.app.JoinInstance(r.Context(), &services.JoinInstanceCommand{ InstanceID: req.InstanceID, PlayerID: req.PlayerID, PlayerName: req.PlayerName, Level: req.Level, Role: req.Role, }); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } writeJSON(w, map[string]string{"status": "ok"}) } // LeaveInstance 离开实例 func (h *ReplicationHTTPHandlers) LeaveInstance(w http.ResponseWriter, r *http.Request) { var req struct { InstanceID string `json:"instance_id"` PlayerID string `json:"player_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } if err := h.app.LeaveInstance(r.Context(), &services.LeaveInstanceCommand{ InstanceID: req.InstanceID, PlayerID: req.PlayerID, }); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } writeJSON(w, map[string]string{"status": "ok"}) } // GetInstanceInfo 获取实例信息(通过查询参数传入instance_id) func (h *ReplicationHTTPHandlers) GetInstanceInfo(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("instance_id") if id == "" { http.Error(w, "missing instance_id", http.StatusBadRequest) return } dto, err := h.app.GetInstanceInfo(r.Context(), id) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } writeJSON(w, dto) } // ListActiveInstances 列出活跃实例 func (h *ReplicationHTTPHandlers) ListActiveInstances(w http.ResponseWriter, r *http.Request) { dtos, err := h.app.ListActiveInstances(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, dtos) } // CleanupExpiredInstances 清理过期实例 func (h *ReplicationHTTPHandlers) CleanupExpiredInstances(w http.ResponseWriter, r *http.Request) { // 可选:从查询参数读取limit等 _ = r.URL.Query().Get("limit") _, _ = strconv.Atoi(r.URL.Query().Get("limit")) count, err := h.app.CleanupExpiredInstances(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]int{"count": count}) } func writeJSON(w http.ResponseWriter, v interface{}) { w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) _ = enc.Encode(v) } ================================================ FILE: internal/interfaces/http/response.go ================================================ package http import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" ) // APIResponse 统一API响应结构 type APIResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` Data interface{} `json:"data,omitempty"` Error *APIError `json:"error,omitempty"` Meta *Meta `json:"meta,omitempty"` Timestamp time.Time `json:"timestamp"` RequestID string `json:"request_id,omitempty"` } // APIError API错误信息 type APIError struct { Code string `json:"code"` Message string `json:"message"` Details interface{} `json:"details,omitempty"` Field string `json:"field,omitempty"` } // Meta 元数据信息 type Meta struct { Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` Total int64 `json:"total,omitempty"` TotalPages int `json:"total_pages,omitempty"` } // PaginationRequest 分页请求 type PaginationRequest struct { Page int `form:"page" json:"page" binding:"min=1"` PageSize int `form:"page_size" json:"page_size" binding:"min=1,max=100"` } // GetPagination 获取分页参数 func (p *PaginationRequest) GetPagination() (int, int) { if p.Page <= 0 { p.Page = 1 } if p.PageSize <= 0 { p.PageSize = 20 } if p.PageSize > 100 { p.PageSize = 100 } return p.Page, p.PageSize } // GetOffset 获取偏移量 func (p *PaginationRequest) GetOffset() int { page, pageSize := p.GetPagination() return (page - 1) * pageSize } // GetLimit 获取限制数量 func (p *PaginationRequest) GetLimit() int { _, pageSize := p.GetPagination() return pageSize } // 响应构建器 // SuccessResponse 成功响应 func SuccessResponse(c *gin.Context, data interface{}, message ...string) { msg := "Success" if len(message) > 0 && message[0] != "" { msg = message[0] } response := APIResponse{ Success: true, Message: msg, Data: data, Timestamp: time.Now(), RequestID: getRequestID(c), } c.JSON(http.StatusOK, response) } // SuccessResponseWithMeta 带元数据的成功响应 func SuccessResponseWithMeta(c *gin.Context, data interface{}, meta *Meta, message ...string) { msg := "Success" if len(message) > 0 && message[0] != "" { msg = message[0] } response := APIResponse{ Success: true, Message: msg, Data: data, Meta: meta, Timestamp: time.Now(), RequestID: getRequestID(c), } c.JSON(http.StatusOK, response) } // CreatedResponse 创建成功响应 func CreatedResponse(c *gin.Context, data interface{}, message ...string) { msg := "Created successfully" if len(message) > 0 && message[0] != "" { msg = message[0] } response := APIResponse{ Success: true, Message: msg, Data: data, Timestamp: time.Now(), RequestID: getRequestID(c), } c.JSON(http.StatusCreated, response) } // NoContentResponse 无内容响应 func NoContentResponse(c *gin.Context, message ...string) { msg := "No content" if len(message) > 0 && message[0] != "" { msg = message[0] } response := APIResponse{ Success: true, Message: msg, Timestamp: time.Now(), RequestID: getRequestID(c), } c.JSON(http.StatusNoContent, response) } // ErrorResponse 错误响应 func ErrorResponse(c *gin.Context, statusCode int, code, message string, details ...interface{}) { apiError := &APIError{ Code: code, Message: message, } if len(details) > 0 { apiError.Details = details[0] } response := APIResponse{ Success: false, Error: apiError, Timestamp: time.Now(), RequestID: getRequestID(c), } c.JSON(statusCode, response) } // BadRequestResponse 400错误响应 func BadRequestResponse(c *gin.Context, message string, details ...interface{}) { ErrorResponse(c, http.StatusBadRequest, "BAD_REQUEST", message, details...) } // UnauthorizedResponse 401错误响应 func UnauthorizedResponse(c *gin.Context, message ...string) { msg := "Unauthorized" if len(message) > 0 && message[0] != "" { msg = message[0] } ErrorResponse(c, http.StatusUnauthorized, "UNAUTHORIZED", msg) } // ForbiddenResponse 403错误响应 func ForbiddenResponse(c *gin.Context, message ...string) { msg := "Forbidden" if len(message) > 0 && message[0] != "" { msg = message[0] } ErrorResponse(c, http.StatusForbidden, "FORBIDDEN", msg) } // NotFoundResponse 404错误响应 func NotFoundResponse(c *gin.Context, message ...string) { msg := "Resource not found" if len(message) > 0 && message[0] != "" { msg = message[0] } ErrorResponse(c, http.StatusNotFound, "NOT_FOUND", msg) } // ConflictResponse 409错误响应 func ConflictResponse(c *gin.Context, message string, details ...interface{}) { ErrorResponse(c, http.StatusConflict, "CONFLICT", message, details...) } // ValidationErrorResponse 422验证错误响应 func ValidationErrorResponse(c *gin.Context, field, message string, details ...interface{}) { apiError := &APIError{ Code: "VALIDATION_ERROR", Message: message, Field: field, } if len(details) > 0 { apiError.Details = details[0] } response := APIResponse{ Success: false, Error: apiError, Timestamp: time.Now(), RequestID: getRequestID(c), } c.JSON(http.StatusUnprocessableEntity, response) } // InternalServerErrorResponse 500错误响应 func InternalServerErrorResponse(c *gin.Context, message ...string) { msg := "Internal server error" if len(message) > 0 && message[0] != "" { msg = message[0] } ErrorResponse(c, http.StatusInternalServerError, "INTERNAL_SERVER_ERROR", msg) } // ServiceUnavailableResponse 503错误响应 func ServiceUnavailableResponse(c *gin.Context, message ...string) { msg := "Service unavailable" if len(message) > 0 && message[0] != "" { msg = message[0] } ErrorResponse(c, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", msg) } // 辅助函数 // getRequestID 获取请求ID func getRequestID(c *gin.Context) string { return c.GetHeader("X-Request-ID") } // BindAndValidate 绑定并验证请求数据 func BindAndValidate(c *gin.Context, obj interface{}) bool { if err := c.ShouldBindJSON(obj); err != nil { BadRequestResponse(c, "Invalid request data", err.Error()) return false } return true } // BindQueryAndValidate 绑定并验证查询参数 func BindQueryAndValidate(c *gin.Context, obj interface{}) bool { if err := c.ShouldBindQuery(obj); err != nil { BadRequestResponse(c, "Invalid query parameters", err.Error()) return false } return true } // BindURIAndValidate 绑定并验证URI参数 func BindURIAndValidate(c *gin.Context, obj interface{}) bool { if err := c.ShouldBindUri(obj); err != nil { BadRequestResponse(c, "Invalid URI parameters", err.Error()) return false } return true } // GetIDParam 获取ID参数 func GetIDParam(c *gin.Context) string { return c.Param("id") } // GetPlayerIDParam 获取玩家ID参数 func GetPlayerIDParam(c *gin.Context) string { return c.Param("player_id") } // ValidateID 验证ID参数 func ValidateID(c *gin.Context, paramName string) (string, bool) { id := c.Param(paramName) if id == "" { BadRequestResponse(c, fmt.Sprintf("%s is required", paramName)) return "", false } return id, true } // CreateMeta 创建元数据 func CreateMeta(page, pageSize int, total int64) *Meta { totalPages := int((total + int64(pageSize) - 1) / int64(pageSize)) return &Meta{ Page: page, PageSize: pageSize, Total: total, TotalPages: totalPages, } } // HandleError 处理错误 func HandleError(c *gin.Context, err error) { if err == nil { return } // 根据错误类型返回不同的响应 switch { case isNotFoundError(err): NotFoundResponse(c, err.Error()) case isValidationError(err): BadRequestResponse(c, err.Error()) case isConflictError(err): ConflictResponse(c, err.Error()) default: InternalServerErrorResponse(c, err.Error()) } } // 错误类型检查函数 func isNotFoundError(err error) bool { // TODO: 实现具体的错误类型检查 return false } func isValidationError(err error) bool { // TODO: 实现具体的错误类型检查 return false } func isConflictError(err error) bool { // TODO: 实现具体的错误类型检查 return false } ================================================ FILE: internal/interfaces/http/server.go ================================================ // Package http 提供HTTP服务器实现 package http import ( "context" "fmt" "net/http" "time" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/infrastructure/monitoring" ) // ServerConfig HTTP服务器配置 type ServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` IdleTimeout time.Duration `yaml:"idle_timeout"` } // Server HTTP服务器 type Server struct { config *ServerConfig logger logging.Logger server *http.Server mux *http.ServeMux ctx context.Context cancel context.CancelFunc profilingEnabled bool routes []route } type route struct { method string path string handler http.HandlerFunc } // NewServer 创建HTTP服务器 func NewServer(config *ServerConfig, logger logging.Logger) *Server { ctx, cancel := context.WithCancel(context.Background()) return &Server{ config: config, logger: logger, ctx: ctx, cancel: cancel, } } // EnableProfiling 注册标准pprof处理器。 func (s *Server) EnableProfiling() { s.profilingEnabled = true } // Handle 注册业务路由 func (s *Server) Handle(method, path string, handler http.HandlerFunc) { s.routes = append(s.routes, route{method: method, path: path, handler: handler}) } // Start 启动HTTP服务器 func (s *Server) Start() error { // 创建路由 s.mux = http.NewServeMux() // 健康检查端点 s.mux.HandleFunc("/health", s.healthHandler) s.mux.HandleFunc("/ready", s.readyHandler) if s.profilingEnabled { monitoring.RegisterHandlers(s.mux) } // 注册业务路由 for _, r := range s.routes { handler := r.handler method := r.method s.mux.HandleFunc(r.path, func(w http.ResponseWriter, req *http.Request) { if method != "" && req.Method != method { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } handler(w, req) }) } // 创建HTTP服务器 s.server = &http.Server{ Addr: fmt.Sprintf("%s:%d", s.config.Host, s.config.Port), Handler: s.mux, ReadTimeout: s.config.ReadTimeout, WriteTimeout: s.config.WriteTimeout, IdleTimeout: s.config.IdleTimeout, } // 启动服务器 go func() { s.logger.Info("HTTP server starting", logging.Fields{ "addr": s.server.Addr, }) if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { s.logger.Error("HTTP server failed", err, logging.Fields{ "addr": s.server.Addr, }) } }() return nil } // Stop 停止HTTP服务器 func (s *Server) Stop() error { s.logger.Info("Stopping HTTP server") // 取消上下文 s.cancel() // 创建关闭上下文 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // 关闭服务器 if err := s.server.Shutdown(ctx); err != nil { s.logger.Error("HTTP server shutdown failed", err) return err } s.logger.Info("HTTP server stopped") return nil } // healthHandler 健康检查处理器 func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"healthy","timestamp":"` + time.Now().Format(time.RFC3339) + `"}`)) } // readyHandler 就绪检查处理器 func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ready","timestamp":"` + time.Now().Format(time.RFC3339) + `"}`)) } ================================================ FILE: internal/interfaces/rpc/battle_service.go ================================================ // Package rpc 战斗RPC服务实现 package rpc import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" ) // BattleRPCService 战斗RPC服务 type BattleRPCService struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewBattleRPCService 创建战斗RPC服务 func NewBattleRPCService( commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger, ) *BattleRPCService { return &BattleRPCService{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // CreateBattleRequest 创建战斗请求 type CreateBattleRequest struct { PlayerID string `json:"player_id"` OpponentIDs []string `json:"opponent_ids"` BattleType string `json:"battle_type"` } // CreateBattleResponse 创建战斗响应 type CreateBattleResponse struct { Success bool `json:"success"` Message string `json:"message"` BattleID string `json:"battle_id,omitempty"` } // CreateBattle 创建战斗 func (s *BattleRPCService) CreateBattle(req CreateBattleRequest, resp *CreateBattleResponse) error { s.logger.Info("RPC call: Create battle", logging.Fields{ "player_id": req.PlayerID, "opponents": req.OpponentIDs, }) // TODO: 实现创建战斗逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理创建战斗命令 // 3. 返回结果 resp.Success = true resp.Message = "战斗创建成功" resp.BattleID = "battle_123" // 临时ID return nil } // GetBattleRequest 获取战斗请求 type GetBattleRequest struct { BattleID string `json:"battle_id"` } // GetBattleResponse 获取战斗响应 type GetBattleResponse struct { Success bool `json:"success"` Message string `json:"message"` Battle *BattleInfo `json:"battle,omitempty"` } // BattleInfo 战斗信息 type BattleInfo struct { ID string `json:"id"` PlayerID string `json:"player_id"` OpponentIDs []string `json:"opponent_ids"` BattleType string `json:"battle_type"` Status string `json:"status"` CreatedAt string `json:"created_at"` StartedAt string `json:"started_at,omitempty"` EndedAt string `json:"ended_at,omitempty"` } // GetBattle 获取战斗信息 func (s *BattleRPCService) GetBattle(req GetBattleRequest, resp *GetBattleResponse) error { s.logger.Info("RPC call: Get battle", logging.Fields{ "battle_id": req.BattleID, }) // TODO: 实现获取战斗逻辑 // 1. 验证请求参数 // 2. 调用查询总线获取战斗信息 // 3. 返回结果 resp.Success = true resp.Message = "获取战斗信息成功" resp.Battle = &BattleInfo{ ID: req.BattleID, PlayerID: "player_123", OpponentIDs: []string{"opponent_1", "opponent_2"}, BattleType: "pvp", Status: "active", CreatedAt: "2024-01-01T00:00:00Z", StartedAt: "2024-01-01T00:01:00Z", } return nil } // JoinBattleRequest 加入战斗请求 type JoinBattleRequest struct { BattleID string `json:"battle_id"` PlayerID string `json:"player_id"` } // JoinBattleResponse 加入战斗响应 type JoinBattleResponse struct { Success bool `json:"success"` Message string `json:"message"` } // JoinBattle 加入战斗 func (s *BattleRPCService) JoinBattle(req JoinBattleRequest, resp *JoinBattleResponse) error { s.logger.Info("RPC call: Join battle", logging.Fields{ "battle_id": req.BattleID, "player_id": req.PlayerID, }) // TODO: 实现加入战斗逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理加入战斗命令 // 3. 返回结果 resp.Success = true resp.Message = "成功加入战斗" return nil } // LeaveBattleRequest 离开战斗请求 type LeaveBattleRequest struct { BattleID string `json:"battle_id"` PlayerID string `json:"player_id"` } // LeaveBattleResponse 离开战斗响应 type LeaveBattleResponse struct { Success bool `json:"success"` Message string `json:"message"` } // LeaveBattle 离开战斗 func (s *BattleRPCService) LeaveBattle(req LeaveBattleRequest, resp *LeaveBattleResponse) error { s.logger.Info("RPC call: Leave battle", logging.Fields{ "battle_id": req.BattleID, "player_id": req.PlayerID, }) // TODO: 实现离开战斗逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理离开战斗命令 // 3. 返回结果 resp.Success = true resp.Message = "成功离开战斗" return nil } // ExecuteActionRequest 执行动作请求 type ExecuteActionRequest struct { BattleID string `json:"battle_id"` PlayerID string `json:"player_id"` Action string `json:"action"` TargetID string `json:"target_id,omitempty"` } // ExecuteActionResponse 执行动作响应 type ExecuteActionResponse struct { Success bool `json:"success"` Message string `json:"message"` Result *ActionResult `json:"result,omitempty"` } // ActionResult 动作结果 type ActionResult struct { ActionID string `json:"action_id"` Damage int `json:"damage"` Healing int `json:"healing"` IsCritical bool `json:"is_critical"` IsMiss bool `json:"is_miss"` } // ExecuteAction 执行动作 func (s *BattleRPCService) ExecuteAction(req ExecuteActionRequest, resp *ExecuteActionResponse) error { s.logger.Info("RPC call: Execute action", logging.Fields{ "battle_id": req.BattleID, "player_id": req.PlayerID, "action": req.Action, }) // TODO: 实现执行动作逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理执行动作命令 // 3. 返回结果 resp.Success = true resp.Message = "动作执行成功" resp.Result = &ActionResult{ ActionID: "action_123", Damage: 100, Healing: 0, IsCritical: false, IsMiss: false, } return nil } // EndBattleRequest 结束战斗请求 type EndBattleRequest struct { BattleID string `json:"battle_id"` WinnerID string `json:"winner_id,omitempty"` } // EndBattleResponse 结束战斗响应 type EndBattleResponse struct { Success bool `json:"success"` Message string `json:"message"` Result *BattleResult `json:"result,omitempty"` } // BattleResult 战斗结果 type BattleResult struct { WinnerID string `json:"winner_id"` Experience int `json:"experience"` Gold int `json:"gold"` Items []string `json:"items"` Duration int `json:"duration"` // 秒 } // EndBattle 结束战斗 func (s *BattleRPCService) EndBattle(req EndBattleRequest, resp *EndBattleResponse) error { s.logger.Info("RPC call: End battle", logging.Fields{ "battle_id": req.BattleID, "winner_id": req.WinnerID, }) // TODO: 实现结束战斗逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理结束战斗命令 // 3. 返回结果 resp.Success = true resp.Message = "战斗结束" resp.Result = &BattleResult{ WinnerID: req.WinnerID, Experience: 100, Gold: 50, Items: []string{"item_1", "item_2"}, Duration: 300, // 5分钟 } return nil } ================================================ FILE: internal/interfaces/rpc/player_service.go ================================================ // Package rpc 玩家RPC服务实现 package rpc import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" ) // PlayerRPCService 玩家RPC服务 type PlayerRPCService struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewPlayerRPCService 创建玩家RPC服务 func NewPlayerRPCService( commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger, ) *PlayerRPCService { return &PlayerRPCService{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // CreatePlayerRequest 创建玩家请求 type CreatePlayerRequest struct { Name string `json:"name"` Email string `json:"email"` Password string `json:"password"` } // CreatePlayerResponse 创建玩家响应 type CreatePlayerResponse struct { Success bool `json:"success"` Message string `json:"message"` PlayerID string `json:"player_id,omitempty"` } // CreatePlayer 创建玩家 func (s *PlayerRPCService) CreatePlayer(req CreatePlayerRequest, resp *CreatePlayerResponse) error { s.logger.Info("RPC call: Create player", logging.Fields{ "name": req.Name, }) // TODO: 实现创建玩家逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理创建玩家命令 // 3. 返回结果 resp.Success = true resp.Message = "玩家创建成功" resp.PlayerID = "player_123" // 临时ID return nil } // GetPlayerRequest 获取玩家请求 type GetPlayerRequest struct { PlayerID string `json:"player_id"` } // GetPlayerResponse 获取玩家响应 type GetPlayerResponse struct { Success bool `json:"success"` Message string `json:"message"` Player *PlayerInfo `json:"player,omitempty"` } // PlayerInfo 玩家信息 type PlayerInfo struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` Level int `json:"level"` Gold int `json:"gold"` Experience int `json:"experience"` CreatedAt string `json:"created_at"` } // GetPlayer 获取玩家信息 func (s *PlayerRPCService) GetPlayer(req GetPlayerRequest, resp *GetPlayerResponse) error { s.logger.Info("RPC call: Get player", logging.Fields{ "player_id": req.PlayerID, }) // TODO: 实现获取玩家逻辑 // 1. 验证请求参数 // 2. 调用查询总线获取玩家信息 // 3. 返回结果 resp.Success = true resp.Message = "获取玩家信息成功" resp.Player = &PlayerInfo{ ID: req.PlayerID, Name: "测试玩家", Email: "test@example.com", Level: 1, Gold: 1000, Experience: 0, CreatedAt: "2024-01-01T00:00:00Z", } return nil } // UpdatePlayerRequest 更新玩家请求 type UpdatePlayerRequest struct { PlayerID string `json:"player_id"` Updates map[string]interface{} `json:"updates"` } // UpdatePlayerResponse 更新玩家响应 type UpdatePlayerResponse struct { Success bool `json:"success"` Message string `json:"message"` } // UpdatePlayer 更新玩家信息 func (s *PlayerRPCService) UpdatePlayer(req UpdatePlayerRequest, resp *UpdatePlayerResponse) error { s.logger.Info("RPC call: Update player", logging.Fields{ "player_id": req.PlayerID, "updates": req.Updates, }) // TODO: 实现更新玩家逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理更新玩家命令 // 3. 返回结果 resp.Success = true resp.Message = "玩家信息更新成功" return nil } // DeletePlayerRequest 删除玩家请求 type DeletePlayerRequest struct { PlayerID string `json:"player_id"` } // DeletePlayerResponse 删除玩家响应 type DeletePlayerResponse struct { Success bool `json:"success"` Message string `json:"message"` } // DeletePlayer 删除玩家 func (s *PlayerRPCService) DeletePlayer(req DeletePlayerRequest, resp *DeletePlayerResponse) error { s.logger.Info("RPC call: Delete player", logging.Fields{ "player_id": req.PlayerID, }) // TODO: 实现删除玩家逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理删除玩家命令 // 3. 返回结果 resp.Success = true resp.Message = "玩家删除成功" return nil } // ListPlayersRequest 列出玩家请求 type ListPlayersRequest struct { Page int `json:"page"` PageSize int `json:"page_size"` Filter string `json:"filter,omitempty"` } // ListPlayersResponse 列出玩家响应 type ListPlayersResponse struct { Success bool `json:"success"` Message string `json:"message"` Players []PlayerInfo `json:"players"` Total int `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } // ListPlayers 列出玩家 func (s *PlayerRPCService) ListPlayers(req ListPlayersRequest, resp *ListPlayersResponse) error { s.logger.Info("RPC call: List players", logging.Fields{ "page": req.Page, "page_size": req.PageSize, }) // TODO: 实现列出玩家逻辑 // 1. 验证请求参数 // 2. 调用查询总线获取玩家列表 // 3. 返回结果 resp.Success = true resp.Message = "获取玩家列表成功" resp.Players = []PlayerInfo{ { ID: "player_1", Name: "玩家1", Email: "player1@example.com", Level: 10, Gold: 5000, Experience: 1000, CreatedAt: "2024-01-01T00:00:00Z", }, { ID: "player_2", Name: "玩家2", Email: "player2@example.com", Level: 15, Gold: 8000, Experience: 2000, CreatedAt: "2024-01-02T00:00:00Z", }, } resp.Total = 2 resp.Page = req.Page resp.PageSize = req.PageSize return nil } ================================================ FILE: internal/interfaces/rpc/ranking_service.go ================================================ // Package rpc 排行榜RPC服务实现 package rpc import ( "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" ) // RankingRPCService 排行榜RPC服务 type RankingRPCService struct { commandBus *handlers.CommandBus queryBus *handlers.QueryBus logger logging.Logger } // NewRankingRPCService 创建排行榜RPC服务 func NewRankingRPCService( commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger, ) *RankingRPCService { return &RankingRPCService{ commandBus: commandBus, queryBus: queryBus, logger: logger, } } // GetRankingRequest 获取排行榜请求 type GetRankingRequest struct { RankingID string `json:"ranking_id"` Page int `json:"page"` PageSize int `json:"page_size"` } // GetRankingResponse 获取排行榜响应 type GetRankingResponse struct { Success bool `json:"success"` Message string `json:"message"` Ranking *RankingInfo `json:"ranking,omitempty"` Entries []RankEntry `json:"entries"` Total int `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } // RankingInfo 排行榜信息 type RankingInfo struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` Category string `json:"category"` MaxEntries int `json:"max_entries"` IsActive bool `json:"is_active"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // RankEntry 排行榜条目 type RankEntry struct { Rank int `json:"rank"` PlayerID string `json:"player_id"` PlayerName string `json:"player_name"` Score int64 `json:"score"` Level int `json:"level"` UpdatedAt string `json:"updated_at"` } // GetRanking 获取排行榜 func (s *RankingRPCService) GetRanking(req GetRankingRequest, resp *GetRankingResponse) error { s.logger.Info("RPC call: Get ranking", logging.Fields{ "ranking_id": req.RankingID, "page": req.Page, }) // TODO: 实现获取排行榜逻辑 // 1. 验证请求参数 // 2. 调用查询总线获取排行榜信息 // 3. 返回结果 resp.Success = true resp.Message = "获取排行榜成功" resp.Ranking = &RankingInfo{ ID: req.RankingID, Name: "等级排行榜", Description: "玩家等级排行榜", Type: "level", Category: "player", MaxEntries: 1000, IsActive: true, CreatedAt: "2024-01-01T00:00:00Z", UpdatedAt: "2024-01-01T12:00:00Z", } resp.Entries = []RankEntry{ { Rank: 1, PlayerID: "player_1", PlayerName: "玩家1", Score: 1000, Level: 50, UpdatedAt: "2024-01-01T12:00:00Z", }, { Rank: 2, PlayerID: "player_2", PlayerName: "玩家2", Score: 950, Level: 48, UpdatedAt: "2024-01-01T11:30:00Z", }, } resp.Total = 2 resp.Page = req.Page resp.PageSize = req.PageSize return nil } // UpdatePlayerScoreRequest 更新玩家分数请求 type UpdatePlayerScoreRequest struct { RankingID string `json:"ranking_id"` PlayerID string `json:"player_id"` Score int64 `json:"score"` } // UpdatePlayerScoreResponse 更新玩家分数响应 type UpdatePlayerScoreResponse struct { Success bool `json:"success"` Message string `json:"message"` NewRank int `json:"new_rank,omitempty"` } // UpdatePlayerScore 更新玩家分数 func (s *RankingRPCService) UpdatePlayerScore(req UpdatePlayerScoreRequest, resp *UpdatePlayerScoreResponse) error { s.logger.Info("RPC call: Update player score", logging.Fields{ "ranking_id": req.RankingID, "player_id": req.PlayerID, "score": req.Score, }) // TODO: 实现更新玩家分数逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理更新分数命令 // 3. 返回结果 resp.Success = true resp.Message = "玩家分数更新成功" resp.NewRank = 5 return nil } // GetPlayerRankRequest 获取玩家排名请求 type GetPlayerRankRequest struct { RankingID string `json:"ranking_id"` PlayerID string `json:"player_id"` } // GetPlayerRankResponse 获取玩家排名响应 type GetPlayerRankResponse struct { Success bool `json:"success"` Message string `json:"message"` Rank int `json:"rank"` Score int64 `json:"score"` Entry *RankEntry `json:"entry,omitempty"` } // GetPlayerRank 获取玩家排名 func (s *RankingRPCService) GetPlayerRank(req GetPlayerRankRequest, resp *GetPlayerRankResponse) error { s.logger.Info("RPC call: Get player rank", logging.Fields{ "ranking_id": req.RankingID, "player_id": req.PlayerID, }) // TODO: 实现获取玩家排名逻辑 // 1. 验证请求参数 // 2. 调用查询总线获取玩家排名 // 3. 返回结果 resp.Success = true resp.Message = "获取玩家排名成功" resp.Rank = 10 resp.Score = 800 resp.Entry = &RankEntry{ Rank: 10, PlayerID: req.PlayerID, PlayerName: "测试玩家", Score: 800, Level: 40, UpdatedAt: "2024-01-01T10:00:00Z", } return nil } // GetTopPlayersRequest 获取顶级玩家请求 type GetTopPlayersRequest struct { RankingID string `json:"ranking_id"` Limit int `json:"limit"` } // GetTopPlayersResponse 获取顶级玩家响应 type GetTopPlayersResponse struct { Success bool `json:"success"` Message string `json:"message"` Players []RankEntry `json:"players"` } // GetTopPlayers 获取顶级玩家 func (s *RankingRPCService) GetTopPlayers(req GetTopPlayersRequest, resp *GetTopPlayersResponse) error { s.logger.Info("RPC call: Get top players", logging.Fields{ "ranking_id": req.RankingID, "limit": req.Limit, }) // TODO: 实现获取顶级玩家逻辑 // 1. 验证请求参数 // 2. 调用查询总线获取顶级玩家 // 3. 返回结果 resp.Success = true resp.Message = "获取顶级玩家成功" resp.Players = []RankEntry{ { Rank: 1, PlayerID: "player_1", PlayerName: "顶级玩家1", Score: 1000, Level: 50, UpdatedAt: "2024-01-01T12:00:00Z", }, { Rank: 2, PlayerID: "player_2", PlayerName: "顶级玩家2", Score: 950, Level: 48, UpdatedAt: "2024-01-01T11:30:00Z", }, } return nil } // CreateRankingRequest 创建排行榜请求 type CreateRankingRequest struct { Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` Category string `json:"category"` MaxEntries int `json:"max_entries"` } // CreateRankingResponse 创建排行榜响应 type CreateRankingResponse struct { Success bool `json:"success"` Message string `json:"message"` RankingID string `json:"ranking_id,omitempty"` } // CreateRanking 创建排行榜 func (s *RankingRPCService) CreateRanking(req CreateRankingRequest, resp *CreateRankingResponse) error { s.logger.Info("RPC call: Create ranking", logging.Fields{ "name": req.Name, "type": req.Type, }) // TODO: 实现创建排行榜逻辑 // 1. 验证请求参数 // 2. 调用命令总线处理创建排行榜命令 // 3. 返回结果 resp.Success = true resp.Message = "排行榜创建成功" resp.RankingID = "ranking_123" // 临时ID return nil } ================================================ FILE: internal/interfaces/rpc/replication_service.go ================================================ // Package rpc 副本RPC服务 package rpc import ( "context" "greatestworks/internal/application/services" "greatestworks/internal/infrastructure/logging" "time" ) // ReplicationRPCService 副本RPC服务 type ReplicationRPCService struct { app *services.ReplicationService logger logging.Logger } // NewReplicationRPCService 构造 func NewReplicationRPCService(app *services.ReplicationService, logger logging.Logger) *ReplicationRPCService { return &ReplicationRPCService{app: app, logger: logger} } // CreateInstance type CreateInstanceRPCRequest struct { TemplateID string InstanceType int OwnerPlayerID string OwnerName string OwnerLevel int MaxPlayers int Difficulty int LifetimeSec int64 } type CreateInstanceRPCResponse struct { Instance *services.InstanceInfoDTO Error string } func (s *ReplicationRPCService) CreateInstance(req CreateInstanceRPCRequest, resp *CreateInstanceRPCResponse) error { ctx := context.Background() dto, err := s.app.CreateInstance(ctx, &services.CreateInstanceCommand{ TemplateID: req.TemplateID, InstanceType: req.InstanceType, OwnerPlayerID: req.OwnerPlayerID, OwnerName: req.OwnerName, OwnerLevel: req.OwnerLevel, MaxPlayers: req.MaxPlayers, Difficulty: req.Difficulty, Lifetime: time.Duration(req.LifetimeSec) * time.Second, }) if err != nil { resp.Error = err.Error() return nil } resp.Instance = dto return nil } // JoinInstance type JoinInstanceRPCRequest struct { InstanceID string PlayerID string PlayerName string Level int Role string } type SimpleRPCResponse struct{ Error string } func (s *ReplicationRPCService) JoinInstance(req JoinInstanceRPCRequest, resp *SimpleRPCResponse) error { ctx := context.Background() err := s.app.JoinInstance(ctx, &services.JoinInstanceCommand{ InstanceID: req.InstanceID, PlayerID: req.PlayerID, PlayerName: req.PlayerName, Level: req.Level, Role: req.Role, }) if err != nil { resp.Error = err.Error() } return nil } // LeaveInstance type LeaveInstanceRPCRequest struct{ InstanceID, PlayerID string } func (s *ReplicationRPCService) LeaveInstance(req LeaveInstanceRPCRequest, resp *SimpleRPCResponse) error { ctx := context.Background() err := s.app.LeaveInstance(ctx, &services.LeaveInstanceCommand{InstanceID: req.InstanceID, PlayerID: req.PlayerID}) if err != nil { resp.Error = err.Error() } return nil } // GetInstanceInfo type GetInstanceInfoRPCRequest struct{ InstanceID string } type GetInstanceInfoRPCResponse struct { Instance *services.InstanceInfoDTO Error string } func (s *ReplicationRPCService) GetInstanceInfo(req GetInstanceInfoRPCRequest, resp *GetInstanceInfoRPCResponse) error { ctx := context.Background() dto, err := s.app.GetInstanceInfo(ctx, req.InstanceID) if err != nil { resp.Error = err.Error() return nil } resp.Instance = dto return nil } // ListActiveInstances type ListActiveInstancesRPCRequest struct{} type ListActiveInstancesRPCResponse struct { Instances []*services.InstanceInfoDTO Error string } func (s *ReplicationRPCService) ListActiveInstances(_ ListActiveInstancesRPCRequest, resp *ListActiveInstancesRPCResponse) error { ctx := context.Background() dtos, err := s.app.ListActiveInstances(ctx) if err != nil { resp.Error = err.Error() return nil } resp.Instances = dtos return nil } // CleanupExpiredInstances type CleanupExpiredInstancesRPCRequest struct{} type CleanupExpiredInstancesRPCResponse struct { Count int Error string } func (s *ReplicationRPCService) CleanupExpiredInstances(_ CleanupExpiredInstancesRPCRequest, resp *CleanupExpiredInstancesRPCResponse) error { ctx := context.Background() count, err := s.app.CleanupExpiredInstances(ctx) if err != nil { resp.Error = err.Error() return nil } resp.Count = count return nil } ================================================ FILE: internal/interfaces/rpc/server.go ================================================ // Package rpc 提供Go原生RPC服务器实现 // 基于DDD架构的分布式游戏服务RPC接口 package rpc import ( "context" "fmt" "net" "net/rpc" "time" "greatestworks/internal/application/handlers" "greatestworks/internal/infrastructure/logging" ) // RPCServerConfig RPC服务器配置 type RPCServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` MaxConnections int `yaml:"max_connections"` Timeout time.Duration `yaml:"timeout"` KeepAlive bool `yaml:"keep_alive"` KeepAlivePeriod time.Duration `yaml:"keep_alive_period"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` } // RPCServer RPC服务器 type RPCServer struct { config *RPCServerConfig logger logging.Logger server *rpc.Server commandBus *handlers.CommandBus queryBus *handlers.QueryBus listener net.Listener ctx context.Context cancel context.CancelFunc extras []interface{} } // NewRPCServer 创建RPC服务器 func NewRPCServer( config *RPCServerConfig, commandBus *handlers.CommandBus, queryBus *handlers.QueryBus, logger logging.Logger, ) *RPCServer { ctx, cancel := context.WithCancel(context.Background()) return &RPCServer{ config: config, logger: logger, commandBus: commandBus, queryBus: queryBus, ctx: ctx, cancel: cancel, } } // Start 启动RPC服务器 func (s *RPCServer) Start() error { // 创建RPC服务器 s.server = rpc.NewServer() // 注册服务 s.registerServices() // 注册额外服务(由引导器注入) for _, svc := range s.extras { if err := s.server.Register(svc); err != nil { s.logger.Error("failed to register extra RPC service", err, logging.Fields{}) } } // 创建监听器 addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) listener, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("创建监听器失败: %w", err) } s.listener = listener // 启动服务器 go func() { s.logger.Info("RPC server started", logging.Fields{ "address": addr, }) s.server.Accept(listener) }() return nil } // RegisterService 允许引导器注册额外的RPC服务 func (s *RPCServer) RegisterService(service interface{}) { s.extras = append(s.extras, service) } // Stop 停止RPC服务器 func (s *RPCServer) Stop() error { s.logger.Info("停止RPC服务器") // 取消上下文 s.cancel() // 关闭监听器 if s.listener != nil { s.listener.Close() } s.logger.Info("RPC服务器已停止") return nil } // registerServices 注册服务 func (s *RPCServer) registerServices() { s.logger.Info("注册RPC服务") // 注册玩家服务 playerService := NewPlayerRPCService(s.commandBus, s.queryBus, s.logger) s.server.Register(playerService) // 注册战斗服务 battleService := NewBattleRPCService(s.commandBus, s.queryBus, s.logger) s.server.Register(battleService) // 注册排行榜服务 rankingService := NewRankingRPCService(s.commandBus, s.queryBus, s.logger) s.server.Register(rankingService) // 注册其他领域服务 // TODO: 注册更多服务 s.logger.Info("RPC服务注册完成") } // GetStats 获取服务器统计信息 func (s *RPCServer) GetStats() map[string]interface{} { stats := make(map[string]interface{}) stats["status"] = "running" stats["address"] = fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) stats["max_connections"] = s.config.MaxConnections stats["timeout"] = s.config.Timeout.String() return stats } // DefaultRPCServerConfig 默认RPC服务器配置 func DefaultRPCServerConfig() *RPCServerConfig { return &RPCServerConfig{ Host: "0.0.0.0", Port: 8081, MaxConnections: 1000, Timeout: 30 * time.Second, KeepAlive: true, KeepAlivePeriod: 30 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, } } ================================================ FILE: internal/interfaces/tcp/connection/heartbeat.go ================================================ package connection import ( "context" "fmt" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // HeartbeatManager 心跳管理器 type HeartbeatManager struct { sessions map[string]*Session mutex sync.RWMutex logger logging.Logger interval time.Duration timeout time.Duration } // NewHeartbeatManager 创建心跳管理器 func NewHeartbeatManager(logger logging.Logger, interval, timeout time.Duration) *HeartbeatManager { return &HeartbeatManager{ sessions: make(map[string]*Session), logger: logger, interval: interval, timeout: timeout, } } // AddSession 添加会话 func (hm *HeartbeatManager) AddSession(session *Session) { hm.mutex.Lock() defer hm.mutex.Unlock() hm.sessions[session.ID] = session hm.logger.Info("会话已添加到心跳管理", map[string]interface{}{ "session_id": session.ID, }) } // RemoveSession 移除会话 func (hm *HeartbeatManager) RemoveSession(sessionID string) { hm.mutex.Lock() defer hm.mutex.Unlock() delete(hm.sessions, sessionID) hm.logger.Info("会话已从心跳管理移除", map[string]interface{}{ "session_id": sessionID, }) } // Start 启动心跳检查 func (hm *HeartbeatManager) Start(ctx context.Context) { ticker := time.NewTicker(hm.interval) defer ticker.Stop() hm.logger.Info("心跳管理器启动", map[string]interface{}{ "interval": hm.interval, "timeout": hm.timeout, }) for { select { case <-ctx.Done(): hm.logger.Info("心跳管理器停止") return case <-ticker.C: hm.checkHeartbeats() } } } // checkHeartbeats 检查心跳 func (hm *HeartbeatManager) checkHeartbeats() { hm.mutex.RLock() sessions := make([]*Session, 0, len(hm.sessions)) for _, session := range hm.sessions { sessions = append(sessions, session) } hm.mutex.RUnlock() now := time.Now() timeoutCount := 0 for _, session := range sessions { if now.Sub(session.LastActivity) > hm.timeout { hm.logger.Info("会话心跳超时", map[string]interface{}{ "session_id": session.ID, "last_activity": session.LastActivity, "timeout": hm.timeout, }) // 关闭超时会话 session.Close() hm.RemoveSession(session.ID) timeoutCount++ } } if timeoutCount > 0 { hm.logger.Info("心跳检查完成", map[string]interface{}{ "timeout_count": timeoutCount, "active_count": len(sessions) - timeoutCount, }) } } // SendHeartbeat 发送心跳 func (hm *HeartbeatManager) SendHeartbeat(sessionID string) error { hm.mutex.RLock() session, exists := hm.sessions[sessionID] hm.mutex.RUnlock() if !exists { return fmt.Errorf("会话不存在: %s", sessionID) } // 更新最后活跃时间 session.LastActivity = time.Now() hm.logger.Info("心跳已发送", map[string]interface{}{ "session_id": sessionID, }) return nil } // GetActiveSessionCount 获取活跃会话数量 func (hm *HeartbeatManager) GetActiveSessionCount() int { hm.mutex.RLock() defer hm.mutex.RUnlock() return len(hm.sessions) } ================================================ FILE: internal/interfaces/tcp/connection/manager.go ================================================ package connection import ( "context" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // Manager 连接管理器 type Manager struct { connections map[string]*Session // Mapping from player entity ID to session playerSessions map[int32]*Session // Reverse mapping from session ID to player entity ID sessionToPlayer map[string]int32 mutex sync.RWMutex logger logging.Logger } // NewManager 创建连接管理器 func NewManager(logger logging.Logger) *Manager { return &Manager{ connections: make(map[string]*Session), playerSessions: make(map[int32]*Session), sessionToPlayer: make(map[string]int32), logger: logger, } } // AddConnection 添加连接 func (m *Manager) AddConnection(session *Session) { m.mutex.Lock() defer m.mutex.Unlock() m.connections[session.ID] = session m.logger.Info("Connection added", logging.Fields{ "session_id": session.ID, "address": session.RemoteAddr, }) } // RemoveConnection 移除连接 func (m *Manager) RemoveConnection(sessionID string) { m.mutex.Lock() defer m.mutex.Unlock() if session, exists := m.connections[sessionID]; exists { delete(m.connections, sessionID) // Also remove any player-session bindings pointing to this session for pid, s := range m.playerSessions { if s == session { delete(m.playerSessions, pid) } } delete(m.sessionToPlayer, sessionID) m.logger.Info("Connection removed", logging.Fields{ "session_id": sessionID, "address": session.RemoteAddr, }) } } // GetConnection 获取连接 func (m *Manager) GetConnection(sessionID string) (*Session, bool) { m.mutex.RLock() defer m.mutex.RUnlock() session, exists := m.connections[sessionID] return session, exists } // GetAllConnections 获取所有连接 func (m *Manager) GetAllConnections() map[string]*Session { m.mutex.RLock() defer m.mutex.RUnlock() // 返回副本 connections := make(map[string]*Session) for id, session := range m.connections { connections[id] = session } return connections } // GetConnectionCount 获取连接数量 func (m *Manager) GetConnectionCount() int { m.mutex.RLock() defer m.mutex.RUnlock() return len(m.connections) } // BindPlayer binds a player entity ID to a session for targeted sends. func (m *Manager) BindPlayer(entityID int32, session *Session) { m.mutex.Lock() defer m.mutex.Unlock() m.playerSessions[entityID] = session m.sessionToPlayer[session.ID] = entityID m.logger.Info("Player bound to session", logging.Fields{ "entity_id": entityID, "session_id": session.ID, }) } // UnbindPlayer removes the binding between a player entity ID and any session. func (m *Manager) UnbindPlayer(entityID int32) { m.mutex.Lock() defer m.mutex.Unlock() if s, ok := m.playerSessions[entityID]; ok { delete(m.sessionToPlayer, s.ID) delete(m.playerSessions, entityID) } else { delete(m.playerSessions, entityID) } m.logger.Info("Player unbound from session", logging.Fields{ "entity_id": entityID, }) } // GetSessionByPlayer retrieves the session bound to the given player entity ID. func (m *Manager) GetSessionByPlayer(entityID int32) (*Session, bool) { m.mutex.RLock() defer m.mutex.RUnlock() s, ok := m.playerSessions[entityID] return s, ok } // GetPlayerBySession retrieves the bound player entity ID from a session ID. func (m *Manager) GetPlayerBySession(sessionID string) (int32, bool) { m.mutex.RLock() defer m.mutex.RUnlock() pid, ok := m.sessionToPlayer[sessionID] return pid, ok } // Broadcast 广播消息 func (m *Manager) Broadcast(message []byte) { m.mutex.RLock() defer m.mutex.RUnlock() for _, session := range m.connections { if err := session.Send(message); err != nil { m.logger.Error("Failed to broadcast message", err, logging.Fields{ "session_id": session.ID, }) } } } // BroadcastToGroup 向指定组广播消息 func (m *Manager) BroadcastToGroup(groupID string, message []byte) { m.mutex.RLock() defer m.mutex.RUnlock() for _, session := range m.connections { if session.GroupID == groupID { if err := session.Send(message); err != nil { m.logger.Error("Failed to broadcast to group", err, logging.Fields{ "session_id": session.ID, "group_id": groupID, }) } } } } // CleanupInactiveConnections 清理非活跃连接 func (m *Manager) CleanupInactiveConnections(timeout time.Duration) { m.mutex.Lock() defer m.mutex.Unlock() now := time.Now() for id, session := range m.connections { if now.Sub(session.LastActivity) > timeout { session.Close() delete(m.connections, id) m.logger.Info("Cleaned up inactive connection", logging.Fields{ "session_id": id, "last_activity": session.LastActivity, }) } } } // StartCleanupRoutine 启动清理例程 func (m *Manager) StartCleanupRoutine(ctx context.Context, interval, timeout time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): m.logger.Info("Connection cleanup routine stopped", logging.Fields{}) return case <-ticker.C: m.CleanupInactiveConnections(timeout) } } } ================================================ FILE: internal/interfaces/tcp/connection/session.go ================================================ package connection import ( "fmt" "net" "sync" "time" "greatestworks/internal/infrastructure/logging" ) // Session 会话 type Session struct { ID string Conn net.Conn RemoteAddr string GroupID string UserID string CreatedAt time.Time LastActivity time.Time Status string mutex sync.RWMutex logger logging.Logger } // NewSession 创建会话 func NewSession(id string, conn net.Conn, logger logging.Logger) *Session { return &Session{ ID: id, Conn: conn, RemoteAddr: conn.RemoteAddr().String(), CreatedAt: time.Now(), LastActivity: time.Now(), Status: "active", logger: logger, } } // Send 发送消息 func (s *Session) Send(data []byte) error { s.mutex.Lock() defer s.mutex.Unlock() if s.Conn == nil { return fmt.Errorf("连接已关闭") } _, err := s.Conn.Write(data) if err != nil { return fmt.Errorf("发送消息失败: %w", err) } s.LastActivity = time.Now() s.logger.Info("消息已发送", map[string]interface{}{ "session_id": s.ID, "data_length": len(data), "remote_addr": s.RemoteAddr, }) return nil } // Receive 接收消息 func (s *Session) Receive() ([]byte, error) { s.mutex.Lock() defer s.mutex.Unlock() if s.Conn == nil { return nil, fmt.Errorf("连接已关闭") } buffer := make([]byte, 4096) n, err := s.Conn.Read(buffer) if err != nil { return nil, fmt.Errorf("接收消息失败: %w", err) } s.LastActivity = time.Now() s.logger.Info("消息已接收", map[string]interface{}{ "session_id": s.ID, "data_length": n, "remote_addr": s.RemoteAddr, }) return buffer[:n], nil } // Close 关闭会话 func (s *Session) Close() error { s.mutex.Lock() defer s.mutex.Unlock() if s.Conn == nil { return nil } err := s.Conn.Close() s.Conn = nil s.Status = "closed" s.logger.Info("会话已关闭", map[string]interface{}{ "session_id": s.ID, "remote_addr": s.RemoteAddr, }) return err } // SetUserID 设置用户ID func (s *Session) SetUserID(userID string) { s.mutex.Lock() defer s.mutex.Unlock() s.UserID = userID s.logger.Info("用户ID已设置", map[string]interface{}{ "session_id": s.ID, "user_id": userID, }) } // GetUserID 获取用户ID func (s *Session) GetUserID() string { s.mutex.RLock() defer s.mutex.RUnlock() return s.UserID } // SetGroupID 设置组ID func (s *Session) SetGroupID(groupID string) { s.mutex.Lock() defer s.mutex.Unlock() s.GroupID = groupID s.logger.Info("组ID已设置", map[string]interface{}{ "session_id": s.ID, "group_id": groupID, }) } // GetGroupID 获取组ID func (s *Session) GetGroupID() string { s.mutex.RLock() defer s.mutex.RUnlock() return s.GroupID } // SetStatus 设置状态 func (s *Session) SetStatus(status string) { s.mutex.Lock() defer s.mutex.Unlock() s.Status = status s.logger.Info("状态已更新", map[string]interface{}{ "session_id": s.ID, "status": status, }) } // GetStatus 获取状态 func (s *Session) GetStatus() string { s.mutex.RLock() defer s.mutex.RUnlock() return s.Status } // IsActive 检查是否活跃 func (s *Session) IsActive() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.Status == "active" && s.Conn != nil } // SetReadTimeout 设置读取超时 func (s *Session) SetReadTimeout(timeout time.Duration) error { s.mutex.Lock() defer s.mutex.Unlock() if s.Conn == nil { return fmt.Errorf("连接已关闭") } return s.Conn.SetReadDeadline(time.Now().Add(timeout)) } // SetWriteTimeout 设置写入超时 func (s *Session) SetWriteTimeout(timeout time.Duration) error { s.mutex.Lock() defer s.mutex.Unlock() if s.Conn == nil { return fmt.Errorf("连接已关闭") } return s.Conn.SetWriteDeadline(time.Now().Add(timeout)) } // GetInfo 获取会话信息 func (s *Session) GetInfo() map[string]interface{} { s.mutex.RLock() defer s.mutex.RUnlock() return map[string]interface{}{ "id": s.ID, "remote_addr": s.RemoteAddr, "group_id": s.GroupID, "user_id": s.UserID, "created_at": s.CreatedAt, "last_activity": s.LastActivity, "status": s.Status, } } ================================================ FILE: internal/interfaces/tcp/handlers/game_handler.go ================================================ package handlers import ( "context" "encoding/json" "fmt" "strconv" "time" appServices "greatestworks/internal/application/services" "greatestworks/internal/domain/character" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/interfaces/tcp/connection" "greatestworks/internal/interfaces/tcp/protocol" ) // GameHandler 游戏处理器 type GameHandler struct { logger logging.Logger connManager *connection.Manager mapService *appServices.MapService fightService *appServices.FightService characterService *appServices.CharacterService } // NewGameHandler 创建游戏处理器 func NewGameHandler(commandBus interface{}, queryBus interface{}, connManager *connection.Manager, logger logging.Logger) *GameHandler { return &GameHandler{ logger: logger, connManager: connManager, } } // SetMapService 注入地图服务 func (h *GameHandler) SetMapService(ms *appServices.MapService) { h.mapService = ms } // SetFightService 注入战斗服务 func (h *GameHandler) SetFightService(fs *appServices.FightService) { h.fightService = fs } // SetCharacterService 注入角色服务 func (h *GameHandler) SetCharacterService(cs *appServices.CharacterService) { h.characterService = cs } // HandleMessage 处理消息 func (h *GameHandler) HandleMessage(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理游戏消息", map[string]interface{}{ "message_type": message.Header.MessageType, "player_id": message.Header.PlayerID, }) switch message.Header.MessageType { case protocol.MsgPlayerLogin: return h.handlePlayerLogin(session, message) case protocol.MsgPlayerLogout: return h.handlePlayerLogout(session, message) case protocol.MsgPlayerMove: return h.handlePlayerMove(session, message) case protocol.MsgBattleSkill: return h.handleSkillCast(session, message) case protocol.MsgPlayerStatus: return h.handlePlayerChat(session, message) case protocol.MsgPlayerStats: return h.handlePlayerAction(session, message) case protocol.MsgChatMessage: return h.handleChatMessage(session, message) case protocol.MsgTeamCreate: return h.handleTeamCreate(session, message) case protocol.MsgTeamJoin: return h.handleTeamJoin(session, message) case protocol.MsgTeamLeave: return h.handleTeamLeave(session, message) case protocol.MsgTeamInfo: return h.handleTeamInfo(session, message) default: return fmt.Errorf("未知的消息类型: %d", message.Header.MessageType) } } // handlePlayerLogin 处理玩家登录 func (h *GameHandler) handlePlayerLogin(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理玩家登录", map[string]interface{}{ "player_id": message.Header.PlayerID, }) // 解析请求负载 var req protocol.PlayerLoginRequest if payloadMap, ok := message.Payload.(map[string]interface{}); ok { if b, err := json.Marshal(payloadMap); err == nil { _ = json.Unmarshal(b, &req) } } // 简化绑定:假设协议中的 PlayerID 为实体ID或可转换的数字ID var entityID int32 var characterID int64 if req.PlayerID != "" { if id64, err := strconv.ParseInt(req.PlayerID, 10, 64); err == nil { entityID = int32(id64) characterID = id64 } } if entityID == 0 && message.Header.PlayerID != 0 { entityID = int32(message.Header.PlayerID) characterID = int64(message.Header.PlayerID) } // 绑定会话与玩家 if entityID != 0 && h.connManager != nil { h.connManager.BindPlayer(entityID, session) } session.SetUserID(req.PlayerID) // 推断地图ID与位置:优先从角色服务加载持久化位置 var mapID int32 = 1 var x, y, z float32 = 0, 0, 0 if h.characterService != nil && characterID != 0 { if dbChar, err := h.characterService.GetCharacter(context.Background(), characterID); err == nil && dbChar != nil { mapID = dbChar.MapID x, y, z = dbChar.PositionX, dbChar.PositionY, dbChar.PositionZ } } // 允许客户端覆盖map_id(可选协议字段) if payloadMap, ok := message.Payload.(map[string]interface{}); ok { if v, ok := payloadMap["map_id"]; ok { switch vv := v.(type) { case float64: mapID = int32(vv) case string: if id64, err := strconv.ParseInt(vv, 10, 32); err == nil { mapID = int32(id64) } } } } session.SetGroupID(fmt.Sprintf("map:%d", mapID)) // 确保地图加载并注册入地图(以便后续移动/AOI广播可用) if h.mapService != nil && entityID != 0 { _ = h.mapService.LoadMap(context.Background(), mapID) e := character.NewEntity(character.EntityID(entityID), character.EntityTypePlayer, 1, character.NewVector3(0, 0, 0), character.NewVector3(0, 0, 1)) _ = h.mapService.EnterMap(context.Background(), e, mapID, x, y, z) } // 构造登录响应 resp := &protocol.Message{ Header: protocol.MessageHeader{ Magic: protocol.MessageMagic, MessageID: message.Header.MessageID, MessageType: uint32(protocol.MsgPlayerLogin), Flags: protocol.FlagResponse, PlayerID: message.Header.PlayerID, Timestamp: time.Now().Unix(), Sequence: 0, }, Payload: protocol.PlayerLoginResponse{ BaseResponse: protocol.NewBaseResponse(true, "login ok"), SessionID: session.ID, ServerTime: time.Now().Unix(), }, } data, err := json.Marshal(resp) if err != nil { return fmt.Errorf("序列化登录响应失败: %w", err) } return session.Send(data) } // handlePlayerLogout 处理玩家登出 func (h *GameHandler) handlePlayerLogout(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理玩家登出", map[string]interface{}{ "player_id": message.Header.PlayerID, }) // 清理绑定 if h.connManager != nil { // 尝试从会话反查实体ID if entityID, ok := h.connManager.GetPlayerBySession(session.ID); ok { // 尝试从GroupID解析地图,并从地图移除实体 var mapID int32 = 1 if h.mapService != nil { if gid := session.GetGroupID(); gid != "" { if len(gid) > 4 && gid[:4] == "map:" { if v, err := strconv.ParseInt(gid[4:], 10, 32); err == nil { mapID = int32(v) } } } // 从地图中获取最终位置并保存 if h.characterService != nil && mapID > 0 { if m, err := h.mapService.GetMap(mapID); err == nil && m != nil { if e := m.GetEntity(character.EntityID(entityID)); e != nil { pos := e.Position() _ = h.characterService.UpdateLastLocation( context.Background(), int64(entityID), mapID, pos.X, pos.Y, pos.Z, ) } } } _ = h.mapService.LeaveMapByID(context.Background(), mapID, entityID) } h.connManager.UnbindPlayer(entityID) } else if message.Header.PlayerID != 0 { h.connManager.UnbindPlayer(int32(message.Header.PlayerID)) } } return nil } // handlePlayerMove 处理玩家移动 func (h *GameHandler) handlePlayerMove(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理玩家移动", map[string]interface{}{ "player_id": message.Header.PlayerID, }) if h.mapService == nil || h.connManager == nil { return fmt.Errorf("map service or connection manager not ready") } // 解析请求 var req protocol.PlayerMoveRequest if payloadMap, ok := message.Payload.(map[string]interface{}); ok { if b, err := json.Marshal(payloadMap); err == nil { _ = json.Unmarshal(b, &req) } } // 获取玩家绑定的实体ID entityID, ok := h.connManager.GetPlayerBySession(session.ID) if !ok { // 回退到Header PlayerID entityID = int32(message.Header.PlayerID) } if entityID == 0 { return fmt.Errorf("no bound entity for session") } // 推断mapID: 从会话GroupID形如"map:"中解析;否则默认1 var mapID int32 = 1 if gid := session.GetGroupID(); gid != "" { if len(gid) > 4 && gid[:4] == "map:" { if v, err := strconv.ParseInt(gid[4:], 10, 32); err == nil { mapID = int32(v) } } } // 执行位置更新 if err := h.mapService.UpdatePositionByID( context.Background(), mapID, entityID, float32(req.Position.X), float32(req.Position.Y), float32(req.Position.Z), ); err != nil { return err } // 回执 resp := &protocol.Message{ Header: protocol.MessageHeader{ Magic: protocol.MessageMagic, MessageID: message.Header.MessageID, MessageType: uint32(protocol.MsgPlayerMove), Flags: protocol.FlagResponse, PlayerID: message.Header.PlayerID, Timestamp: time.Now().Unix(), }, Payload: protocol.PlayerMoveResponse{BaseResponse: protocol.NewBaseResponse(true, "move ok")}, } data, err := json.Marshal(resp) if err != nil { return fmt.Errorf("序列化移动响应失败: %w", err) } return session.Send(data) } // handlePlayerChat 处理玩家聊天 func (h *GameHandler) handlePlayerChat(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理玩家聊天", map[string]interface{}{ "player_id": message.Header.PlayerID, }) // TODO: 实现玩家聊天逻辑 // 1. 验证聊天内容 // 2. 过滤敏感词 // 3. 广播聊天消息 return nil } // handlePlayerAction 处理玩家动作 func (h *GameHandler) handlePlayerAction(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理玩家动作", logging.Fields{ "player_id": message.Header.PlayerID, }) // TODO: 实现玩家动作逻辑 // 1. 验证动作合法性 // 2. 执行动作 // 3. 更新游戏状态 return nil } // handleSkillCast 处理技能释放(占位实现,待接FightService) func (h *GameHandler) handleSkillCast(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理技能释放", logging.Fields{ "player_id": message.Header.PlayerID, "message_id": message.Header.MessageID, "message_type": message.Header.MessageType, }) // 解析payload允许简单格式: { "skill_id": string|number, "target_id": string|number } var skillIDStr string var targetIDStr string var skillID int32 var targetID int32 if payloadMap, ok := message.Payload.(map[string]interface{}); ok { if v, ok := payloadMap["skill_id"]; ok { switch vv := v.(type) { case string: skillIDStr = vv case float64: skillID = int32(vv) } } if v, ok := payloadMap["target_id"]; ok { switch vv := v.(type) { case string: targetIDStr = vv case float64: targetID = int32(vv) } } } if skillID == 0 && skillIDStr != "" { if id64, err := strconv.ParseInt(skillIDStr, 10, 32); err == nil { skillID = int32(id64) } } if targetID == 0 && targetIDStr != "" { if id64, err := strconv.ParseInt(targetIDStr, 10, 32); err == nil { targetID = int32(id64) } } // 获取施法者实体ID casterID, ok := h.connManager.GetPlayerBySession(session.ID) if !ok { casterID = int32(message.Header.PlayerID) } // 调用战斗服务计算伤害 var castResult *appServices.SkillCastResult var castErr error if h.fightService != nil { castResult, castErr = h.fightService.CastSkillByID(context.Background(), casterID, targetID, skillID) } // 回执 resp := &protocol.Message{ Header: protocol.MessageHeader{ Magic: protocol.MessageMagic, MessageID: message.Header.MessageID, MessageType: uint32(protocol.MsgBattleSkill), Flags: protocol.FlagResponse, PlayerID: message.Header.PlayerID, Timestamp: time.Now().Unix(), }, Payload: map[string]interface{}{ "result": func() interface{} { if castErr != nil { return protocol.NewBaseResponse(false, castErr.Error()) } return protocol.NewBaseResponse(true, "cast ok") }(), "skill_id": func() interface{} { if skillID != 0 { return skillID } return skillIDStr }(), "target_id": func() interface{} { if targetID != 0 { return targetID } return targetIDStr }(), "caster_id": casterID, "damage": func() int32 { if castResult != nil { return castResult.Damage } return 0 }(), "is_critical": func() bool { if castResult != nil { return castResult.IsCritical } return false }(), }, } data, err := json.Marshal(resp) if err != nil { return fmt.Errorf("序列化技能响应失败: %w", err) } if err := session.Send(data); err != nil { return err } // 施法广播给AOI内玩家 if h.mapService != nil { // 推断mapID var mapID int32 = 1 if gid := session.GetGroupID(); gid != "" { if len(gid) > 4 && gid[:4] == "map:" { if v, err := strconv.ParseInt(gid[4:], 10, 32); err == nil { mapID = int32(v) } } } payload := map[string]interface{}{ "caster_id": casterID, "skill_id": func() interface{} { if skillID != 0 { return skillID } return skillIDStr }(), "target_id": func() interface{} { if targetID != 0 { return targetID } return targetIDStr }(), "ts": time.Now().UnixMilli(), } if m, err := h.mapService.GetMap(mapID); err == nil { ents := m.GetAllEntities() recvs := make([]character.EntityID, 0, len(ents)) for _, e := range ents { recvs = append(recvs, e.ID()) } m.BroadcastTo(recvs, "skill_cast", payload) } } return nil } // (removed unused helper) // handleChatMessage 处理聊天消息 func (h *GameHandler) handleChatMessage(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理聊天消息", logging.Fields{ "player_id": message.Header.PlayerID, "message_id": message.Header.MessageID, "message_type": message.Header.MessageType, }) // 简单回执响应 resp := &protocol.Message{ Header: protocol.MessageHeader{ Magic: protocol.MessageMagic, MessageID: message.Header.MessageID, MessageType: uint32(protocol.MsgChatMessage), Flags: protocol.FlagResponse, PlayerID: message.Header.PlayerID, Timestamp: message.Header.Timestamp, Sequence: 0, }, Payload: protocol.NewBaseResponse(true, "chat received"), } data, err := json.Marshal(resp) if err != nil { return fmt.Errorf("序列化聊天响应失败: %w", err) } return session.Send(data) } // handleTeamCreate 处理创建队伍 func (h *GameHandler) handleTeamCreate(session *connection.Session, message *protocol.Message) error { return h.replyOK(session, message, uint32(protocol.MsgTeamCreate), "team created") } // handleTeamJoin 处理加入队伍 func (h *GameHandler) handleTeamJoin(session *connection.Session, message *protocol.Message) error { return h.replyOK(session, message, uint32(protocol.MsgTeamJoin), "team joined") } // handleTeamLeave 处理离开队伍 func (h *GameHandler) handleTeamLeave(session *connection.Session, message *protocol.Message) error { return h.replyOK(session, message, uint32(protocol.MsgTeamLeave), "team left") } // handleTeamInfo 处理队伍信息 func (h *GameHandler) handleTeamInfo(session *connection.Session, message *protocol.Message) error { return h.replyOK(session, message, uint32(protocol.MsgTeamInfo), "team info") } // replyOK 通用成功回执 func (h *GameHandler) replyOK(session *connection.Session, message *protocol.Message, msgType uint32, text string) error { resp := &protocol.Message{ Header: protocol.MessageHeader{ Magic: protocol.MessageMagic, MessageID: message.Header.MessageID, MessageType: msgType, Flags: protocol.FlagResponse, PlayerID: message.Header.PlayerID, Timestamp: message.Header.Timestamp, Sequence: 0, }, Payload: protocol.NewBaseResponse(true, text), } data, err := json.Marshal(resp) if err != nil { return fmt.Errorf("序列化响应失败: %w", err) } return session.Send(data) } // SendResponse 发送响应 func (h *GameHandler) SendResponse(playerID uint64, responseType uint32, data interface{}) error { _ = &protocol.Message{ Header: protocol.MessageHeader{ MessageType: responseType, PlayerID: playerID, }, Payload: data, } // TODO: 实现发送响应逻辑 h.logger.Info("发送响应", logging.Fields{ "player_id": playerID, "response_type": responseType, }) return nil } ================================================ FILE: internal/interfaces/tcp/npc_handler.go ================================================ package tcp import ( "fmt" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/interfaces/tcp/connection" "greatestworks/internal/interfaces/tcp/protocol" ) // NPCHandler NPC处理器 type NPCHandler struct { logger logging.Logger } // NewNPCHandler 创建NPC处理器 func NewNPCHandler(logger logging.Logger) *NPCHandler { return &NPCHandler{ logger: logger, } } // HandleMessage 处理消息 func (h *NPCHandler) HandleMessage(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理NPC消息", map[string]interface{}{ "message_type": message.Header.MessageType, "player_id": message.Header.PlayerID, }) switch message.Header.MessageType { case uint32(protocol.MsgPlayerInfo): return h.handleNPCInteraction(session, message) case uint32(protocol.MsgQuestAccept): return h.handleNPCQuest(session, message) case uint32(protocol.MsgItemTrade): return h.handleNPCTrade(session, message) default: return fmt.Errorf("未知的NPC消息类型: %d", message.Header.MessageType) } } // handleNPCInteraction 处理NPC交互 func (h *NPCHandler) handleNPCInteraction(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理NPC交互", map[string]interface{}{ "player_id": message.Header.PlayerID, }) // TODO: 实现NPC交互逻辑 // 1. 验证NPC存在 // 2. 检查交互条件 // 3. 执行交互逻辑 // 4. 发送响应 return nil } // handleNPCQuest 处理NPC任务 func (h *NPCHandler) handleNPCQuest(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理NPC任务", map[string]interface{}{ "player_id": message.Header.PlayerID, }) // TODO: 实现NPC任务逻辑 // 1. 验证任务存在 // 2. 检查任务条件 // 3. 执行任务逻辑 // 4. 发送响应 return nil } // handleNPCTrade 处理NPC交易 func (h *NPCHandler) handleNPCTrade(session *connection.Session, message *protocol.Message) error { h.logger.Info("处理NPC交易", map[string]interface{}{ "player_id": message.Header.PlayerID, }) // TODO: 实现NPC交易逻辑 // 1. 验证交易物品 // 2. 检查交易条件 // 3. 执行交易逻辑 // 4. 发送响应 return nil } ================================================ FILE: internal/interfaces/tcp/pet_handler.go ================================================ package tcp import ( "context" "fmt" "greatestworks/internal/application/services" "greatestworks/internal/interfaces/tcp/protocol" "greatestworks/internal/network/session" ) // PetHandler 宠物TCP处理器 type PetHandler struct { petService *services.PetApplicationService } // NewPetHandler 创建宠物处理器 func NewPetHandler(petService *services.PetApplicationService) *PetHandler { return &PetHandler{ petService: petService, } } // HandleCreatePet 处理创建宠物请求 func (h *PetHandler) HandleCreatePet(ctx context.Context, req *protocol.CreatePetRequest) (*protocol.CreatePetResponse, error) { // TODO: 实现宠物创建功能 return nil, fmt.Errorf("pet creation not implemented") } // HandleFeedPet 处理喂养宠物请求 func (h *PetHandler) HandleFeedPet(ctx context.Context, req *protocol.FeedPetRequest) (*protocol.FeedPetResponse, error) { // TODO: 实现宠物喂养功能 return nil, fmt.Errorf("pet feeding not implemented") } // HandleTrainPet 处理训练宠物请求 func (h *PetHandler) HandleTrainPet(ctx context.Context, req *protocol.TrainPetRequest) (*protocol.TrainPetResponse, error) { // TODO: 实现宠物训练功能 return nil, fmt.Errorf("pet training not implemented") } // HandleGetPet 处理获取宠物请求 func (h *PetHandler) HandleGetPet(ctx context.Context, req *protocol.GetPetRequest) (*protocol.GetPetResponse, error) { // TODO: 实现获取宠物功能 return nil, fmt.Errorf("get pet not implemented") } // HandleGetPlayerPets 处理获取玩家宠物列表请求 func (h *PetHandler) HandleGetPlayerPets(ctx context.Context, req *protocol.GetPlayerPetsRequest) (*protocol.GetPlayerPetsResponse, error) { // TODO: 实现获取玩家宠物列表功能 return nil, fmt.Errorf("get player pets not implemented") } // HandleEvolvePet 处理宠物进化请求 func (h *PetHandler) HandleEvolvePet(ctx context.Context, req *protocol.EvolvePetRequest) (*protocol.EvolvePetResponse, error) { // TODO: 实现宠物进化功能 return nil, fmt.Errorf("pet evolution not implemented") } // HandleEquipPetSkin 处理装备宠物皮肤请求 func (h *PetHandler) HandleEquipPetSkin(ctx context.Context, req *protocol.EquipPetSkinRequest) (*protocol.EquipPetSkinResponse, error) { // TODO: 实现宠物皮肤装备功能 return nil, fmt.Errorf("pet skin equipment not implemented") } // HandleSynthesizePet 处理宠物合成请求 func (h *PetHandler) HandleSynthesizePet(ctx context.Context, req *protocol.SynthesizePetRequest) (*protocol.SynthesizePetResponse, error) { // TODO: 实现宠物合成功能 return nil, fmt.Errorf("pet synthesis not implemented") } // RegisterHandlers 注册处理器到路由器 func (h *PetHandler) RegisterHandlers(router *protocol.TCPRouter) { // TODO: 实现路由器注册功能 } // 消息处理器包装函数 func (h *PetHandler) handleCreatePetMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现创建宠物消息处理 return fmt.Errorf("create pet message not implemented") } func (h *PetHandler) handleFeedPetMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现喂养宠物消息处理 return fmt.Errorf("feed pet message not implemented") } func (h *PetHandler) handleTrainPetMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现训练宠物消息处理 return fmt.Errorf("train pet message not implemented") } func (h *PetHandler) handleGetPetMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现获取宠物消息处理 return fmt.Errorf("get pet message not implemented") } func (h *PetHandler) handleGetPlayerPetsMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现获取玩家宠物列表消息处理 return fmt.Errorf("get player pets message not implemented") } func (h *PetHandler) handleEvolvePetMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现宠物进化消息处理 return fmt.Errorf("evolve pet message not implemented") } func (h *PetHandler) handleEquipPetSkinMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现宠物皮肤装备消息处理 return fmt.Errorf("equip pet skin message not implemented") } func (h *PetHandler) handleSynthesizePetMessage(ctx context.Context, session session.Session, packet *protocol.Message) error { // TODO: 实现宠物合成消息处理 return fmt.Errorf("synthesize pet message not implemented") } ================================================ FILE: internal/interfaces/tcp/protocol/base_protocol.go ================================================ package protocol import ( "context" "encoding/json" "fmt" "net" "sync" "time" ) // BaseMessage 基础消息结构 type BaseMessage struct { ID string `json:"id"` Type string `json:"type"` Data json.RawMessage `json:"data,omitempty"` Timestamp int64 `json:"timestamp"` PlayerID uint64 `json:"player_id,omitempty"` } // Response 响应消息结构 type Response struct { ID string `json:"id"` Type string `json:"type"` Success bool `json:"success"` Data interface{} `json:"data,omitempty"` Error *ErrorInfo `json:"error,omitempty"` Timestamp int64 `json:"timestamp"` } // ErrorInfo 错误信息 type ErrorInfo struct { Code string `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` } // Notification 通知消息结构 type Notification struct { Type string `json:"type"` Data interface{} `json:"data"` Timestamp int64 `json:"timestamp"` PlayerID uint64 `json:"player_id,omitempty"` } // 通用消息类型 const ( // 连接相关 MsgTypeConnect = "connect" MsgTypeDisconnect = "disconnect" MsgTypePing = "ping" MsgTypePong = "pong" // 认证相关 MsgTypeAuth = "auth" MsgTypeAuthSuccess = "auth_success" MsgTypeAuthFailed = "auth_failed" // 错误相关 MsgTypeError = "error" // 通知相关 MsgTypeNotification = "notification" ) // 错误代码 const ( ErrorCodeInvalidMessage = "INVALID_MESSAGE" ErrorCodeUnauthorized = "UNAUTHORIZED" ErrorCodeNotFound = "NOT_FOUND" ErrorCodeInternalError = "INTERNAL_ERROR" ErrorCodeInvalidParameter = "INVALID_PARAMETER" ErrorCodePermissionDenied = "PERMISSION_DENIED" ErrorCodeRateLimit = "RATE_LIMIT" ErrorCodeServiceUnavailable = "SERVICE_UNAVAILABLE" ) // ConnectRequest 连接请求 type ConnectRequest struct { ClientID string `json:"client_id"` Version string `json:"version"` Platform string `json:"platform,omitempty"` DeviceID string `json:"device_id,omitempty"` Compression bool `json:"compression,omitempty"` } // ConnectResponse 连接响应 type ConnectResponse struct { SessionID string `json:"session_id"` ServerTime int64 `json:"server_time"` Heartbeat int32 `json:"heartbeat"` Compression bool `json:"compression"` } // 认证相关的结构体已在message_types.go中定义 // PingRequest已在game_protocol.go中定义 // PongResponse Pong响应 type PongResponse struct { Timestamp int64 `json:"timestamp"` ServerTime int64 `json:"server_time"` RoundTrip int64 `json:"round_trip,omitempty"` } // TCPConnection TCP连接封装 type TCPConnection struct { conn net.Conn sessionID string playerID uint64 isAuth bool lastPing time.Time mutex sync.RWMutex closed bool ctx context.Context cancel context.CancelFunc messageChan chan []byte } // NewTCPConnection 创建TCP连接 func NewTCPConnection(conn net.Conn, sessionID string) *TCPConnection { ctx, cancel := context.WithCancel(context.Background()) return &TCPConnection{ conn: conn, sessionID: sessionID, lastPing: time.Now(), ctx: ctx, cancel: cancel, messageChan: make(chan []byte, 100), } } // GetSessionID 获取会话ID func (c *TCPConnection) GetSessionID() string { c.mutex.RLock() defer c.mutex.RUnlock() return c.sessionID } // GetPlayerID 获取玩家ID func (c *TCPConnection) GetPlayerID() uint64 { c.mutex.RLock() defer c.mutex.RUnlock() return c.playerID } // SetPlayerID 设置玩家ID func (c *TCPConnection) SetPlayerID(playerID uint64) { c.mutex.Lock() defer c.mutex.Unlock() c.playerID = playerID } // IsAuthenticated 检查是否已认证 func (c *TCPConnection) IsAuthenticated() bool { c.mutex.RLock() defer c.mutex.RUnlock() return c.isAuth } // SetAuthenticated 设置认证状态 func (c *TCPConnection) SetAuthenticated(auth bool) { c.mutex.Lock() defer c.mutex.Unlock() c.isAuth = auth } // IsClosed 检查连接是否已关闭 func (c *TCPConnection) IsClosed() bool { c.mutex.RLock() defer c.mutex.RUnlock() return c.closed } // UpdatePing 更新Ping时间 func (c *TCPConnection) UpdatePing() { c.mutex.Lock() defer c.mutex.Unlock() c.lastPing = time.Now() } // GetLastPing 获取最后Ping时间 func (c *TCPConnection) GetLastPing() time.Time { c.mutex.RLock() defer c.mutex.RUnlock() return c.lastPing } // SendMessage 发送消息 func (c *TCPConnection) SendMessage(msg *Message) error { c.mutex.RLock() if c.closed { c.mutex.RUnlock() return fmt.Errorf("connection is closed") } c.mutex.RUnlock() data, err := json.Marshal(msg) if err != nil { return fmt.Errorf("failed to marshal message: %w", err) } select { case c.messageChan <- data: return nil case <-c.ctx.Done(): return fmt.Errorf("connection context cancelled") default: return fmt.Errorf("message channel is full") } } // SendResponse 发送响应 func (c *TCPConnection) SendResponse(msgID, msgType string, data interface{}) error { resp := &Response{ ID: msgID, Type: msgType, Success: true, Data: data, Timestamp: time.Now().Unix(), } // 创建消息头 header := MessageHeader{ MessageType: 1, // 响应消息类型 MessageID: uint32(time.Now().UnixNano()), Timestamp: time.Now().Unix(), Length: uint32(len(mustMarshal(resp))), } // 创建消息 message := &Message{ Header: header, Payload: mustMarshal(resp), } return c.SendMessage(message) } // SendError 发送错误响应 func (c *TCPConnection) SendError(msgID, msgType, errorCode, errorMessage string) error { resp := &Response{ ID: msgID, Type: msgType, Success: false, Error: &ErrorInfo{ Code: errorCode, Message: errorMessage, }, Timestamp: time.Now().Unix(), } // 创建消息头 header := MessageHeader{ MessageType: 2, // 错误消息类型 MessageID: uint32(time.Now().UnixNano()), Timestamp: time.Now().Unix(), Length: uint32(len(mustMarshal(resp))), } // 创建消息 message := &Message{ Header: header, Payload: mustMarshal(resp), } return c.SendMessage(message) } // SendNotification 发送通知 func (c *TCPConnection) SendNotification(notificationType string, data interface{}) error { notification := &Notification{ Type: notificationType, Data: data, Timestamp: time.Now().Unix(), PlayerID: c.GetPlayerID(), } // 创建消息头 header := MessageHeader{ MessageType: 3, // 通知消息类型 MessageID: uint32(time.Now().UnixNano()), Timestamp: time.Now().Unix(), Length: uint32(len(mustMarshal(notification))), } // 创建消息 message := &Message{ Header: header, Payload: mustMarshal(notification), } return c.SendMessage(message) } // ReadMessage 读取消息 func (c *TCPConnection) ReadMessage() (*Message, error) { c.mutex.RLock() if c.closed { c.mutex.RUnlock() return nil, fmt.Errorf("connection is closed") } c.mutex.RUnlock() // 设置读取超时 c.conn.SetReadDeadline(time.Now().Add(30 * time.Second)) // 读取消息长度(4字节) lengthBytes := make([]byte, 4) if _, err := c.conn.Read(lengthBytes); err != nil { return nil, fmt.Errorf("failed to read message length: %w", err) } // 解析消息长度 length := int(lengthBytes[0])<<24 | int(lengthBytes[1])<<16 | int(lengthBytes[2])<<8 | int(lengthBytes[3]) if length <= 0 || length > 1024*1024 { // 最大1MB return nil, fmt.Errorf("invalid message length: %d", length) } // 读取消息内容 msgBytes := make([]byte, length) if _, err := c.conn.Read(msgBytes); err != nil { return nil, fmt.Errorf("failed to read message data: %w", err) } // 解析消息 var msg Message if err := json.Unmarshal(msgBytes, &msg); err != nil { return nil, fmt.Errorf("failed to unmarshal message: %w", err) } return &msg, nil } // WriteMessage 写入消息 func (c *TCPConnection) WriteMessage(data []byte) error { c.mutex.RLock() if c.closed { c.mutex.RUnlock() return fmt.Errorf("connection is closed") } c.mutex.RUnlock() // 设置写入超时 c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) // 写入消息长度(4字节) length := len(data) lengthBytes := []byte{ byte(length >> 24), byte(length >> 16), byte(length >> 8), byte(length), } if _, err := c.conn.Write(lengthBytes); err != nil { return fmt.Errorf("failed to write message length: %w", err) } // 写入消息内容 if _, err := c.conn.Write(data); err != nil { return fmt.Errorf("failed to write message data: %w", err) } return nil } // StartMessageWriter 启动消息写入协程 func (c *TCPConnection) StartMessageWriter() { go func() { for { select { case data := <-c.messageChan: if err := c.WriteMessage(data); err != nil { // 写入失败,关闭连接 c.Close() return } case <-c.ctx.Done(): return } } }() } // Close 关闭连接 func (c *TCPConnection) Close() error { c.mutex.Lock() defer c.mutex.Unlock() if c.closed { return nil } c.closed = true c.cancel() close(c.messageChan) return c.conn.Close() } // GetRemoteAddr 获取远程地址 func (c *TCPConnection) GetRemoteAddr() net.Addr { return c.conn.RemoteAddr() } // 辅助函数 func mustMarshal(v interface{}) json.RawMessage { data, err := json.Marshal(v) if err != nil { panic(fmt.Sprintf("failed to marshal: %v", err)) } return data } // MessageHandler 消息处理器接口 type MessageHandler func(ctx context.Context, conn *TCPConnection, msg *Message) error // TCPRouter TCP路由器 type TCPRouter struct { handlers map[uint16]MessageHandler mutex sync.RWMutex } // NewTCPRouter 创建TCP路由器 func NewTCPRouter() *TCPRouter { return &TCPRouter{ handlers: make(map[uint16]MessageHandler), } } // RegisterHandler 注册消息处理器 func (r *TCPRouter) RegisterHandler(msgType uint16, handler MessageHandler) { r.mutex.Lock() defer r.mutex.Unlock() r.handlers[msgType] = handler } // HandleMessage 处理消息 func (r *TCPRouter) HandleMessage(ctx context.Context, conn *TCPConnection, msg *Message) error { r.mutex.RLock() handler, exists := r.handlers[uint16(msg.Header.MessageType)] r.mutex.RUnlock() if !exists { return fmt.Errorf("no handler for message type: %d", msg.Header.MessageType) } return handler(ctx, conn, msg) } // GetHandlers 获取所有处理器 func (r *TCPRouter) GetHandlers() map[uint16]MessageHandler { r.mutex.RLock() defer r.mutex.RUnlock() handlers := make(map[uint16]MessageHandler) for k, v := range r.handlers { handlers[k] = v } return handlers } ================================================ FILE: internal/interfaces/tcp/protocol/errors.go ================================================ package protocol import ( "errors" protoerrors "greatestworks/internal/proto/errors" ) // Error codes - 使用proto生成的常量 const ( ErrCodeInvalidMessage = int32(protoerrors.CommonErrorCode_ERR_INVALID_MESSAGE) ErrCodeAuthFailed = int32(protoerrors.CommonErrorCode_ERR_AUTH_FAILED) ErrCodePlayerNotFound = int32(protoerrors.CommonErrorCode_ERR_PLAYER_NOT_FOUND) ErrCodeBattleNotFound = int32(protoerrors.CommonErrorCode_ERR_BATTLE_NOT_FOUND) ErrCodeUnknownMessage = int32(protoerrors.CommonErrorCode_ERR_UNKNOWN_MESSAGE) ErrCodeServerBusy = int32(protoerrors.CommonErrorCode_ERR_SERVER_BUSY) ErrCodeInvalidPlayer = int32(protoerrors.CommonErrorCode_ERR_INVALID_PLAYER) ErrCodeUnknown = int32(protoerrors.CommonErrorCode_ERR_UNKNOWN) ) // Error definitions for protocol var ( ErrInvalidMessage = errors.New("invalid message") ErrAuthFailed = errors.New("authentication failed") ErrPlayerNotFound = errors.New("player not found") ErrBattleNotFound = errors.New("battle not found") ErrUnknownMessage = errors.New("unknown message type") ErrServerBusy = errors.New("server busy") ErrInvalidPlayer = errors.New("invalid player") ) ================================================ FILE: internal/interfaces/tcp/protocol/game_protocol.go ================================================ package protocol import ( "fmt" "greatestworks/internal/proto/messages" "time" ) // 协议消息类型定义 - 使用proto生成的常量 const ( // 玩家相关协议 (0x1000 - 0x1FFF) - 使用proto生成的常量 MsgPlayerLogin uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_LOGIN) MsgPlayerLogout uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_LOGOUT) MsgPlayerMove uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_MOVE) MsgPlayerInfo uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_INFO) MsgPlayerCreate uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_CREATE) MsgPlayerUpdate uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_UPDATE) MsgPlayerDelete uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_DELETE) MsgPlayerLevelUp uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_LEVEL) MsgPlayerExpGain uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_EXP_GAIN) MsgPlayerStatusSync uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_SYNC) MsgPlayerStatus uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_STATUS) MsgPlayerStats uint32 = uint32(messages.PlayerMessageID_MSG_PLAYER_STATS) // 战斗相关协议 (0x2000 - 0x2FFF) - 使用proto生成的常量 MsgCreateBattle uint32 = uint32(messages.BattleMessageID_MSG_CREATE_BATTLE) MsgJoinBattle uint32 = uint32(messages.BattleMessageID_MSG_JOIN_BATTLE) MsgStartBattle uint32 = uint32(messages.BattleMessageID_MSG_START_BATTLE) MsgBattleAction uint32 = uint32(messages.BattleMessageID_MSG_BATTLE_ACTION) MsgLeaveBattle uint32 = uint32(messages.BattleMessageID_MSG_LEAVE_BATTLE) MsgBattleResult uint32 = uint32(messages.BattleMessageID_MSG_BATTLE_RESULT) MsgBattleStatus uint32 = uint32(messages.BattleMessageID_MSG_BATTLE_STATUS) MsgBattleRound uint32 = uint32(messages.BattleMessageID_MSG_BATTLE_ROUND) MsgBattleSkill uint32 = uint32(messages.BattleMessageID_MSG_SKILL_CAST) MsgBattleDamage uint32 = uint32(messages.BattleMessageID_MSG_DAMAGE_DEALT) // 查询相关协议 (0x3000 - 0x3FFF) - 使用proto生成的常量 MsgGetPlayerInfo uint32 = uint32(messages.QueryMessageID_MSG_GET_PLAYER_INFO) MsgGetOnlinePlayers uint32 = uint32(messages.QueryMessageID_MSG_GET_ONLINE_PLAYERS) MsgGetBattleInfo uint32 = uint32(messages.QueryMessageID_MSG_GET_BATTLE_INFO) MsgGetPlayerStats uint32 = uint32(messages.QueryMessageID_MSG_GET_PLAYER_INFO) // 使用玩家信息代替 MsgGetBattleList uint32 = uint32(messages.QueryMessageID_MSG_GET_BATTLE_INFO) // 使用战斗信息代替 MsgGetRankings uint32 = uint32(messages.QueryMessageID_MSG_GET_RANKING_LIST) MsgGetServerInfo uint32 = uint32(messages.QueryMessageID_MSG_GET_SERVER_INFO) // 系统相关协议 (0x9000 - 0x9FFF) // 使用message_types.go中定义的消息类型 ) // 基础协议结构 // BaseRequest 基础请求结构 type BaseRequest struct { RequestID string `json:"request_id,omitempty"` Timestamp int64 `json:"timestamp,omitempty"` } // BaseResponse 基础响应结构 type BaseResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` RequestID string `json:"request_id,omitempty"` Timestamp int64 `json:"timestamp,omitempty"` } // ErrorResponse 错误响应 type ErrorResponse struct { BaseResponse ErrorCode int `json:"error_code,omitempty"` ErrorType string `json:"error_type,omitempty"` } // 玩家协议结构 // PlayerLoginRequest 玩家登录请求 type PlayerLoginRequest struct { BaseRequest PlayerID string `json:"player_id"` Token string `json:"token"` Version string `json:"version,omitempty"` } // PlayerLoginResponse 玩家登录响应 type PlayerLoginResponse struct { BaseResponse Player *PlayerInfo `json:"player,omitempty"` SessionID string `json:"session_id,omitempty"` ServerTime int64 `json:"server_time,omitempty"` Permissions []string `json:"permissions,omitempty"` } // PlayerCreateRequest 创建玩家请求 type PlayerCreateRequest struct { BaseRequest Name string `json:"name"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty"` } // PlayerCreateResponse 创建玩家响应 type PlayerCreateResponse struct { BaseResponse PlayerID string `json:"player_id,omitempty"` Name string `json:"name,omitempty"` Level int `json:"level,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` } // PlayerMoveRequest 玩家移动请求 type PlayerMoveRequest struct { BaseRequest Position Position `json:"position"` Speed float64 `json:"speed,omitempty"` } // PlayerMoveResponse 玩家移动响应 type PlayerMoveResponse struct { BaseResponse OldPosition Position `json:"old_position,omitempty"` NewPosition Position `json:"new_position,omitempty"` MoveTime int64 `json:"move_time,omitempty"` } // PlayerInfoRequest 获取玩家信息请求 type PlayerInfoRequest struct { BaseRequest TargetPlayerID string `json:"target_player_id,omitempty"` } // PlayerInfoResponse 获取玩家信息响应 type PlayerInfoResponse struct { BaseResponse Player *PlayerInfo `json:"player,omitempty"` } // 战斗协议结构 // CreateBattleRequest 创建战斗请求 type CreateBattleRequest struct { BaseRequest BattleType int `json:"battle_type"` Settings *BattleSettings `json:"settings,omitempty"` Players []string `json:"players,omitempty"` Options map[string]string `json:"options,omitempty"` } // CreateBattleResponse 创建战斗响应 type CreateBattleResponse struct { BaseResponse BattleID string `json:"battle_id,omitempty"` BattleType int `json:"battle_type,omitempty"` Status string `json:"status,omitempty"` Settings *BattleSettings `json:"settings,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` } // JoinBattleRequest 加入战斗请求 type JoinBattleRequest struct { BaseRequest BattleID string `json:"battle_id"` Team int `json:"team,omitempty"` Position int `json:"position,omitempty"` } // JoinBattleResponse 加入战斗响应 type JoinBattleResponse struct { BaseResponse BattleID string `json:"battle_id,omitempty"` PlayerTeam int `json:"player_team,omitempty"` PlayerPos int `json:"player_position,omitempty"` BattleInfo *BattleInfo `json:"battle_info,omitempty"` OtherPlayers []*PlayerInfo `json:"other_players,omitempty"` } // BattleActionRequest 战斗行动请求 type BattleActionRequest struct { BaseRequest BattleID string `json:"battle_id"` ActionType string `json:"action_type"` TargetID string `json:"target_id,omitempty"` SkillID string `json:"skill_id,omitempty"` Params map[string]interface{} `json:"params,omitempty"` } // BattleActionResponse 战斗行动响应 type BattleActionResponse struct { BaseResponse BattleID string `json:"battle_id,omitempty"` ActionResult *ActionResult `json:"action_result,omitempty"` BattleState *BattleState `json:"battle_state,omitempty"` NextTurn string `json:"next_turn,omitempty"` } // 查询协议结构 // GetOnlinePlayersRequest 获取在线玩家请求 type GetOnlinePlayersRequest struct { BaseRequest Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` } // GetOnlinePlayersResponse 获取在线玩家响应 type GetOnlinePlayersResponse struct { BaseResponse Players []*PlayerInfo `json:"players,omitempty"` Total int `json:"total,omitempty"` Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` ServerTime int64 `json:"server_time,omitempty"` } // GetBattleInfoRequest 获取战斗信息请求 type GetBattleInfoRequest struct { BaseRequest BattleID string `json:"battle_id"` } // GetBattleInfoResponse 获取战斗信息响应 type GetBattleInfoResponse struct { BaseResponse BattleInfo *BattleInfo `json:"battle_info,omitempty"` } // 数据结构定义 // PlayerInfo 玩家信息 type PlayerInfo struct { ID string `json:"id"` Name string `json:"name"` Level int `json:"level"` Exp int64 `json:"exp"` Status string `json:"status"` Position Position `json:"position"` Stats Stats `json:"stats"` Avatar string `json:"avatar,omitempty"` Gender int `json:"gender,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LastActive time.Time `json:"last_active,omitempty"` } // Position 位置信息 type Position struct { X float64 `json:"x"` Y float64 `json:"y"` Z float64 `json:"z"` } // Stats 属性信息 type Stats struct { HP int `json:"hp"` MaxHP int `json:"max_hp"` MP int `json:"mp"` MaxMP int `json:"max_mp"` Attack int `json:"attack"` Defense int `json:"defense"` Speed int `json:"speed"` Crit int `json:"crit,omitempty"` Hit int `json:"hit,omitempty"` Dodge int `json:"dodge,omitempty"` } // BattleInfo 战斗信息 type BattleInfo struct { ID string `json:"id"` Type int `json:"type"` Status string `json:"status"` Players []*PlayerInfo `json:"players"` Settings *BattleSettings `json:"settings"` State *BattleState `json:"state,omitempty"` StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // BattleSettings 战斗设置 type BattleSettings struct { MaxPlayers int `json:"max_players"` TurnTimeout time.Duration `json:"turn_timeout"` MaxRounds int `json:"max_rounds"` AutoStart bool `json:"auto_start"` AllowSpectate bool `json:"allow_spectate"` TeamMode bool `json:"team_mode"` } // BattleState 战斗状态 type BattleState struct { CurrentRound int `json:"current_round"` CurrentTurn string `json:"current_turn"` TurnOrder []string `json:"turn_order"` PlayerStates map[string]*PlayerState `json:"player_states"` RoundHistory []*RoundResult `json:"round_history,omitempty"` } // PlayerState 玩家战斗状态 type PlayerState struct { PlayerID string `json:"player_id"` HP int `json:"hp"` MP int `json:"mp"` Status []string `json:"status,omitempty"` Buffs []*Buff `json:"buffs,omitempty"` Debuffs []*Debuff `json:"debuffs,omitempty"` SkillCDs map[string]int `json:"skill_cds,omitempty"` Position int `json:"position"` Team int `json:"team"` IsAlive bool `json:"is_alive"` LastAction *ActionResult `json:"last_action,omitempty"` } // ActionResult 行动结果 type ActionResult struct { ActionID string `json:"action_id"` PlayerID string `json:"player_id"` ActionType string `json:"action_type"` TargetID string `json:"target_id,omitempty"` SkillID string `json:"skill_id,omitempty"` Damage int `json:"damage,omitempty"` Healing int `json:"healing,omitempty"` Effects []*Effect `json:"effects,omitempty"` Success bool `json:"success"` Message string `json:"message,omitempty"` Timestamp time.Time `json:"timestamp"` Params map[string]interface{} `json:"params,omitempty"` } // RoundResult 回合结果 type RoundResult struct { RoundNumber int `json:"round_number"` Actions []*ActionResult `json:"actions"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Winner string `json:"winner,omitempty"` } // Buff 增益效果 type Buff struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` Duration int `json:"duration"` Effect *Effect `json:"effect"` StartTime time.Time `json:"start_time"` } // Debuff 减益效果 type Debuff struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` Duration int `json:"duration"` Effect *Effect `json:"effect"` StartTime time.Time `json:"start_time"` } // Effect 效果 type Effect struct { Type string `json:"type"` Value int `json:"value,omitempty"` Target string `json:"target,omitempty"` Duration int `json:"duration,omitempty"` Params map[string]interface{} `json:"params,omitempty"` Trigger string `json:"trigger,omitempty"` } // 系统协议结构 // HeartbeatRequest 心跳请求 type HeartbeatRequest struct { BaseRequest ClientTime int64 `json:"client_time"` } // HeartbeatResponse 心跳响应 type HeartbeatResponse struct { BaseResponse ServerTime int64 `json:"server_time"` Latency int64 `json:"latency,omitempty"` } // PingRequest Ping请求 type PingRequest struct { BaseRequest Sequence int `json:"sequence"` ClientTime int64 `json:"client_time"` } // PingResponse Ping响应 type PingResponse struct { BaseResponse Sequence int `json:"sequence"` ServerTime int64 `json:"server_time"` RoundTrip int64 `json:"round_trip,omitempty"` } // 协议工具函数 // NewBaseRequest 创建基础请求 func NewBaseRequest() BaseRequest { return BaseRequest{ Timestamp: time.Now().Unix(), } } // NewBaseResponse 创建基础响应 func NewBaseResponse(success bool, message string) BaseResponse { return BaseResponse{ Success: success, Message: message, Timestamp: time.Now().Unix(), } } // NewErrorResponse 创建错误响应 func NewErrorResponse(message string, errorCode int, errorType string) ErrorResponse { return ErrorResponse{ BaseResponse: NewBaseResponse(false, message), ErrorCode: errorCode, ErrorType: errorType, } } // IsValidMessageType 检查消息类型是否有效 func IsValidMessageType(msgType uint32) bool { switch { case msgType >= 0x1000 && msgType <= 0x1FFF: // 玩家协议 return true case msgType >= 0x2000 && msgType <= 0x2FFF: // 战斗协议 return true case msgType >= 0x3000 && msgType <= 0x3FFF: // 查询协议 return true case msgType >= 0x9000 && msgType <= 0x9FFF: // 系统协议 return true default: return false } } // GetMessageTypeName 获取消息类型名称 func GetMessageTypeName(msgType uint32) string { msgNames := map[uint32]string{ MsgPlayerLogin: "PlayerLogin", MsgPlayerLogout: "PlayerLogout", MsgPlayerMove: "PlayerMove", MsgPlayerInfo: "PlayerInfo", MsgPlayerCreate: "PlayerCreate", MsgCreateBattle: "CreateBattle", MsgJoinBattle: "JoinBattle", MsgStartBattle: "StartBattle", MsgBattleAction: "BattleAction", MsgLeaveBattle: "LeaveBattle", MsgGetPlayerInfo: "GetPlayerInfo", MsgGetOnlinePlayers: "GetOnlinePlayers", MsgGetBattleInfo: "GetBattleInfo", uint32(MsgHeartbeat): "Heartbeat", uint32(MsgPing): "Ping", uint32(MsgError): "Error", } if name, exists := msgNames[msgType]; exists { return name } return fmt.Sprintf("Unknown(0x%04X)", msgType) } ================================================ FILE: internal/interfaces/tcp/protocol/message_types.go ================================================ package protocol import ( "fmt" "greatestworks/internal/proto/messages" "greatestworks/internal/proto/protocol" ) // import ( // "time" // ) // 消息类型常量定义 - 使用proto生成的常量 const ( // 系统消息 (0x0000 - 0x00FF) MsgHeartbeat uint32 = uint32(messages.SystemMessageID_MSG_HEARTBEAT) MsgHandshake uint32 = uint32(messages.SystemMessageID_MSG_HANDSHAKE) MsgAuth uint32 = uint32(messages.SystemMessageID_MSG_AUTH) MsgDisconnect uint32 = uint32(messages.SystemMessageID_MSG_DISCONNECT) MsgError uint32 = uint32(messages.SystemMessageID_MSG_ERROR) MsgPing uint32 = uint32(messages.SystemMessageID_MSG_PING) MsgPong uint32 = uint32(messages.SystemMessageID_MSG_PONG) // 玩家相关消息 (0x0100 - 0x01FF) - 定义在game_protocol.go中 // 战斗相关消息 (0x0200 - 0x02FF) - 定义在game_protocol.go中 // 宠物相关消息 (0x0300 - 0x03FF) - 使用proto生成的常量 MsgPetSummon uint32 = uint32(messages.PetMessageID_MSG_PET_SUMMON) // 召唤宠物 MsgPetDismiss uint32 = uint32(messages.PetMessageID_MSG_PET_DISMISS) // 收回宠物 MsgPetInfo uint32 = uint32(messages.PetMessageID_MSG_PET_INFO) // 宠物信息 MsgPetMove uint32 = uint32(messages.PetMessageID_MSG_PET_MOVE) // 宠物移动 MsgPetAction uint32 = uint32(messages.PetMessageID_MSG_PET_ACTION) // 宠物行动 MsgPetLevelUp uint32 = uint32(messages.PetMessageID_MSG_PET_LEVEL_UP) // 宠物升级 MsgPetEvolution uint32 = uint32(messages.PetMessageID_MSG_PET_EVOLUTION) // 宠物进化 MsgPetTrain uint32 = uint32(messages.PetMessageID_MSG_PET_TRAIN) // 宠物训练 MsgPetFeed uint32 = uint32(messages.PetMessageID_MSG_PET_FEED) // 宠物喂养 MsgPetStatus uint32 = uint32(messages.PetMessageID_MSG_PET_STATUS) // 宠物状态 // 建筑相关消息 (0x0400 - 0x04FF) - 使用proto生成的常量 MsgBuildingCreate uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_CREATE) // 创建建筑 MsgBuildingUpgrade uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_UPGRADE) // 升级建筑 MsgBuildingDestroy uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_DESTROY) // 摧毁建筑 MsgBuildingInfo uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_INFO) // 建筑信息 MsgBuildingProduce uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_PRODUCE) // 建筑生产 MsgBuildingCollect uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_COLLECT) // 收集资源 MsgBuildingRepair uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_REPAIR) // 修复建筑 MsgBuildingStatus uint32 = uint32(messages.BuildingMessageID_MSG_BUILDING_STATUS) // 建筑状态 // 社交相关消息 (0x0500 - 0x05FF) - 使用proto生成的常量 MsgChatMessage uint32 = uint32(messages.SocialMessageID_MSG_CHAT_MESSAGE) // 聊天消息 MsgFriendRequest uint32 = uint32(messages.SocialMessageID_MSG_FRIEND_REQUEST) // 好友请求 MsgFriendAccept uint32 = uint32(messages.SocialMessageID_MSG_FRIEND_ACCEPT) // 接受好友 MsgFriendReject uint32 = uint32(messages.SocialMessageID_MSG_FRIEND_REJECT) // 拒绝好友 MsgFriendRemove uint32 = uint32(messages.SocialMessageID_MSG_FRIEND_REMOVE) // 删除好友 MsgFriendList uint32 = uint32(messages.SocialMessageID_MSG_FRIEND_LIST) // 好友列表 MsgGuildCreate uint32 = uint32(messages.SocialMessageID_MSG_GUILD_CREATE) // 创建公会 MsgGuildJoin uint32 = uint32(messages.SocialMessageID_MSG_GUILD_JOIN) // 加入公会 MsgGuildLeave uint32 = uint32(messages.SocialMessageID_MSG_GUILD_LEAVE) // 离开公会 MsgGuildInfo uint32 = uint32(messages.SocialMessageID_MSG_GUILD_INFO) // 公会信息 MsgTeamCreate uint32 = uint32(messages.SocialMessageID_MSG_TEAM_CREATE) // 创建队伍 MsgTeamJoin uint32 = uint32(messages.SocialMessageID_MSG_TEAM_JOIN) // 加入队伍 MsgTeamLeave uint32 = uint32(messages.SocialMessageID_MSG_TEAM_LEAVE) // 离开队伍 MsgTeamInfo uint32 = uint32(messages.SocialMessageID_MSG_TEAM_INFO) // 队伍信息 // 物品相关消息 (0x0600 - 0x06FF) - 使用proto生成的常量 MsgItemUse uint32 = uint32(messages.ItemMessageID_MSG_ITEM_USE) // 使用物品 MsgItemEquip uint32 = uint32(messages.ItemMessageID_MSG_ITEM_EQUIP) // 装备物品 MsgItemUnequip uint32 = uint32(messages.ItemMessageID_MSG_ITEM_UNEQUIP) // 卸下装备 MsgItemDrop uint32 = uint32(messages.ItemMessageID_MSG_ITEM_DROP) // 丢弃物品 MsgItemPickup uint32 = uint32(messages.ItemMessageID_MSG_ITEM_PICKUP) // 拾取物品 MsgItemTrade uint32 = uint32(messages.ItemMessageID_MSG_ITEM_TRADE) // 交易物品 MsgInventoryInfo uint32 = uint32(messages.ItemMessageID_MSG_INVENTORY_INFO) // 背包信息 MsgItemInfo uint32 = uint32(messages.ItemMessageID_MSG_ITEM_INFO) // 物品信息 MsgItemCraft uint32 = uint32(messages.ItemMessageID_MSG_ITEM_CRAFT) // 制作物品 MsgItemEnhance uint32 = uint32(messages.ItemMessageID_MSG_ITEM_ENHANCE) // 强化物品 // 任务相关消息 (0x0700 - 0x07FF) - 使用proto生成的常量 MsgQuestAccept uint32 = uint32(messages.QuestMessageID_MSG_QUEST_ACCEPT) // 接受任务 MsgQuestComplete uint32 = uint32(messages.QuestMessageID_MSG_QUEST_COMPLETE) // 完成任务 MsgQuestCancel uint32 = uint32(messages.QuestMessageID_MSG_QUEST_CANCEL) // 取消任务 MsgQuestProgress uint32 = uint32(messages.QuestMessageID_MSG_QUEST_PROGRESS) // 任务进度 MsgQuestList uint32 = uint32(messages.QuestMessageID_MSG_QUEST_LIST) // 任务列表 MsgQuestInfo uint32 = uint32(messages.QuestMessageID_MSG_QUEST_INFO) // 任务信息 MsgQuestReward uint32 = uint32(messages.QuestMessageID_MSG_QUEST_REWARD) // 任务奖励 // 查询相关消息 (0x0800 - 0x08FF) - 定义在game_protocol.go中 ) // 消息魔数 const MessageMagic uint32 = 0x47574B53 // "GWKS" - GreatestWorks // 消息头大小 const MessageHeaderSize = 32 // 消息头固定大小 // ParseMessageHeader 解析消息头 func ParseMessageHeader(data []byte) (*MessageHeader, error) { if len(data) < MessageHeaderSize { return nil, fmt.Errorf("invalid message header size: %d", len(data)) } header := &MessageHeader{} // 解析魔数 (4字节) header.Magic = uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]) if header.Magic != MessageMagic { return nil, fmt.Errorf("invalid message magic: 0x%08X", header.Magic) } // 解析消息ID (4字节) header.MessageID = uint32(data[4])<<24 | uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]) // 解析消息类型 (4字节) header.MessageType = uint32(data[8])<<24 | uint32(data[9])<<16 | uint32(data[10])<<8 | uint32(data[11]) // 解析标志位 (2字节) header.Flags = uint16(data[12])<<8 | uint16(data[13]) // 解析玩家ID (8字节) header.PlayerID = uint64(data[14])<<56 | uint64(data[15])<<48 | uint64(data[16])<<40 | uint64(data[17])<<32 | uint64(data[18])<<24 | uint64(data[19])<<16 | uint64(data[20])<<8 | uint64(data[21]) // 解析时间戳 (8字节) header.Timestamp = int64(data[22])<<56 | int64(data[23])<<48 | int64(data[24])<<40 | int64(data[25])<<32 | int64(data[26])<<24 | int64(data[27])<<16 | int64(data[28])<<8 | int64(data[29]) // 解析序列号 (4字节) header.Sequence = uint32(data[30])<<8 | uint32(data[31]) // 解析消息体长度 (4字节) - 这个在消息头中不包含,需要从其他地方获取 header.Length = 0 return header, nil } // MessageHeader TCP消息头 type MessageHeader struct { Magic uint32 `json:"magic"` // 魔数标识 MessageID uint32 `json:"message_id"` // 消息ID(用于请求响应匹配) MessageType uint32 `json:"message_type"` // 消息类型 Flags uint16 `json:"flags"` // 标志位 PlayerID uint64 `json:"player_id"` // 玩家ID Timestamp int64 `json:"timestamp"` // 时间戳 Sequence uint32 `json:"sequence"` // 序列号 Length uint32 `json:"length"` // 消息体长度 } // Message TCP消息 type Message struct { Header MessageHeader `json:"header"` Payload interface{} `json:"payload"` } // 消息标志位 - 使用proto生成的常量 const ( FlagRequest uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_REQUEST) // 请求消息 FlagResponse uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_RESPONSE) // 响应消息 FlagError uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_ERROR) // 错误消息 FlagAsync uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_ASYNC) // 异步消息 FlagBroadcast uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_BROADCAST) // 广播消息 FlagEncrypted uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_ENCRYPTED) // 加密消息 FlagCompressed uint16 = uint16(protocol.MessageFlag_MESSAGE_FLAG_COMPRESSED) // 压缩消息 ) // BaseResponse 基础响应 - 定义在game_protocol.go中 // type BaseResponse struct { // Success bool `json:"success"` // Message string `json:"message,omitempty"` // ErrorCode int `json:"error_code,omitempty"` // } // NewBaseResponse 创建基础响应 - 定义在game_protocol.go中 // func NewBaseResponse(success bool, message string) BaseResponse { // return BaseResponse{ // Success: success, // Message: message, // } // } // Position 位置信息 - 定义在game_protocol.go中 // type Position struct { // X float64 `json:"x"` // Y float64 `json:"y"` // Z float64 `json:"z"` // } // Stats 属性信息 - 定义在game_protocol.go中 // type Stats struct { // HP int `json:"hp"` // MaxHP int `json:"max_hp"` // MP int `json:"mp"` // MaxMP int `json:"max_mp"` // Attack int `json:"attack"` // Defense int `json:"defense"` // Speed int `json:"speed"` // } // PlayerInfo 玩家信息 - 定义在game_protocol.go中 // type PlayerInfo struct { // ID string `json:"id"` // Name string `json:"name"` // Level int `json:"level"` // Exp int64 `json:"exp"` // Status string `json:"status"` // Position Position `json:"position"` // Stats Stats `json:"stats"` // Avatar string `json:"avatar,omitempty"` // Gender int `json:"gender,omitempty"` // CreatedAt time.Time `json:"created_at"` // UpdatedAt time.Time `json:"updated_at"` // } // 系统消息 // HeartbeatRequest 心跳请求 - 定义在game_protocol.go中 // type HeartbeatRequest struct { // Timestamp int64 `json:"timestamp"` // } // HeartbeatResponse 心跳响应 - 定义在game_protocol.go中 // type HeartbeatResponse struct { // BaseResponse // ServerTime int64 `json:"server_time"` // } // AuthRequest 认证请求 type AuthRequest struct { Token string `json:"token"` PlayerID string `json:"player_id"` ClientInfo ClientInfo `json:"client_info"` } // AuthResponse 认证响应 type AuthResponse struct { BaseResponse SessionID string `json:"session_id,omitempty"` PlayerInfo *PlayerInfo `json:"player_info,omitempty"` ServerTime int64 `json:"server_time"` } // ClientInfo 客户端信息 type ClientInfo struct { Version string `json:"version"` Platform string `json:"platform"` DeviceID string `json:"device_id"` IPAddress string `json:"ip_address,omitempty"` } // 玩家相关消息 // PlayerLoginRequest 玩家登录请求 - 定义在game_protocol.go中 // type PlayerLoginRequest struct { // PlayerID string `json:"player_id"` // Token string `json:"token"` // } // PlayerLoginResponse 玩家登录响应 - 定义在game_protocol.go中 // type PlayerLoginResponse struct { // BaseResponse // Player *PlayerInfo `json:"player,omitempty"` // SessionID string `json:"session_id,omitempty"` // ServerTime int64 `json:"server_time"` // } // PlayerMoveRequest 玩家移动请求 - 定义在game_protocol.go中 // type PlayerMoveRequest struct { // Position Position `json:"position"` // Speed float64 `json:"speed,omitempty"` // } // PlayerMoveResponse 玩家移动响应 - 定义在game_protocol.go中 // type PlayerMoveResponse struct { // BaseResponse // OldPosition Position `json:"old_position"` // NewPosition Position `json:"new_position"` // MoveTime int64 `json:"move_time"` // } // PlayerCreateRequest 创建玩家请求 - 定义在game_protocol.go中 // type PlayerCreateRequest struct { // Name string `json:"name"` // Avatar string `json:"avatar,omitempty"` // Gender int `json:"gender,omitempty"` // } // PlayerCreateResponse 创建玩家响应 - 定义在game_protocol.go中 // type PlayerCreateResponse struct { // BaseResponse // PlayerID string `json:"player_id,omitempty"` // Name string `json:"name,omitempty"` // Level int `json:"level,omitempty"` // CreatedAt time.Time `json:"created_at,omitempty"` // } // 战斗相关消息 // CreateBattleRequest 创建战斗请求 - 定义在game_protocol.go中 // type CreateBattleRequest struct { // BattleType string `json:"battle_type"` // MaxPlayers int `json:"max_players"` // Settings BattleSettings `json:"settings,omitempty"` // } // CreateBattleResponse 创建战斗响应 - 定义在game_protocol.go中 // type CreateBattleResponse struct { // BaseResponse // BattleID string `json:"battle_id,omitempty"` // BattleInfo *BattleInfo `json:"battle_info,omitempty"` // } // BattleSettings 战斗设置 - 定义在game_protocol.go中 // type BattleSettings struct { // TimeLimit int `json:"time_limit,omitempty"` // AllowPets bool `json:"allow_pets"` // AllowItems bool `json:"allow_items"` // FriendlyFire bool `json:"friendly_fire"` // } // BattleInfo 战斗信息 - 定义在game_protocol.go中 // type BattleInfo struct { // ID string `json:"id"` // Type string `json:"type"` // Status string `json:"status"` // Players []PlayerInfo `json:"players"` // Settings BattleSettings `json:"settings"` // StartTime *time.Time `json:"start_time,omitempty"` // EndTime *time.Time `json:"end_time,omitempty"` // CreatedAt time.Time `json:"created_at"` // } // JoinBattleRequest 加入战斗请求 - 定义在game_protocol.go中 // type JoinBattleRequest struct { // BattleID string `json:"battle_id"` // Team int `json:"team,omitempty"` // } // JoinBattleResponse 加入战斗响应 - 定义在game_protocol.go中 // type JoinBattleResponse struct { // BaseResponse // BattleInfo *BattleInfo `json:"battle_info,omitempty"` // PlayerTeam int `json:"player_team,omitempty"` // } // BattleActionRequest 战斗行动请求 - 定义在game_protocol.go中 // type BattleActionRequest struct { // BattleID string `json:"battle_id"` // ActionType string `json:"action_type"` // TargetID string `json:"target_id,omitempty"` // SkillID string `json:"skill_id,omitempty"` // ItemID string `json:"item_id,omitempty"` // Position *Position `json:"position,omitempty"` // Params interface{} `json:"params,omitempty"` // } // BattleActionResponse 战斗行动响应 - 定义在game_protocol.go中 // type BattleActionResponse struct { // BaseResponse // ActionResult *ActionResult `json:"action_result,omitempty"` // BattleState *BattleState `json:"battle_state,omitempty"` // } // ActionResult 行动结果 - 定义在game_protocol.go中 // type ActionResult struct { // ActionID string `json:"action_id"` // PlayerID string `json:"player_id"` // ActionType string `json:"action_type"` // TargetID string `json:"target_id,omitempty"` // Damage int `json:"damage,omitempty"` // Healing int `json:"healing,omitempty"` // Effects []Effect `json:"effects,omitempty"` // Success bool `json:"success"` // Message string `json:"message,omitempty"` // Timestamp time.Time `json:"timestamp"` // } // Effect 效果 - 定义在game_protocol.go中 // type Effect struct { // Type string `json:"type"` // Value int `json:"value"` // Duration int `json:"duration,omitempty"` // Params interface{} `json:"params,omitempty"` // } // BattleState 战斗状态 - 定义在game_protocol.go中 // type BattleState struct { // BattleID string `json:"battle_id"` // Status string `json:"status"` // CurrentTurn string `json:"current_turn,omitempty"` // TurnNumber int `json:"turn_number"` // Players map[string]*PlayerState `json:"players"` // TimeLeft int `json:"time_left,omitempty"` // UpdatedAt time.Time `json:"updated_at"` // } // PlayerState 玩家状态 - 定义在game_protocol.go中 // type PlayerState struct { // PlayerID string `json:"player_id"` // HP int `json:"hp"` // MaxHP int `json:"max_hp"` // MP int `json:"mp"` // MaxMP int `json:"max_mp"` // Position Position `json:"position"` // Status string `json:"status"` // Effects []Effect `json:"effects,omitempty"` // IsAlive bool `json:"is_alive"` // Team int `json:"team"` // UpdatedAt time.Time `json:"updated_at"` // } // 以下所有结构体都定义在game_protocol.go中,这里注释掉避免重复定义 /* // 宠物相关消息 // PetSummonRequest 召唤宠物请求 type PetSummonRequest struct { PetID string `json:"pet_id"` Slot int `json:"slot,omitempty"` } // PetSummonResponse 召唤宠物响应 type PetSummonResponse struct { BaseResponse PetInfo *PetInfo `json:"pet_info,omitempty"` } // PetInfo 宠物信息 type PetInfo struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` Level int `json:"level"` Exp int64 `json:"exp"` Stats Stats `json:"stats"` Skills []string `json:"skills,omitempty"` Status string `json:"status"` Position Position `json:"position"` OwnerID string `json:"owner_id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // 聊天相关消息 // ChatMessageRequest 聊天消息请求 type ChatMessageRequest struct { Channel string `json:"channel"` Content string `json:"content"` TargetID string `json:"target_id,omitempty"` MessageType string `json:"message_type,omitempty"` } // ChatMessageResponse 聊天消息响应 type ChatMessageResponse struct { BaseResponse MessageID string `json:"message_id,omitempty"` Timestamp time.Time `json:"timestamp,omitempty"` } // ChatMessage 聊天消息 type ChatMessage struct { ID string `json:"id"` Channel string `json:"channel"` SenderID string `json:"sender_id"` SenderName string `json:"sender_name"` Content string `json:"content"` TargetID string `json:"target_id,omitempty"` MessageType string `json:"message_type"` Timestamp time.Time `json:"timestamp"` } // 错误消息 // ErrorMessage 错误消息 type ErrorMessage struct { ErrorCode int `json:"error_code"` Message string `json:"message"` Details string `json:"details,omitempty"` Timestamp int64 `json:"timestamp"` } // 错误码定义 const ( ErrSuccess = 0 // 成功 ErrUnknown = 1000 // 未知错误 ErrInvalidMessage = 1001 // 无效消息 ErrInvalidPlayer = 1002 // 无效玩家 ErrPlayerNotFound = 1003 // 玩家未找到 ErrPlayerOffline = 1004 // 玩家离线 ErrAuthFailed = 1005 // 认证失败 ErrPermissionDenied = 1006 // 权限不足 ErrRateLimited = 1007 // 请求过于频繁 ErrServerBusy = 1008 // 服务器繁忙 ErrMaintenance = 1009 // 服务器维护 // 战斗相关错误 ErrBattleNotFound = 2001 // 战斗未找到 ErrBattleFull = 2002 // 战斗已满 ErrBattleStarted = 2003 // 战斗已开始 ErrBattleEnded = 2004 // 战斗已结束 ErrInvalidAction = 2005 // 无效行动 ErrNotYourTurn = 2006 // 不是你的回合 ErrSkillCooldown = 2007 // 技能冷却中 ErrInsufficientMP = 2008 // MP不足 // 宠物相关错误 ErrPetNotFound = 3001 // 宠物未找到 ErrPetAlreadyActive = 3002 // 宠物已激活 ErrPetNotActive = 3003 // 宠物未激活 ErrPetLevelTooLow = 3004 // 宠物等级过低 ErrPetEvolutionFail = 3005 // 宠物进化失败 // 物品相关错误 ErrItemNotFound = 4001 // 物品未找到 ErrItemNotUsable = 4002 // 物品不可使用 ErrInventoryFull = 4003 // 背包已满 ErrInsufficientItem = 4004 // 物品数量不足 ErrItemEquipFailed = 4005 // 装备失败 ) */ ================================================ FILE: internal/interfaces/tcp/protocol/pet_protocol.go ================================================ package protocol // 宠物相关消息类型 const ( // 请求消息类型 MsgTypeCreatePet = "create_pet" MsgTypeFeedPet = "feed_pet" MsgTypeTrainPet = "train_pet" MsgTypeGetPet = "get_pet" MsgTypeGetPlayerPets = "get_player_pets" MsgTypeEvolvePet = "evolve_pet" MsgTypeEquipPetSkin = "equip_pet_skin" MsgTypeSynthesizePet = "synthesize_pet" // 响应消息类型 MsgTypeCreatePetResponse = "create_pet_response" MsgTypeFeedPetResponse = "feed_pet_response" MsgTypeTrainPetResponse = "train_pet_response" MsgTypeGetPetResponse = "get_pet_response" MsgTypeGetPlayerPetsResponse = "get_player_pets_response" MsgTypeEvolvePetResponse = "evolve_pet_response" MsgTypeEquipPetSkinResponse = "equip_pet_skin_response" MsgTypeSynthesizePetResponse = "synthesize_pet_response" // 通知消息类型 MsgTypePetLevelUp = "pet_level_up" MsgTypePetMoodChanged = "pet_mood_changed" MsgTypePetEvolved = "pet_evolved" MsgTypePetSkinEquipped = "pet_skin_equipped" ) // CreatePetRequest 创建宠物请求 type CreatePetRequest struct { PlayerID uint64 `json:"player_id"` SpeciesID string `json:"species_id"` Name string `json:"name"` Rarity string `json:"rarity,omitempty"` Quality string `json:"quality,omitempty"` } // CreatePetResponse 创建宠物响应 type CreatePetResponse struct { PetID string `json:"pet_id"` PlayerID uint64 `json:"player_id"` SpeciesID string `json:"species_id"` Name string `json:"name"` Level int32 `json:"level"` Exp int64 `json:"exp"` MaxExp int64 `json:"max_exp"` Rarity string `json:"rarity"` Quality string `json:"quality"` CreatedAt int64 `json:"created_at"` } // FeedPetRequest 喂养宠物请求 type FeedPetRequest struct { PetID string `json:"pet_id"` FoodID string `json:"food_id"` Quantity int32 `json:"quantity"` } // FeedPetResponse 喂养宠物响应 type FeedPetResponse struct { PetID string `json:"pet_id"` OldHunger int32 `json:"old_hunger"` NewHunger int32 `json:"new_hunger"` OldHappiness int32 `json:"old_happiness"` NewHappiness int32 `json:"new_happiness"` ExpGained int64 `json:"exp_gained"` LevelUp bool `json:"level_up"` FedAt int64 `json:"fed_at"` } // TrainPetRequest 训练宠物请求 type TrainPetRequest struct { PetID string `json:"pet_id"` TrainingType string `json:"training_type"` Duration int32 `json:"duration"` Intensity string `json:"intensity,omitempty"` } // TrainPetResponse 训练宠物响应 type TrainPetResponse struct { PetID string `json:"pet_id"` TrainingType string `json:"training_type"` ExpGained int64 `json:"exp_gained"` AttributeChanges map[string]int64 `json:"attribute_changes"` EnergyConsumed int32 `json:"energy_consumed"` LevelUp bool `json:"level_up"` SkillsLearned []string `json:"skills_learned,omitempty"` TrainedAt int64 `json:"trained_at"` } // GetPetRequest 获取宠物请求 type GetPetRequest struct { PetID string `json:"pet_id"` } // GetPetResponse 获取宠物响应 type GetPetResponse struct { Found bool `json:"found"` Pet *PetInfo `json:"pet,omitempty"` } // GetPlayerPetsRequest 获取玩家宠物列表请求 type GetPlayerPetsRequest struct { PlayerID uint64 `json:"player_id"` Rarity string `json:"rarity,omitempty"` Quality string `json:"quality,omitempty"` MinLevel int32 `json:"min_level,omitempty"` MaxLevel int32 `json:"max_level,omitempty"` Page int `json:"page,omitempty"` PageSize int `json:"page_size,omitempty"` } // GetPlayerPetsResponse 获取玩家宠物列表响应 type GetPlayerPetsResponse struct { PlayerID uint64 `json:"player_id"` Pets []*PetInfo `json:"pets"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int64 `json:"total_pages"` } // EvolvePetRequest 宠物进化请求 type EvolvePetRequest struct { PetID string `json:"pet_id"` TargetSpecies string `json:"target_species"` Materials map[string]int32 `json:"materials,omitempty"` } // EvolvePetResponse 宠物进化响应 type EvolvePetResponse struct { PetID string `json:"pet_id"` OldSpecies string `json:"old_species"` NewSpecies string `json:"new_species"` OldRarity string `json:"old_rarity"` NewRarity string `json:"new_rarity"` AttributeBonus map[string]int64 `json:"attribute_bonus"` NewSkills []string `json:"new_skills,omitempty"` MaterialsUsed map[string]int32 `json:"materials_used"` EvolvedAt int64 `json:"evolved_at"` } // EquipPetSkinRequest 装备宠物皮肤请求 type EquipPetSkinRequest struct { PetID string `json:"pet_id"` SkinID string `json:"skin_id"` } // EquipPetSkinResponse 装备宠物皮肤响应 type EquipPetSkinResponse struct { PetID string `json:"pet_id"` OldSkinID string `json:"old_skin_id,omitempty"` NewSkinID string `json:"new_skin_id"` EffectChanges map[string]float64 `json:"effect_changes,omitempty"` EquippedAt int64 `json:"equipped_at"` } // SynthesizePetRequest 宠物合成请求 type SynthesizePetRequest struct { PlayerID uint64 `json:"player_id"` FragmentID string `json:"fragment_id"` Quantity int32 `json:"quantity"` } // SynthesizePetResponse 宠物合成响应 type SynthesizePetResponse struct { PlayerID uint64 `json:"player_id"` FragmentID string `json:"fragment_id"` QuantityUsed int32 `json:"quantity_used"` PetID string `json:"pet_id,omitempty"` SpeciesID string `json:"species_id,omitempty"` Rarity string `json:"rarity,omitempty"` Success bool `json:"success"` SynthesizedAt int64 `json:"synthesized_at"` } // PetInfo 宠物信息 type PetInfo struct { PetID string `json:"pet_id"` PlayerID uint64 `json:"player_id"` SpeciesID string `json:"species_id"` Name string `json:"name"` Level int32 `json:"level"` Exp int64 `json:"exp"` MaxExp int64 `json:"max_exp"` Rarity string `json:"rarity"` Quality string `json:"quality"` Attributes map[string]int64 `json:"attributes"` Skills []string `json:"skills"` EquippedSkin string `json:"equipped_skin,omitempty"` Mood string `json:"mood"` Hunger int32 `json:"hunger"` Energy int32 `json:"energy"` Health int32 `json:"health"` Happiness int32 `json:"happiness"` IsActive bool `json:"is_active"` LastFedAt int64 `json:"last_fed_at"` LastPlayedAt int64 `json:"last_played_at"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } // 通知消息结构 // PetLevelUpNotification 宠物升级通知 type PetLevelUpNotification struct { PetID string `json:"pet_id"` PlayerID uint64 `json:"player_id"` OldLevel int32 `json:"old_level"` NewLevel int32 `json:"new_level"` NewMaxExp int64 `json:"new_max_exp"` AttributeGain map[string]int64 `json:"attribute_gain,omitempty"` SkillsLearned []string `json:"skills_learned,omitempty"` LevelUpAt int64 `json:"level_up_at"` } // PetMoodChangedNotification 宠物心情变化通知 type PetMoodChangedNotification struct { PetID string `json:"pet_id"` PlayerID uint64 `json:"player_id"` OldMood string `json:"old_mood"` NewMood string `json:"new_mood"` Reason string `json:"reason"` ChangedAt int64 `json:"changed_at"` } // PetEvolvedNotification 宠物进化通知 type PetEvolvedNotification struct { PetID string `json:"pet_id"` PlayerID uint64 `json:"player_id"` OldSpecies string `json:"old_species"` NewSpecies string `json:"new_species"` OldRarity string `json:"old_rarity"` NewRarity string `json:"new_rarity"` AttributeGain map[string]int64 `json:"attribute_gain"` NewSkills []string `json:"new_skills,omitempty"` EvolvedAt int64 `json:"evolved_at"` } // PetSkinEquippedNotification 宠物皮肤装备通知 type PetSkinEquippedNotification struct { PetID string `json:"pet_id"` PlayerID uint64 `json:"player_id"` OldSkinID string `json:"old_skin_id,omitempty"` NewSkinID string `json:"new_skin_id"` SkinName string `json:"skin_name"` EffectChanges map[string]float64 `json:"effect_changes,omitempty"` EquippedAt int64 `json:"equipped_at"` } // PetFragmentInfo 宠物碎片信息 type PetFragmentInfo struct { FragmentID string `json:"fragment_id"` PlayerID uint64 `json:"player_id"` SpeciesID string `json:"species_id"` Quantity int32 `json:"quantity"` Required int32 `json:"required"` Source string `json:"source"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } // PetSkinInfo 宠物皮肤信息 type PetSkinInfo struct { SkinID string `json:"skin_id"` PlayerID uint64 `json:"player_id"` SpeciesID string `json:"species_id"` Name string `json:"name"` Rarity string `json:"rarity"` Effects map[string]float64 `json:"effects"` IsUnlocked bool `json:"is_unlocked"` UnlockedAt int64 `json:"unlocked_at,omitempty"` CreatedAt int64 `json:"created_at"` } // PetBondInfo 宠物羁绊信息 type PetBondInfo struct { BondID string `json:"bond_id"` PlayerID uint64 `json:"player_id"` PetIDs []string `json:"pet_ids"` BondType string `json:"bond_type"` Level int32 `json:"level"` Exp int64 `json:"exp"` MaxExp int64 `json:"max_exp"` Effects map[string]float64 `json:"effects"` IsActive bool `json:"is_active"` ActivatedAt int64 `json:"activated_at,omitempty"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } // PetPictorialInfo 宠物图鉴信息 type PetPictorialInfo struct { PictorialID string `json:"pictorial_id"` PlayerID uint64 `json:"player_id"` SpeciesID string `json:"species_id"` IsUnlocked bool `json:"is_unlocked"` FirstSeenAt int64 `json:"first_seen_at,omitempty"` FirstOwnedAt int64 `json:"first_owned_at,omitempty"` TotalOwned int32 `json:"total_owned"` HighestLevel int32 `json:"highest_level"` HighestRarity string `json:"highest_rarity"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } ================================================ FILE: internal/interfaces/tcp/router.go ================================================ package tcp import ( "encoding/json" "fmt" "sync" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/interfaces/tcp/connection" "greatestworks/internal/interfaces/tcp/handlers" "greatestworks/internal/interfaces/tcp/protocol" ) // 使用其他地方定义的Logger接口 // MessageHandler 消息处理器接口 type MessageHandler interface { HandleMessage(session *connection.Session, msg *protocol.Message) error } // Router TCP消息路由器 type Router struct { handlers map[uint16]MessageHandler mutex sync.RWMutex logger logging.Logger } // NewRouter 创建新的路由器 func NewRouter(logger logging.Logger) *Router { return &Router{ handlers: make(map[uint16]MessageHandler), logger: logger, } } // RegisterHandler 注册消息处理器 func (r *Router) RegisterHandler(messageType uint16, handler MessageHandler) { r.mutex.Lock() defer r.mutex.Unlock() r.handlers[messageType] = handler r.logger.Info("Message handler registered", logging.Fields{ "message_type": messageType, }) } // RegisterGameHandler 注册游戏处理器的所有消息类型 func (r *Router) RegisterGameHandler(handler *handlers.GameHandler) { // 系统消息 r.RegisterHandler(uint16(protocol.MsgHeartbeat), handler) r.RegisterHandler(uint16(protocol.MsgAuth), handler) r.RegisterHandler(uint16(protocol.MsgError), handler) // 玩家相关消息 r.RegisterHandler(uint16(protocol.MsgPlayerLogin), handler) r.RegisterHandler(uint16(protocol.MsgPlayerLogout), handler) r.RegisterHandler(uint16(protocol.MsgPlayerMove), handler) r.RegisterHandler(uint16(protocol.MsgPlayerStatus), handler) r.RegisterHandler(uint16(protocol.MsgBattleSkill), handler) r.RegisterHandler(uint16(protocol.MsgPlayerInfo), handler) r.RegisterHandler(uint16(protocol.MsgPlayerCreate), handler) r.RegisterHandler(uint16(protocol.MsgPlayerStatus), handler) r.RegisterHandler(uint16(protocol.MsgPlayerStats), handler) //r.RegisterHandler(uint16(protocol.MsgPlayerInventory), handler) //r.RegisterHandler(uint16(protocol.MsgPlayerSkills), handler) //r.RegisterHandler(uint16(protocol.MsgPlayerQuests), handler) // 战斗相关消息 r.RegisterHandler(uint16(protocol.MsgCreateBattle), handler) r.RegisterHandler(uint16(protocol.MsgJoinBattle), handler) r.RegisterHandler(uint16(protocol.MsgStartBattle), handler) r.RegisterHandler(uint16(protocol.MsgBattleAction), handler) r.RegisterHandler(uint16(protocol.MsgBattleSkill), handler) r.RegisterHandler(uint16(protocol.MsgLeaveBattle), handler) r.RegisterHandler(uint16(protocol.MsgBattleStatus), handler) r.RegisterHandler(uint16(protocol.MsgBattleResult), handler) // 宠物相关消息 r.RegisterHandler(uint16(protocol.MsgPetSummon), handler) r.RegisterHandler(uint16(protocol.MsgPetDismiss), handler) r.RegisterHandler(uint16(protocol.MsgPetAction), handler) r.RegisterHandler(uint16(protocol.MsgPetStatus), handler) r.RegisterHandler(uint16(protocol.MsgPetTrain), handler) //r.RegisterHandler(uint16(protocol.MsgPetEvolve), handler) // 建筑相关消息 r.RegisterHandler(uint16(protocol.MsgBuildingCreate), handler) r.RegisterHandler(uint16(protocol.MsgBuildingUpgrade), handler) r.RegisterHandler(uint16(protocol.MsgBuildingDestroy), handler) r.RegisterHandler(uint16(protocol.MsgBuildingStatus), handler) //r.RegisterHandler(uint16(protocol.MsgBuildingList), handler) // 社交相关消息 // 聊天与社交 r.RegisterHandler(uint16(protocol.MsgChatMessage), handler) //r.RegisterHandler(uint16(protocol.MsgFriendAdd), handler) r.RegisterHandler(uint16(protocol.MsgFriendRemove), handler) r.RegisterHandler(uint16(protocol.MsgFriendList), handler) r.RegisterHandler(uint16(protocol.MsgGuildJoin), handler) r.RegisterHandler(uint16(protocol.MsgGuildLeave), handler) r.RegisterHandler(uint16(protocol.MsgGuildInfo), handler) r.RegisterHandler(uint16(protocol.MsgTeamCreate), handler) r.RegisterHandler(uint16(protocol.MsgTeamJoin), handler) r.RegisterHandler(uint16(protocol.MsgTeamLeave), handler) r.RegisterHandler(uint16(protocol.MsgTeamInfo), handler) // 物品相关消息 r.RegisterHandler(uint16(protocol.MsgItemUse), handler) //r.RegisterHandler(uint16(protocol.MsgItemMove), handler) r.RegisterHandler(uint16(protocol.MsgItemDrop), handler) r.RegisterHandler(uint16(protocol.MsgItemPickup), handler) r.RegisterHandler(uint16(protocol.MsgItemTrade), handler) r.RegisterHandler(uint16(protocol.MsgItemCraft), handler) // 任务相关消息 r.RegisterHandler(uint16(protocol.MsgQuestAccept), handler) r.RegisterHandler(uint16(protocol.MsgQuestComplete), handler) r.RegisterHandler(uint16(protocol.MsgQuestCancel), handler) r.RegisterHandler(uint16(protocol.MsgQuestProgress), handler) r.RegisterHandler(uint16(protocol.MsgQuestList), handler) // 查询相关消息 r.RegisterHandler(uint16(protocol.MsgGetPlayerInfo), handler) r.RegisterHandler(uint16(protocol.MsgGetOnlinePlayers), handler) r.RegisterHandler(uint16(protocol.MsgGetBattleInfo), handler) r.RegisterHandler(uint16(protocol.MsgGetServerInfo), handler) //r.RegisterHandler(uint16(protocol.MsgGetWorldInfo), handler) r.logger.Info("Game handler registered with all message types") } // RouteMessage 路由消息到对应的处理器 func (r *Router) RouteMessage(session *connection.Session, msg *protocol.Message) error { r.mutex.RLock() handler, exists := r.handlers[uint16(msg.Header.MessageType)] r.mutex.RUnlock() if !exists { r.logger.Info("No handler found for message type", logging.Fields{ "message_type": msg.Header.MessageType, "session_id": session.ID, "user_id": session.UserID, }) return r.sendUnhandledMessageError(session, msg) } // 记录消息处理日志 r.logger.Debug("Routing message", logging.Fields{ "message_type": msg.Header.MessageType, "message_id": msg.Header.MessageID, "session_id": session.ID, "user_id": session.UserID, }) // 调用处理器 err := handler.HandleMessage(session, msg) if err != nil { r.logger.Error("Message handler error", err, logging.Fields{ "message_type": msg.Header.MessageType, "message_id": msg.Header.MessageID, "session_id": session.ID, "user_id": session.UserID, }) return err } return nil } // GetHandler 获取指定消息类型的处理器 func (r *Router) GetHandler(messageType uint16) (MessageHandler, bool) { r.mutex.RLock() defer r.mutex.RUnlock() handler, exists := r.handlers[messageType] return handler, exists } // UnregisterHandler 注销消息处理器 func (r *Router) UnregisterHandler(messageType uint16) { r.mutex.Lock() defer r.mutex.Unlock() delete(r.handlers, messageType) r.logger.Info("Message handler unregistered", logging.Fields{ "message_type": messageType, }) } // GetRegisteredMessageTypes 获取所有已注册的消息类型 func (r *Router) GetRegisteredMessageTypes() []uint16 { r.mutex.RLock() defer r.mutex.RUnlock() types := make([]uint16, 0, len(r.handlers)) for msgType := range r.handlers { types = append(types, msgType) } return types } // GetHandlerCount 获取已注册处理器数量 func (r *Router) GetHandlerCount() int { r.mutex.RLock() defer r.mutex.RUnlock() return len(r.handlers) } // IsMessageTypeSupported 检查消息类型是否被支持 func (r *Router) IsMessageTypeSupported(messageType uint16) bool { r.mutex.RLock() defer r.mutex.RUnlock() _, exists := r.handlers[messageType] return exists } // sendUnhandledMessageError 发送未处理消息错误 func (r *Router) sendUnhandledMessageError(session *connection.Session, msg *protocol.Message) error { errorMsg := &protocol.ErrorResponse{ BaseResponse: protocol.NewBaseResponse(false, fmt.Sprintf("Unhandled message type: %d", msg.Header.MessageType)), ErrorCode: int(protocol.ErrCodeInvalidMessage), ErrorType: "UNHANDLED_MESSAGE", } errorResponse := &protocol.Message{ Header: protocol.MessageHeader{ Magic: protocol.MessageMagic, MessageID: msg.Header.MessageID, MessageType: protocol.MsgError, Flags: protocol.FlagResponse | protocol.FlagError, PlayerID: msg.Header.PlayerID, Timestamp: msg.Header.Timestamp, Sequence: 0, }, Payload: errorMsg, } // 序列化消息并发送 data, err := json.Marshal(errorResponse) if err != nil { return fmt.Errorf("序列化错误消息失败: %w", err) } return session.Send(data) } // ValidateMessage 验证消息格式 func (r *Router) ValidateMessage(msg *protocol.Message) error { if msg == nil { return fmt.Errorf("message is nil") } // 验证魔数 if msg.Header.Magic != protocol.MessageMagic { return fmt.Errorf("invalid magic number: %d", msg.Header.Magic) } // 验证消息类型 if msg.Header.MessageType == 0 { return fmt.Errorf("invalid message type: %d", msg.Header.MessageType) } // 验证消息ID if msg.Header.MessageID == 0 { return fmt.Errorf("invalid message ID: %d", msg.Header.MessageID) } // 验证时间戳 if msg.Header.Timestamp <= 0 { return fmt.Errorf("invalid timestamp: %d", msg.Header.Timestamp) } return nil } // LogRouterStats 记录路由器统计信息 func (r *Router) LogRouterStats() { r.mutex.RLock() handlerCount := len(r.handlers) messageTypes := make([]uint16, 0, handlerCount) for msgType := range r.handlers { messageTypes = append(messageTypes, msgType) } r.mutex.RUnlock() r.logger.Info("Router statistics", logging.Fields{ "handler_count": handlerCount, "message_types": messageTypes, }) } ================================================ FILE: internal/interfaces/tcp/scene_handler.go ================================================ package tcp import ( "greatestworks/internal/application/services" "greatestworks/internal/infrastructure/logging" ) // SceneHandler 场景TCP处理器 type SceneHandler struct { weatherService *services.WeatherService plantService *services.PlantService logger logging.Logger } // NewSceneHandler 创建场景处理器 func NewSceneHandler(weatherService *services.WeatherService, plantService *services.PlantService, logger logging.Logger) *SceneHandler { return &SceneHandler{ weatherService: weatherService, plantService: plantService, logger: logger, } } // RegisterHandlers 注册处理器 func (h *SceneHandler) RegisterHandlers(server interface{}) error { // TODO: 实现场景处理器注册功能 h.logger.Info("Scene handlers registration not implemented", map[string]interface{}{}) return nil } ================================================ FILE: internal/interfaces/tcp/server.go ================================================ package tcp import ( "context" "encoding/json" "fmt" "io" "net" "strconv" "sync" "time" appHandlers "greatestworks/internal/application/handlers" appServices "greatestworks/internal/application/services" "greatestworks/internal/domain/character" "greatestworks/internal/infrastructure/logging" "greatestworks/internal/interfaces/tcp/connection" tcpHandlers "greatestworks/internal/interfaces/tcp/handlers" "greatestworks/internal/interfaces/tcp/protocol" ) // ServerConfig TCP服务器配置 type ServerConfig struct { Addr string MaxConnections int ReadTimeout time.Duration WriteTimeout time.Duration EnableCompression bool BufferSize int } // DefaultServerConfig 默认服务器配置 func DefaultServerConfig() *ServerConfig { return &ServerConfig{ Addr: ":9090", MaxConnections: 10000, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, EnableCompression: false, BufferSize: 4096, } } // TCPServer TCP服务器 type TCPServer struct { config *ServerConfig listener net.Listener gameHandler *tcpHandlers.GameHandler router *Router connManager *connection.Manager heartbeatManager *connection.HeartbeatManager logger logging.Logger ctx context.Context cancel context.CancelFunc wg sync.WaitGroup running bool mutex sync.RWMutex // optional references for wiring mapService *appServices.MapService fightService *appServices.FightService characterService *appServices.CharacterService } // NewTCPServer 创建TCP服务器 func NewTCPServer(config *ServerConfig, commandBus *appHandlers.CommandBus, queryBus *appHandlers.QueryBus, logger logging.Logger) *TCPServer { if config == nil { config = DefaultServerConfig() } ctx, cancel := context.WithCancel(context.Background()) // 创建连接管理器 connManager := connection.NewManager(logger) // 创建心跳管理器 heartbeatManager := connection.NewHeartbeatManager(logger, 30*time.Second, 60*time.Second) // 创建游戏处理器 gameHandler := tcpHandlers.NewGameHandler(commandBus, queryBus, connManager, logger) // 创建路由器 router := NewRouter(logger) router.RegisterGameHandler(gameHandler) server := &TCPServer{ config: config, gameHandler: gameHandler, router: router, connManager: connManager, heartbeatManager: heartbeatManager, logger: logger, ctx: ctx, cancel: cancel, running: false, } return server } // SetMapService allows injecting MapService for potential handler usage. func (s *TCPServer) SetMapService(ms *appServices.MapService) { s.mapService = ms if s.gameHandler != nil { s.gameHandler.SetMapService(ms) } } // SetFightService allows injecting FightService for handler usage. func (s *TCPServer) SetFightService(fs *appServices.FightService) { s.fightService = fs if s.gameHandler != nil { s.gameHandler.SetFightService(fs) } } // SetCharacterService allows injecting CharacterService for handler usage. func (s *TCPServer) SetCharacterService(cs *appServices.CharacterService) { s.characterService = cs if s.gameHandler != nil { s.gameHandler.SetCharacterService(cs) } } // GetConnectionManager exposes the underlying connection manager for wiring. func (s *TCPServer) GetConnectionManager() *connection.Manager { return s.connManager } // Start 启动TCP服务器 func (s *TCPServer) Start() error { s.mutex.Lock() if s.running { s.mutex.Unlock() return fmt.Errorf("server is already running") } s.mutex.Unlock() s.logger.Info("Starting TCP server", map[string]interface{}{ "address": s.config.Addr, }) // 创建监听器 listener, err := net.Listen("tcp", s.config.Addr) if err != nil { s.logger.Error("Failed to create listener", err, logging.Fields{ "address": s.config.Addr, }) return fmt.Errorf("failed to create listener: %w", err) } s.listener = listener s.mutex.Lock() s.running = true s.mutex.Unlock() // 启动心跳管理器 go s.heartbeatManager.Start(s.ctx) // 启动接受连接的协程 s.wg.Add(1) go s.acceptConnections() s.logger.Info("TCP server started successfully", map[string]interface{}{ "address": s.config.Addr, }) return nil } // Stop 停止TCP服务器 func (s *TCPServer) Stop() error { s.mutex.Lock() if !s.running { s.mutex.Unlock() return fmt.Errorf("server is not running") } s.running = false s.mutex.Unlock() s.logger.Info("Stopping TCP server") // 取消上下文 s.cancel() // 关闭监听器 if s.listener != nil { if err := s.listener.Close(); err != nil { s.logger.Error("Failed to close listener", err) } } // 等待所有协程结束 s.wg.Wait() s.logger.Info("TCP server stopped successfully") return nil } // acceptConnections 接受连接 func (s *TCPServer) acceptConnections() { defer s.wg.Done() for { select { case <-s.ctx.Done(): s.logger.Info("Accept connections routine stopped") return default: // 设置接受超时 if tcpListener, ok := s.listener.(*net.TCPListener); ok { tcpListener.SetDeadline(time.Now().Add(1 * time.Second)) } conn, err := s.listener.Accept() if err != nil { // 检查是否是超时错误 if netErr, ok := err.(net.Error); ok && netErr.Timeout() { continue } // 检查服务器是否正在关闭 select { case <-s.ctx.Done(): return default: s.logger.Error("Failed to accept connection", err) continue } } // 检查连接数限制 connectionCount := s.connManager.GetConnectionCount() if connectionCount >= s.config.MaxConnections { s.logger.Warn("Connection limit reached, rejecting new connection", logging.Fields{ "current_count": connectionCount, "max_connections": s.config.MaxConnections, }) conn.Close() continue } // 处理新连接 s.wg.Add(1) go s.handleConnection(conn) } } } // handleConnection 处理连接 func (s *TCPServer) handleConnection(netConn net.Conn) { defer s.wg.Done() // 设置连接超时 if tcpConn, ok := netConn.(*net.TCPConn); ok { tcpConn.SetKeepAlive(true) tcpConn.SetKeepAlivePeriod(30 * time.Second) } // 创建会话 session := connection.NewSession(fmt.Sprintf("session_%d", time.Now().UnixNano()), netConn, s.logger) // 添加到连接管理器 s.connManager.AddConnection(session) // 添加到心跳管理器 s.heartbeatManager.AddSession(session) s.logger.Info("New connection established", map[string]interface{}{ "session_id": session.ID, "remote_addr": netConn.RemoteAddr(), }) // 处理连接消息 defer func() { // 清理连接 s.cleanupConnection(session) }() // 消息处理循环 for { select { case <-s.ctx.Done(): return default: // 设置读取超时 netConn.SetReadDeadline(time.Now().Add(s.config.ReadTimeout)) // 读取消息 msg, err := s.readMessage(netConn) if err != nil { if err == io.EOF { s.logger.Info("Connection closed by client", map[string]interface{}{ "session_id": session.ID, }) } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { s.logger.Debug("Read timeout", map[string]interface{}{ "session_id": session.ID, }) continue } else { s.logger.Error("Failed to read message", err, logging.Fields{ "session_id": session.ID, }) } return } // 验证消息 if err := s.router.ValidateMessage(msg); err != nil { s.logger.Error("Invalid message received", err, logging.Fields{ "session_id": session.ID, }) continue } // 路由消息 if err := s.router.RouteMessage(session, msg); err != nil { s.logger.Error("Failed to route message", err, logging.Fields{ "session_id": session.ID, "message_type": msg.Header.MessageType, }) } } } } // readMessage 读取消息 func (s *TCPServer) readMessage(conn net.Conn) (*protocol.Message, error) { // 读取消息头 headerBytes := make([]byte, protocol.MessageHeaderSize) if _, err := io.ReadFull(conn, headerBytes); err != nil { return nil, err } // 解析消息头 header, err := protocol.ParseMessageHeader(headerBytes) if err != nil { return nil, fmt.Errorf("failed to parse message header: %w", err) } // 读取消息体 var payload interface{} if header.Length > 0 { payloadBytes := make([]byte, header.Length) if _, err := io.ReadFull(conn, payloadBytes); err != nil { return nil, fmt.Errorf("failed to read message payload: %w", err) } // 解析消息体 if err := json.Unmarshal(payloadBytes, &payload); err != nil { return nil, fmt.Errorf("failed to unmarshal message payload: %w", err) } } return &protocol.Message{ Header: *header, Payload: payload, }, nil } // cleanupConnection 清理连接 func (s *TCPServer) cleanupConnection(session *connection.Session) { s.logger.Info("Cleaning up connection", map[string]interface{}{ "session_id": session.ID, "user_id": session.UserID, }) // 从地图中移除与该会话绑定的实体;并保存最后位置 if s.mapService != nil { if entityID, ok := s.connManager.GetPlayerBySession(session.ID); ok { var mapID int32 = 1 if session.GroupID != "" && len(session.GroupID) > 4 && session.GroupID[:4] == "map:" { if v, err := strconv.ParseInt(session.GroupID[4:], 10, 32); err == nil { mapID = int32(v) } } // 保存位置 if s.characterService != nil && mapID > 0 { if m, err := s.mapService.GetMap(mapID); err == nil && m != nil { if e := m.GetEntity(character.EntityID(entityID)); e != nil { pos := e.Position() _ = s.characterService.UpdateLastLocation( s.ctx, int64(entityID), mapID, pos.X, pos.Y, pos.Z, ) } } } _ = s.mapService.LeaveMapByID(s.ctx, mapID, entityID) } } // 从心跳管理器移除 s.heartbeatManager.RemoveSession(session.ID) // 从连接管理器移除 s.connManager.RemoveConnection(session.ID) // 关闭连接 session.Close() s.logger.Debug("Connection cleanup completed", map[string]interface{}{ "session_id": session.ID, }) } // IsRunning 检查服务器是否运行中 func (s *TCPServer) IsRunning() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.running } // GetStats 获取服务器统计信息 func (s *TCPServer) GetStats() map[string]interface{} { connectionCount := s.connManager.GetConnectionCount() activeSessionCount := s.heartbeatManager.GetActiveSessionCount() return map[string]interface{}{ "running": s.IsRunning(), "address": s.config.Addr, "max_connections": s.config.MaxConnections, "connection_count": connectionCount, "active_sessions": activeSessionCount, "router_stats": map[string]interface{}{ "handler_count": s.router.GetHandlerCount(), "message_types": s.router.GetRegisteredMessageTypes(), }, } } ================================================ FILE: internal/metrics_base.go ================================================ package internal import ( "encoding/json" "fmt" "log" "sync" "sync/atomic" "time" ) // MetricType 指标类型 type MetricType int const ( MetricTypeCounter MetricType = iota // 计数器 MetricTypeGauge // 仪表盘 MetricTypeHistogram // 直方图 MetricTypeSummary // 摘要 ) // MetricValue 指标值 type MetricValue struct { Value float64 `json:"value"` Timestamp time.Time `json:"timestamp"` Labels map[string]string `json:"labels,omitempty"` } // MetricsConfig 指标配置 type MetricsConfig struct { Enabled bool `json:"enabled"` CollectInterval time.Duration `json:"collect_interval"` RetentionTime time.Duration `json:"retention_time"` MaxSamples int `json:"max_samples"` } // MetricsBase 指标基类 type MetricsBase struct { Name string `json:"name"` Description string `json:"description"` Type MetricType `json:"type"` Labels map[string]string `json:"labels"` Values []MetricValue `json:"values"` Config *MetricsConfig `json:"config"` mu sync.RWMutex counter int64 gauge int64 lastUpdate time.Time active bool } // NewMetricsBase 创建指标基类实例 func NewMetricsBase(name, description string, metricType MetricType, config *MetricsConfig) *MetricsBase { return &MetricsBase{ Name: name, Description: description, Type: metricType, Labels: make(map[string]string), Values: make([]MetricValue, 0), Config: config, lastUpdate: time.Now(), active: true, } } // GetDescription 获取指标描述 func (m *MetricsBase) GetDescription() string { m.mu.RLock() defer m.mu.RUnlock() return m.Description } // SetDescription 设置指标描述 func (m *MetricsBase) SetDescription(desc string) { m.mu.Lock() defer m.mu.Unlock() m.Description = desc } // GetName 获取指标名称 func (m *MetricsBase) GetName() string { m.mu.RLock() defer m.mu.RUnlock() return m.Name } // SetName 设置指标名称 func (m *MetricsBase) SetName(name string) { m.mu.Lock() defer m.mu.Unlock() m.Name = name } // AddLabel 添加标签 func (m *MetricsBase) AddLabel(key, value string) { m.mu.Lock() defer m.mu.Unlock() m.Labels[key] = value } // RemoveLabel 移除标签 func (m *MetricsBase) RemoveLabel(key string) { m.mu.Lock() defer m.mu.Unlock() delete(m.Labels, key) } // GetLabels 获取所有标签 func (m *MetricsBase) GetLabels() map[string]string { m.mu.RLock() defer m.mu.RUnlock() labels := make(map[string]string) for k, v := range m.Labels { labels[k] = v } return labels } // Inc 计数器递增 func (m *MetricsBase) Inc() { if m.Type != MetricTypeCounter { return } atomic.AddInt64(&m.counter, 1) m.recordValue(float64(atomic.LoadInt64(&m.counter))) } // Add 计数器增加指定值 func (m *MetricsBase) Add(value float64) { if m.Type != MetricTypeCounter { return } atomic.AddInt64(&m.counter, int64(value)) m.recordValue(float64(atomic.LoadInt64(&m.counter))) } // Set 设置仪表盘值 func (m *MetricsBase) Set(value float64) { if m.Type != MetricTypeGauge { return } atomic.StoreInt64(&m.gauge, int64(value)) m.recordValue(value) } // Get 获取当前值 func (m *MetricsBase) Get() float64 { switch m.Type { case MetricTypeCounter: return float64(atomic.LoadInt64(&m.counter)) case MetricTypeGauge: return float64(atomic.LoadInt64(&m.gauge)) default: return 0 } } // recordValue 记录指标值 func (m *MetricsBase) recordValue(value float64) { if !m.Config.Enabled || !m.active { return } m.mu.Lock() defer m.mu.Unlock() // 创建新的指标值 metricValue := MetricValue{ Value: value, Timestamp: time.Now(), Labels: m.getLabelsSnapshot(), } // 添加到值列表 m.Values = append(m.Values, metricValue) m.lastUpdate = time.Now() // 清理过期数据 m.cleanupExpiredValues() // 限制最大样本数 if len(m.Values) > m.Config.MaxSamples { m.Values = m.Values[len(m.Values)-m.Config.MaxSamples:] } log.Printf("[MetricsBase] 记录指标 %s: %f", m.Name, value) } // getLabelsSnapshot 获取标签快照 func (m *MetricsBase) getLabelsSnapshot() map[string]string { labels := make(map[string]string) for k, v := range m.Labels { labels[k] = v } return labels } // cleanupExpiredValues 清理过期值 func (m *MetricsBase) cleanupExpiredValues() { if m.Config.RetentionTime <= 0 { return } cutoff := time.Now().Add(-m.Config.RetentionTime) validValues := make([]MetricValue, 0) for _, value := range m.Values { if value.Timestamp.After(cutoff) { validValues = append(validValues, value) } } m.Values = validValues } // GetValues 获取所有指标值 func (m *MetricsBase) GetValues() []MetricValue { m.mu.RLock() defer m.mu.RUnlock() values := make([]MetricValue, len(m.Values)) copy(values, m.Values) return values } // GetLatestValue 获取最新指标值 func (m *MetricsBase) GetLatestValue() *MetricValue { m.mu.RLock() defer m.mu.RUnlock() if len(m.Values) == 0 { return nil } latest := m.Values[len(m.Values)-1] return &latest } // GetValuesSince 获取指定时间以来的指标值 func (m *MetricsBase) GetValuesSince(since time.Time) []MetricValue { m.mu.RLock() defer m.mu.RUnlock() values := make([]MetricValue, 0) for _, value := range m.Values { if value.Timestamp.After(since) { values = append(values, value) } } return values } // GetStatistics 获取统计信息 func (m *MetricsBase) GetStatistics() map[string]interface{} { m.mu.RLock() defer m.mu.RUnlock() if len(m.Values) == 0 { return map[string]interface{}{ "count": 0, "min": 0, "max": 0, "avg": 0, "sum": 0, } } var min, max, sum float64 min = m.Values[0].Value max = m.Values[0].Value for _, value := range m.Values { if value.Value < min { min = value.Value } if value.Value > max { max = value.Value } sum += value.Value } avg := sum / float64(len(m.Values)) return map[string]interface{}{ "count": len(m.Values), "min": min, "max": max, "avg": avg, "sum": sum, } } // Reset 重置指标 func (m *MetricsBase) Reset() { m.mu.Lock() defer m.mu.Unlock() atomic.StoreInt64(&m.counter, 0) atomic.StoreInt64(&m.gauge, 0) m.Values = make([]MetricValue, 0) m.lastUpdate = time.Now() log.Printf("[MetricsBase] 重置指标 %s", m.Name) } // Enable 启用指标收集 func (m *MetricsBase) Enable() { m.mu.Lock() defer m.mu.Unlock() m.active = true log.Printf("[MetricsBase] 启用指标 %s", m.Name) } // Disable 禁用指标收集 func (m *MetricsBase) Disable() { m.mu.Lock() defer m.mu.Unlock() m.active = false log.Printf("[MetricsBase] 禁用指标 %s", m.Name) } // IsActive 检查指标是否活跃 func (m *MetricsBase) IsActive() bool { m.mu.RLock() defer m.mu.RUnlock() return m.active } // GetLastUpdate 获取最后更新时间 func (m *MetricsBase) GetLastUpdate() time.Time { m.mu.RLock() defer m.mu.RUnlock() return m.lastUpdate } // ToJSON 转换为JSON格式 func (m *MetricsBase) ToJSON() ([]byte, error) { m.mu.RLock() defer m.mu.RUnlock() data := map[string]interface{}{ "name": m.Name, "description": m.Description, "type": m.Type, "labels": m.Labels, "current": m.Get(), "statistics": m.GetStatistics(), "last_update": m.lastUpdate, "active": m.active, } return json.Marshal(data) } // String 字符串表示 func (m *MetricsBase) String() string { m.mu.RLock() defer m.mu.RUnlock() return fmt.Sprintf("Metric{name=%s, type=%d, value=%f, active=%t}", m.Name, m.Type, m.Get(), m.active) } ================================================ FILE: internal/module_manager.go ================================================ package internal import ( "context" "fmt" "sync" ) var ( ModuleManager *ModuleManagerImpl ) // ModuleManagerImpl 模块管理器实现 type ModuleManagerImpl struct { mu sync.RWMutex moduleName2Module map[string]Module started bool } // NewModuleManager 创建新的模块管理器 func NewModuleManager() *ModuleManagerImpl { return &ModuleManagerImpl{ moduleName2Module: make(map[string]Module), } } // GetModule 获取模块 func (m *ModuleManagerImpl) GetModule(name string) Module { m.mu.RLock() defer m.mu.RUnlock() return m.moduleName2Module[name] } // RegisterModule 注册模块 func (m *ModuleManagerImpl) RegisterModule(moduleName string, module Module) error { m.mu.Lock() defer m.mu.Unlock() if _, exist := m.moduleName2Module[moduleName]; exist { return fmt.Errorf("重复注册模块: %v", moduleName) } m.moduleName2Module[moduleName] = module return nil } // UnregisterModule 注销模块 func (m *ModuleManagerImpl) UnregisterModule(moduleName string) error { m.mu.Lock() defer m.mu.Unlock() if _, exist := m.moduleName2Module[moduleName]; !exist { return fmt.Errorf("模块不存在: %v", moduleName) } delete(m.moduleName2Module, moduleName) return nil } // GetModuleNames 获取所有模块名称 func (m *ModuleManagerImpl) GetModuleNames() []string { m.mu.RLock() defer m.mu.RUnlock() names := make([]string, 0, len(m.moduleName2Module)) for name := range m.moduleName2Module { names = append(names, name) } return names } // Start 启动所有模块 func (m *ModuleManagerImpl) Start(ctx context.Context) error { m.mu.Lock() defer m.mu.Unlock() if m.started { return fmt.Errorf("模块管理器已经启动") } // 注册处理器 for _, module := range m.moduleName2Module { module.RegisterHandler() } // 启动模块 for _, module := range m.moduleName2Module { module.OnStart() // 如果模块实现了Manager接口,调用AfterStart if manager, ok := module.(Manager); ok { manager.AfterStart() } } m.started = true return nil } // Stop 停止所有模块 func (m *ModuleManagerImpl) Stop(ctx context.Context) error { m.mu.Lock() defer m.mu.Unlock() if !m.started { return nil } // 停止模块(逆序) modules := make([]Module, 0, len(m.moduleName2Module)) for _, module := range m.moduleName2Module { modules = append(modules, module) } for i := len(modules) - 1; i >= 0; i-- { module := modules[i] module.OnStop() // 如果模块实现了Manager接口,调用AfterStop if manager, ok := module.(Manager); ok { manager.AfterStop() } } m.started = false return nil } // IsStarted 检查是否已启动 func (m *ModuleManagerImpl) IsStarted() bool { m.mu.RLock() defer m.mu.RUnlock() return m.started } // 初始化全局模块管理器 func init() { ModuleManager = NewModuleManager() } ================================================ FILE: internal/network/codec.go ================================================ // Package network 消息编解码器 // Author: MMO Server Team // Created: 2024 package network import ( "bytes" "compress/gzip" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/binary" "encoding/json" "fmt" "io" "reflect" "sync" ) // Codec 编解码器接口 type Codec interface { // Encode 编码消息 Encode(msg interface{}) ([]byte, error) // Decode 解码消息 Decode(data []byte, msg interface{}) error // Name 编解码器名称 Name() string } // JSONCodec JSON编解码器 type JSONCodec struct{} // NewJSONCodec 创建JSON编解码器 func NewJSONCodec() *JSONCodec { return &JSONCodec{} } // Encode JSON编码 func (c *JSONCodec) Encode(msg interface{}) ([]byte, error) { return json.Marshal(msg) } // Decode JSON解码 func (c *JSONCodec) Decode(data []byte, msg interface{}) error { return json.Unmarshal(data, msg) } // Name 编解码器名称 func (c *JSONCodec) Name() string { return "json" } // BinaryCodec 二进制编解码器 type BinaryCodec struct { typeRegistry map[string]reflect.Type mutex sync.RWMutex } // NewBinaryCodec 创建二进制编解码器 func NewBinaryCodec() *BinaryCodec { return &BinaryCodec{ typeRegistry: make(map[string]reflect.Type), } } // RegisterType 注册类型 func (c *BinaryCodec) RegisterType(name string, t reflect.Type) { c.mutex.Lock() defer c.mutex.Unlock() c.typeRegistry[name] = t } // Encode 二进制编码 func (c *BinaryCodec) Encode(msg interface{}) ([]byte, error) { buf := new(bytes.Buffer) // 写入类型名 typeName := reflect.TypeOf(msg).Name() if err := c.writeString(buf, typeName); err != nil { return nil, err } // 编码数据 if err := c.encodeValue(buf, reflect.ValueOf(msg)); err != nil { return nil, err } return buf.Bytes(), nil } // Decode 二进制解码 func (c *BinaryCodec) Decode(data []byte, msg interface{}) error { buf := bytes.NewReader(data) // 读取类型名 typeName, err := c.readString(buf) if err != nil { return err } // 验证类型 c.mutex.RLock() expectedType, exists := c.typeRegistry[typeName] c.mutex.RUnlock() if !exists { return fmt.Errorf("unknown type: %s", typeName) } msgValue := reflect.ValueOf(msg) if msgValue.Kind() != reflect.Ptr { return fmt.Errorf("msg must be a pointer") } msgElem := msgValue.Elem() if msgElem.Type() != expectedType { return fmt.Errorf("type mismatch: expected %s, got %s", expectedType, msgElem.Type()) } // 解码数据 return c.decodeValue(buf, msgElem) } // Name 编解码器名称 func (c *BinaryCodec) Name() string { return "binary" } // encodeValue 编码值 func (c *BinaryCodec) encodeValue(buf *bytes.Buffer, v reflect.Value) error { switch v.Kind() { case reflect.Bool: if v.Bool() { return binary.Write(buf, binary.BigEndian, uint8(1)) } return binary.Write(buf, binary.BigEndian, uint8(0)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return binary.Write(buf, binary.BigEndian, v.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return binary.Write(buf, binary.BigEndian, v.Uint()) case reflect.Float32, reflect.Float64: return binary.Write(buf, binary.BigEndian, v.Float()) case reflect.String: return c.writeString(buf, v.String()) case reflect.Slice: // 写入长度 if err := binary.Write(buf, binary.BigEndian, uint32(v.Len())); err != nil { return err } // 写入元素 for i := 0; i < v.Len(); i++ { if err := c.encodeValue(buf, v.Index(i)); err != nil { return err } } return nil case reflect.Map: // 写入长度 if err := binary.Write(buf, binary.BigEndian, uint32(v.Len())); err != nil { return err } // 写入键值对 for _, key := range v.MapKeys() { if err := c.encodeValue(buf, key); err != nil { return err } if err := c.encodeValue(buf, v.MapIndex(key)); err != nil { return err } } return nil case reflect.Struct: // 写入字段数量 if err := binary.Write(buf, binary.BigEndian, uint32(v.NumField())); err != nil { return err } // 写入字段 for i := 0; i < v.NumField(); i++ { field := v.Field(i) if field.CanInterface() { if err := c.encodeValue(buf, field); err != nil { return err } } } return nil case reflect.Ptr: if v.IsNil() { return binary.Write(buf, binary.BigEndian, uint8(0)) } if err := binary.Write(buf, binary.BigEndian, uint8(1)); err != nil { return err } return c.encodeValue(buf, v.Elem()) default: return fmt.Errorf("unsupported type: %s", v.Kind()) } } // decodeValue 解码值 func (c *BinaryCodec) decodeValue(buf *bytes.Reader, v reflect.Value) error { switch v.Kind() { case reflect.Bool: var b uint8 if err := binary.Read(buf, binary.BigEndian, &b); err != nil { return err } v.SetBool(b != 0) return nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var val int64 if err := binary.Read(buf, binary.BigEndian, &val); err != nil { return err } v.SetInt(val) return nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var val uint64 if err := binary.Read(buf, binary.BigEndian, &val); err != nil { return err } v.SetUint(val) return nil case reflect.Float32, reflect.Float64: var val float64 if err := binary.Read(buf, binary.BigEndian, &val); err != nil { return err } v.SetFloat(val) return nil case reflect.String: str, err := c.readString(buf) if err != nil { return err } v.SetString(str) return nil case reflect.Slice: // 读取长度 var length uint32 if err := binary.Read(buf, binary.BigEndian, &length); err != nil { return err } // 创建切片 slice := reflect.MakeSlice(v.Type(), int(length), int(length)) // 读取元素 for i := 0; i < int(length); i++ { if err := c.decodeValue(buf, slice.Index(i)); err != nil { return err } } v.Set(slice) return nil case reflect.Map: // 读取长度 var length uint32 if err := binary.Read(buf, binary.BigEndian, &length); err != nil { return err } // 创建映射 mapValue := reflect.MakeMap(v.Type()) // 读取键值对 for i := 0; i < int(length); i++ { key := reflect.New(v.Type().Key()).Elem() val := reflect.New(v.Type().Elem()).Elem() if err := c.decodeValue(buf, key); err != nil { return err } if err := c.decodeValue(buf, val); err != nil { return err } mapValue.SetMapIndex(key, val) } v.Set(mapValue) return nil case reflect.Struct: // 读取字段数量 var fieldCount uint32 if err := binary.Read(buf, binary.BigEndian, &fieldCount); err != nil { return err } // 读取字段 for i := 0; i < int(fieldCount) && i < v.NumField(); i++ { field := v.Field(i) if field.CanSet() { if err := c.decodeValue(buf, field); err != nil { return err } } } return nil case reflect.Ptr: var isNil uint8 if err := binary.Read(buf, binary.BigEndian, &isNil); err != nil { return err } if isNil == 0 { v.Set(reflect.Zero(v.Type())) return nil } // 创建新值 newVal := reflect.New(v.Type().Elem()) if err := c.decodeValue(buf, newVal.Elem()); err != nil { return err } v.Set(newVal) return nil default: return fmt.Errorf("unsupported type: %s", v.Kind()) } } // writeString 写入字符串 func (c *BinaryCodec) writeString(buf *bytes.Buffer, s string) error { data := []byte(s) if err := binary.Write(buf, binary.BigEndian, uint32(len(data))); err != nil { return err } _, err := buf.Write(data) return err } // readString 读取字符串 func (c *BinaryCodec) readString(buf *bytes.Reader) (string, error) { var length uint32 if err := binary.Read(buf, binary.BigEndian, &length); err != nil { return "", err } data := make([]byte, length) if _, err := io.ReadFull(buf, data); err != nil { return "", err } return string(data), nil } // CompressCodec 压缩编解码器 type CompressCodec struct { baseCodec Codec } // NewCompressCodec 创建压缩编解码器 func NewCompressCodec(baseCodec Codec) *CompressCodec { return &CompressCodec{ baseCodec: baseCodec, } } // Encode 压缩编码 func (c *CompressCodec) Encode(msg interface{}) ([]byte, error) { // 先用基础编解码器编码 data, err := c.baseCodec.Encode(msg) if err != nil { return nil, err } // 压缩数据 var buf bytes.Buffer writer := gzip.NewWriter(&buf) if _, err := writer.Write(data); err != nil { return nil, err } if err := writer.Close(); err != nil { return nil, err } return buf.Bytes(), nil } // Decode 解压解码 func (c *CompressCodec) Decode(data []byte, msg interface{}) error { // 解压数据 reader, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { return err } defer reader.Close() decompressed, err := io.ReadAll(reader) if err != nil { return err } // 用基础编解码器解码 return c.baseCodec.Decode(decompressed, msg) } // Name 编解码器名称 func (c *CompressCodec) Name() string { return "compress_" + c.baseCodec.Name() } // EncryptCodec 加密编解码器 type EncryptCodec struct { baseCodec Codec key []byte } // NewEncryptCodec 创建加密编解码器 func NewEncryptCodec(baseCodec Codec, key []byte) *EncryptCodec { return &EncryptCodec{ baseCodec: baseCodec, key: key, } } // Encode 加密编码 func (c *EncryptCodec) Encode(msg interface{}) ([]byte, error) { // 先用基础编解码器编码 data, err := c.baseCodec.Encode(msg) if err != nil { return nil, err } // 创建AES加密器 block, err := aes.NewCipher(c.key) if err != nil { return nil, err } // 生成随机IV iv := make([]byte, aes.BlockSize) if _, err := rand.Read(iv); err != nil { return nil, err } // 加密数据 stream := cipher.NewCFBEncrypter(block, iv) encrypted := make([]byte, len(data)) stream.XORKeyStream(encrypted, data) // 返回IV+加密数据 result := make([]byte, len(iv)+len(encrypted)) copy(result, iv) copy(result[len(iv):], encrypted) return result, nil } // Decode 解密解码 func (c *EncryptCodec) Decode(data []byte, msg interface{}) error { if len(data) < aes.BlockSize { return fmt.Errorf("encrypted data too short") } // 提取IV和加密数据 iv := data[:aes.BlockSize] encrypted := data[aes.BlockSize:] // 创建AES解密器 block, err := aes.NewCipher(c.key) if err != nil { return err } // 解密数据 stream := cipher.NewCFBDecrypter(block, iv) decrypted := make([]byte, len(encrypted)) stream.XORKeyStream(decrypted, encrypted) // 用基础编解码器解码 return c.baseCodec.Decode(decrypted, msg) } // Name 编解码器名称 func (c *EncryptCodec) Name() string { return "encrypt_" + c.baseCodec.Name() } // CodecManager 编解码器管理器 type CodecManager struct { codecs map[string]Codec mutex sync.RWMutex } // NewCodecManager 创建编解码器管理器 func NewCodecManager() *CodecManager { m := &CodecManager{ codecs: make(map[string]Codec), } // 注册默认编解码器 m.RegisterCodec(NewJSONCodec()) m.RegisterCodec(NewBinaryCodec()) return m } // RegisterCodec 注册编解码器 func (m *CodecManager) RegisterCodec(codec Codec) { m.mutex.Lock() defer m.mutex.Unlock() m.codecs[codec.Name()] = codec } // GetCodec 获取编解码器 func (m *CodecManager) GetCodec(name string) (Codec, bool) { m.mutex.RLock() defer m.mutex.RUnlock() codec, exists := m.codecs[name] return codec, exists } // ListCodecs 列出所有编解码器 func (m *CodecManager) ListCodecs() []string { m.mutex.RLock() defer m.mutex.RUnlock() names := make([]string, 0, len(m.codecs)) for name := range m.codecs { names = append(names, name) } return names } // MessageProcessor 消息处理器 type MessageProcessor struct { codecManager *CodecManager defaultCodec string } // NewMessageProcessor 创建消息处理器 func NewMessageProcessor() *MessageProcessor { return &MessageProcessor{ codecManager: NewCodecManager(), defaultCodec: "json", } } // SetDefaultCodec 设置默认编解码器 func (p *MessageProcessor) SetDefaultCodec(name string) { p.defaultCodec = name } // RegisterCodec 注册编解码器 func (p *MessageProcessor) RegisterCodec(codec Codec) { p.codecManager.RegisterCodec(codec) } // EncodeMessage 编码消息 func (p *MessageProcessor) EncodeMessage(msgType MessageType, data interface{}, codecName string) (*Message, error) { if codecName == "" { codecName = p.defaultCodec } codec, exists := p.codecManager.GetCodec(codecName) if !exists { return nil, fmt.Errorf("codec %s not found", codecName) } body, err := codec.Encode(data) if err != nil { return nil, err } msg := &Message{ Header: MessageHeader{ Type: msgType, }, Body: body, } return msg, nil } // DecodeMessage 解码消息 func (p *MessageProcessor) DecodeMessage(msg *Message, data interface{}, codecName string) error { if codecName == "" { codecName = p.defaultCodec } codec, exists := p.codecManager.GetCodec(codecName) if !exists { return fmt.Errorf("codec %s not found", codecName) } return codec.Decode(msg.Body, data) } // ProcessMessage 处理消息(自动检测编解码器) func (p *MessageProcessor) ProcessMessage(msg *Message, data interface{}) error { // 尝试不同的编解码器 codecs := p.codecManager.ListCodecs() for _, codecName := range codecs { if err := p.DecodeMessage(msg, data, codecName); err == nil { return nil } } return fmt.Errorf("failed to decode message with any codec") } ================================================ FILE: internal/network/protocol.go ================================================ // Package network 网络协议定义 // Author: MMO Server Team // Created: 2024 package network import ( "context" "encoding/binary" "fmt" "sync" "time" // "github.com/phuhao00/netcore-go/pkg/core" // TODO: 暂时注释掉有问题的依赖 ) // MessageType 消息类型定义 type MessageType uint16 const ( // 系统消息 MsgTypeHeartbeat MessageType = 1000 + iota MsgTypeLogin MsgTypeLogout MsgTypeAuth MsgTypeError // 玩家消息 MsgTypePlayerInfo MessageType = 2000 + iota MsgTypePlayerMove MsgTypePlayerAction MsgTypePlayerChat MsgTypePlayerMail // 游戏消息 MsgTypeGameBattle MessageType = 3000 + iota MsgTypeGameShop MsgTypeGameBag MsgTypeGamePet MsgTypeGameBuilding // RPC消息 MsgTypeRPCRequest MessageType = 9000 + iota MsgTypeRPCResponse MsgTypeRPCNotify ) // MessageHeader 消息头定义 type MessageHeader struct { Magic uint32 // 魔数 0x12345678 Length uint32 // 消息总长度(包含头部) Type MessageType // 消息类型 Sequence uint32 // 序列号 Flags uint16 // 标志位 Checksum uint16 // 校验和 } // Message 完整消息结构 type Message struct { Header MessageHeader Body []byte } const ( MessageMagic = 0x12345678 MessageHeaderSize = 20 // 消息头大小 MaxMessageSize = 1024 * 1024 // 最大消息大小 1MB MinMessageSize = MessageHeaderSize ) // MessageFlags 消息标志位 const ( FlagCompressed = 1 << iota // 压缩标志 FlagEncrypted // 加密标志 FlagFragment // 分片标志 FlagAck // 需要确认标志 ) // Connection 连接接口 type Connection interface { Send(data []byte) error Close() error RemoteAddr() string } // TCPConnection TCP连接封装 type TCPConnection struct { conn Connection // 使用自定义的Connection接口 readBuf []byte writeBuf []byte mutex sync.RWMutex closed bool lastPing time.Time userID string sessionID string } // NewTCPConnection 创建新的TCP连接 func NewTCPConnection(conn Connection) *TCPConnection { return &TCPConnection{ conn: conn, readBuf: make([]byte, 4096), writeBuf: make([]byte, 4096), lastPing: time.Now(), } } // ReadMessage 读取消息 - 注意:netcore-go框架通过回调提供数据,此方法主要用于兼容性 func (c *TCPConnection) ReadMessage() (*Message, error) { c.mutex.RLock() defer c.mutex.RUnlock() if c.closed { return nil, fmt.Errorf("connection closed") } // 在netcore-go架构中,消息读取通过onMessage回调处理 // 这个方法主要用于兼容性,实际的消息处理在parseMessage中进行 return nil, fmt.Errorf("direct message reading not supported in netcore-go architecture") } // WriteMessage 写入消息 func (c *TCPConnection) WriteMessage(msg *Message) error { c.mutex.Lock() defer c.mutex.Unlock() if c.closed { return fmt.Errorf("connection closed") } // 设置消息长度和校验和 msg.Header.Length = uint32(MessageHeaderSize + len(msg.Body)) msg.Header.Magic = MessageMagic msg.Header.Checksum = calculateChecksum(&msg.Header, msg.Body) // 序列化消息头 headerBuf := serializeMessageHeader(&msg.Header) // 组合完整消息 fullMsg := make([]byte, len(headerBuf)+len(msg.Body)) copy(fullMsg, headerBuf) copy(fullMsg[len(headerBuf):], msg.Body) // 使用netcore-go的Send方法发送消息 err := c.conn.Send(fullMsg) if err != nil { return fmt.Errorf("send message failed: %w", err) } return nil } // Close 关闭连接 func (c *TCPConnection) Close() error { c.mutex.Lock() defer c.mutex.Unlock() if c.closed { return nil } c.closed = true return c.conn.Close() } // SetUserID 设置用户ID func (c *TCPConnection) SetUserID(userID string) { c.mutex.Lock() defer c.mutex.Unlock() c.userID = userID } // GetUserID 获取用户ID func (c *TCPConnection) GetUserID() string { c.mutex.RLock() defer c.mutex.RUnlock() return c.userID } // SetSessionID 设置会话ID func (c *TCPConnection) SetSessionID(sessionID string) { c.mutex.Lock() defer c.mutex.Unlock() c.sessionID = sessionID } // GetSessionID 获取会话ID func (c *TCPConnection) GetSessionID() string { c.mutex.RLock() defer c.mutex.RUnlock() return c.sessionID } // UpdatePing 更新心跳时间 func (c *TCPConnection) UpdatePing() { c.mutex.Lock() defer c.mutex.Unlock() c.lastPing = time.Now() } // GetLastPing 获取最后心跳时间 func (c *TCPConnection) GetLastPing() time.Time { c.mutex.RLock() defer c.mutex.RUnlock() return c.lastPing } // parseMessageHeader 解析消息头 func parseMessageHeader(data []byte) (*MessageHeader, error) { if len(data) < MessageHeaderSize { return nil, fmt.Errorf("header data too short") } header := &MessageHeader{ Magic: binary.BigEndian.Uint32(data[0:4]), Length: binary.BigEndian.Uint32(data[4:8]), Type: MessageType(binary.BigEndian.Uint16(data[8:10])), Sequence: binary.BigEndian.Uint32(data[10:14]), Flags: binary.BigEndian.Uint16(data[14:16]), Checksum: binary.BigEndian.Uint16(data[16:18]), } if header.Magic != MessageMagic { return nil, fmt.Errorf("invalid magic number: 0x%x", header.Magic) } return header, nil } // serializeMessageHeader 序列化消息头 func serializeMessageHeader(header *MessageHeader) []byte { data := make([]byte, MessageHeaderSize) binary.BigEndian.PutUint32(data[0:4], header.Magic) binary.BigEndian.PutUint32(data[4:8], header.Length) binary.BigEndian.PutUint16(data[8:10], uint16(header.Type)) binary.BigEndian.PutUint32(data[10:14], header.Sequence) binary.BigEndian.PutUint16(data[14:16], header.Flags) binary.BigEndian.PutUint16(data[16:18], header.Checksum) return data } // calculateChecksum 计算校验和 func calculateChecksum(header *MessageHeader, body []byte) uint16 { sum := uint32(0) // 计算头部校验和(除了校验和字段本身) sum += uint32(header.Magic) sum += uint32(header.Length) sum += uint32(header.Type) sum += uint32(header.Sequence) sum += uint32(header.Flags) // 计算消息体校验和 for i := 0; i < len(body); i += 2 { if i+1 < len(body) { sum += uint32(binary.BigEndian.Uint16(body[i : i+2])) } else { sum += uint32(body[i]) << 8 } } // 折叠为16位 for sum>>16 != 0 { sum = (sum & 0xFFFF) + (sum >> 16) } return uint16(^sum) } // verifyChecksum 验证校验和 func verifyChecksum(header *MessageHeader, body []byte) bool { expected := calculateChecksum(header, body) return header.Checksum == expected } // NetworkService netcore-go网络服务接口 type NetworkService interface { // StartTCPServer 启动TCP服务器 StartTCPServer(ctx context.Context, addr string) error // StopTCPServer 停止TCP服务器 StopTCPServer(ctx context.Context) error // SendMessage 发送消息 SendMessage(ctx context.Context, userID string, msg *Message) error // BroadcastMessage 广播消息 BroadcastMessage(ctx context.Context, msg *Message) error // GetConnectionCount 获取连接数 GetConnectionCount(ctx context.Context) (int, error) } // Logger 日志接口 type Logger interface { Info(msg string, args ...interface{}) Error(msg string, args ...interface{}) Debug(msg string, args ...interface{}) } // networkServiceImpl 网络服务实现 type networkServiceImpl struct { // tcpServer *tcp.Server // TODO: 待实现服务器集成 // connPool *pool.ConnPool // TODO: 待实现连接池 connections map[string]*TCPConnection mutex sync.RWMutex running bool logger Logger } // StartTCPServer 启动TCP服务器 func (n *networkServiceImpl) StartTCPServer(ctx context.Context, addr string) error { n.mutex.Lock() defer n.mutex.Unlock() if n.running { return fmt.Errorf("server already running") } // TODO: 实现TCP服务器创建 // 创建netcore-go TCP服务器配置 // config := &tcp.ServerConfig{ // Address: addr, // MaxConn: 10000, // ReadTimeout: 30 * time.Second, // WriteTimeout: 30 * time.Second, // } // 创建TCP服务器 // server, err := tcp.NewServer(config) // if err != nil { // return fmt.Errorf("create tcp server failed: %w", err) // } // 设置连接处理器 // server.SetOnConnect(n.onConnect) // server.SetOnMessage(n.onMessage) // server.SetOnDisconnect(n.onDisconnect) // n.tcpServer = server n.connections = make(map[string]*TCPConnection) n.running = true // 启动服务器 // go func() { // if err := n.tcpServer.Start(); err != nil { // n.logger.Error("TCP server start failed", "error", err) // } // }() n.logger.Info(fmt.Sprintf("TCP server started on %s", addr)) return nil } // StopTCPServer 停止TCP服务器 func (n *networkServiceImpl) StopTCPServer(ctx context.Context) error { n.mutex.Lock() defer n.mutex.Unlock() if !n.running { return nil } n.running = false // TODO: 停止TCP服务器 // if n.tcpServer != nil { // n.tcpServer.Stop() // } // TODO: 关闭连接池 // if n.connPool != nil { // n.connPool.Close() // } // 关闭所有连接 for _, conn := range n.connections { conn.Close() } n.connections = nil n.logger.Info("TCP server stopped") return nil } // SendMessage 发送消息 func (n *networkServiceImpl) SendMessage(ctx context.Context, userID string, msg *Message) error { n.mutex.RLock() conn, exists := n.connections[userID] n.mutex.RUnlock() if !exists { return fmt.Errorf("user %s not connected", userID) } return conn.WriteMessage(msg) } // BroadcastMessage 广播消息 func (n *networkServiceImpl) BroadcastMessage(ctx context.Context, msg *Message) error { n.mutex.RLock() connections := make([]*TCPConnection, 0, len(n.connections)) for _, conn := range n.connections { connections = append(connections, conn) } n.mutex.RUnlock() for _, conn := range connections { if err := conn.WriteMessage(msg); err != nil { n.logger.Error(fmt.Sprintf("broadcast message failed: %v", err)) } } return nil } // GetConnectionCount 获取连接数 func (n *networkServiceImpl) GetConnectionCount(ctx context.Context) (int, error) { n.mutex.RLock() defer n.mutex.RUnlock() return len(n.connections), nil } // onConnect 连接建立回调 func (n *networkServiceImpl) onConnect(conn Connection) { n.logger.Info(fmt.Sprintf("new connection established from %s", conn.RemoteAddr())) // 创建TCP连接包装器 tcpConn := &TCPConnection{ conn: conn, readBuf: make([]byte, 4096), writeBuf: make([]byte, 4096), lastPing: time.Now(), } // 暂时使用连接地址作为临时ID tempID := conn.RemoteAddr() n.mutex.Lock() n.connections[tempID] = tcpConn n.mutex.Unlock() } // onMessage 消息接收回调 func (n *networkServiceImpl) onMessage(conn Connection, data []byte) { // 解析消息 msg, err := n.parseMessage(data) if err != nil { n.logger.Error(fmt.Sprintf("parse message failed: %v", err)) return } // 处理消息 // 需要找到对应的TCPConnection和创建context n.mutex.RLock() var tcpConn *TCPConnection for _, c := range n.connections { if c.conn == conn { tcpConn = c break } } n.mutex.RUnlock() if tcpConn != nil { ctx := context.Background() if err := n.handleMessage(ctx, tcpConn, msg); err != nil { n.logger.Error(fmt.Sprintf("handle message failed: %v", err)) } } } // onDisconnect 连接断开回调 func (n *networkServiceImpl) onDisconnect(conn Connection) { remoteAddr := conn.RemoteAddr() n.logger.Info(fmt.Sprintf("connection disconnected from %s", remoteAddr)) n.mutex.Lock() defer n.mutex.Unlock() // 查找并移除连接 for userID, tcpConn := range n.connections { if tcpConn.conn == conn { delete(n.connections, userID) break } } } // parseMessage 解析消息 func (n *networkServiceImpl) parseMessage(data []byte) (*Message, error) { if len(data) < MessageHeaderSize { return nil, fmt.Errorf("message too short") } // 解析消息头 header, err := parseMessageHeader(data[:MessageHeaderSize]) if err != nil { return nil, err } // 验证消息长度 if int(header.Length) != len(data) { return nil, fmt.Errorf("message length mismatch") } // 提取消息体 body := data[MessageHeaderSize:] // 验证校验和 if !verifyChecksum(header, body) { return nil, fmt.Errorf("checksum verification failed") } return &Message{ Header: *header, Body: body, }, nil } // NewNetworkService 创建网络服务 func NewNetworkService(logger Logger) NetworkService { return &networkServiceImpl{ connections: make(map[string]*TCPConnection), logger: logger, } } // handleMessage 处理消息 func (n *networkServiceImpl) handleMessage(ctx context.Context, conn *TCPConnection, msg *Message) error { switch msg.Header.Type { case MsgTypeHeartbeat: conn.UpdatePing() // 回复心跳 resp := &Message{ Header: MessageHeader{ Type: MsgTypeHeartbeat, Sequence: msg.Header.Sequence, }, } return conn.WriteMessage(resp) case MsgTypeLogin: // 处理登录消息 return n.handleLogin(ctx, conn, msg) case MsgTypeLogout: // 处理登出消息 return n.handleLogout(ctx, conn, msg) default: // 其他消息类型的处理 n.logger.Debug(fmt.Sprintf("received message type: %d, size: %d", msg.Header.Type, len(msg.Body))) } return nil } // handleLogin 处理登录 func (n *networkServiceImpl) handleLogin(ctx context.Context, conn *TCPConnection, msg *Message) error { // TODO: 实现登录逻辑 // 这里应该验证用户凭据,设置用户ID等 // 临时实现:直接设置用户ID userID := fmt.Sprintf("user_%d", time.Now().UnixNano()) conn.SetUserID(userID) // 添加到连接映射 n.mutex.Lock() n.connections[userID] = conn n.mutex.Unlock() // 回复登录成功 resp := &Message{ Header: MessageHeader{ Type: MsgTypeLogin, Sequence: msg.Header.Sequence, }, Body: []byte(fmt.Sprintf(`{"status":"success","userID":"%s"}`, userID)), } return conn.WriteMessage(resp) } // handleLogout 处理登出 func (n *networkServiceImpl) handleLogout(ctx context.Context, conn *TCPConnection, msg *Message) error { userID := conn.GetUserID() if userID != "" { n.mutex.Lock() delete(n.connections, userID) n.mutex.Unlock() } // 回复登出成功 resp := &Message{ Header: MessageHeader{ Type: MsgTypeLogout, Sequence: msg.Header.Sequence, }, Body: []byte(`{"status":"success"}`), } return conn.WriteMessage(resp) } ================================================ FILE: internal/network/session/session.go ================================================ package session import ( "fmt" "net" "sync" "time" ) // Session 会话接口 type Session interface { ID() string PlayerID() string SetPlayerID(playerID string) Conn() net.Conn Send(data []byte) error Close() error IsActive() bool LastActivity() time.Time UpdateActivity() GetAttribute(key string) interface{} SetAttribute(key string, value interface{}) } // TCPSession TCP会话实现 type TCPSession struct { id string playerID string conn net.Conn lastActivity time.Time active bool attributes map[string]interface{} mutex sync.RWMutex } // NewTCPSession 创建新的TCP会话 func NewTCPSession(id string, conn net.Conn) *TCPSession { return &TCPSession{ id: id, conn: conn, lastActivity: time.Now(), active: true, attributes: make(map[string]interface{}), } } // ID 获取会话ID func (s *TCPSession) ID() string { return s.id } // PlayerID 获取玩家ID func (s *TCPSession) PlayerID() string { s.mutex.RLock() defer s.mutex.RUnlock() return s.playerID } // SetPlayerID 设置玩家ID func (s *TCPSession) SetPlayerID(playerID string) { s.mutex.Lock() defer s.mutex.Unlock() s.playerID = playerID } // Conn 获取连接 func (s *TCPSession) Conn() net.Conn { return s.conn } // Send 发送数据 func (s *TCPSession) Send(data []byte) error { s.mutex.Lock() defer s.mutex.Unlock() if !s.active { return ErrSessionClosed } _, err := s.conn.Write(data) if err != nil { s.active = false return err } s.lastActivity = time.Now() return nil } // Close 关闭会话 func (s *TCPSession) Close() error { s.mutex.Lock() defer s.mutex.Unlock() if !s.active { return nil } s.active = false return s.conn.Close() } // IsActive 检查会话是否活跃 func (s *TCPSession) IsActive() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.active } // LastActivity 获取最后活动时间 func (s *TCPSession) LastActivity() time.Time { s.mutex.RLock() defer s.mutex.RUnlock() return s.lastActivity } // UpdateActivity 更新活动时间 func (s *TCPSession) UpdateActivity() { s.mutex.Lock() defer s.mutex.Unlock() s.lastActivity = time.Now() } // GetAttribute 获取属性 func (s *TCPSession) GetAttribute(key string) interface{} { s.mutex.RLock() defer s.mutex.RUnlock() return s.attributes[key] } // SetAttribute 设置属性 func (s *TCPSession) SetAttribute(key string, value interface{}) { s.mutex.Lock() defer s.mutex.Unlock() s.attributes[key] = value } // NewSession 创建新会话(简单实现,用于兼容) func NewSession(id string) Session { return &TCPSession{ id: id, conn: nil, // 没有实际连接 lastActivity: time.Now(), active: true, attributes: make(map[string]interface{}), } } // 错误定义 var ( ErrSessionClosed = fmt.Errorf("session is closed") ) ================================================ FILE: internal/platform.go ================================================ package internal ================================================ FILE: internal/proto/README.md ================================================ # Proto 常量管理 本项目将所有交互相关的枚举、常量和协议都定义在proto文件中,通过代码生成来统一管理。 ## 文件结构 ``` internal/proto/ ├── battle/ # 战斗相关proto ├── common/ # 通用proto ├── errors/ # 错误码proto ├── messages/ # 消息号proto ├── pet/ # 宠物相关proto ├── player/ # 玩家相关proto └── protocol/ # 协议常量proto ``` ## Proto文件说明 ### 1. errors.proto - 错误码管理 包含所有错误码和错误消息: - `CommonErrorCode`: 通用错误 (1000-1999) - `BattleErrorCode`: 战斗相关错误 (2000-2999) - `PetErrorCode`: 宠物相关错误 (3000-3999) - `ItemErrorCode`: 物品相关错误 (4000-4999) - `BuildingErrorCode`: 建筑相关错误 (5000-5999) - `SocialErrorCode`: 社交相关错误 (6000-6999) - `QuestErrorCode`: 任务相关错误 (7000-7999) - `SystemErrorCode`: 系统相关错误 (8000-8999) ### 2. messages.proto - 消息号管理 包含所有消息类型常量: - `SystemMessageID`: 系统消息 (0x0000-0x00FF) - `PlayerMessageID`: 玩家相关消息 (0x0100-0x01FF) - `BattleMessageID`: 战斗相关消息 (0x0200-0x02FF) - `PetMessageID`: 宠物相关消息 (0x0300-0x03FF) - `BuildingMessageID`: 建筑相关消息 (0x0400-0x04FF) - `SocialMessageID`: 社交相关消息 (0x0500-0x05FF) - `ItemMessageID`: 物品相关消息 (0x0600-0x06FF) - `QuestMessageID`: 任务相关消息 (0x0700-0x07FF) - `QueryMessageID`: 查询相关消息 (0x0800-0x08FF) - `AdminMessageID`: 系统管理消息 (0x0900-0x09FF) ### 3. protocol.proto - 协议常量 包含协议相关的枚举和常量: - 各种消息类型枚举 - 错误码枚举 - 状态枚举 - 消息标志位枚举 - 协议常量定义 ### 4. 其他模块proto文件 - `battle.proto`: 战斗相关枚举和消息 - `player.proto`: 玩家相关枚举和消息 - `pet.proto`: 宠物相关枚举和消息 - `common.proto`: 通用枚举和消息 ## 使用方法 ### 导入生成的包 ```go import ( "greatestworks/internal/proto/errors" "greatestworks/internal/proto/messages" "greatestworks/internal/proto/protocol" "greatestworks/internal/proto/common" "greatestworks/internal/proto/battle" "greatestworks/internal/proto/player" "greatestworks/internal/proto/pet" ) ``` ### 使用错误码 ```go // 使用通用错误码 if err != nil { return errors.CommonErrorCode_ERR_PLAYER_NOT_FOUND } // 使用战斗错误码 if battleFull { return errors.BattleErrorCode_ERR_BATTLE_FULL } ``` ### 使用消息号 ```go // 创建消息头 header := &messages.MessageHeader{ MessageId: uint32(messages.PlayerMessageID_MSG_PLAYER_LOGIN), MessageType: uint32(messages.SystemMessageID_MSG_AUTH), Flags: uint32(messages.MessageFlag_MESSAGE_FLAG_REQUEST), } ``` ### 使用状态枚举 ```go // 检查玩家状态 if player.Status == protocol.PlayerStatus_PLAYER_STATUS_ONLINE { // 处理在线玩家 } // 检查战斗状态 if battle.Status == protocol.BattleStatus_BATTLE_STATUS_ACTIVE { // 处理进行中的战斗 } ``` ## 代码生成 使用以下命令重新生成proto代码: ```bash # 生成所有proto文件 .\protoc\bin\protoc.exe --go_out=. --go_opt=paths=source_relative proto/*.proto # 或者使用脚本 scripts/generate_proto.sh ``` ## 优势 1. **类型安全**: 所有常量都有明确的类型定义 2. **统一管理**: 所有枚举和常量都在proto文件中定义 3. **代码生成**: 自动生成Go代码,避免手动维护 4. **版本控制**: proto文件可以版本化,便于管理 5. **多语言支持**: 可以生成多种语言的代码 6. **文档化**: proto文件本身就是很好的文档 ## 注意事项 1. 修改proto文件后需要重新生成代码 2. 枚举值不能重复,需要合理分配范围 3. 添加新的枚举值时需要考虑向后兼容性 4. 错误码和消息号需要按模块分类管理 ================================================ FILE: internal/proto/battle/battle.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/battle.proto package battle import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 战斗状态 type BattleStatus int32 const ( BattleStatus_BATTLE_STATUS_UNSPECIFIED BattleStatus = 0 BattleStatus_BATTLE_STATUS_WAITING BattleStatus = 1 BattleStatus_BATTLE_STATUS_STARTING BattleStatus = 2 BattleStatus_BATTLE_STATUS_ACTIVE BattleStatus = 3 BattleStatus_BATTLE_STATUS_ENDING BattleStatus = 4 BattleStatus_BATTLE_STATUS_FINISHED BattleStatus = 5 BattleStatus_BATTLE_STATUS_CANCELLED BattleStatus = 6 ) // Enum value maps for BattleStatus. var ( BattleStatus_name = map[int32]string{ 0: "BATTLE_STATUS_UNSPECIFIED", 1: "BATTLE_STATUS_WAITING", 2: "BATTLE_STATUS_STARTING", 3: "BATTLE_STATUS_ACTIVE", 4: "BATTLE_STATUS_ENDING", 5: "BATTLE_STATUS_FINISHED", 6: "BATTLE_STATUS_CANCELLED", } BattleStatus_value = map[string]int32{ "BATTLE_STATUS_UNSPECIFIED": 0, "BATTLE_STATUS_WAITING": 1, "BATTLE_STATUS_STARTING": 2, "BATTLE_STATUS_ACTIVE": 3, "BATTLE_STATUS_ENDING": 4, "BATTLE_STATUS_FINISHED": 5, "BATTLE_STATUS_CANCELLED": 6, } ) func (x BattleStatus) Enum() *BattleStatus { p := new(BattleStatus) *p = x return p } func (x BattleStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_battle_proto_enumTypes[0].Descriptor() } func (BattleStatus) Type() protoreflect.EnumType { return &file_proto_battle_proto_enumTypes[0] } func (x BattleStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleStatus.Descriptor instead. func (BattleStatus) EnumDescriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{0} } // 战斗类型枚举 type BattleType int32 const ( BattleType_BATTLE_TYPE_UNSPECIFIED BattleType = 0 BattleType_BATTLE_TYPE_PVP BattleType = 1 // 玩家对战 BattleType_BATTLE_TYPE_PVE BattleType = 2 // 玩家对环境 BattleType_BATTLE_TYPE_ARENA BattleType = 3 // 竞技场 BattleType_BATTLE_TYPE_RAID BattleType = 4 // 团队副本 BattleType_BATTLE_TYPE_DUNGEON BattleType = 5 // 地下城 BattleType_BATTLE_TYPE_BOSS BattleType = 6 // BOSS战 BattleType_BATTLE_TYPE_TOURNAMENT BattleType = 7 // 锦标赛 ) // Enum value maps for BattleType. var ( BattleType_name = map[int32]string{ 0: "BATTLE_TYPE_UNSPECIFIED", 1: "BATTLE_TYPE_PVP", 2: "BATTLE_TYPE_PVE", 3: "BATTLE_TYPE_ARENA", 4: "BATTLE_TYPE_RAID", 5: "BATTLE_TYPE_DUNGEON", 6: "BATTLE_TYPE_BOSS", 7: "BATTLE_TYPE_TOURNAMENT", } BattleType_value = map[string]int32{ "BATTLE_TYPE_UNSPECIFIED": 0, "BATTLE_TYPE_PVP": 1, "BATTLE_TYPE_PVE": 2, "BATTLE_TYPE_ARENA": 3, "BATTLE_TYPE_RAID": 4, "BATTLE_TYPE_DUNGEON": 5, "BATTLE_TYPE_BOSS": 6, "BATTLE_TYPE_TOURNAMENT": 7, } ) func (x BattleType) Enum() *BattleType { p := new(BattleType) *p = x return p } func (x BattleType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleType) Descriptor() protoreflect.EnumDescriptor { return file_proto_battle_proto_enumTypes[1].Descriptor() } func (BattleType) Type() protoreflect.EnumType { return &file_proto_battle_proto_enumTypes[1] } func (x BattleType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleType.Descriptor instead. func (BattleType) EnumDescriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{1} } // 战斗行动类型枚举 type BattleActionType int32 const ( BattleActionType_BATTLE_ACTION_TYPE_UNSPECIFIED BattleActionType = 0 BattleActionType_BATTLE_ACTION_TYPE_ATTACK BattleActionType = 1 // 攻击 BattleActionType_BATTLE_ACTION_TYPE_SKILL BattleActionType = 2 // 技能 BattleActionType_BATTLE_ACTION_TYPE_ITEM BattleActionType = 3 // 使用物品 BattleActionType_BATTLE_ACTION_TYPE_DEFEND BattleActionType = 4 // 防御 BattleActionType_BATTLE_ACTION_TYPE_ESCAPE BattleActionType = 5 // 逃跑 BattleActionType_BATTLE_ACTION_TYPE_WAIT BattleActionType = 6 // 等待 ) // Enum value maps for BattleActionType. var ( BattleActionType_name = map[int32]string{ 0: "BATTLE_ACTION_TYPE_UNSPECIFIED", 1: "BATTLE_ACTION_TYPE_ATTACK", 2: "BATTLE_ACTION_TYPE_SKILL", 3: "BATTLE_ACTION_TYPE_ITEM", 4: "BATTLE_ACTION_TYPE_DEFEND", 5: "BATTLE_ACTION_TYPE_ESCAPE", 6: "BATTLE_ACTION_TYPE_WAIT", } BattleActionType_value = map[string]int32{ "BATTLE_ACTION_TYPE_UNSPECIFIED": 0, "BATTLE_ACTION_TYPE_ATTACK": 1, "BATTLE_ACTION_TYPE_SKILL": 2, "BATTLE_ACTION_TYPE_ITEM": 3, "BATTLE_ACTION_TYPE_DEFEND": 4, "BATTLE_ACTION_TYPE_ESCAPE": 5, "BATTLE_ACTION_TYPE_WAIT": 6, } ) func (x BattleActionType) Enum() *BattleActionType { p := new(BattleActionType) *p = x return p } func (x BattleActionType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleActionType) Descriptor() protoreflect.EnumDescriptor { return file_proto_battle_proto_enumTypes[2].Descriptor() } func (BattleActionType) Type() protoreflect.EnumType { return &file_proto_battle_proto_enumTypes[2] } func (x BattleActionType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleActionType.Descriptor instead. func (BattleActionType) EnumDescriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{2} } // 战斗结果类型枚举 type BattleResultType int32 const ( BattleResultType_BATTLE_RESULT_TYPE_UNSPECIFIED BattleResultType = 0 BattleResultType_BATTLE_RESULT_TYPE_VICTORY BattleResultType = 1 // 胜利 BattleResultType_BATTLE_RESULT_TYPE_DEFEAT BattleResultType = 2 // 失败 BattleResultType_BATTLE_RESULT_TYPE_DRAW BattleResultType = 3 // 平局 BattleResultType_BATTLE_RESULT_TYPE_ESCAPE BattleResultType = 4 // 逃跑 BattleResultType_BATTLE_RESULT_TYPE_TIMEOUT BattleResultType = 5 // 超时 BattleResultType_BATTLE_RESULT_TYPE_DISCONNECT BattleResultType = 6 // 断线 ) // Enum value maps for BattleResultType. var ( BattleResultType_name = map[int32]string{ 0: "BATTLE_RESULT_TYPE_UNSPECIFIED", 1: "BATTLE_RESULT_TYPE_VICTORY", 2: "BATTLE_RESULT_TYPE_DEFEAT", 3: "BATTLE_RESULT_TYPE_DRAW", 4: "BATTLE_RESULT_TYPE_ESCAPE", 5: "BATTLE_RESULT_TYPE_TIMEOUT", 6: "BATTLE_RESULT_TYPE_DISCONNECT", } BattleResultType_value = map[string]int32{ "BATTLE_RESULT_TYPE_UNSPECIFIED": 0, "BATTLE_RESULT_TYPE_VICTORY": 1, "BATTLE_RESULT_TYPE_DEFEAT": 2, "BATTLE_RESULT_TYPE_DRAW": 3, "BATTLE_RESULT_TYPE_ESCAPE": 4, "BATTLE_RESULT_TYPE_TIMEOUT": 5, "BATTLE_RESULT_TYPE_DISCONNECT": 6, } ) func (x BattleResultType) Enum() *BattleResultType { p := new(BattleResultType) *p = x return p } func (x BattleResultType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleResultType) Descriptor() protoreflect.EnumDescriptor { return file_proto_battle_proto_enumTypes[3].Descriptor() } func (BattleResultType) Type() protoreflect.EnumType { return &file_proto_battle_proto_enumTypes[3] } func (x BattleResultType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleResultType.Descriptor instead. func (BattleResultType) EnumDescriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{3} } // 战斗事件类型枚举 type BattleEventType int32 const ( BattleEventType_BATTLE_EVENT_TYPE_UNSPECIFIED BattleEventType = 0 BattleEventType_BATTLE_EVENT_TYPE_DAMAGE BattleEventType = 1 // 伤害 BattleEventType_BATTLE_EVENT_TYPE_HEAL BattleEventType = 2 // 治疗 BattleEventType_BATTLE_EVENT_TYPE_BUFF BattleEventType = 3 // 增益 BattleEventType_BATTLE_EVENT_TYPE_DEBUFF BattleEventType = 4 // 减益 BattleEventType_BATTLE_EVENT_TYPE_CRITICAL BattleEventType = 5 // 暴击 BattleEventType_BATTLE_EVENT_TYPE_MISS BattleEventType = 6 // 未命中 BattleEventType_BATTLE_EVENT_TYPE_DODGE BattleEventType = 7 // 闪避 BattleEventType_BATTLE_EVENT_TYPE_BLOCK BattleEventType = 8 // 格挡 BattleEventType_BATTLE_EVENT_TYPE_DEATH BattleEventType = 9 // 死亡 BattleEventType_BATTLE_EVENT_TYPE_REVIVE BattleEventType = 10 // 复活 ) // Enum value maps for BattleEventType. var ( BattleEventType_name = map[int32]string{ 0: "BATTLE_EVENT_TYPE_UNSPECIFIED", 1: "BATTLE_EVENT_TYPE_DAMAGE", 2: "BATTLE_EVENT_TYPE_HEAL", 3: "BATTLE_EVENT_TYPE_BUFF", 4: "BATTLE_EVENT_TYPE_DEBUFF", 5: "BATTLE_EVENT_TYPE_CRITICAL", 6: "BATTLE_EVENT_TYPE_MISS", 7: "BATTLE_EVENT_TYPE_DODGE", 8: "BATTLE_EVENT_TYPE_BLOCK", 9: "BATTLE_EVENT_TYPE_DEATH", 10: "BATTLE_EVENT_TYPE_REVIVE", } BattleEventType_value = map[string]int32{ "BATTLE_EVENT_TYPE_UNSPECIFIED": 0, "BATTLE_EVENT_TYPE_DAMAGE": 1, "BATTLE_EVENT_TYPE_HEAL": 2, "BATTLE_EVENT_TYPE_BUFF": 3, "BATTLE_EVENT_TYPE_DEBUFF": 4, "BATTLE_EVENT_TYPE_CRITICAL": 5, "BATTLE_EVENT_TYPE_MISS": 6, "BATTLE_EVENT_TYPE_DODGE": 7, "BATTLE_EVENT_TYPE_BLOCK": 8, "BATTLE_EVENT_TYPE_DEATH": 9, "BATTLE_EVENT_TYPE_REVIVE": 10, } ) func (x BattleEventType) Enum() *BattleEventType { p := new(BattleEventType) *p = x return p } func (x BattleEventType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleEventType) Descriptor() protoreflect.EnumDescriptor { return file_proto_battle_proto_enumTypes[4].Descriptor() } func (BattleEventType) Type() protoreflect.EnumType { return &file_proto_battle_proto_enumTypes[4] } func (x BattleEventType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleEventType.Descriptor instead. func (BattleEventType) EnumDescriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{4} } // 创建战斗请求 type CreateBattleRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CreatorId string `protobuf:"bytes,1,opt,name=creator_id,json=creatorId,proto3" json:"creator_id,omitempty"` BattleType string `protobuf:"bytes,2,opt,name=battle_type,json=battleType,proto3" json:"battle_type,omitempty"` MaxPlayers int32 `protobuf:"varint,3,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` MapId string `protobuf:"bytes,4,opt,name=map_id,json=mapId,proto3" json:"map_id,omitempty"` Settings map[string]string `protobuf:"bytes,5,rep,name=settings,proto3" json:"settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateBattleRequest) Reset() { *x = CreateBattleRequest{} mi := &file_proto_battle_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateBattleRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateBattleRequest) ProtoMessage() {} func (x *CreateBattleRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateBattleRequest.ProtoReflect.Descriptor instead. func (*CreateBattleRequest) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{0} } func (x *CreateBattleRequest) GetCreatorId() string { if x != nil { return x.CreatorId } return "" } func (x *CreateBattleRequest) GetBattleType() string { if x != nil { return x.BattleType } return "" } func (x *CreateBattleRequest) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *CreateBattleRequest) GetMapId() string { if x != nil { return x.MapId } return "" } func (x *CreateBattleRequest) GetSettings() map[string]string { if x != nil { return x.Settings } return nil } // 创建战斗响应 type CreateBattleResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` BattleId string `protobuf:"bytes,2,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` Battle *BattleInfo `protobuf:"bytes,3,opt,name=battle,proto3" json:"battle,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateBattleResponse) Reset() { *x = CreateBattleResponse{} mi := &file_proto_battle_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateBattleResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateBattleResponse) ProtoMessage() {} func (x *CreateBattleResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateBattleResponse.ProtoReflect.Descriptor instead. func (*CreateBattleResponse) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{1} } func (x *CreateBattleResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *CreateBattleResponse) GetBattleId() string { if x != nil { return x.BattleId } return "" } func (x *CreateBattleResponse) GetBattle() *BattleInfo { if x != nil { return x.Battle } return nil } // 加入战斗请求 type JoinBattleRequest struct { state protoimpl.MessageState `protogen:"open.v1"` BattleId string `protobuf:"bytes,1,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` TeamId string `protobuf:"bytes,3,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinBattleRequest) Reset() { *x = JoinBattleRequest{} mi := &file_proto_battle_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinBattleRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinBattleRequest) ProtoMessage() {} func (x *JoinBattleRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinBattleRequest.ProtoReflect.Descriptor instead. func (*JoinBattleRequest) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{2} } func (x *JoinBattleRequest) GetBattleId() string { if x != nil { return x.BattleId } return "" } func (x *JoinBattleRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *JoinBattleRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } // 加入战斗响应 type JoinBattleResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` BattleId string `protobuf:"bytes,2,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` TeamId string `protobuf:"bytes,3,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` Position int32 `protobuf:"varint,4,opt,name=position,proto3" json:"position,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinBattleResponse) Reset() { *x = JoinBattleResponse{} mi := &file_proto_battle_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinBattleResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinBattleResponse) ProtoMessage() {} func (x *JoinBattleResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinBattleResponse.ProtoReflect.Descriptor instead. func (*JoinBattleResponse) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{3} } func (x *JoinBattleResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *JoinBattleResponse) GetBattleId() string { if x != nil { return x.BattleId } return "" } func (x *JoinBattleResponse) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *JoinBattleResponse) GetPosition() int32 { if x != nil { return x.Position } return 0 } // 离开战斗请求 type LeaveBattleRequest struct { state protoimpl.MessageState `protogen:"open.v1"` BattleId string `protobuf:"bytes,1,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveBattleRequest) Reset() { *x = LeaveBattleRequest{} mi := &file_proto_battle_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveBattleRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveBattleRequest) ProtoMessage() {} func (x *LeaveBattleRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveBattleRequest.ProtoReflect.Descriptor instead. func (*LeaveBattleRequest) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{4} } func (x *LeaveBattleRequest) GetBattleId() string { if x != nil { return x.BattleId } return "" } func (x *LeaveBattleRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } // 离开战斗响应 type LeaveBattleResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` BattleId string `protobuf:"bytes,2,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveBattleResponse) Reset() { *x = LeaveBattleResponse{} mi := &file_proto_battle_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveBattleResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveBattleResponse) ProtoMessage() {} func (x *LeaveBattleResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveBattleResponse.ProtoReflect.Descriptor instead. func (*LeaveBattleResponse) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{5} } func (x *LeaveBattleResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *LeaveBattleResponse) GetBattleId() string { if x != nil { return x.BattleId } return "" } // 执行战斗动作请求 type ExecuteActionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` BattleId string `protobuf:"bytes,1,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` ActionType string `protobuf:"bytes,3,opt,name=action_type,json=actionType,proto3" json:"action_type,omitempty"` Parameters map[string]string `protobuf:"bytes,4,rep,name=parameters,proto3" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` TargetPosition *common.Position `protobuf:"bytes,5,opt,name=target_position,json=targetPosition,proto3" json:"target_position,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ExecuteActionRequest) Reset() { *x = ExecuteActionRequest{} mi := &file_proto_battle_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ExecuteActionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ExecuteActionRequest) ProtoMessage() {} func (x *ExecuteActionRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ExecuteActionRequest.ProtoReflect.Descriptor instead. func (*ExecuteActionRequest) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{6} } func (x *ExecuteActionRequest) GetBattleId() string { if x != nil { return x.BattleId } return "" } func (x *ExecuteActionRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *ExecuteActionRequest) GetActionType() string { if x != nil { return x.ActionType } return "" } func (x *ExecuteActionRequest) GetParameters() map[string]string { if x != nil { return x.Parameters } return nil } func (x *ExecuteActionRequest) GetTargetPosition() *common.Position { if x != nil { return x.TargetPosition } return nil } // 执行战斗动作响应 type ExecuteActionResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ActionId string `protobuf:"bytes,2,opt,name=action_id,json=actionId,proto3" json:"action_id,omitempty"` Result *BattleResult `protobuf:"bytes,3,opt,name=result,proto3" json:"result,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ExecuteActionResponse) Reset() { *x = ExecuteActionResponse{} mi := &file_proto_battle_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ExecuteActionResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ExecuteActionResponse) ProtoMessage() {} func (x *ExecuteActionResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ExecuteActionResponse.ProtoReflect.Descriptor instead. func (*ExecuteActionResponse) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{7} } func (x *ExecuteActionResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *ExecuteActionResponse) GetActionId() string { if x != nil { return x.ActionId } return "" } func (x *ExecuteActionResponse) GetResult() *BattleResult { if x != nil { return x.Result } return nil } // 获取战斗信息请求 type GetBattleInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` BattleId string `protobuf:"bytes,1,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetBattleInfoRequest) Reset() { *x = GetBattleInfoRequest{} mi := &file_proto_battle_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetBattleInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetBattleInfoRequest) ProtoMessage() {} func (x *GetBattleInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetBattleInfoRequest.ProtoReflect.Descriptor instead. func (*GetBattleInfoRequest) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{8} } func (x *GetBattleInfoRequest) GetBattleId() string { if x != nil { return x.BattleId } return "" } // 获取战斗信息响应 type GetBattleInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Battle *BattleInfo `protobuf:"bytes,2,opt,name=battle,proto3" json:"battle,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetBattleInfoResponse) Reset() { *x = GetBattleInfoResponse{} mi := &file_proto_battle_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetBattleInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetBattleInfoResponse) ProtoMessage() {} func (x *GetBattleInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetBattleInfoResponse.ProtoReflect.Descriptor instead. func (*GetBattleInfoResponse) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{9} } func (x *GetBattleInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetBattleInfoResponse) GetBattle() *BattleInfo { if x != nil { return x.Battle } return nil } // 获取战斗列表请求 type GetBattleListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` BattleType string `protobuf:"bytes,1,opt,name=battle_type,json=battleType,proto3" json:"battle_type,omitempty"` Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetBattleListRequest) Reset() { *x = GetBattleListRequest{} mi := &file_proto_battle_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetBattleListRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetBattleListRequest) ProtoMessage() {} func (x *GetBattleListRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetBattleListRequest.ProtoReflect.Descriptor instead. func (*GetBattleListRequest) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{10} } func (x *GetBattleListRequest) GetBattleType() string { if x != nil { return x.BattleType } return "" } func (x *GetBattleListRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetBattleListRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 获取战斗列表响应 type GetBattleListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Battles []*BattleInfo `protobuf:"bytes,2,rep,name=battles,proto3" json:"battles,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetBattleListResponse) Reset() { *x = GetBattleListResponse{} mi := &file_proto_battle_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetBattleListResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetBattleListResponse) ProtoMessage() {} func (x *GetBattleListResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetBattleListResponse.ProtoReflect.Descriptor instead. func (*GetBattleListResponse) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{11} } func (x *GetBattleListResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetBattleListResponse) GetBattles() []*BattleInfo { if x != nil { return x.Battles } return nil } func (x *GetBattleListResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 战斗信息 type BattleInfo struct { state protoimpl.MessageState `protogen:"open.v1"` BattleId string `protobuf:"bytes,1,opt,name=battle_id,json=battleId,proto3" json:"battle_id,omitempty"` BattleType string `protobuf:"bytes,2,opt,name=battle_type,json=battleType,proto3" json:"battle_type,omitempty"` MapId string `protobuf:"bytes,3,opt,name=map_id,json=mapId,proto3" json:"map_id,omitempty"` Status BattleStatus `protobuf:"varint,4,opt,name=status,proto3,enum=greatestworks.battle.BattleStatus" json:"status,omitempty"` MaxPlayers int32 `protobuf:"varint,5,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` CurrentPlayers int32 `protobuf:"varint,6,opt,name=current_players,json=currentPlayers,proto3" json:"current_players,omitempty"` Players []*BattlePlayer `protobuf:"bytes,7,rep,name=players,proto3" json:"players,omitempty"` CreatedAt int64 `protobuf:"varint,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` StartedAt int64 `protobuf:"varint,9,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` EndedAt int64 `protobuf:"varint,10,opt,name=ended_at,json=endedAt,proto3" json:"ended_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BattleInfo) Reset() { *x = BattleInfo{} mi := &file_proto_battle_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BattleInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*BattleInfo) ProtoMessage() {} func (x *BattleInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BattleInfo.ProtoReflect.Descriptor instead. func (*BattleInfo) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{12} } func (x *BattleInfo) GetBattleId() string { if x != nil { return x.BattleId } return "" } func (x *BattleInfo) GetBattleType() string { if x != nil { return x.BattleType } return "" } func (x *BattleInfo) GetMapId() string { if x != nil { return x.MapId } return "" } func (x *BattleInfo) GetStatus() BattleStatus { if x != nil { return x.Status } return BattleStatus_BATTLE_STATUS_UNSPECIFIED } func (x *BattleInfo) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *BattleInfo) GetCurrentPlayers() int32 { if x != nil { return x.CurrentPlayers } return 0 } func (x *BattleInfo) GetPlayers() []*BattlePlayer { if x != nil { return x.Players } return nil } func (x *BattleInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *BattleInfo) GetStartedAt() int64 { if x != nil { return x.StartedAt } return 0 } func (x *BattleInfo) GetEndedAt() int64 { if x != nil { return x.EndedAt } return 0 } // 战斗玩家 type BattlePlayer struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` TeamId string `protobuf:"bytes,3,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` Position int32 `protobuf:"varint,4,opt,name=position,proto3" json:"position,omitempty"` Stats *PlayerBattleStats `protobuf:"bytes,5,opt,name=stats,proto3" json:"stats,omitempty"` IsReady bool `protobuf:"varint,6,opt,name=is_ready,json=isReady,proto3" json:"is_ready,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BattlePlayer) Reset() { *x = BattlePlayer{} mi := &file_proto_battle_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BattlePlayer) String() string { return protoimpl.X.MessageStringOf(x) } func (*BattlePlayer) ProtoMessage() {} func (x *BattlePlayer) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BattlePlayer.ProtoReflect.Descriptor instead. func (*BattlePlayer) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{13} } func (x *BattlePlayer) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *BattlePlayer) GetName() string { if x != nil { return x.Name } return "" } func (x *BattlePlayer) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *BattlePlayer) GetPosition() int32 { if x != nil { return x.Position } return 0 } func (x *BattlePlayer) GetStats() *PlayerBattleStats { if x != nil { return x.Stats } return nil } func (x *BattlePlayer) GetIsReady() bool { if x != nil { return x.IsReady } return false } // 玩家战斗属性 type PlayerBattleStats struct { state protoimpl.MessageState `protogen:"open.v1"` Health int32 `protobuf:"varint,1,opt,name=health,proto3" json:"health,omitempty"` MaxHealth int32 `protobuf:"varint,2,opt,name=max_health,json=maxHealth,proto3" json:"max_health,omitempty"` Mana int32 `protobuf:"varint,3,opt,name=mana,proto3" json:"mana,omitempty"` MaxMana int32 `protobuf:"varint,4,opt,name=max_mana,json=maxMana,proto3" json:"max_mana,omitempty"` Attack int32 `protobuf:"varint,5,opt,name=attack,proto3" json:"attack,omitempty"` Defense int32 `protobuf:"varint,6,opt,name=defense,proto3" json:"defense,omitempty"` Speed int32 `protobuf:"varint,7,opt,name=speed,proto3" json:"speed,omitempty"` Level int32 `protobuf:"varint,8,opt,name=level,proto3" json:"level,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PlayerBattleStats) Reset() { *x = PlayerBattleStats{} mi := &file_proto_battle_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PlayerBattleStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlayerBattleStats) ProtoMessage() {} func (x *PlayerBattleStats) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlayerBattleStats.ProtoReflect.Descriptor instead. func (*PlayerBattleStats) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{14} } func (x *PlayerBattleStats) GetHealth() int32 { if x != nil { return x.Health } return 0 } func (x *PlayerBattleStats) GetMaxHealth() int32 { if x != nil { return x.MaxHealth } return 0 } func (x *PlayerBattleStats) GetMana() int32 { if x != nil { return x.Mana } return 0 } func (x *PlayerBattleStats) GetMaxMana() int32 { if x != nil { return x.MaxMana } return 0 } func (x *PlayerBattleStats) GetAttack() int32 { if x != nil { return x.Attack } return 0 } func (x *PlayerBattleStats) GetDefense() int32 { if x != nil { return x.Defense } return 0 } func (x *PlayerBattleStats) GetSpeed() int32 { if x != nil { return x.Speed } return 0 } func (x *PlayerBattleStats) GetLevel() int32 { if x != nil { return x.Level } return 0 } // 战斗结果 type BattleResult struct { state protoimpl.MessageState `protogen:"open.v1"` ActionId string `protobuf:"bytes,1,opt,name=action_id,json=actionId,proto3" json:"action_id,omitempty"` ResultType string `protobuf:"bytes,2,opt,name=result_type,json=resultType,proto3" json:"result_type,omitempty"` Effects map[string]string `protobuf:"bytes,3,rep,name=effects,proto3" json:"effects,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Events []*BattleEvent `protobuf:"bytes,4,rep,name=events,proto3" json:"events,omitempty"` Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BattleResult) Reset() { *x = BattleResult{} mi := &file_proto_battle_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BattleResult) String() string { return protoimpl.X.MessageStringOf(x) } func (*BattleResult) ProtoMessage() {} func (x *BattleResult) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BattleResult.ProtoReflect.Descriptor instead. func (*BattleResult) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{15} } func (x *BattleResult) GetActionId() string { if x != nil { return x.ActionId } return "" } func (x *BattleResult) GetResultType() string { if x != nil { return x.ResultType } return "" } func (x *BattleResult) GetEffects() map[string]string { if x != nil { return x.Effects } return nil } func (x *BattleResult) GetEvents() []*BattleEvent { if x != nil { return x.Events } return nil } func (x *BattleResult) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } // 战斗事件 type BattleEvent struct { state protoimpl.MessageState `protogen:"open.v1"` EventType string `protobuf:"bytes,1,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` SourceId string `protobuf:"bytes,2,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` TargetId string `protobuf:"bytes,3,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"` Data map[string]string `protobuf:"bytes,4,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BattleEvent) Reset() { *x = BattleEvent{} mi := &file_proto_battle_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BattleEvent) String() string { return protoimpl.X.MessageStringOf(x) } func (*BattleEvent) ProtoMessage() {} func (x *BattleEvent) ProtoReflect() protoreflect.Message { mi := &file_proto_battle_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BattleEvent.ProtoReflect.Descriptor instead. func (*BattleEvent) Descriptor() ([]byte, []int) { return file_proto_battle_proto_rawDescGZIP(), []int{16} } func (x *BattleEvent) GetEventType() string { if x != nil { return x.EventType } return "" } func (x *BattleEvent) GetSourceId() string { if x != nil { return x.SourceId } return "" } func (x *BattleEvent) GetTargetId() string { if x != nil { return x.TargetId } return "" } func (x *BattleEvent) GetData() map[string]string { if x != nil { return x.Data } return nil } func (x *BattleEvent) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } var File_proto_battle_proto protoreflect.FileDescriptor const file_proto_battle_proto_rawDesc = "" + "\n" + "\x12proto/battle.proto\x12\x14greatestworks.battle\x1a\x12proto/common.proto\"\x9f\x02\n" + "\x13CreateBattleRequest\x12\x1d\n" + "\n" + "creator_id\x18\x01 \x01(\tR\tcreatorId\x12\x1f\n" + "\vbattle_type\x18\x02 \x01(\tR\n" + "battleType\x12\x1f\n" + "\vmax_players\x18\x03 \x01(\x05R\n" + "maxPlayers\x12\x15\n" + "\x06map_id\x18\x04 \x01(\tR\x05mapId\x12S\n" + "\bsettings\x18\x05 \x03(\v27.greatestworks.battle.CreateBattleRequest.SettingsEntryR\bsettings\x1a;\n" + "\rSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xab\x01\n" + "\x14CreateBattleResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1b\n" + "\tbattle_id\x18\x02 \x01(\tR\bbattleId\x128\n" + "\x06battle\x18\x03 \x01(\v2 .greatestworks.battle.BattleInfoR\x06battle\"f\n" + "\x11JoinBattleRequest\x12\x1b\n" + "\tbattle_id\x18\x01 \x01(\tR\bbattleId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\x12\x17\n" + "\ateam_id\x18\x03 \x01(\tR\x06teamId\"\xa4\x01\n" + "\x12JoinBattleResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1b\n" + "\tbattle_id\x18\x02 \x01(\tR\bbattleId\x12\x17\n" + "\ateam_id\x18\x03 \x01(\tR\x06teamId\x12\x1a\n" + "\bposition\x18\x04 \x01(\x05R\bposition\"N\n" + "\x12LeaveBattleRequest\x12\x1b\n" + "\tbattle_id\x18\x01 \x01(\tR\bbattleId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\"p\n" + "\x13LeaveBattleResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1b\n" + "\tbattle_id\x18\x02 \x01(\tR\bbattleId\"\xd5\x02\n" + "\x14ExecuteActionRequest\x12\x1b\n" + "\tbattle_id\x18\x01 \x01(\tR\bbattleId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\x12\x1f\n" + "\vaction_type\x18\x03 \x01(\tR\n" + "actionType\x12Z\n" + "\n" + "parameters\x18\x04 \x03(\v2:.greatestworks.battle.ExecuteActionRequest.ParametersEntryR\n" + "parameters\x12G\n" + "\x0ftarget_position\x18\x05 \x01(\v2\x1e.greatestworks.common.PositionR\x0etargetPosition\x1a=\n" + "\x0fParametersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xae\x01\n" + "\x15ExecuteActionResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1b\n" + "\taction_id\x18\x02 \x01(\tR\bactionId\x12:\n" + "\x06result\x18\x03 \x01(\v2\".greatestworks.battle.BattleResultR\x06result\"3\n" + "\x14GetBattleInfoRequest\x12\x1b\n" + "\tbattle_id\x18\x01 \x01(\tR\bbattleId\"\x8f\x01\n" + "\x15GetBattleInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x128\n" + "\x06battle\x18\x02 \x01(\v2 .greatestworks.battle.BattleInfoR\x06battle\"e\n" + "\x14GetBattleListRequest\x12\x1f\n" + "\vbattle_type\x18\x01 \x01(\tR\n" + "battleType\x12\x14\n" + "\x05limit\x18\x02 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\x05R\x06offset\"\xd7\x01\n" + "\x15GetBattleListResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12:\n" + "\abattles\x18\x02 \x03(\v2 .greatestworks.battle.BattleInfoR\abattles\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\xfe\x02\n" + "\n" + "BattleInfo\x12\x1b\n" + "\tbattle_id\x18\x01 \x01(\tR\bbattleId\x12\x1f\n" + "\vbattle_type\x18\x02 \x01(\tR\n" + "battleType\x12\x15\n" + "\x06map_id\x18\x03 \x01(\tR\x05mapId\x12:\n" + "\x06status\x18\x04 \x01(\x0e2\".greatestworks.battle.BattleStatusR\x06status\x12\x1f\n" + "\vmax_players\x18\x05 \x01(\x05R\n" + "maxPlayers\x12'\n" + "\x0fcurrent_players\x18\x06 \x01(\x05R\x0ecurrentPlayers\x12<\n" + "\aplayers\x18\a \x03(\v2\".greatestworks.battle.BattlePlayerR\aplayers\x12\x1d\n" + "\n" + "created_at\x18\b \x01(\x03R\tcreatedAt\x12\x1d\n" + "\n" + "started_at\x18\t \x01(\x03R\tstartedAt\x12\x19\n" + "\bended_at\x18\n" + " \x01(\x03R\aendedAt\"\xce\x01\n" + "\fBattlePlayer\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x17\n" + "\ateam_id\x18\x03 \x01(\tR\x06teamId\x12\x1a\n" + "\bposition\x18\x04 \x01(\x05R\bposition\x12=\n" + "\x05stats\x18\x05 \x01(\v2'.greatestworks.battle.PlayerBattleStatsR\x05stats\x12\x19\n" + "\bis_ready\x18\x06 \x01(\bR\aisReady\"\xd7\x01\n" + "\x11PlayerBattleStats\x12\x16\n" + "\x06health\x18\x01 \x01(\x05R\x06health\x12\x1d\n" + "\n" + "max_health\x18\x02 \x01(\x05R\tmaxHealth\x12\x12\n" + "\x04mana\x18\x03 \x01(\x05R\x04mana\x12\x19\n" + "\bmax_mana\x18\x04 \x01(\x05R\amaxMana\x12\x16\n" + "\x06attack\x18\x05 \x01(\x05R\x06attack\x12\x18\n" + "\adefense\x18\x06 \x01(\x05R\adefense\x12\x14\n" + "\x05speed\x18\a \x01(\x05R\x05speed\x12\x14\n" + "\x05level\x18\b \x01(\x05R\x05level\"\xac\x02\n" + "\fBattleResult\x12\x1b\n" + "\taction_id\x18\x01 \x01(\tR\bactionId\x12\x1f\n" + "\vresult_type\x18\x02 \x01(\tR\n" + "resultType\x12I\n" + "\aeffects\x18\x03 \x03(\v2/.greatestworks.battle.BattleResult.EffectsEntryR\aeffects\x129\n" + "\x06events\x18\x04 \x03(\v2!.greatestworks.battle.BattleEventR\x06events\x12\x1c\n" + "\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\x1a:\n" + "\fEffectsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xfe\x01\n" + "\vBattleEvent\x12\x1d\n" + "\n" + "event_type\x18\x01 \x01(\tR\teventType\x12\x1b\n" + "\tsource_id\x18\x02 \x01(\tR\bsourceId\x12\x1b\n" + "\ttarget_id\x18\x03 \x01(\tR\btargetId\x12?\n" + "\x04data\x18\x04 \x03(\v2+.greatestworks.battle.BattleEvent.DataEntryR\x04data\x12\x1c\n" + "\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\x1a7\n" + "\tDataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*\xd1\x01\n" + "\fBattleStatus\x12\x1d\n" + "\x19BATTLE_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15BATTLE_STATUS_WAITING\x10\x01\x12\x1a\n" + "\x16BATTLE_STATUS_STARTING\x10\x02\x12\x18\n" + "\x14BATTLE_STATUS_ACTIVE\x10\x03\x12\x18\n" + "\x14BATTLE_STATUS_ENDING\x10\x04\x12\x1a\n" + "\x16BATTLE_STATUS_FINISHED\x10\x05\x12\x1b\n" + "\x17BATTLE_STATUS_CANCELLED\x10\x06*\xcb\x01\n" + "\n" + "BattleType\x12\x1b\n" + "\x17BATTLE_TYPE_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fBATTLE_TYPE_PVP\x10\x01\x12\x13\n" + "\x0fBATTLE_TYPE_PVE\x10\x02\x12\x15\n" + "\x11BATTLE_TYPE_ARENA\x10\x03\x12\x14\n" + "\x10BATTLE_TYPE_RAID\x10\x04\x12\x17\n" + "\x13BATTLE_TYPE_DUNGEON\x10\x05\x12\x14\n" + "\x10BATTLE_TYPE_BOSS\x10\x06\x12\x1a\n" + "\x16BATTLE_TYPE_TOURNAMENT\x10\a*\xeb\x01\n" + "\x10BattleActionType\x12\"\n" + "\x1eBATTLE_ACTION_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19BATTLE_ACTION_TYPE_ATTACK\x10\x01\x12\x1c\n" + "\x18BATTLE_ACTION_TYPE_SKILL\x10\x02\x12\x1b\n" + "\x17BATTLE_ACTION_TYPE_ITEM\x10\x03\x12\x1d\n" + "\x19BATTLE_ACTION_TYPE_DEFEND\x10\x04\x12\x1d\n" + "\x19BATTLE_ACTION_TYPE_ESCAPE\x10\x05\x12\x1b\n" + "\x17BATTLE_ACTION_TYPE_WAIT\x10\x06*\xf4\x01\n" + "\x10BattleResultType\x12\"\n" + "\x1eBATTLE_RESULT_TYPE_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aBATTLE_RESULT_TYPE_VICTORY\x10\x01\x12\x1d\n" + "\x19BATTLE_RESULT_TYPE_DEFEAT\x10\x02\x12\x1b\n" + "\x17BATTLE_RESULT_TYPE_DRAW\x10\x03\x12\x1d\n" + "\x19BATTLE_RESULT_TYPE_ESCAPE\x10\x04\x12\x1e\n" + "\x1aBATTLE_RESULT_TYPE_TIMEOUT\x10\x05\x12!\n" + "\x1dBATTLE_RESULT_TYPE_DISCONNECT\x10\x06*\xd9\x02\n" + "\x0fBattleEventType\x12!\n" + "\x1dBATTLE_EVENT_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n" + "\x18BATTLE_EVENT_TYPE_DAMAGE\x10\x01\x12\x1a\n" + "\x16BATTLE_EVENT_TYPE_HEAL\x10\x02\x12\x1a\n" + "\x16BATTLE_EVENT_TYPE_BUFF\x10\x03\x12\x1c\n" + "\x18BATTLE_EVENT_TYPE_DEBUFF\x10\x04\x12\x1e\n" + "\x1aBATTLE_EVENT_TYPE_CRITICAL\x10\x05\x12\x1a\n" + "\x16BATTLE_EVENT_TYPE_MISS\x10\x06\x12\x1b\n" + "\x17BATTLE_EVENT_TYPE_DODGE\x10\a\x12\x1b\n" + "\x17BATTLE_EVENT_TYPE_BLOCK\x10\b\x12\x1b\n" + "\x17BATTLE_EVENT_TYPE_DEATH\x10\t\x12\x1c\n" + "\x18BATTLE_EVENT_TYPE_REVIVE\x10\n" + "2\xf9\x04\n" + "\rBattleService\x12e\n" + "\fCreateBattle\x12).greatestworks.battle.CreateBattleRequest\x1a*.greatestworks.battle.CreateBattleResponse\x12_\n" + "\n" + "JoinBattle\x12'.greatestworks.battle.JoinBattleRequest\x1a(.greatestworks.battle.JoinBattleResponse\x12b\n" + "\vLeaveBattle\x12(.greatestworks.battle.LeaveBattleRequest\x1a).greatestworks.battle.LeaveBattleResponse\x12h\n" + "\rExecuteAction\x12*.greatestworks.battle.ExecuteActionRequest\x1a+.greatestworks.battle.ExecuteActionResponse\x12h\n" + "\rGetBattleInfo\x12*.greatestworks.battle.GetBattleInfoRequest\x1a+.greatestworks.battle.GetBattleInfoResponse\x12h\n" + "\rGetBattleList\x12*.greatestworks.battle.GetBattleListRequest\x1a+.greatestworks.battle.GetBattleListResponseB greatestworks.battle.CreateBattleRequest.SettingsEntry 26, // 1: greatestworks.battle.CreateBattleResponse.common:type_name -> greatestworks.common.CommonResponse 17, // 2: greatestworks.battle.CreateBattleResponse.battle:type_name -> greatestworks.battle.BattleInfo 26, // 3: greatestworks.battle.JoinBattleResponse.common:type_name -> greatestworks.common.CommonResponse 26, // 4: greatestworks.battle.LeaveBattleResponse.common:type_name -> greatestworks.common.CommonResponse 23, // 5: greatestworks.battle.ExecuteActionRequest.parameters:type_name -> greatestworks.battle.ExecuteActionRequest.ParametersEntry 27, // 6: greatestworks.battle.ExecuteActionRequest.target_position:type_name -> greatestworks.common.Position 26, // 7: greatestworks.battle.ExecuteActionResponse.common:type_name -> greatestworks.common.CommonResponse 20, // 8: greatestworks.battle.ExecuteActionResponse.result:type_name -> greatestworks.battle.BattleResult 26, // 9: greatestworks.battle.GetBattleInfoResponse.common:type_name -> greatestworks.common.CommonResponse 17, // 10: greatestworks.battle.GetBattleInfoResponse.battle:type_name -> greatestworks.battle.BattleInfo 26, // 11: greatestworks.battle.GetBattleListResponse.common:type_name -> greatestworks.common.CommonResponse 17, // 12: greatestworks.battle.GetBattleListResponse.battles:type_name -> greatestworks.battle.BattleInfo 28, // 13: greatestworks.battle.GetBattleListResponse.pagination:type_name -> greatestworks.common.PaginationInfo 0, // 14: greatestworks.battle.BattleInfo.status:type_name -> greatestworks.battle.BattleStatus 18, // 15: greatestworks.battle.BattleInfo.players:type_name -> greatestworks.battle.BattlePlayer 19, // 16: greatestworks.battle.BattlePlayer.stats:type_name -> greatestworks.battle.PlayerBattleStats 24, // 17: greatestworks.battle.BattleResult.effects:type_name -> greatestworks.battle.BattleResult.EffectsEntry 21, // 18: greatestworks.battle.BattleResult.events:type_name -> greatestworks.battle.BattleEvent 25, // 19: greatestworks.battle.BattleEvent.data:type_name -> greatestworks.battle.BattleEvent.DataEntry 5, // 20: greatestworks.battle.BattleService.CreateBattle:input_type -> greatestworks.battle.CreateBattleRequest 7, // 21: greatestworks.battle.BattleService.JoinBattle:input_type -> greatestworks.battle.JoinBattleRequest 9, // 22: greatestworks.battle.BattleService.LeaveBattle:input_type -> greatestworks.battle.LeaveBattleRequest 11, // 23: greatestworks.battle.BattleService.ExecuteAction:input_type -> greatestworks.battle.ExecuteActionRequest 13, // 24: greatestworks.battle.BattleService.GetBattleInfo:input_type -> greatestworks.battle.GetBattleInfoRequest 15, // 25: greatestworks.battle.BattleService.GetBattleList:input_type -> greatestworks.battle.GetBattleListRequest 6, // 26: greatestworks.battle.BattleService.CreateBattle:output_type -> greatestworks.battle.CreateBattleResponse 8, // 27: greatestworks.battle.BattleService.JoinBattle:output_type -> greatestworks.battle.JoinBattleResponse 10, // 28: greatestworks.battle.BattleService.LeaveBattle:output_type -> greatestworks.battle.LeaveBattleResponse 12, // 29: greatestworks.battle.BattleService.ExecuteAction:output_type -> greatestworks.battle.ExecuteActionResponse 14, // 30: greatestworks.battle.BattleService.GetBattleInfo:output_type -> greatestworks.battle.GetBattleInfoResponse 16, // 31: greatestworks.battle.BattleService.GetBattleList:output_type -> greatestworks.battle.GetBattleListResponse 26, // [26:32] is the sub-list for method output_type 20, // [20:26] is the sub-list for method input_type 20, // [20:20] is the sub-list for extension type_name 20, // [20:20] is the sub-list for extension extendee 0, // [0:20] is the sub-list for field type_name } func init() { file_proto_battle_proto_init() } func file_proto_battle_proto_init() { if File_proto_battle_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_battle_proto_rawDesc), len(file_proto_battle_proto_rawDesc)), NumEnums: 5, NumMessages: 21, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_battle_proto_goTypes, DependencyIndexes: file_proto_battle_proto_depIdxs, EnumInfos: file_proto_battle_proto_enumTypes, MessageInfos: file_proto_battle_proto_msgTypes, }.Build() File_proto_battle_proto = out.File file_proto_battle_proto_goTypes = nil file_proto_battle_proto_depIdxs = nil } ================================================ FILE: internal/proto/chat/chat.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/chat.proto package chat import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 消息类型枚举 type MessageType int32 const ( MessageType_MESSAGE_TYPE_UNSPECIFIED MessageType = 0 MessageType_MESSAGE_TYPE_TEXT MessageType = 1 // 文本消息 MessageType_MESSAGE_TYPE_EMOJI MessageType = 2 // 表情消息 MessageType_MESSAGE_TYPE_IMAGE MessageType = 3 // 图片消息 MessageType_MESSAGE_TYPE_FILE MessageType = 4 // 文件消息 MessageType_MESSAGE_TYPE_VOICE MessageType = 5 // 语音消息 MessageType_MESSAGE_TYPE_SYSTEM MessageType = 6 // 系统消息 MessageType_MESSAGE_TYPE_ANNOUNCEMENT MessageType = 7 // 公告消息 MessageType_MESSAGE_TYPE_COMMAND MessageType = 8 // 命令消息 ) // Enum value maps for MessageType. var ( MessageType_name = map[int32]string{ 0: "MESSAGE_TYPE_UNSPECIFIED", 1: "MESSAGE_TYPE_TEXT", 2: "MESSAGE_TYPE_EMOJI", 3: "MESSAGE_TYPE_IMAGE", 4: "MESSAGE_TYPE_FILE", 5: "MESSAGE_TYPE_VOICE", 6: "MESSAGE_TYPE_SYSTEM", 7: "MESSAGE_TYPE_ANNOUNCEMENT", 8: "MESSAGE_TYPE_COMMAND", } MessageType_value = map[string]int32{ "MESSAGE_TYPE_UNSPECIFIED": 0, "MESSAGE_TYPE_TEXT": 1, "MESSAGE_TYPE_EMOJI": 2, "MESSAGE_TYPE_IMAGE": 3, "MESSAGE_TYPE_FILE": 4, "MESSAGE_TYPE_VOICE": 5, "MESSAGE_TYPE_SYSTEM": 6, "MESSAGE_TYPE_ANNOUNCEMENT": 7, "MESSAGE_TYPE_COMMAND": 8, } ) func (x MessageType) Enum() *MessageType { p := new(MessageType) *p = x return p } func (x MessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_chat_proto_enumTypes[0].Descriptor() } func (MessageType) Type() protoreflect.EnumType { return &file_proto_chat_proto_enumTypes[0] } func (x MessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MessageType.Descriptor instead. func (MessageType) EnumDescriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{0} } // 用户状态枚举 type UserStatus int32 const ( UserStatus_USER_STATUS_UNSPECIFIED UserStatus = 0 UserStatus_USER_STATUS_ONLINE UserStatus = 1 // 在线 UserStatus_USER_STATUS_AWAY UserStatus = 2 // 离开 UserStatus_USER_STATUS_BUSY UserStatus = 3 // 忙碌 UserStatus_USER_STATUS_INVISIBLE UserStatus = 4 // 隐身 UserStatus_USER_STATUS_OFFLINE UserStatus = 5 // 离线 ) // Enum value maps for UserStatus. var ( UserStatus_name = map[int32]string{ 0: "USER_STATUS_UNSPECIFIED", 1: "USER_STATUS_ONLINE", 2: "USER_STATUS_AWAY", 3: "USER_STATUS_BUSY", 4: "USER_STATUS_INVISIBLE", 5: "USER_STATUS_OFFLINE", } UserStatus_value = map[string]int32{ "USER_STATUS_UNSPECIFIED": 0, "USER_STATUS_ONLINE": 1, "USER_STATUS_AWAY": 2, "USER_STATUS_BUSY": 3, "USER_STATUS_INVISIBLE": 4, "USER_STATUS_OFFLINE": 5, } ) func (x UserStatus) Enum() *UserStatus { p := new(UserStatus) *p = x return p } func (x UserStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (UserStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_chat_proto_enumTypes[1].Descriptor() } func (UserStatus) Type() protoreflect.EnumType { return &file_proto_chat_proto_enumTypes[1] } func (x UserStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use UserStatus.Descriptor instead. func (UserStatus) EnumDescriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{1} } // 用户角色枚举 type UserRole int32 const ( UserRole_USER_ROLE_UNSPECIFIED UserRole = 0 UserRole_USER_ROLE_MEMBER UserRole = 1 // 普通成员 UserRole_USER_ROLE_MODERATOR UserRole = 2 // 版主 UserRole_USER_ROLE_ADMIN UserRole = 3 // 管理员 UserRole_USER_ROLE_OWNER UserRole = 4 // 所有者 ) // Enum value maps for UserRole. var ( UserRole_name = map[int32]string{ 0: "USER_ROLE_UNSPECIFIED", 1: "USER_ROLE_MEMBER", 2: "USER_ROLE_MODERATOR", 3: "USER_ROLE_ADMIN", 4: "USER_ROLE_OWNER", } UserRole_value = map[string]int32{ "USER_ROLE_UNSPECIFIED": 0, "USER_ROLE_MEMBER": 1, "USER_ROLE_MODERATOR": 2, "USER_ROLE_ADMIN": 3, "USER_ROLE_OWNER": 4, } ) func (x UserRole) Enum() *UserRole { p := new(UserRole) *p = x return p } func (x UserRole) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (UserRole) Descriptor() protoreflect.EnumDescriptor { return file_proto_chat_proto_enumTypes[2].Descriptor() } func (UserRole) Type() protoreflect.EnumType { return &file_proto_chat_proto_enumTypes[2] } func (x UserRole) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use UserRole.Descriptor instead. func (UserRole) EnumDescriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{2} } // 举报原因枚举 type ReportReason int32 const ( ReportReason_REPORT_REASON_UNSPECIFIED ReportReason = 0 ReportReason_REPORT_REASON_SPAM ReportReason = 1 // 垃圾信息 ReportReason_REPORT_REASON_HARASSMENT ReportReason = 2 // 骚扰 ReportReason_REPORT_REASON_HATE_SPEECH ReportReason = 3 // 仇恨言论 ReportReason_REPORT_REASON_INAPPROPRIATE ReportReason = 4 // 不当内容 ReportReason_REPORT_REASON_CHEATING ReportReason = 5 // 作弊 ReportReason_REPORT_REASON_OTHER ReportReason = 6 // 其他 ) // Enum value maps for ReportReason. var ( ReportReason_name = map[int32]string{ 0: "REPORT_REASON_UNSPECIFIED", 1: "REPORT_REASON_SPAM", 2: "REPORT_REASON_HARASSMENT", 3: "REPORT_REASON_HATE_SPEECH", 4: "REPORT_REASON_INAPPROPRIATE", 5: "REPORT_REASON_CHEATING", 6: "REPORT_REASON_OTHER", } ReportReason_value = map[string]int32{ "REPORT_REASON_UNSPECIFIED": 0, "REPORT_REASON_SPAM": 1, "REPORT_REASON_HARASSMENT": 2, "REPORT_REASON_HATE_SPEECH": 3, "REPORT_REASON_INAPPROPRIATE": 4, "REPORT_REASON_CHEATING": 5, "REPORT_REASON_OTHER": 6, } ) func (x ReportReason) Enum() *ReportReason { p := new(ReportReason) *p = x return p } func (x ReportReason) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ReportReason) Descriptor() protoreflect.EnumDescriptor { return file_proto_chat_proto_enumTypes[3].Descriptor() } func (ReportReason) Type() protoreflect.EnumType { return &file_proto_chat_proto_enumTypes[3] } func (x ReportReason) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ReportReason.Descriptor instead. func (ReportReason) EnumDescriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{3} } // 发送消息请求 type SendMessageRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SenderId string `protobuf:"bytes,1,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` Channel common.ChatChannel `protobuf:"varint,3,opt,name=channel,proto3,enum=greatestworks.common.ChatChannel" json:"channel,omitempty"` TargetId string `protobuf:"bytes,4,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"` // 私聊目标用户ID或频道ID MessageType MessageType `protobuf:"varint,5,opt,name=message_type,json=messageType,proto3,enum=greatestworks.chat.MessageType" json:"message_type,omitempty"` Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SendMessageRequest) Reset() { *x = SendMessageRequest{} mi := &file_proto_chat_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SendMessageRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendMessageRequest) ProtoMessage() {} func (x *SendMessageRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendMessageRequest.ProtoReflect.Descriptor instead. func (*SendMessageRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{0} } func (x *SendMessageRequest) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *SendMessageRequest) GetContent() string { if x != nil { return x.Content } return "" } func (x *SendMessageRequest) GetChannel() common.ChatChannel { if x != nil { return x.Channel } return common.ChatChannel(0) } func (x *SendMessageRequest) GetTargetId() string { if x != nil { return x.TargetId } return "" } func (x *SendMessageRequest) GetMessageType() MessageType { if x != nil { return x.MessageType } return MessageType_MESSAGE_TYPE_UNSPECIFIED } func (x *SendMessageRequest) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 发送消息响应 type SendMessageResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` MessageId string `protobuf:"bytes,2,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SendMessageResponse) Reset() { *x = SendMessageResponse{} mi := &file_proto_chat_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SendMessageResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendMessageResponse) ProtoMessage() {} func (x *SendMessageResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendMessageResponse.ProtoReflect.Descriptor instead. func (*SendMessageResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{1} } func (x *SendMessageResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SendMessageResponse) GetMessageId() string { if x != nil { return x.MessageId } return "" } func (x *SendMessageResponse) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } // 获取消息历史请求 type GetMessagesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Channel common.ChatChannel `protobuf:"varint,1,opt,name=channel,proto3,enum=greatestworks.common.ChatChannel" json:"channel,omitempty"` ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` BeforeTimestamp int64 `protobuf:"varint,5,opt,name=before_timestamp,json=beforeTimestamp,proto3" json:"before_timestamp,omitempty"` AfterTimestamp int64 `protobuf:"varint,6,opt,name=after_timestamp,json=afterTimestamp,proto3" json:"after_timestamp,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetMessagesRequest) Reset() { *x = GetMessagesRequest{} mi := &file_proto_chat_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetMessagesRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetMessagesRequest) ProtoMessage() {} func (x *GetMessagesRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetMessagesRequest.ProtoReflect.Descriptor instead. func (*GetMessagesRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{2} } func (x *GetMessagesRequest) GetChannel() common.ChatChannel { if x != nil { return x.Channel } return common.ChatChannel(0) } func (x *GetMessagesRequest) GetChannelId() string { if x != nil { return x.ChannelId } return "" } func (x *GetMessagesRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *GetMessagesRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetMessagesRequest) GetBeforeTimestamp() int64 { if x != nil { return x.BeforeTimestamp } return 0 } func (x *GetMessagesRequest) GetAfterTimestamp() int64 { if x != nil { return x.AfterTimestamp } return 0 } // 获取消息历史响应 type GetMessagesResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Messages []*ChatMessage `protobuf:"bytes,2,rep,name=messages,proto3" json:"messages,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetMessagesResponse) Reset() { *x = GetMessagesResponse{} mi := &file_proto_chat_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetMessagesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetMessagesResponse) ProtoMessage() {} func (x *GetMessagesResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetMessagesResponse.ProtoReflect.Descriptor instead. func (*GetMessagesResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{3} } func (x *GetMessagesResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetMessagesResponse) GetMessages() []*ChatMessage { if x != nil { return x.Messages } return nil } func (x *GetMessagesResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 加入聊天频道请求 type JoinChannelRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Channel common.ChatChannel `protobuf:"varint,2,opt,name=channel,proto3,enum=greatestworks.common.ChatChannel" json:"channel,omitempty"` ChannelId string `protobuf:"bytes,3,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` // 如果是私有频道 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinChannelRequest) Reset() { *x = JoinChannelRequest{} mi := &file_proto_chat_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinChannelRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinChannelRequest) ProtoMessage() {} func (x *JoinChannelRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinChannelRequest.ProtoReflect.Descriptor instead. func (*JoinChannelRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{4} } func (x *JoinChannelRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *JoinChannelRequest) GetChannel() common.ChatChannel { if x != nil { return x.Channel } return common.ChatChannel(0) } func (x *JoinChannelRequest) GetChannelId() string { if x != nil { return x.ChannelId } return "" } func (x *JoinChannelRequest) GetPassword() string { if x != nil { return x.Password } return "" } // 加入聊天频道响应 type JoinChannelResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ChannelInfo *ChannelInfo `protobuf:"bytes,2,opt,name=channel_info,json=channelInfo,proto3" json:"channel_info,omitempty"` OnlineUsers []*ChatUser `protobuf:"bytes,3,rep,name=online_users,json=onlineUsers,proto3" json:"online_users,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinChannelResponse) Reset() { *x = JoinChannelResponse{} mi := &file_proto_chat_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinChannelResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinChannelResponse) ProtoMessage() {} func (x *JoinChannelResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinChannelResponse.ProtoReflect.Descriptor instead. func (*JoinChannelResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{5} } func (x *JoinChannelResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *JoinChannelResponse) GetChannelInfo() *ChannelInfo { if x != nil { return x.ChannelInfo } return nil } func (x *JoinChannelResponse) GetOnlineUsers() []*ChatUser { if x != nil { return x.OnlineUsers } return nil } // 离开聊天频道请求 type LeaveChannelRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Channel common.ChatChannel `protobuf:"varint,2,opt,name=channel,proto3,enum=greatestworks.common.ChatChannel" json:"channel,omitempty"` ChannelId string `protobuf:"bytes,3,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveChannelRequest) Reset() { *x = LeaveChannelRequest{} mi := &file_proto_chat_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveChannelRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveChannelRequest) ProtoMessage() {} func (x *LeaveChannelRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveChannelRequest.ProtoReflect.Descriptor instead. func (*LeaveChannelRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{6} } func (x *LeaveChannelRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *LeaveChannelRequest) GetChannel() common.ChatChannel { if x != nil { return x.Channel } return common.ChatChannel(0) } func (x *LeaveChannelRequest) GetChannelId() string { if x != nil { return x.ChannelId } return "" } // 离开聊天频道响应 type LeaveChannelResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveChannelResponse) Reset() { *x = LeaveChannelResponse{} mi := &file_proto_chat_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveChannelResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveChannelResponse) ProtoMessage() {} func (x *LeaveChannelResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveChannelResponse.ProtoReflect.Descriptor instead. func (*LeaveChannelResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{7} } func (x *LeaveChannelResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 创建私聊请求 type CreatePrivateChatRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CreatorId string `protobuf:"bytes,1,opt,name=creator_id,json=creatorId,proto3" json:"creator_id,omitempty"` ParticipantIds []string `protobuf:"bytes,2,rep,name=participant_ids,json=participantIds,proto3" json:"participant_ids,omitempty"` ChatName string `protobuf:"bytes,3,opt,name=chat_name,json=chatName,proto3" json:"chat_name,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePrivateChatRequest) Reset() { *x = CreatePrivateChatRequest{} mi := &file_proto_chat_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePrivateChatRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePrivateChatRequest) ProtoMessage() {} func (x *CreatePrivateChatRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePrivateChatRequest.ProtoReflect.Descriptor instead. func (*CreatePrivateChatRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{8} } func (x *CreatePrivateChatRequest) GetCreatorId() string { if x != nil { return x.CreatorId } return "" } func (x *CreatePrivateChatRequest) GetParticipantIds() []string { if x != nil { return x.ParticipantIds } return nil } func (x *CreatePrivateChatRequest) GetChatName() string { if x != nil { return x.ChatName } return "" } // 创建私聊响应 type CreatePrivateChatResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"` ChatInfo *ChannelInfo `protobuf:"bytes,3,opt,name=chat_info,json=chatInfo,proto3" json:"chat_info,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePrivateChatResponse) Reset() { *x = CreatePrivateChatResponse{} mi := &file_proto_chat_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePrivateChatResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePrivateChatResponse) ProtoMessage() {} func (x *CreatePrivateChatResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePrivateChatResponse.ProtoReflect.Descriptor instead. func (*CreatePrivateChatResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{9} } func (x *CreatePrivateChatResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *CreatePrivateChatResponse) GetChatId() string { if x != nil { return x.ChatId } return "" } func (x *CreatePrivateChatResponse) GetChatInfo() *ChannelInfo { if x != nil { return x.ChatInfo } return nil } // 获取在线用户列表请求 type GetOnlineUsersRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Channel common.ChatChannel `protobuf:"varint,1,opt,name=channel,proto3,enum=greatestworks.common.ChatChannel" json:"channel,omitempty"` ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,4,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetOnlineUsersRequest) Reset() { *x = GetOnlineUsersRequest{} mi := &file_proto_chat_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetOnlineUsersRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetOnlineUsersRequest) ProtoMessage() {} func (x *GetOnlineUsersRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetOnlineUsersRequest.ProtoReflect.Descriptor instead. func (*GetOnlineUsersRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{10} } func (x *GetOnlineUsersRequest) GetChannel() common.ChatChannel { if x != nil { return x.Channel } return common.ChatChannel(0) } func (x *GetOnlineUsersRequest) GetChannelId() string { if x != nil { return x.ChannelId } return "" } func (x *GetOnlineUsersRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetOnlineUsersRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 获取在线用户列表响应 type GetOnlineUsersResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Users []*ChatUser `protobuf:"bytes,2,rep,name=users,proto3" json:"users,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetOnlineUsersResponse) Reset() { *x = GetOnlineUsersResponse{} mi := &file_proto_chat_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetOnlineUsersResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetOnlineUsersResponse) ProtoMessage() {} func (x *GetOnlineUsersResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetOnlineUsersResponse.ProtoReflect.Descriptor instead. func (*GetOnlineUsersResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{11} } func (x *GetOnlineUsersResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetOnlineUsersResponse) GetUsers() []*ChatUser { if x != nil { return x.Users } return nil } func (x *GetOnlineUsersResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 设置用户状态请求 type SetUserStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Status UserStatus `protobuf:"varint,2,opt,name=status,proto3,enum=greatestworks.chat.UserStatus" json:"status,omitempty"` StatusMessage string `protobuf:"bytes,3,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetUserStatusRequest) Reset() { *x = SetUserStatusRequest{} mi := &file_proto_chat_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetUserStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetUserStatusRequest) ProtoMessage() {} func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetUserStatusRequest.ProtoReflect.Descriptor instead. func (*SetUserStatusRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{12} } func (x *SetUserStatusRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *SetUserStatusRequest) GetStatus() UserStatus { if x != nil { return x.Status } return UserStatus_USER_STATUS_UNSPECIFIED } func (x *SetUserStatusRequest) GetStatusMessage() string { if x != nil { return x.StatusMessage } return "" } // 设置用户状态响应 type SetUserStatusResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewStatus UserStatus `protobuf:"varint,2,opt,name=new_status,json=newStatus,proto3,enum=greatestworks.chat.UserStatus" json:"new_status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetUserStatusResponse) Reset() { *x = SetUserStatusResponse{} mi := &file_proto_chat_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetUserStatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetUserStatusResponse) ProtoMessage() {} func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetUserStatusResponse.ProtoReflect.Descriptor instead. func (*SetUserStatusResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{13} } func (x *SetUserStatusResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SetUserStatusResponse) GetNewStatus() UserStatus { if x != nil { return x.NewStatus } return UserStatus_USER_STATUS_UNSPECIFIED } // 屏蔽用户请求 type BlockUserRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` TargetUserId string `protobuf:"bytes,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` Block bool `protobuf:"varint,3,opt,name=block,proto3" json:"block,omitempty"` // true=屏蔽, false=解除屏蔽 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BlockUserRequest) Reset() { *x = BlockUserRequest{} mi := &file_proto_chat_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BlockUserRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*BlockUserRequest) ProtoMessage() {} func (x *BlockUserRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BlockUserRequest.ProtoReflect.Descriptor instead. func (*BlockUserRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{14} } func (x *BlockUserRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *BlockUserRequest) GetTargetUserId() string { if x != nil { return x.TargetUserId } return "" } func (x *BlockUserRequest) GetBlock() bool { if x != nil { return x.Block } return false } // 屏蔽用户响应 type BlockUserResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BlockUserResponse) Reset() { *x = BlockUserResponse{} mi := &file_proto_chat_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BlockUserResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*BlockUserResponse) ProtoMessage() {} func (x *BlockUserResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BlockUserResponse.ProtoReflect.Descriptor instead. func (*BlockUserResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{15} } func (x *BlockUserResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 举报消息请求 type ReportMessageRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ReporterId string `protobuf:"bytes,1,opt,name=reporter_id,json=reporterId,proto3" json:"reporter_id,omitempty"` MessageId string `protobuf:"bytes,2,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` Reason ReportReason `protobuf:"varint,3,opt,name=reason,proto3,enum=greatestworks.chat.ReportReason" json:"reason,omitempty"` Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ReportMessageRequest) Reset() { *x = ReportMessageRequest{} mi := &file_proto_chat_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ReportMessageRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ReportMessageRequest) ProtoMessage() {} func (x *ReportMessageRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ReportMessageRequest.ProtoReflect.Descriptor instead. func (*ReportMessageRequest) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{16} } func (x *ReportMessageRequest) GetReporterId() string { if x != nil { return x.ReporterId } return "" } func (x *ReportMessageRequest) GetMessageId() string { if x != nil { return x.MessageId } return "" } func (x *ReportMessageRequest) GetReason() ReportReason { if x != nil { return x.Reason } return ReportReason_REPORT_REASON_UNSPECIFIED } func (x *ReportMessageRequest) GetDescription() string { if x != nil { return x.Description } return "" } // 举报消息响应 type ReportMessageResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ReportId string `protobuf:"bytes,2,opt,name=report_id,json=reportId,proto3" json:"report_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ReportMessageResponse) Reset() { *x = ReportMessageResponse{} mi := &file_proto_chat_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ReportMessageResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ReportMessageResponse) ProtoMessage() {} func (x *ReportMessageResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ReportMessageResponse.ProtoReflect.Descriptor instead. func (*ReportMessageResponse) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{17} } func (x *ReportMessageResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *ReportMessageResponse) GetReportId() string { if x != nil { return x.ReportId } return "" } // 聊天消息 type ChatMessage struct { state protoimpl.MessageState `protogen:"open.v1"` MessageId string `protobuf:"bytes,1,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` SenderId string `protobuf:"bytes,2,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` SenderName string `protobuf:"bytes,3,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` Channel common.ChatChannel `protobuf:"varint,5,opt,name=channel,proto3,enum=greatestworks.common.ChatChannel" json:"channel,omitempty"` ChannelId string `protobuf:"bytes,6,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` MessageType MessageType `protobuf:"varint,7,opt,name=message_type,json=messageType,proto3,enum=greatestworks.chat.MessageType" json:"message_type,omitempty"` Timestamp int64 `protobuf:"varint,8,opt,name=timestamp,proto3" json:"timestamp,omitempty"` IsEdited bool `protobuf:"varint,9,opt,name=is_edited,json=isEdited,proto3" json:"is_edited,omitempty"` EditedTimestamp int64 `protobuf:"varint,10,opt,name=edited_timestamp,json=editedTimestamp,proto3" json:"edited_timestamp,omitempty"` Metadata map[string]string `protobuf:"bytes,11,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ChatMessage) Reset() { *x = ChatMessage{} mi := &file_proto_chat_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ChatMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatMessage) ProtoMessage() {} func (x *ChatMessage) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead. func (*ChatMessage) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{18} } func (x *ChatMessage) GetMessageId() string { if x != nil { return x.MessageId } return "" } func (x *ChatMessage) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *ChatMessage) GetSenderName() string { if x != nil { return x.SenderName } return "" } func (x *ChatMessage) GetContent() string { if x != nil { return x.Content } return "" } func (x *ChatMessage) GetChannel() common.ChatChannel { if x != nil { return x.Channel } return common.ChatChannel(0) } func (x *ChatMessage) GetChannelId() string { if x != nil { return x.ChannelId } return "" } func (x *ChatMessage) GetMessageType() MessageType { if x != nil { return x.MessageType } return MessageType_MESSAGE_TYPE_UNSPECIFIED } func (x *ChatMessage) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *ChatMessage) GetIsEdited() bool { if x != nil { return x.IsEdited } return false } func (x *ChatMessage) GetEditedTimestamp() int64 { if x != nil { return x.EditedTimestamp } return 0 } func (x *ChatMessage) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 频道信息 type ChannelInfo struct { state protoimpl.MessageState `protogen:"open.v1"` ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` ChannelType common.ChatChannel `protobuf:"varint,4,opt,name=channel_type,json=channelType,proto3,enum=greatestworks.common.ChatChannel" json:"channel_type,omitempty"` MaxUsers int32 `protobuf:"varint,5,opt,name=max_users,json=maxUsers,proto3" json:"max_users,omitempty"` CurrentUsers int32 `protobuf:"varint,6,opt,name=current_users,json=currentUsers,proto3" json:"current_users,omitempty"` IsPrivate bool `protobuf:"varint,7,opt,name=is_private,json=isPrivate,proto3" json:"is_private,omitempty"` OwnerId string `protobuf:"bytes,8,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` ModeratorIds []string `protobuf:"bytes,9,rep,name=moderator_ids,json=moderatorIds,proto3" json:"moderator_ids,omitempty"` CreatedAt int64 `protobuf:"varint,10,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ChannelInfo) Reset() { *x = ChannelInfo{} mi := &file_proto_chat_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ChannelInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChannelInfo) ProtoMessage() {} func (x *ChannelInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChannelInfo.ProtoReflect.Descriptor instead. func (*ChannelInfo) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{19} } func (x *ChannelInfo) GetChannelId() string { if x != nil { return x.ChannelId } return "" } func (x *ChannelInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *ChannelInfo) GetDescription() string { if x != nil { return x.Description } return "" } func (x *ChannelInfo) GetChannelType() common.ChatChannel { if x != nil { return x.ChannelType } return common.ChatChannel(0) } func (x *ChannelInfo) GetMaxUsers() int32 { if x != nil { return x.MaxUsers } return 0 } func (x *ChannelInfo) GetCurrentUsers() int32 { if x != nil { return x.CurrentUsers } return 0 } func (x *ChannelInfo) GetIsPrivate() bool { if x != nil { return x.IsPrivate } return false } func (x *ChannelInfo) GetOwnerId() string { if x != nil { return x.OwnerId } return "" } func (x *ChannelInfo) GetModeratorIds() []string { if x != nil { return x.ModeratorIds } return nil } func (x *ChannelInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } // 聊天用户 type ChatUser struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` Status UserStatus `protobuf:"varint,4,opt,name=status,proto3,enum=greatestworks.chat.UserStatus" json:"status,omitempty"` StatusMessage string `protobuf:"bytes,5,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"` Role UserRole `protobuf:"varint,6,opt,name=role,proto3,enum=greatestworks.chat.UserRole" json:"role,omitempty"` LastActive int64 `protobuf:"varint,7,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` IsOnline bool `protobuf:"varint,8,opt,name=is_online,json=isOnline,proto3" json:"is_online,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ChatUser) Reset() { *x = ChatUser{} mi := &file_proto_chat_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ChatUser) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatUser) ProtoMessage() {} func (x *ChatUser) ProtoReflect() protoreflect.Message { mi := &file_proto_chat_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatUser.ProtoReflect.Descriptor instead. func (*ChatUser) Descriptor() ([]byte, []int) { return file_proto_chat_proto_rawDescGZIP(), []int{20} } func (x *ChatUser) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *ChatUser) GetUsername() string { if x != nil { return x.Username } return "" } func (x *ChatUser) GetDisplayName() string { if x != nil { return x.DisplayName } return "" } func (x *ChatUser) GetStatus() UserStatus { if x != nil { return x.Status } return UserStatus_USER_STATUS_UNSPECIFIED } func (x *ChatUser) GetStatusMessage() string { if x != nil { return x.StatusMessage } return "" } func (x *ChatUser) GetRole() UserRole { if x != nil { return x.Role } return UserRole_USER_ROLE_UNSPECIFIED } func (x *ChatUser) GetLastActive() int64 { if x != nil { return x.LastActive } return 0 } func (x *ChatUser) GetIsOnline() bool { if x != nil { return x.IsOnline } return false } var File_proto_chat_proto protoreflect.FileDescriptor const file_proto_chat_proto_rawDesc = "" + "\n" + "\x10proto/chat.proto\x12\x12greatestworks.chat\x1a\x12proto/common.proto\"\xf8\x02\n" + "\x12SendMessageRequest\x12\x1b\n" + "\tsender_id\x18\x01 \x01(\tR\bsenderId\x12\x18\n" + "\acontent\x18\x02 \x01(\tR\acontent\x12;\n" + "\achannel\x18\x03 \x01(\x0e2!.greatestworks.common.ChatChannelR\achannel\x12\x1b\n" + "\ttarget_id\x18\x04 \x01(\tR\btargetId\x12B\n" + "\fmessage_type\x18\x05 \x01(\x0e2\x1f.greatestworks.chat.MessageTypeR\vmessageType\x12P\n" + "\bmetadata\x18\x06 \x03(\v24.greatestworks.chat.SendMessageRequest.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x90\x01\n" + "\x13SendMessageResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1d\n" + "\n" + "message_id\x18\x02 \x01(\tR\tmessageId\x12\x1c\n" + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\"\xf3\x01\n" + "\x12GetMessagesRequest\x12;\n" + "\achannel\x18\x01 \x01(\x0e2!.greatestworks.common.ChatChannelR\achannel\x12\x1d\n" + "\n" + "channel_id\x18\x02 \x01(\tR\tchannelId\x12\x17\n" + "\auser_id\x18\x03 \x01(\tR\x06userId\x12\x14\n" + "\x05limit\x18\x04 \x01(\x05R\x05limit\x12)\n" + "\x10before_timestamp\x18\x05 \x01(\x03R\x0fbeforeTimestamp\x12'\n" + "\x0fafter_timestamp\x18\x06 \x01(\x03R\x0eafterTimestamp\"\xd6\x01\n" + "\x13GetMessagesResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12;\n" + "\bmessages\x18\x02 \x03(\v2\x1f.greatestworks.chat.ChatMessageR\bmessages\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\xa5\x01\n" + "\x12JoinChannelRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12;\n" + "\achannel\x18\x02 \x01(\x0e2!.greatestworks.common.ChatChannelR\achannel\x12\x1d\n" + "\n" + "channel_id\x18\x03 \x01(\tR\tchannelId\x12\x1a\n" + "\bpassword\x18\x04 \x01(\tR\bpassword\"\xd8\x01\n" + "\x13JoinChannelResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12B\n" + "\fchannel_info\x18\x02 \x01(\v2\x1f.greatestworks.chat.ChannelInfoR\vchannelInfo\x12?\n" + "\fonline_users\x18\x03 \x03(\v2\x1c.greatestworks.chat.ChatUserR\vonlineUsers\"\x8a\x01\n" + "\x13LeaveChannelRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12;\n" + "\achannel\x18\x02 \x01(\x0e2!.greatestworks.common.ChatChannelR\achannel\x12\x1d\n" + "\n" + "channel_id\x18\x03 \x01(\tR\tchannelId\"T\n" + "\x14LeaveChannelResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x7f\n" + "\x18CreatePrivateChatRequest\x12\x1d\n" + "\n" + "creator_id\x18\x01 \x01(\tR\tcreatorId\x12'\n" + "\x0fparticipant_ids\x18\x02 \x03(\tR\x0eparticipantIds\x12\x1b\n" + "\tchat_name\x18\x03 \x01(\tR\bchatName\"\xb0\x01\n" + "\x19CreatePrivateChatResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x17\n" + "\achat_id\x18\x02 \x01(\tR\x06chatId\x12<\n" + "\tchat_info\x18\x03 \x01(\v2\x1f.greatestworks.chat.ChannelInfoR\bchatInfo\"\xa1\x01\n" + "\x15GetOnlineUsersRequest\x12;\n" + "\achannel\x18\x01 \x01(\x0e2!.greatestworks.common.ChatChannelR\achannel\x12\x1d\n" + "\n" + "channel_id\x18\x02 \x01(\tR\tchannelId\x12\x14\n" + "\x05limit\x18\x03 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x04 \x01(\x05R\x06offset\"\xd0\x01\n" + "\x16GetOnlineUsersResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x05users\x18\x02 \x03(\v2\x1c.greatestworks.chat.ChatUserR\x05users\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\x8e\x01\n" + "\x14SetUserStatusRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x126\n" + "\x06status\x18\x02 \x01(\x0e2\x1e.greatestworks.chat.UserStatusR\x06status\x12%\n" + "\x0estatus_message\x18\x03 \x01(\tR\rstatusMessage\"\x94\x01\n" + "\x15SetUserStatusResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\n" + "new_status\x18\x02 \x01(\x0e2\x1e.greatestworks.chat.UserStatusR\tnewStatus\"g\n" + "\x10BlockUserRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12$\n" + "\x0etarget_user_id\x18\x02 \x01(\tR\ftargetUserId\x12\x14\n" + "\x05block\x18\x03 \x01(\bR\x05block\"Q\n" + "\x11BlockUserResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\xb2\x01\n" + "\x14ReportMessageRequest\x12\x1f\n" + "\vreporter_id\x18\x01 \x01(\tR\n" + "reporterId\x12\x1d\n" + "\n" + "message_id\x18\x02 \x01(\tR\tmessageId\x128\n" + "\x06reason\x18\x03 \x01(\x0e2 .greatestworks.chat.ReportReasonR\x06reason\x12 \n" + "\vdescription\x18\x04 \x01(\tR\vdescription\"r\n" + "\x15ReportMessageResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1b\n" + "\treport_id\x18\x02 \x01(\tR\breportId\"\x92\x04\n" + "\vChatMessage\x12\x1d\n" + "\n" + "message_id\x18\x01 \x01(\tR\tmessageId\x12\x1b\n" + "\tsender_id\x18\x02 \x01(\tR\bsenderId\x12\x1f\n" + "\vsender_name\x18\x03 \x01(\tR\n" + "senderName\x12\x18\n" + "\acontent\x18\x04 \x01(\tR\acontent\x12;\n" + "\achannel\x18\x05 \x01(\x0e2!.greatestworks.common.ChatChannelR\achannel\x12\x1d\n" + "\n" + "channel_id\x18\x06 \x01(\tR\tchannelId\x12B\n" + "\fmessage_type\x18\a \x01(\x0e2\x1f.greatestworks.chat.MessageTypeR\vmessageType\x12\x1c\n" + "\ttimestamp\x18\b \x01(\x03R\ttimestamp\x12\x1b\n" + "\tis_edited\x18\t \x01(\bR\bisEdited\x12)\n" + "\x10edited_timestamp\x18\n" + " \x01(\x03R\x0feditedTimestamp\x12I\n" + "\bmetadata\x18\v \x03(\v2-.greatestworks.chat.ChatMessage.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x02\n" + "\vChannelInfo\x12\x1d\n" + "\n" + "channel_id\x18\x01 \x01(\tR\tchannelId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x12D\n" + "\fchannel_type\x18\x04 \x01(\x0e2!.greatestworks.common.ChatChannelR\vchannelType\x12\x1b\n" + "\tmax_users\x18\x05 \x01(\x05R\bmaxUsers\x12#\n" + "\rcurrent_users\x18\x06 \x01(\x05R\fcurrentUsers\x12\x1d\n" + "\n" + "is_private\x18\a \x01(\bR\tisPrivate\x12\x19\n" + "\bowner_id\x18\b \x01(\tR\aownerId\x12#\n" + "\rmoderator_ids\x18\t \x03(\tR\fmoderatorIds\x12\x1d\n" + "\n" + "created_at\x18\n" + " \x01(\x03R\tcreatedAt\"\xb1\x02\n" + "\bChatUser\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1a\n" + "\busername\x18\x02 \x01(\tR\busername\x12!\n" + "\fdisplay_name\x18\x03 \x01(\tR\vdisplayName\x126\n" + "\x06status\x18\x04 \x01(\x0e2\x1e.greatestworks.chat.UserStatusR\x06status\x12%\n" + "\x0estatus_message\x18\x05 \x01(\tR\rstatusMessage\x120\n" + "\x04role\x18\x06 \x01(\x0e2\x1c.greatestworks.chat.UserRoleR\x04role\x12\x1f\n" + "\vlast_active\x18\a \x01(\x03R\n" + "lastActive\x12\x1b\n" + "\tis_online\x18\b \x01(\bR\bisOnline*\xf3\x01\n" + "\vMessageType\x12\x1c\n" + "\x18MESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11MESSAGE_TYPE_TEXT\x10\x01\x12\x16\n" + "\x12MESSAGE_TYPE_EMOJI\x10\x02\x12\x16\n" + "\x12MESSAGE_TYPE_IMAGE\x10\x03\x12\x15\n" + "\x11MESSAGE_TYPE_FILE\x10\x04\x12\x16\n" + "\x12MESSAGE_TYPE_VOICE\x10\x05\x12\x17\n" + "\x13MESSAGE_TYPE_SYSTEM\x10\x06\x12\x1d\n" + "\x19MESSAGE_TYPE_ANNOUNCEMENT\x10\a\x12\x18\n" + "\x14MESSAGE_TYPE_COMMAND\x10\b*\xa1\x01\n" + "\n" + "UserStatus\x12\x1b\n" + "\x17USER_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12USER_STATUS_ONLINE\x10\x01\x12\x14\n" + "\x10USER_STATUS_AWAY\x10\x02\x12\x14\n" + "\x10USER_STATUS_BUSY\x10\x03\x12\x19\n" + "\x15USER_STATUS_INVISIBLE\x10\x04\x12\x17\n" + "\x13USER_STATUS_OFFLINE\x10\x05*~\n" + "\bUserRole\x12\x19\n" + "\x15USER_ROLE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10USER_ROLE_MEMBER\x10\x01\x12\x17\n" + "\x13USER_ROLE_MODERATOR\x10\x02\x12\x13\n" + "\x0fUSER_ROLE_ADMIN\x10\x03\x12\x13\n" + "\x0fUSER_ROLE_OWNER\x10\x04*\xd8\x01\n" + "\fReportReason\x12\x1d\n" + "\x19REPORT_REASON_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12REPORT_REASON_SPAM\x10\x01\x12\x1c\n" + "\x18REPORT_REASON_HARASSMENT\x10\x02\x12\x1d\n" + "\x19REPORT_REASON_HATE_SPEECH\x10\x03\x12\x1f\n" + "\x1bREPORT_REASON_INAPPROPRIATE\x10\x04\x12\x1a\n" + "\x16REPORT_REASON_CHEATING\x10\x05\x12\x17\n" + "\x13REPORT_REASON_OTHER\x10\x062\x91\a\n" + "\vChatService\x12^\n" + "\vSendMessage\x12&.greatestworks.chat.SendMessageRequest\x1a'.greatestworks.chat.SendMessageResponse\x12^\n" + "\vGetMessages\x12&.greatestworks.chat.GetMessagesRequest\x1a'.greatestworks.chat.GetMessagesResponse\x12^\n" + "\vJoinChannel\x12&.greatestworks.chat.JoinChannelRequest\x1a'.greatestworks.chat.JoinChannelResponse\x12a\n" + "\fLeaveChannel\x12'.greatestworks.chat.LeaveChannelRequest\x1a(.greatestworks.chat.LeaveChannelResponse\x12p\n" + "\x11CreatePrivateChat\x12,.greatestworks.chat.CreatePrivateChatRequest\x1a-.greatestworks.chat.CreatePrivateChatResponse\x12g\n" + "\x0eGetOnlineUsers\x12).greatestworks.chat.GetOnlineUsersRequest\x1a*.greatestworks.chat.GetOnlineUsersResponse\x12d\n" + "\rSetUserStatus\x12(.greatestworks.chat.SetUserStatusRequest\x1a).greatestworks.chat.SetUserStatusResponse\x12X\n" + "\tBlockUser\x12$.greatestworks.chat.BlockUserRequest\x1a%.greatestworks.chat.BlockUserResponse\x12d\n" + "\rReportMessage\x12(.greatestworks.chat.ReportMessageRequest\x1a).greatestworks.chat.ReportMessageResponseB8Z!greatestworks/internal/proto/chat\xaa\x02\x12GreatestWorks.Chatb\x06proto3" var ( file_proto_chat_proto_rawDescOnce sync.Once file_proto_chat_proto_rawDescData []byte ) func file_proto_chat_proto_rawDescGZIP() []byte { file_proto_chat_proto_rawDescOnce.Do(func() { file_proto_chat_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_chat_proto_rawDesc), len(file_proto_chat_proto_rawDesc))) }) return file_proto_chat_proto_rawDescData } var file_proto_chat_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_proto_chat_proto_msgTypes = make([]protoimpl.MessageInfo, 23) var file_proto_chat_proto_goTypes = []any{ (MessageType)(0), // 0: greatestworks.chat.MessageType (UserStatus)(0), // 1: greatestworks.chat.UserStatus (UserRole)(0), // 2: greatestworks.chat.UserRole (ReportReason)(0), // 3: greatestworks.chat.ReportReason (*SendMessageRequest)(nil), // 4: greatestworks.chat.SendMessageRequest (*SendMessageResponse)(nil), // 5: greatestworks.chat.SendMessageResponse (*GetMessagesRequest)(nil), // 6: greatestworks.chat.GetMessagesRequest (*GetMessagesResponse)(nil), // 7: greatestworks.chat.GetMessagesResponse (*JoinChannelRequest)(nil), // 8: greatestworks.chat.JoinChannelRequest (*JoinChannelResponse)(nil), // 9: greatestworks.chat.JoinChannelResponse (*LeaveChannelRequest)(nil), // 10: greatestworks.chat.LeaveChannelRequest (*LeaveChannelResponse)(nil), // 11: greatestworks.chat.LeaveChannelResponse (*CreatePrivateChatRequest)(nil), // 12: greatestworks.chat.CreatePrivateChatRequest (*CreatePrivateChatResponse)(nil), // 13: greatestworks.chat.CreatePrivateChatResponse (*GetOnlineUsersRequest)(nil), // 14: greatestworks.chat.GetOnlineUsersRequest (*GetOnlineUsersResponse)(nil), // 15: greatestworks.chat.GetOnlineUsersResponse (*SetUserStatusRequest)(nil), // 16: greatestworks.chat.SetUserStatusRequest (*SetUserStatusResponse)(nil), // 17: greatestworks.chat.SetUserStatusResponse (*BlockUserRequest)(nil), // 18: greatestworks.chat.BlockUserRequest (*BlockUserResponse)(nil), // 19: greatestworks.chat.BlockUserResponse (*ReportMessageRequest)(nil), // 20: greatestworks.chat.ReportMessageRequest (*ReportMessageResponse)(nil), // 21: greatestworks.chat.ReportMessageResponse (*ChatMessage)(nil), // 22: greatestworks.chat.ChatMessage (*ChannelInfo)(nil), // 23: greatestworks.chat.ChannelInfo (*ChatUser)(nil), // 24: greatestworks.chat.ChatUser nil, // 25: greatestworks.chat.SendMessageRequest.MetadataEntry nil, // 26: greatestworks.chat.ChatMessage.MetadataEntry (common.ChatChannel)(0), // 27: greatestworks.common.ChatChannel (*common.CommonResponse)(nil), // 28: greatestworks.common.CommonResponse (*common.PaginationInfo)(nil), // 29: greatestworks.common.PaginationInfo } var file_proto_chat_proto_depIdxs = []int32{ 27, // 0: greatestworks.chat.SendMessageRequest.channel:type_name -> greatestworks.common.ChatChannel 0, // 1: greatestworks.chat.SendMessageRequest.message_type:type_name -> greatestworks.chat.MessageType 25, // 2: greatestworks.chat.SendMessageRequest.metadata:type_name -> greatestworks.chat.SendMessageRequest.MetadataEntry 28, // 3: greatestworks.chat.SendMessageResponse.common:type_name -> greatestworks.common.CommonResponse 27, // 4: greatestworks.chat.GetMessagesRequest.channel:type_name -> greatestworks.common.ChatChannel 28, // 5: greatestworks.chat.GetMessagesResponse.common:type_name -> greatestworks.common.CommonResponse 22, // 6: greatestworks.chat.GetMessagesResponse.messages:type_name -> greatestworks.chat.ChatMessage 29, // 7: greatestworks.chat.GetMessagesResponse.pagination:type_name -> greatestworks.common.PaginationInfo 27, // 8: greatestworks.chat.JoinChannelRequest.channel:type_name -> greatestworks.common.ChatChannel 28, // 9: greatestworks.chat.JoinChannelResponse.common:type_name -> greatestworks.common.CommonResponse 23, // 10: greatestworks.chat.JoinChannelResponse.channel_info:type_name -> greatestworks.chat.ChannelInfo 24, // 11: greatestworks.chat.JoinChannelResponse.online_users:type_name -> greatestworks.chat.ChatUser 27, // 12: greatestworks.chat.LeaveChannelRequest.channel:type_name -> greatestworks.common.ChatChannel 28, // 13: greatestworks.chat.LeaveChannelResponse.common:type_name -> greatestworks.common.CommonResponse 28, // 14: greatestworks.chat.CreatePrivateChatResponse.common:type_name -> greatestworks.common.CommonResponse 23, // 15: greatestworks.chat.CreatePrivateChatResponse.chat_info:type_name -> greatestworks.chat.ChannelInfo 27, // 16: greatestworks.chat.GetOnlineUsersRequest.channel:type_name -> greatestworks.common.ChatChannel 28, // 17: greatestworks.chat.GetOnlineUsersResponse.common:type_name -> greatestworks.common.CommonResponse 24, // 18: greatestworks.chat.GetOnlineUsersResponse.users:type_name -> greatestworks.chat.ChatUser 29, // 19: greatestworks.chat.GetOnlineUsersResponse.pagination:type_name -> greatestworks.common.PaginationInfo 1, // 20: greatestworks.chat.SetUserStatusRequest.status:type_name -> greatestworks.chat.UserStatus 28, // 21: greatestworks.chat.SetUserStatusResponse.common:type_name -> greatestworks.common.CommonResponse 1, // 22: greatestworks.chat.SetUserStatusResponse.new_status:type_name -> greatestworks.chat.UserStatus 28, // 23: greatestworks.chat.BlockUserResponse.common:type_name -> greatestworks.common.CommonResponse 3, // 24: greatestworks.chat.ReportMessageRequest.reason:type_name -> greatestworks.chat.ReportReason 28, // 25: greatestworks.chat.ReportMessageResponse.common:type_name -> greatestworks.common.CommonResponse 27, // 26: greatestworks.chat.ChatMessage.channel:type_name -> greatestworks.common.ChatChannel 0, // 27: greatestworks.chat.ChatMessage.message_type:type_name -> greatestworks.chat.MessageType 26, // 28: greatestworks.chat.ChatMessage.metadata:type_name -> greatestworks.chat.ChatMessage.MetadataEntry 27, // 29: greatestworks.chat.ChannelInfo.channel_type:type_name -> greatestworks.common.ChatChannel 1, // 30: greatestworks.chat.ChatUser.status:type_name -> greatestworks.chat.UserStatus 2, // 31: greatestworks.chat.ChatUser.role:type_name -> greatestworks.chat.UserRole 4, // 32: greatestworks.chat.ChatService.SendMessage:input_type -> greatestworks.chat.SendMessageRequest 6, // 33: greatestworks.chat.ChatService.GetMessages:input_type -> greatestworks.chat.GetMessagesRequest 8, // 34: greatestworks.chat.ChatService.JoinChannel:input_type -> greatestworks.chat.JoinChannelRequest 10, // 35: greatestworks.chat.ChatService.LeaveChannel:input_type -> greatestworks.chat.LeaveChannelRequest 12, // 36: greatestworks.chat.ChatService.CreatePrivateChat:input_type -> greatestworks.chat.CreatePrivateChatRequest 14, // 37: greatestworks.chat.ChatService.GetOnlineUsers:input_type -> greatestworks.chat.GetOnlineUsersRequest 16, // 38: greatestworks.chat.ChatService.SetUserStatus:input_type -> greatestworks.chat.SetUserStatusRequest 18, // 39: greatestworks.chat.ChatService.BlockUser:input_type -> greatestworks.chat.BlockUserRequest 20, // 40: greatestworks.chat.ChatService.ReportMessage:input_type -> greatestworks.chat.ReportMessageRequest 5, // 41: greatestworks.chat.ChatService.SendMessage:output_type -> greatestworks.chat.SendMessageResponse 7, // 42: greatestworks.chat.ChatService.GetMessages:output_type -> greatestworks.chat.GetMessagesResponse 9, // 43: greatestworks.chat.ChatService.JoinChannel:output_type -> greatestworks.chat.JoinChannelResponse 11, // 44: greatestworks.chat.ChatService.LeaveChannel:output_type -> greatestworks.chat.LeaveChannelResponse 13, // 45: greatestworks.chat.ChatService.CreatePrivateChat:output_type -> greatestworks.chat.CreatePrivateChatResponse 15, // 46: greatestworks.chat.ChatService.GetOnlineUsers:output_type -> greatestworks.chat.GetOnlineUsersResponse 17, // 47: greatestworks.chat.ChatService.SetUserStatus:output_type -> greatestworks.chat.SetUserStatusResponse 19, // 48: greatestworks.chat.ChatService.BlockUser:output_type -> greatestworks.chat.BlockUserResponse 21, // 49: greatestworks.chat.ChatService.ReportMessage:output_type -> greatestworks.chat.ReportMessageResponse 41, // [41:50] is the sub-list for method output_type 32, // [32:41] is the sub-list for method input_type 32, // [32:32] is the sub-list for extension type_name 32, // [32:32] is the sub-list for extension extendee 0, // [0:32] is the sub-list for field type_name } func init() { file_proto_chat_proto_init() } func file_proto_chat_proto_init() { if File_proto_chat_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_chat_proto_rawDesc), len(file_proto_chat_proto_rawDesc)), NumEnums: 4, NumMessages: 23, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_chat_proto_goTypes, DependencyIndexes: file_proto_chat_proto_depIdxs, EnumInfos: file_proto_chat_proto_enumTypes, MessageInfos: file_proto_chat_proto_msgTypes, }.Build() File_proto_chat_proto = out.File file_proto_chat_proto_goTypes = nil file_proto_chat_proto_depIdxs = nil } ================================================ FILE: internal/proto/common/common.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/common.proto package common import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 物品类型枚举 type ItemType int32 const ( ItemType_ITEM_TYPE_UNSPECIFIED ItemType = 0 ItemType_ITEM_TYPE_WEAPON ItemType = 1 // 武器 ItemType_ITEM_TYPE_ARMOR ItemType = 2 // 护甲 ItemType_ITEM_TYPE_ACCESSORY ItemType = 3 // 饰品 ItemType_ITEM_TYPE_CONSUMABLE ItemType = 4 // 消耗品 ItemType_ITEM_TYPE_MATERIAL ItemType = 5 // 材料 ItemType_ITEM_TYPE_QUEST ItemType = 6 // 任务物品 ItemType_ITEM_TYPE_CURRENCY ItemType = 7 // 货币 ItemType_ITEM_TYPE_SPECIAL ItemType = 8 // 特殊物品 ) // Enum value maps for ItemType. var ( ItemType_name = map[int32]string{ 0: "ITEM_TYPE_UNSPECIFIED", 1: "ITEM_TYPE_WEAPON", 2: "ITEM_TYPE_ARMOR", 3: "ITEM_TYPE_ACCESSORY", 4: "ITEM_TYPE_CONSUMABLE", 5: "ITEM_TYPE_MATERIAL", 6: "ITEM_TYPE_QUEST", 7: "ITEM_TYPE_CURRENCY", 8: "ITEM_TYPE_SPECIAL", } ItemType_value = map[string]int32{ "ITEM_TYPE_UNSPECIFIED": 0, "ITEM_TYPE_WEAPON": 1, "ITEM_TYPE_ARMOR": 2, "ITEM_TYPE_ACCESSORY": 3, "ITEM_TYPE_CONSUMABLE": 4, "ITEM_TYPE_MATERIAL": 5, "ITEM_TYPE_QUEST": 6, "ITEM_TYPE_CURRENCY": 7, "ITEM_TYPE_SPECIAL": 8, } ) func (x ItemType) Enum() *ItemType { p := new(ItemType) *p = x return p } func (x ItemType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ItemType) Descriptor() protoreflect.EnumDescriptor { return file_proto_common_proto_enumTypes[0].Descriptor() } func (ItemType) Type() protoreflect.EnumType { return &file_proto_common_proto_enumTypes[0] } func (x ItemType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ItemType.Descriptor instead. func (ItemType) EnumDescriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{0} } // 技能类型枚举 type SkillType int32 const ( SkillType_SKILL_TYPE_UNSPECIFIED SkillType = 0 SkillType_SKILL_TYPE_ATTACK SkillType = 1 // 攻击技能 SkillType_SKILL_TYPE_DEFENSE SkillType = 2 // 防御技能 SkillType_SKILL_TYPE_HEAL SkillType = 3 // 治疗技能 SkillType_SKILL_TYPE_BUFF SkillType = 4 // 增益技能 SkillType_SKILL_TYPE_DEBUFF SkillType = 5 // 减益技能 SkillType_SKILL_TYPE_PASSIVE SkillType = 6 // 被动技能 SkillType_SKILL_TYPE_ULTIMATE SkillType = 7 // 终极技能 ) // Enum value maps for SkillType. var ( SkillType_name = map[int32]string{ 0: "SKILL_TYPE_UNSPECIFIED", 1: "SKILL_TYPE_ATTACK", 2: "SKILL_TYPE_DEFENSE", 3: "SKILL_TYPE_HEAL", 4: "SKILL_TYPE_BUFF", 5: "SKILL_TYPE_DEBUFF", 6: "SKILL_TYPE_PASSIVE", 7: "SKILL_TYPE_ULTIMATE", } SkillType_value = map[string]int32{ "SKILL_TYPE_UNSPECIFIED": 0, "SKILL_TYPE_ATTACK": 1, "SKILL_TYPE_DEFENSE": 2, "SKILL_TYPE_HEAL": 3, "SKILL_TYPE_BUFF": 4, "SKILL_TYPE_DEBUFF": 5, "SKILL_TYPE_PASSIVE": 6, "SKILL_TYPE_ULTIMATE": 7, } ) func (x SkillType) Enum() *SkillType { p := new(SkillType) *p = x return p } func (x SkillType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SkillType) Descriptor() protoreflect.EnumDescriptor { return file_proto_common_proto_enumTypes[1].Descriptor() } func (SkillType) Type() protoreflect.EnumType { return &file_proto_common_proto_enumTypes[1] } func (x SkillType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SkillType.Descriptor instead. func (SkillType) EnumDescriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{1} } // 聊天频道枚举 type ChatChannel int32 const ( ChatChannel_CHAT_CHANNEL_UNSPECIFIED ChatChannel = 0 ChatChannel_CHAT_CHANNEL_WORLD ChatChannel = 1 // 世界频道 ChatChannel_CHAT_CHANNEL_GUILD ChatChannel = 2 // 公会频道 ChatChannel_CHAT_CHANNEL_TEAM ChatChannel = 3 // 队伍频道 ChatChannel_CHAT_CHANNEL_PRIVATE ChatChannel = 4 // 私聊频道 ChatChannel_CHAT_CHANNEL_SYSTEM ChatChannel = 5 // 系统频道 ChatChannel_CHAT_CHANNEL_TRADE ChatChannel = 6 // 交易频道 ChatChannel_CHAT_CHANNEL_HELP ChatChannel = 7 // 帮助频道 ) // Enum value maps for ChatChannel. var ( ChatChannel_name = map[int32]string{ 0: "CHAT_CHANNEL_UNSPECIFIED", 1: "CHAT_CHANNEL_WORLD", 2: "CHAT_CHANNEL_GUILD", 3: "CHAT_CHANNEL_TEAM", 4: "CHAT_CHANNEL_PRIVATE", 5: "CHAT_CHANNEL_SYSTEM", 6: "CHAT_CHANNEL_TRADE", 7: "CHAT_CHANNEL_HELP", } ChatChannel_value = map[string]int32{ "CHAT_CHANNEL_UNSPECIFIED": 0, "CHAT_CHANNEL_WORLD": 1, "CHAT_CHANNEL_GUILD": 2, "CHAT_CHANNEL_TEAM": 3, "CHAT_CHANNEL_PRIVATE": 4, "CHAT_CHANNEL_SYSTEM": 5, "CHAT_CHANNEL_TRADE": 6, "CHAT_CHANNEL_HELP": 7, } ) func (x ChatChannel) Enum() *ChatChannel { p := new(ChatChannel) *p = x return p } func (x ChatChannel) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ChatChannel) Descriptor() protoreflect.EnumDescriptor { return file_proto_common_proto_enumTypes[2].Descriptor() } func (ChatChannel) Type() protoreflect.EnumType { return &file_proto_common_proto_enumTypes[2] } func (x ChatChannel) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ChatChannel.Descriptor instead. func (ChatChannel) EnumDescriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{2} } // 任务状态枚举 type QuestStatus int32 const ( QuestStatus_QUEST_STATUS_UNSPECIFIED QuestStatus = 0 QuestStatus_QUEST_STATUS_NOT_STARTED QuestStatus = 1 // 未开始 QuestStatus_QUEST_STATUS_IN_PROGRESS QuestStatus = 2 // 进行中 QuestStatus_QUEST_STATUS_COMPLETED QuestStatus = 3 // 已完成 QuestStatus_QUEST_STATUS_FAILED QuestStatus = 4 // 已失败 QuestStatus_QUEST_STATUS_CANCELLED QuestStatus = 5 // 已取消 ) // Enum value maps for QuestStatus. var ( QuestStatus_name = map[int32]string{ 0: "QUEST_STATUS_UNSPECIFIED", 1: "QUEST_STATUS_NOT_STARTED", 2: "QUEST_STATUS_IN_PROGRESS", 3: "QUEST_STATUS_COMPLETED", 4: "QUEST_STATUS_FAILED", 5: "QUEST_STATUS_CANCELLED", } QuestStatus_value = map[string]int32{ "QUEST_STATUS_UNSPECIFIED": 0, "QUEST_STATUS_NOT_STARTED": 1, "QUEST_STATUS_IN_PROGRESS": 2, "QUEST_STATUS_COMPLETED": 3, "QUEST_STATUS_FAILED": 4, "QUEST_STATUS_CANCELLED": 5, } ) func (x QuestStatus) Enum() *QuestStatus { p := new(QuestStatus) *p = x return p } func (x QuestStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QuestStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_common_proto_enumTypes[3].Descriptor() } func (QuestStatus) Type() protoreflect.EnumType { return &file_proto_common_proto_enumTypes[3] } func (x QuestStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QuestStatus.Descriptor instead. func (QuestStatus) EnumDescriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{3} } // 任务类型枚举 type QuestType int32 const ( QuestType_QUEST_TYPE_UNSPECIFIED QuestType = 0 QuestType_QUEST_TYPE_MAIN QuestType = 1 // 主线任务 QuestType_QUEST_TYPE_SIDE QuestType = 2 // 支线任务 QuestType_QUEST_TYPE_DAILY QuestType = 3 // 日常任务 QuestType_QUEST_TYPE_WEEKLY QuestType = 4 // 周常任务 QuestType_QUEST_TYPE_EVENT QuestType = 5 // 活动任务 QuestType_QUEST_TYPE_ACHIEVEMENT QuestType = 6 // 成就任务 ) // Enum value maps for QuestType. var ( QuestType_name = map[int32]string{ 0: "QUEST_TYPE_UNSPECIFIED", 1: "QUEST_TYPE_MAIN", 2: "QUEST_TYPE_SIDE", 3: "QUEST_TYPE_DAILY", 4: "QUEST_TYPE_WEEKLY", 5: "QUEST_TYPE_EVENT", 6: "QUEST_TYPE_ACHIEVEMENT", } QuestType_value = map[string]int32{ "QUEST_TYPE_UNSPECIFIED": 0, "QUEST_TYPE_MAIN": 1, "QUEST_TYPE_SIDE": 2, "QUEST_TYPE_DAILY": 3, "QUEST_TYPE_WEEKLY": 4, "QUEST_TYPE_EVENT": 5, "QUEST_TYPE_ACHIEVEMENT": 6, } ) func (x QuestType) Enum() *QuestType { p := new(QuestType) *p = x return p } func (x QuestType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QuestType) Descriptor() protoreflect.EnumDescriptor { return file_proto_common_proto_enumTypes[4].Descriptor() } func (QuestType) Type() protoreflect.EnumType { return &file_proto_common_proto_enumTypes[4] } func (x QuestType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QuestType.Descriptor instead. func (QuestType) EnumDescriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{4} } // 物品稀有度枚举 type ItemRarity int32 const ( ItemRarity_ITEM_RARITY_UNSPECIFIED ItemRarity = 0 ItemRarity_ITEM_RARITY_COMMON ItemRarity = 1 // 普通 ItemRarity_ITEM_RARITY_UNCOMMON ItemRarity = 2 // 不常见 ItemRarity_ITEM_RARITY_RARE ItemRarity = 3 // 稀有 ItemRarity_ITEM_RARITY_EPIC ItemRarity = 4 // 史诗 ItemRarity_ITEM_RARITY_LEGENDARY ItemRarity = 5 // 传说 ItemRarity_ITEM_RARITY_MYTHIC ItemRarity = 6 // 神话 ) // Enum value maps for ItemRarity. var ( ItemRarity_name = map[int32]string{ 0: "ITEM_RARITY_UNSPECIFIED", 1: "ITEM_RARITY_COMMON", 2: "ITEM_RARITY_UNCOMMON", 3: "ITEM_RARITY_RARE", 4: "ITEM_RARITY_EPIC", 5: "ITEM_RARITY_LEGENDARY", 6: "ITEM_RARITY_MYTHIC", } ItemRarity_value = map[string]int32{ "ITEM_RARITY_UNSPECIFIED": 0, "ITEM_RARITY_COMMON": 1, "ITEM_RARITY_UNCOMMON": 2, "ITEM_RARITY_RARE": 3, "ITEM_RARITY_EPIC": 4, "ITEM_RARITY_LEGENDARY": 5, "ITEM_RARITY_MYTHIC": 6, } ) func (x ItemRarity) Enum() *ItemRarity { p := new(ItemRarity) *p = x return p } func (x ItemRarity) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ItemRarity) Descriptor() protoreflect.EnumDescriptor { return file_proto_common_proto_enumTypes[5].Descriptor() } func (ItemRarity) Type() protoreflect.EnumType { return &file_proto_common_proto_enumTypes[5] } func (x ItemRarity) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ItemRarity.Descriptor instead. func (ItemRarity) EnumDescriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{5} } // 通用响应结构 type CommonResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` Code int32 `protobuf:"varint,3,opt,name=code,proto3" json:"code,omitempty"` Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CommonResponse) Reset() { *x = CommonResponse{} mi := &file_proto_common_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CommonResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CommonResponse) ProtoMessage() {} func (x *CommonResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_common_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CommonResponse.ProtoReflect.Descriptor instead. func (*CommonResponse) Descriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{0} } func (x *CommonResponse) GetSuccess() bool { if x != nil { return x.Success } return false } func (x *CommonResponse) GetMessage() string { if x != nil { return x.Message } return "" } func (x *CommonResponse) GetCode() int32 { if x != nil { return x.Code } return 0 } func (x *CommonResponse) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } // 通用请求结构 type CommonRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CommonRequest) Reset() { *x = CommonRequest{} mi := &file_proto_common_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CommonRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CommonRequest) ProtoMessage() {} func (x *CommonRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_common_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CommonRequest.ProtoReflect.Descriptor instead. func (*CommonRequest) Descriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{1} } func (x *CommonRequest) GetRequestId() string { if x != nil { return x.RequestId } return "" } func (x *CommonRequest) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *CommonRequest) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 分页信息 type PaginationInfo struct { state protoimpl.MessageState `protogen:"open.v1"` Page int32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` Total int32 `protobuf:"varint,3,opt,name=total,proto3" json:"total,omitempty"` TotalPages int32 `protobuf:"varint,4,opt,name=total_pages,json=totalPages,proto3" json:"total_pages,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PaginationInfo) Reset() { *x = PaginationInfo{} mi := &file_proto_common_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PaginationInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*PaginationInfo) ProtoMessage() {} func (x *PaginationInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_common_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PaginationInfo.ProtoReflect.Descriptor instead. func (*PaginationInfo) Descriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{2} } func (x *PaginationInfo) GetPage() int32 { if x != nil { return x.Page } return 0 } func (x *PaginationInfo) GetPageSize() int32 { if x != nil { return x.PageSize } return 0 } func (x *PaginationInfo) GetTotal() int32 { if x != nil { return x.Total } return 0 } func (x *PaginationInfo) GetTotalPages() int32 { if x != nil { return x.TotalPages } return 0 } // 位置信息 type Position struct { state protoimpl.MessageState `protogen:"open.v1"` X float32 `protobuf:"fixed32,1,opt,name=x,proto3" json:"x,omitempty"` Y float32 `protobuf:"fixed32,2,opt,name=y,proto3" json:"y,omitempty"` Z float32 `protobuf:"fixed32,3,opt,name=z,proto3" json:"z,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Position) Reset() { *x = Position{} mi := &file_proto_common_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Position) String() string { return protoimpl.X.MessageStringOf(x) } func (*Position) ProtoMessage() {} func (x *Position) ProtoReflect() protoreflect.Message { mi := &file_proto_common_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Position.ProtoReflect.Descriptor instead. func (*Position) Descriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{3} } func (x *Position) GetX() float32 { if x != nil { return x.X } return 0 } func (x *Position) GetY() float32 { if x != nil { return x.Y } return 0 } func (x *Position) GetZ() float32 { if x != nil { return x.Z } return 0 } // 玩家基础信息 type PlayerBasicInfo struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` Experience int32 `protobuf:"varint,4,opt,name=experience,proto3" json:"experience,omitempty"` Position *Position `protobuf:"bytes,5,opt,name=position,proto3" json:"position,omitempty"` LastLogin int64 `protobuf:"varint,6,opt,name=last_login,json=lastLogin,proto3" json:"last_login,omitempty"` CreatedAt int64 `protobuf:"varint,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PlayerBasicInfo) Reset() { *x = PlayerBasicInfo{} mi := &file_proto_common_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PlayerBasicInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlayerBasicInfo) ProtoMessage() {} func (x *PlayerBasicInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_common_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlayerBasicInfo.ProtoReflect.Descriptor instead. func (*PlayerBasicInfo) Descriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{4} } func (x *PlayerBasicInfo) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *PlayerBasicInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *PlayerBasicInfo) GetLevel() int32 { if x != nil { return x.Level } return 0 } func (x *PlayerBasicInfo) GetExperience() int32 { if x != nil { return x.Experience } return 0 } func (x *PlayerBasicInfo) GetPosition() *Position { if x != nil { return x.Position } return nil } func (x *PlayerBasicInfo) GetLastLogin() int64 { if x != nil { return x.LastLogin } return 0 } func (x *PlayerBasicInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } // 服务器信息 type ServerInfo struct { state protoimpl.MessageState `protogen:"open.v1"` ServerId string `protobuf:"bytes,1,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` MaxPlayers int32 `protobuf:"varint,4,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` CurrentPlayers int32 `protobuf:"varint,5,opt,name=current_players,json=currentPlayers,proto3" json:"current_players,omitempty"` IsOnline bool `protobuf:"varint,6,opt,name=is_online,json=isOnline,proto3" json:"is_online,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ServerInfo) Reset() { *x = ServerInfo{} mi := &file_proto_common_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ServerInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerInfo) ProtoMessage() {} func (x *ServerInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_common_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerInfo.ProtoReflect.Descriptor instead. func (*ServerInfo) Descriptor() ([]byte, []int) { return file_proto_common_proto_rawDescGZIP(), []int{5} } func (x *ServerInfo) GetServerId() string { if x != nil { return x.ServerId } return "" } func (x *ServerInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *ServerInfo) GetVersion() string { if x != nil { return x.Version } return "" } func (x *ServerInfo) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *ServerInfo) GetCurrentPlayers() int32 { if x != nil { return x.CurrentPlayers } return 0 } func (x *ServerInfo) GetIsOnline() bool { if x != nil { return x.IsOnline } return false } var File_proto_common_proto protoreflect.FileDescriptor const file_proto_common_proto_rawDesc = "" + "\n" + "\x12proto/common.proto\x12\x14greatestworks.common\"v\n" + "\x0eCommonResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x12\n" + "\x04code\x18\x03 \x01(\x05R\x04code\x12\x1c\n" + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\"\xd8\x01\n" + "\rCommonRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + "\ttimestamp\x18\x02 \x01(\x03R\ttimestamp\x12M\n" + "\bmetadata\x18\x03 \x03(\v21.greatestworks.common.CommonRequest.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"x\n" + "\x0ePaginationInfo\x12\x12\n" + "\x04page\x18\x01 \x01(\x05R\x04page\x12\x1b\n" + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x14\n" + "\x05total\x18\x03 \x01(\x05R\x05total\x12\x1f\n" + "\vtotal_pages\x18\x04 \x01(\x05R\n" + "totalPages\"4\n" + "\bPosition\x12\f\n" + "\x01x\x18\x01 \x01(\x02R\x01x\x12\f\n" + "\x01y\x18\x02 \x01(\x02R\x01y\x12\f\n" + "\x01z\x18\x03 \x01(\x02R\x01z\"\xf2\x01\n" + "\x0fPlayerBasicInfo\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + "\x05level\x18\x03 \x01(\x05R\x05level\x12\x1e\n" + "\n" + "experience\x18\x04 \x01(\x05R\n" + "experience\x12:\n" + "\bposition\x18\x05 \x01(\v2\x1e.greatestworks.common.PositionR\bposition\x12\x1d\n" + "\n" + "last_login\x18\x06 \x01(\x03R\tlastLogin\x12\x1d\n" + "\n" + "created_at\x18\a \x01(\x03R\tcreatedAt\"\xbe\x01\n" + "\n" + "ServerInfo\x12\x1b\n" + "\tserver_id\x18\x01 \x01(\tR\bserverId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" + "\aversion\x18\x03 \x01(\tR\aversion\x12\x1f\n" + "\vmax_players\x18\x04 \x01(\x05R\n" + "maxPlayers\x12'\n" + "\x0fcurrent_players\x18\x05 \x01(\x05R\x0ecurrentPlayers\x12\x1b\n" + "\tis_online\x18\x06 \x01(\bR\bisOnline*\xdf\x01\n" + "\bItemType\x12\x19\n" + "\x15ITEM_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10ITEM_TYPE_WEAPON\x10\x01\x12\x13\n" + "\x0fITEM_TYPE_ARMOR\x10\x02\x12\x17\n" + "\x13ITEM_TYPE_ACCESSORY\x10\x03\x12\x18\n" + "\x14ITEM_TYPE_CONSUMABLE\x10\x04\x12\x16\n" + "\x12ITEM_TYPE_MATERIAL\x10\x05\x12\x13\n" + "\x0fITEM_TYPE_QUEST\x10\x06\x12\x16\n" + "\x12ITEM_TYPE_CURRENCY\x10\a\x12\x15\n" + "\x11ITEM_TYPE_SPECIAL\x10\b*\xc8\x01\n" + "\tSkillType\x12\x1a\n" + "\x16SKILL_TYPE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11SKILL_TYPE_ATTACK\x10\x01\x12\x16\n" + "\x12SKILL_TYPE_DEFENSE\x10\x02\x12\x13\n" + "\x0fSKILL_TYPE_HEAL\x10\x03\x12\x13\n" + "\x0fSKILL_TYPE_BUFF\x10\x04\x12\x15\n" + "\x11SKILL_TYPE_DEBUFF\x10\x05\x12\x16\n" + "\x12SKILL_TYPE_PASSIVE\x10\x06\x12\x17\n" + "\x13SKILL_TYPE_ULTIMATE\x10\a*\xd4\x01\n" + "\vChatChannel\x12\x1c\n" + "\x18CHAT_CHANNEL_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12CHAT_CHANNEL_WORLD\x10\x01\x12\x16\n" + "\x12CHAT_CHANNEL_GUILD\x10\x02\x12\x15\n" + "\x11CHAT_CHANNEL_TEAM\x10\x03\x12\x18\n" + "\x14CHAT_CHANNEL_PRIVATE\x10\x04\x12\x17\n" + "\x13CHAT_CHANNEL_SYSTEM\x10\x05\x12\x16\n" + "\x12CHAT_CHANNEL_TRADE\x10\x06\x12\x15\n" + "\x11CHAT_CHANNEL_HELP\x10\a*\xb8\x01\n" + "\vQuestStatus\x12\x1c\n" + "\x18QUEST_STATUS_UNSPECIFIED\x10\x00\x12\x1c\n" + "\x18QUEST_STATUS_NOT_STARTED\x10\x01\x12\x1c\n" + "\x18QUEST_STATUS_IN_PROGRESS\x10\x02\x12\x1a\n" + "\x16QUEST_STATUS_COMPLETED\x10\x03\x12\x17\n" + "\x13QUEST_STATUS_FAILED\x10\x04\x12\x1a\n" + "\x16QUEST_STATUS_CANCELLED\x10\x05*\xb0\x01\n" + "\tQuestType\x12\x1a\n" + "\x16QUEST_TYPE_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fQUEST_TYPE_MAIN\x10\x01\x12\x13\n" + "\x0fQUEST_TYPE_SIDE\x10\x02\x12\x14\n" + "\x10QUEST_TYPE_DAILY\x10\x03\x12\x15\n" + "\x11QUEST_TYPE_WEEKLY\x10\x04\x12\x14\n" + "\x10QUEST_TYPE_EVENT\x10\x05\x12\x1a\n" + "\x16QUEST_TYPE_ACHIEVEMENT\x10\x06*\xba\x01\n" + "\n" + "ItemRarity\x12\x1b\n" + "\x17ITEM_RARITY_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12ITEM_RARITY_COMMON\x10\x01\x12\x18\n" + "\x14ITEM_RARITY_UNCOMMON\x10\x02\x12\x14\n" + "\x10ITEM_RARITY_RARE\x10\x03\x12\x14\n" + "\x10ITEM_RARITY_EPIC\x10\x04\x12\x19\n" + "\x15ITEM_RARITY_LEGENDARY\x10\x05\x12\x16\n" + "\x12ITEM_RARITY_MYTHIC\x10\x06B greatestworks.common.CommonRequest.MetadataEntry 9, // 1: greatestworks.common.PlayerBasicInfo.position:type_name -> greatestworks.common.Position 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_proto_common_proto_init() } func file_proto_common_proto_init() { if File_proto_common_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_common_proto_rawDesc), len(file_proto_common_proto_rawDesc)), NumEnums: 6, NumMessages: 7, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proto_common_proto_goTypes, DependencyIndexes: file_proto_common_proto_depIdxs, EnumInfos: file_proto_common_proto_enumTypes, MessageInfos: file_proto_common_proto_msgTypes, }.Build() File_proto_common_proto = out.File file_proto_common_proto_goTypes = nil file_proto_common_proto_depIdxs = nil } ================================================ FILE: internal/proto/errors/errors.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/errors.proto package errors import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 错误码枚举 - 通用错误 (1000-1999) type CommonErrorCode int32 const ( CommonErrorCode_COMMON_ERROR_CODE_UNSPECIFIED CommonErrorCode = 0 // 成功状态 CommonErrorCode_ERR_SUCCESS CommonErrorCode = 0 // 成功 // 通用错误 CommonErrorCode_ERR_UNKNOWN CommonErrorCode = 1000 // 未知错误 CommonErrorCode_ERR_INVALID_MESSAGE CommonErrorCode = 1001 // 无效消息 CommonErrorCode_ERR_AUTH_FAILED CommonErrorCode = 1002 // 认证失败 CommonErrorCode_ERR_PLAYER_NOT_FOUND CommonErrorCode = 1003 // 玩家未找到 CommonErrorCode_ERR_BATTLE_NOT_FOUND CommonErrorCode = 1004 // 战斗未找到 CommonErrorCode_ERR_UNKNOWN_MESSAGE CommonErrorCode = 1005 // 未知消息类型 CommonErrorCode_ERR_SERVER_BUSY CommonErrorCode = 1006 // 服务器繁忙 CommonErrorCode_ERR_INVALID_PLAYER CommonErrorCode = 1007 // 无效玩家 CommonErrorCode_ERR_PERMISSION_DENIED CommonErrorCode = 1008 // 权限不足 CommonErrorCode_ERR_RATE_LIMITED CommonErrorCode = 1009 // 请求过于频繁 CommonErrorCode_ERR_MAINTENANCE CommonErrorCode = 1010 // 服务器维护 CommonErrorCode_ERR_INVALID_REQUEST CommonErrorCode = 1011 // 无效请求 CommonErrorCode_ERR_TIMEOUT CommonErrorCode = 1012 // 请求超时 CommonErrorCode_ERR_CONNECTION_LOST CommonErrorCode = 1013 // 连接丢失 CommonErrorCode_ERR_INVALID_TOKEN CommonErrorCode = 1014 // 无效令牌 CommonErrorCode_ERR_SESSION_EXPIRED CommonErrorCode = 1015 // 会话过期 ) // Enum value maps for CommonErrorCode. var ( CommonErrorCode_name = map[int32]string{ 0: "COMMON_ERROR_CODE_UNSPECIFIED", // Duplicate value: 0: "ERR_SUCCESS", 1000: "ERR_UNKNOWN", 1001: "ERR_INVALID_MESSAGE", 1002: "ERR_AUTH_FAILED", 1003: "ERR_PLAYER_NOT_FOUND", 1004: "ERR_BATTLE_NOT_FOUND", 1005: "ERR_UNKNOWN_MESSAGE", 1006: "ERR_SERVER_BUSY", 1007: "ERR_INVALID_PLAYER", 1008: "ERR_PERMISSION_DENIED", 1009: "ERR_RATE_LIMITED", 1010: "ERR_MAINTENANCE", 1011: "ERR_INVALID_REQUEST", 1012: "ERR_TIMEOUT", 1013: "ERR_CONNECTION_LOST", 1014: "ERR_INVALID_TOKEN", 1015: "ERR_SESSION_EXPIRED", } CommonErrorCode_value = map[string]int32{ "COMMON_ERROR_CODE_UNSPECIFIED": 0, "ERR_SUCCESS": 0, "ERR_UNKNOWN": 1000, "ERR_INVALID_MESSAGE": 1001, "ERR_AUTH_FAILED": 1002, "ERR_PLAYER_NOT_FOUND": 1003, "ERR_BATTLE_NOT_FOUND": 1004, "ERR_UNKNOWN_MESSAGE": 1005, "ERR_SERVER_BUSY": 1006, "ERR_INVALID_PLAYER": 1007, "ERR_PERMISSION_DENIED": 1008, "ERR_RATE_LIMITED": 1009, "ERR_MAINTENANCE": 1010, "ERR_INVALID_REQUEST": 1011, "ERR_TIMEOUT": 1012, "ERR_CONNECTION_LOST": 1013, "ERR_INVALID_TOKEN": 1014, "ERR_SESSION_EXPIRED": 1015, } ) func (x CommonErrorCode) Enum() *CommonErrorCode { p := new(CommonErrorCode) *p = x return p } func (x CommonErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (CommonErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[0].Descriptor() } func (CommonErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[0] } func (x CommonErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use CommonErrorCode.Descriptor instead. func (CommonErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{0} } // 错误码枚举 - 战斗相关错误 (2000-2999) type BattleErrorCode int32 const ( BattleErrorCode_BATTLE_ERROR_CODE_UNSPECIFIED BattleErrorCode = 0 // 战斗基础错误 BattleErrorCode_ERR_INVALID_CREATOR_ID BattleErrorCode = 2001 // 无效创建者ID BattleErrorCode_ERR_INVALID_BATTLE_TYPE BattleErrorCode = 2002 // 无效战斗类型 BattleErrorCode_ERR_INVALID_BATTLE_ID BattleErrorCode = 2003 // 无效战斗ID BattleErrorCode_ERR_INVALID_PLAYER_ID BattleErrorCode = 2004 // 无效玩家ID BattleErrorCode_ERR_INVALID_TARGET_ID BattleErrorCode = 2005 // 无效目标ID BattleErrorCode_ERR_INVALID_SKILL_ID BattleErrorCode = 2006 // 无效技能ID BattleErrorCode_ERR_INVALID_TEAM BattleErrorCode = 2007 // 无效队伍 BattleErrorCode_ERR_BATTLE_ALREADY_STARTED BattleErrorCode = 2008 // 战斗已开始 BattleErrorCode_ERR_BATTLE_NOT_STARTED BattleErrorCode = 2009 // 战斗未开始 BattleErrorCode_ERR_PLAYER_NOT_IN_BATTLE BattleErrorCode = 2010 // 玩家不在战斗中 BattleErrorCode_ERR_INSUFFICIENT_MANA BattleErrorCode = 2011 // MP不足 BattleErrorCode_ERR_SKILL_ON_COOLDOWN BattleErrorCode = 2012 // 技能冷却中 BattleErrorCode_ERR_INVALID_ACTION BattleErrorCode = 2013 // 无效行动 BattleErrorCode_ERR_BATTLE_FULL BattleErrorCode = 2014 // 战斗已满 BattleErrorCode_ERR_BATTLE_ENDED BattleErrorCode = 2015 // 战斗已结束 BattleErrorCode_ERR_NOT_YOUR_TURN BattleErrorCode = 2016 // 不是你的回合 BattleErrorCode_ERR_BATTLE_CANCELLED BattleErrorCode = 2017 // 战斗已取消 BattleErrorCode_ERR_INVALID_BATTLE_STATE BattleErrorCode = 2018 // 无效战斗状态 BattleErrorCode_ERR_BATTLE_TIMEOUT BattleErrorCode = 2019 // 战斗超时 ) // Enum value maps for BattleErrorCode. var ( BattleErrorCode_name = map[int32]string{ 0: "BATTLE_ERROR_CODE_UNSPECIFIED", 2001: "ERR_INVALID_CREATOR_ID", 2002: "ERR_INVALID_BATTLE_TYPE", 2003: "ERR_INVALID_BATTLE_ID", 2004: "ERR_INVALID_PLAYER_ID", 2005: "ERR_INVALID_TARGET_ID", 2006: "ERR_INVALID_SKILL_ID", 2007: "ERR_INVALID_TEAM", 2008: "ERR_BATTLE_ALREADY_STARTED", 2009: "ERR_BATTLE_NOT_STARTED", 2010: "ERR_PLAYER_NOT_IN_BATTLE", 2011: "ERR_INSUFFICIENT_MANA", 2012: "ERR_SKILL_ON_COOLDOWN", 2013: "ERR_INVALID_ACTION", 2014: "ERR_BATTLE_FULL", 2015: "ERR_BATTLE_ENDED", 2016: "ERR_NOT_YOUR_TURN", 2017: "ERR_BATTLE_CANCELLED", 2018: "ERR_INVALID_BATTLE_STATE", 2019: "ERR_BATTLE_TIMEOUT", } BattleErrorCode_value = map[string]int32{ "BATTLE_ERROR_CODE_UNSPECIFIED": 0, "ERR_INVALID_CREATOR_ID": 2001, "ERR_INVALID_BATTLE_TYPE": 2002, "ERR_INVALID_BATTLE_ID": 2003, "ERR_INVALID_PLAYER_ID": 2004, "ERR_INVALID_TARGET_ID": 2005, "ERR_INVALID_SKILL_ID": 2006, "ERR_INVALID_TEAM": 2007, "ERR_BATTLE_ALREADY_STARTED": 2008, "ERR_BATTLE_NOT_STARTED": 2009, "ERR_PLAYER_NOT_IN_BATTLE": 2010, "ERR_INSUFFICIENT_MANA": 2011, "ERR_SKILL_ON_COOLDOWN": 2012, "ERR_INVALID_ACTION": 2013, "ERR_BATTLE_FULL": 2014, "ERR_BATTLE_ENDED": 2015, "ERR_NOT_YOUR_TURN": 2016, "ERR_BATTLE_CANCELLED": 2017, "ERR_INVALID_BATTLE_STATE": 2018, "ERR_BATTLE_TIMEOUT": 2019, } ) func (x BattleErrorCode) Enum() *BattleErrorCode { p := new(BattleErrorCode) *p = x return p } func (x BattleErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[1].Descriptor() } func (BattleErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[1] } func (x BattleErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleErrorCode.Descriptor instead. func (BattleErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{1} } // 错误码枚举 - 宠物相关错误 (3000-3999) type PetErrorCode int32 const ( PetErrorCode_PET_ERROR_CODE_UNSPECIFIED PetErrorCode = 0 PetErrorCode_ERR_PET_NOT_FOUND PetErrorCode = 3001 // 宠物未找到 PetErrorCode_ERR_PET_ALREADY_ACTIVE PetErrorCode = 3002 // 宠物已激活 PetErrorCode_ERR_PET_NOT_ACTIVE PetErrorCode = 3003 // 宠物未激活 PetErrorCode_ERR_PET_LEVEL_TOO_LOW PetErrorCode = 3004 // 宠物等级过低 PetErrorCode_ERR_PET_EVOLUTION_FAIL PetErrorCode = 3005 // 宠物进化失败 PetErrorCode_ERR_PET_INSUFFICIENT_EXP PetErrorCode = 3006 // 宠物经验不足 PetErrorCode_ERR_PET_ALREADY_EVOLVED PetErrorCode = 3007 // 宠物已进化 PetErrorCode_ERR_PET_TRAINING_FAILED PetErrorCode = 3008 // 宠物训练失败 PetErrorCode_ERR_PET_FEEDING_FAILED PetErrorCode = 3009 // 宠物喂养失败 PetErrorCode_ERR_PET_SKILL_NOT_LEARNED PetErrorCode = 3010 // 宠物技能未学习 PetErrorCode_ERR_PET_INSUFFICIENT_ENERGY PetErrorCode = 3011 // 宠物能量不足 PetErrorCode_ERR_PET_SICK PetErrorCode = 3012 // 宠物生病 PetErrorCode_ERR_PET_DEAD PetErrorCode = 3013 // 宠物死亡 PetErrorCode_ERR_PET_BOND_FAILED PetErrorCode = 3014 // 宠物羁绊失败 ) // Enum value maps for PetErrorCode. var ( PetErrorCode_name = map[int32]string{ 0: "PET_ERROR_CODE_UNSPECIFIED", 3001: "ERR_PET_NOT_FOUND", 3002: "ERR_PET_ALREADY_ACTIVE", 3003: "ERR_PET_NOT_ACTIVE", 3004: "ERR_PET_LEVEL_TOO_LOW", 3005: "ERR_PET_EVOLUTION_FAIL", 3006: "ERR_PET_INSUFFICIENT_EXP", 3007: "ERR_PET_ALREADY_EVOLVED", 3008: "ERR_PET_TRAINING_FAILED", 3009: "ERR_PET_FEEDING_FAILED", 3010: "ERR_PET_SKILL_NOT_LEARNED", 3011: "ERR_PET_INSUFFICIENT_ENERGY", 3012: "ERR_PET_SICK", 3013: "ERR_PET_DEAD", 3014: "ERR_PET_BOND_FAILED", } PetErrorCode_value = map[string]int32{ "PET_ERROR_CODE_UNSPECIFIED": 0, "ERR_PET_NOT_FOUND": 3001, "ERR_PET_ALREADY_ACTIVE": 3002, "ERR_PET_NOT_ACTIVE": 3003, "ERR_PET_LEVEL_TOO_LOW": 3004, "ERR_PET_EVOLUTION_FAIL": 3005, "ERR_PET_INSUFFICIENT_EXP": 3006, "ERR_PET_ALREADY_EVOLVED": 3007, "ERR_PET_TRAINING_FAILED": 3008, "ERR_PET_FEEDING_FAILED": 3009, "ERR_PET_SKILL_NOT_LEARNED": 3010, "ERR_PET_INSUFFICIENT_ENERGY": 3011, "ERR_PET_SICK": 3012, "ERR_PET_DEAD": 3013, "ERR_PET_BOND_FAILED": 3014, } ) func (x PetErrorCode) Enum() *PetErrorCode { p := new(PetErrorCode) *p = x return p } func (x PetErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[2].Descriptor() } func (PetErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[2] } func (x PetErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetErrorCode.Descriptor instead. func (PetErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{2} } // 错误码枚举 - 物品相关错误 (4000-4999) type ItemErrorCode int32 const ( ItemErrorCode_ITEM_ERROR_CODE_UNSPECIFIED ItemErrorCode = 0 ItemErrorCode_ERR_ITEM_NOT_FOUND ItemErrorCode = 4001 // 物品未找到 ItemErrorCode_ERR_ITEM_NOT_USABLE ItemErrorCode = 4002 // 物品不可使用 ItemErrorCode_ERR_INVENTORY_FULL ItemErrorCode = 4003 // 背包已满 ItemErrorCode_ERR_INSUFFICIENT_ITEM ItemErrorCode = 4004 // 物品数量不足 ItemErrorCode_ERR_ITEM_EQUIP_FAILED ItemErrorCode = 4005 // 装备失败 ItemErrorCode_ERR_ITEM_UNEQUIP_FAILED ItemErrorCode = 4006 // 卸装失败 ItemErrorCode_ERR_ITEM_CRAFT_FAILED ItemErrorCode = 4007 // 制作失败 ItemErrorCode_ERR_ITEM_ENHANCE_FAILED ItemErrorCode = 4008 // 强化失败 ItemErrorCode_ERR_ITEM_TRADE_FAILED ItemErrorCode = 4009 // 交易失败 ItemErrorCode_ERR_ITEM_DROP_FAILED ItemErrorCode = 4010 // 丢弃失败 ItemErrorCode_ERR_ITEM_PICKUP_FAILED ItemErrorCode = 4011 // 拾取失败 ItemErrorCode_ERR_ITEM_STACK_FULL ItemErrorCode = 4012 // 物品堆叠已满 ItemErrorCode_ERR_ITEM_NOT_TRADEABLE ItemErrorCode = 4013 // 物品不可交易 ItemErrorCode_ERR_ITEM_BOUND ItemErrorCode = 4014 // 物品已绑定 ItemErrorCode_ERR_ITEM_LEVEL_TOO_LOW ItemErrorCode = 4015 // 物品等级过低 ) // Enum value maps for ItemErrorCode. var ( ItemErrorCode_name = map[int32]string{ 0: "ITEM_ERROR_CODE_UNSPECIFIED", 4001: "ERR_ITEM_NOT_FOUND", 4002: "ERR_ITEM_NOT_USABLE", 4003: "ERR_INVENTORY_FULL", 4004: "ERR_INSUFFICIENT_ITEM", 4005: "ERR_ITEM_EQUIP_FAILED", 4006: "ERR_ITEM_UNEQUIP_FAILED", 4007: "ERR_ITEM_CRAFT_FAILED", 4008: "ERR_ITEM_ENHANCE_FAILED", 4009: "ERR_ITEM_TRADE_FAILED", 4010: "ERR_ITEM_DROP_FAILED", 4011: "ERR_ITEM_PICKUP_FAILED", 4012: "ERR_ITEM_STACK_FULL", 4013: "ERR_ITEM_NOT_TRADEABLE", 4014: "ERR_ITEM_BOUND", 4015: "ERR_ITEM_LEVEL_TOO_LOW", } ItemErrorCode_value = map[string]int32{ "ITEM_ERROR_CODE_UNSPECIFIED": 0, "ERR_ITEM_NOT_FOUND": 4001, "ERR_ITEM_NOT_USABLE": 4002, "ERR_INVENTORY_FULL": 4003, "ERR_INSUFFICIENT_ITEM": 4004, "ERR_ITEM_EQUIP_FAILED": 4005, "ERR_ITEM_UNEQUIP_FAILED": 4006, "ERR_ITEM_CRAFT_FAILED": 4007, "ERR_ITEM_ENHANCE_FAILED": 4008, "ERR_ITEM_TRADE_FAILED": 4009, "ERR_ITEM_DROP_FAILED": 4010, "ERR_ITEM_PICKUP_FAILED": 4011, "ERR_ITEM_STACK_FULL": 4012, "ERR_ITEM_NOT_TRADEABLE": 4013, "ERR_ITEM_BOUND": 4014, "ERR_ITEM_LEVEL_TOO_LOW": 4015, } ) func (x ItemErrorCode) Enum() *ItemErrorCode { p := new(ItemErrorCode) *p = x return p } func (x ItemErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ItemErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[3].Descriptor() } func (ItemErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[3] } func (x ItemErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ItemErrorCode.Descriptor instead. func (ItemErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{3} } // 错误码枚举 - 建筑相关错误 (5000-5999) type BuildingErrorCode int32 const ( BuildingErrorCode_BUILDING_ERROR_CODE_UNSPECIFIED BuildingErrorCode = 0 BuildingErrorCode_ERR_BUILDING_NOT_FOUND BuildingErrorCode = 5001 // 建筑未找到 BuildingErrorCode_ERR_BUILDING_ALREADY_EXISTS BuildingErrorCode = 5002 // 建筑已存在 BuildingErrorCode_ERR_BUILDING_INSUFFICIENT_RESOURCES BuildingErrorCode = 5003 // 资源不足 BuildingErrorCode_ERR_BUILDING_UPGRADE_FAILED BuildingErrorCode = 5004 // 升级失败 BuildingErrorCode_ERR_BUILDING_DESTROY_FAILED BuildingErrorCode = 5005 // 摧毁失败 BuildingErrorCode_ERR_BUILDING_PRODUCE_FAILED BuildingErrorCode = 5006 // 生产失败 BuildingErrorCode_ERR_BUILDING_COLLECT_FAILED BuildingErrorCode = 5007 // 收集失败 BuildingErrorCode_ERR_BUILDING_REPAIR_FAILED BuildingErrorCode = 5008 // 修复失败 BuildingErrorCode_ERR_BUILDING_LEVEL_TOO_LOW BuildingErrorCode = 5009 // 建筑等级过低 BuildingErrorCode_ERR_BUILDING_LEVEL_TOO_HIGH BuildingErrorCode = 5010 // 建筑等级过高 BuildingErrorCode_ERR_BUILDING_NOT_READY BuildingErrorCode = 5011 // 建筑未就绪 BuildingErrorCode_ERR_BUILDING_UNDER_CONSTRUCTION BuildingErrorCode = 5012 // 建筑建造中 ) // Enum value maps for BuildingErrorCode. var ( BuildingErrorCode_name = map[int32]string{ 0: "BUILDING_ERROR_CODE_UNSPECIFIED", 5001: "ERR_BUILDING_NOT_FOUND", 5002: "ERR_BUILDING_ALREADY_EXISTS", 5003: "ERR_BUILDING_INSUFFICIENT_RESOURCES", 5004: "ERR_BUILDING_UPGRADE_FAILED", 5005: "ERR_BUILDING_DESTROY_FAILED", 5006: "ERR_BUILDING_PRODUCE_FAILED", 5007: "ERR_BUILDING_COLLECT_FAILED", 5008: "ERR_BUILDING_REPAIR_FAILED", 5009: "ERR_BUILDING_LEVEL_TOO_LOW", 5010: "ERR_BUILDING_LEVEL_TOO_HIGH", 5011: "ERR_BUILDING_NOT_READY", 5012: "ERR_BUILDING_UNDER_CONSTRUCTION", } BuildingErrorCode_value = map[string]int32{ "BUILDING_ERROR_CODE_UNSPECIFIED": 0, "ERR_BUILDING_NOT_FOUND": 5001, "ERR_BUILDING_ALREADY_EXISTS": 5002, "ERR_BUILDING_INSUFFICIENT_RESOURCES": 5003, "ERR_BUILDING_UPGRADE_FAILED": 5004, "ERR_BUILDING_DESTROY_FAILED": 5005, "ERR_BUILDING_PRODUCE_FAILED": 5006, "ERR_BUILDING_COLLECT_FAILED": 5007, "ERR_BUILDING_REPAIR_FAILED": 5008, "ERR_BUILDING_LEVEL_TOO_LOW": 5009, "ERR_BUILDING_LEVEL_TOO_HIGH": 5010, "ERR_BUILDING_NOT_READY": 5011, "ERR_BUILDING_UNDER_CONSTRUCTION": 5012, } ) func (x BuildingErrorCode) Enum() *BuildingErrorCode { p := new(BuildingErrorCode) *p = x return p } func (x BuildingErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BuildingErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[4].Descriptor() } func (BuildingErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[4] } func (x BuildingErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BuildingErrorCode.Descriptor instead. func (BuildingErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{4} } // 错误码枚举 - 社交相关错误 (6000-6999) type SocialErrorCode int32 const ( SocialErrorCode_SOCIAL_ERROR_CODE_UNSPECIFIED SocialErrorCode = 0 SocialErrorCode_ERR_FRIEND_NOT_FOUND SocialErrorCode = 6001 // 好友未找到 SocialErrorCode_ERR_FRIEND_ALREADY_EXISTS SocialErrorCode = 6002 // 好友已存在 SocialErrorCode_ERR_FRIEND_REQUEST_FAILED SocialErrorCode = 6003 // 好友请求失败 SocialErrorCode_ERR_FRIEND_ACCEPT_FAILED SocialErrorCode = 6004 // 接受好友失败 SocialErrorCode_ERR_FRIEND_REJECT_FAILED SocialErrorCode = 6005 // 拒绝好友失败 SocialErrorCode_ERR_FRIEND_REMOVE_FAILED SocialErrorCode = 6006 // 删除好友失败 SocialErrorCode_ERR_GUILD_NOT_FOUND SocialErrorCode = 6007 // 公会未找到 SocialErrorCode_ERR_GUILD_ALREADY_EXISTS SocialErrorCode = 6008 // 公会已存在 SocialErrorCode_ERR_GUILD_CREATE_FAILED SocialErrorCode = 6009 // 创建公会失败 SocialErrorCode_ERR_GUILD_JOIN_FAILED SocialErrorCode = 6010 // 加入公会失败 SocialErrorCode_ERR_GUILD_LEAVE_FAILED SocialErrorCode = 6011 // 离开公会失败 SocialErrorCode_ERR_GUILD_PERMISSION_DENIED SocialErrorCode = 6012 // 公会权限不足 SocialErrorCode_ERR_TEAM_NOT_FOUND SocialErrorCode = 6013 // 队伍未找到 SocialErrorCode_ERR_TEAM_ALREADY_EXISTS SocialErrorCode = 6014 // 队伍已存在 SocialErrorCode_ERR_TEAM_CREATE_FAILED SocialErrorCode = 6015 // 创建队伍失败 SocialErrorCode_ERR_TEAM_JOIN_FAILED SocialErrorCode = 6016 // 加入队伍失败 SocialErrorCode_ERR_TEAM_LEAVE_FAILED SocialErrorCode = 6017 // 离开队伍失败 SocialErrorCode_ERR_CHAT_MESSAGE_FAILED SocialErrorCode = 6018 // 聊天消息失败 SocialErrorCode_ERR_CHAT_CHANNEL_NOT_FOUND SocialErrorCode = 6019 // 聊天频道未找到 SocialErrorCode_ERR_CHAT_PERMISSION_DENIED SocialErrorCode = 6020 // 聊天权限不足 ) // Enum value maps for SocialErrorCode. var ( SocialErrorCode_name = map[int32]string{ 0: "SOCIAL_ERROR_CODE_UNSPECIFIED", 6001: "ERR_FRIEND_NOT_FOUND", 6002: "ERR_FRIEND_ALREADY_EXISTS", 6003: "ERR_FRIEND_REQUEST_FAILED", 6004: "ERR_FRIEND_ACCEPT_FAILED", 6005: "ERR_FRIEND_REJECT_FAILED", 6006: "ERR_FRIEND_REMOVE_FAILED", 6007: "ERR_GUILD_NOT_FOUND", 6008: "ERR_GUILD_ALREADY_EXISTS", 6009: "ERR_GUILD_CREATE_FAILED", 6010: "ERR_GUILD_JOIN_FAILED", 6011: "ERR_GUILD_LEAVE_FAILED", 6012: "ERR_GUILD_PERMISSION_DENIED", 6013: "ERR_TEAM_NOT_FOUND", 6014: "ERR_TEAM_ALREADY_EXISTS", 6015: "ERR_TEAM_CREATE_FAILED", 6016: "ERR_TEAM_JOIN_FAILED", 6017: "ERR_TEAM_LEAVE_FAILED", 6018: "ERR_CHAT_MESSAGE_FAILED", 6019: "ERR_CHAT_CHANNEL_NOT_FOUND", 6020: "ERR_CHAT_PERMISSION_DENIED", } SocialErrorCode_value = map[string]int32{ "SOCIAL_ERROR_CODE_UNSPECIFIED": 0, "ERR_FRIEND_NOT_FOUND": 6001, "ERR_FRIEND_ALREADY_EXISTS": 6002, "ERR_FRIEND_REQUEST_FAILED": 6003, "ERR_FRIEND_ACCEPT_FAILED": 6004, "ERR_FRIEND_REJECT_FAILED": 6005, "ERR_FRIEND_REMOVE_FAILED": 6006, "ERR_GUILD_NOT_FOUND": 6007, "ERR_GUILD_ALREADY_EXISTS": 6008, "ERR_GUILD_CREATE_FAILED": 6009, "ERR_GUILD_JOIN_FAILED": 6010, "ERR_GUILD_LEAVE_FAILED": 6011, "ERR_GUILD_PERMISSION_DENIED": 6012, "ERR_TEAM_NOT_FOUND": 6013, "ERR_TEAM_ALREADY_EXISTS": 6014, "ERR_TEAM_CREATE_FAILED": 6015, "ERR_TEAM_JOIN_FAILED": 6016, "ERR_TEAM_LEAVE_FAILED": 6017, "ERR_CHAT_MESSAGE_FAILED": 6018, "ERR_CHAT_CHANNEL_NOT_FOUND": 6019, "ERR_CHAT_PERMISSION_DENIED": 6020, } ) func (x SocialErrorCode) Enum() *SocialErrorCode { p := new(SocialErrorCode) *p = x return p } func (x SocialErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SocialErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[5].Descriptor() } func (SocialErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[5] } func (x SocialErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SocialErrorCode.Descriptor instead. func (SocialErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{5} } // 错误码枚举 - 任务相关错误 (7000-7999) type QuestErrorCode int32 const ( QuestErrorCode_QUEST_ERROR_CODE_UNSPECIFIED QuestErrorCode = 0 QuestErrorCode_ERR_QUEST_NOT_FOUND QuestErrorCode = 7001 // 任务未找到 QuestErrorCode_ERR_QUEST_ALREADY_ACCEPTED QuestErrorCode = 7002 // 任务已接受 QuestErrorCode_ERR_QUEST_NOT_ACCEPTED QuestErrorCode = 7003 // 任务未接受 QuestErrorCode_ERR_QUEST_ALREADY_COMPLETED QuestErrorCode = 7004 // 任务已完成 QuestErrorCode_ERR_QUEST_NOT_COMPLETED QuestErrorCode = 7005 // 任务未完成 QuestErrorCode_ERR_QUEST_ACCEPT_FAILED QuestErrorCode = 7006 // 接受任务失败 QuestErrorCode_ERR_QUEST_COMPLETE_FAILED QuestErrorCode = 7007 // 完成任务失败 QuestErrorCode_ERR_QUEST_CANCEL_FAILED QuestErrorCode = 7008 // 取消任务失败 QuestErrorCode_ERR_QUEST_REQUIREMENTS_NOT_MET QuestErrorCode = 7009 // 任务要求未满足 QuestErrorCode_ERR_QUEST_REWARD_FAILED QuestErrorCode = 7010 // 任务奖励失败 QuestErrorCode_ERR_QUEST_LEVEL_TOO_LOW QuestErrorCode = 7011 // 任务等级过低 QuestErrorCode_ERR_QUEST_LEVEL_TOO_HIGH QuestErrorCode = 7012 // 任务等级过高 QuestErrorCode_ERR_QUEST_PREREQUISITE_NOT_MET QuestErrorCode = 7013 // 前置任务未完成 ) // Enum value maps for QuestErrorCode. var ( QuestErrorCode_name = map[int32]string{ 0: "QUEST_ERROR_CODE_UNSPECIFIED", 7001: "ERR_QUEST_NOT_FOUND", 7002: "ERR_QUEST_ALREADY_ACCEPTED", 7003: "ERR_QUEST_NOT_ACCEPTED", 7004: "ERR_QUEST_ALREADY_COMPLETED", 7005: "ERR_QUEST_NOT_COMPLETED", 7006: "ERR_QUEST_ACCEPT_FAILED", 7007: "ERR_QUEST_COMPLETE_FAILED", 7008: "ERR_QUEST_CANCEL_FAILED", 7009: "ERR_QUEST_REQUIREMENTS_NOT_MET", 7010: "ERR_QUEST_REWARD_FAILED", 7011: "ERR_QUEST_LEVEL_TOO_LOW", 7012: "ERR_QUEST_LEVEL_TOO_HIGH", 7013: "ERR_QUEST_PREREQUISITE_NOT_MET", } QuestErrorCode_value = map[string]int32{ "QUEST_ERROR_CODE_UNSPECIFIED": 0, "ERR_QUEST_NOT_FOUND": 7001, "ERR_QUEST_ALREADY_ACCEPTED": 7002, "ERR_QUEST_NOT_ACCEPTED": 7003, "ERR_QUEST_ALREADY_COMPLETED": 7004, "ERR_QUEST_NOT_COMPLETED": 7005, "ERR_QUEST_ACCEPT_FAILED": 7006, "ERR_QUEST_COMPLETE_FAILED": 7007, "ERR_QUEST_CANCEL_FAILED": 7008, "ERR_QUEST_REQUIREMENTS_NOT_MET": 7009, "ERR_QUEST_REWARD_FAILED": 7010, "ERR_QUEST_LEVEL_TOO_LOW": 7011, "ERR_QUEST_LEVEL_TOO_HIGH": 7012, "ERR_QUEST_PREREQUISITE_NOT_MET": 7013, } ) func (x QuestErrorCode) Enum() *QuestErrorCode { p := new(QuestErrorCode) *p = x return p } func (x QuestErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QuestErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[6].Descriptor() } func (QuestErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[6] } func (x QuestErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QuestErrorCode.Descriptor instead. func (QuestErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{6} } // 错误码枚举 - 系统相关错误 (8000-8999) type SystemErrorCode int32 const ( SystemErrorCode_SYSTEM_ERROR_CODE_UNSPECIFIED SystemErrorCode = 0 SystemErrorCode_ERR_DATABASE_CONNECTION_FAILED SystemErrorCode = 8001 // 数据库连接失败 SystemErrorCode_ERR_DATABASE_QUERY_FAILED SystemErrorCode = 8002 // 数据库查询失败 SystemErrorCode_ERR_DATABASE_UPDATE_FAILED SystemErrorCode = 8003 // 数据库更新失败 SystemErrorCode_ERR_DATABASE_INSERT_FAILED SystemErrorCode = 8004 // 数据库插入失败 SystemErrorCode_ERR_DATABASE_DELETE_FAILED SystemErrorCode = 8005 // 数据库删除失败 SystemErrorCode_ERR_CACHE_CONNECTION_FAILED SystemErrorCode = 8006 // 缓存连接失败 SystemErrorCode_ERR_CACHE_GET_FAILED SystemErrorCode = 8007 // 缓存获取失败 SystemErrorCode_ERR_CACHE_SET_FAILED SystemErrorCode = 8008 // 缓存设置失败 SystemErrorCode_ERR_CACHE_DELETE_FAILED SystemErrorCode = 8009 // 缓存删除失败 SystemErrorCode_ERR_REDIS_CONNECTION_FAILED SystemErrorCode = 8010 // Redis连接失败 SystemErrorCode_ERR_REDIS_OPERATION_FAILED SystemErrorCode = 8011 // Redis操作失败 SystemErrorCode_ERR_MONGODB_CONNECTION_FAILED SystemErrorCode = 8012 // MongoDB连接失败 SystemErrorCode_ERR_MONGODB_OPERATION_FAILED SystemErrorCode = 8013 // MongoDB操作失败 SystemErrorCode_ERR_CONFIG_LOAD_FAILED SystemErrorCode = 8014 // 配置加载失败 SystemErrorCode_ERR_LOG_WRITE_FAILED SystemErrorCode = 8015 // 日志写入失败 ) // Enum value maps for SystemErrorCode. var ( SystemErrorCode_name = map[int32]string{ 0: "SYSTEM_ERROR_CODE_UNSPECIFIED", 8001: "ERR_DATABASE_CONNECTION_FAILED", 8002: "ERR_DATABASE_QUERY_FAILED", 8003: "ERR_DATABASE_UPDATE_FAILED", 8004: "ERR_DATABASE_INSERT_FAILED", 8005: "ERR_DATABASE_DELETE_FAILED", 8006: "ERR_CACHE_CONNECTION_FAILED", 8007: "ERR_CACHE_GET_FAILED", 8008: "ERR_CACHE_SET_FAILED", 8009: "ERR_CACHE_DELETE_FAILED", 8010: "ERR_REDIS_CONNECTION_FAILED", 8011: "ERR_REDIS_OPERATION_FAILED", 8012: "ERR_MONGODB_CONNECTION_FAILED", 8013: "ERR_MONGODB_OPERATION_FAILED", 8014: "ERR_CONFIG_LOAD_FAILED", 8015: "ERR_LOG_WRITE_FAILED", } SystemErrorCode_value = map[string]int32{ "SYSTEM_ERROR_CODE_UNSPECIFIED": 0, "ERR_DATABASE_CONNECTION_FAILED": 8001, "ERR_DATABASE_QUERY_FAILED": 8002, "ERR_DATABASE_UPDATE_FAILED": 8003, "ERR_DATABASE_INSERT_FAILED": 8004, "ERR_DATABASE_DELETE_FAILED": 8005, "ERR_CACHE_CONNECTION_FAILED": 8006, "ERR_CACHE_GET_FAILED": 8007, "ERR_CACHE_SET_FAILED": 8008, "ERR_CACHE_DELETE_FAILED": 8009, "ERR_REDIS_CONNECTION_FAILED": 8010, "ERR_REDIS_OPERATION_FAILED": 8011, "ERR_MONGODB_CONNECTION_FAILED": 8012, "ERR_MONGODB_OPERATION_FAILED": 8013, "ERR_CONFIG_LOAD_FAILED": 8014, "ERR_LOG_WRITE_FAILED": 8015, } ) func (x SystemErrorCode) Enum() *SystemErrorCode { p := new(SystemErrorCode) *p = x return p } func (x SystemErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SystemErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_errors_proto_enumTypes[7].Descriptor() } func (SystemErrorCode) Type() protoreflect.EnumType { return &file_proto_errors_proto_enumTypes[7] } func (x SystemErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SystemErrorCode.Descriptor instead. func (SystemErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{7} } // 错误信息结构 type ErrorInfo struct { state protoimpl.MessageState `protogen:"open.v1"` ErrorCode int32 `protobuf:"varint,1,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` // 错误码 ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` // 错误消息 ErrorType string `protobuf:"bytes,3,opt,name=error_type,json=errorType,proto3" json:"error_type,omitempty"` // 错误类型 Details string `protobuf:"bytes,4,opt,name=details,proto3" json:"details,omitempty"` // 详细信息 Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // 时间戳 RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // 请求ID Context map[string]string `protobuf:"bytes,7,rep,name=context,proto3" json:"context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 上下文信息 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ErrorInfo) Reset() { *x = ErrorInfo{} mi := &file_proto_errors_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ErrorInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*ErrorInfo) ProtoMessage() {} func (x *ErrorInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_errors_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ErrorInfo.ProtoReflect.Descriptor instead. func (*ErrorInfo) Descriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{0} } func (x *ErrorInfo) GetErrorCode() int32 { if x != nil { return x.ErrorCode } return 0 } func (x *ErrorInfo) GetErrorMessage() string { if x != nil { return x.ErrorMessage } return "" } func (x *ErrorInfo) GetErrorType() string { if x != nil { return x.ErrorType } return "" } func (x *ErrorInfo) GetDetails() string { if x != nil { return x.Details } return "" } func (x *ErrorInfo) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *ErrorInfo) GetRequestId() string { if x != nil { return x.RequestId } return "" } func (x *ErrorInfo) GetContext() map[string]string { if x != nil { return x.Context } return nil } // 错误响应结构 type ErrorResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 是否成功 Error *ErrorInfo `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` // 错误信息 Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` // 响应消息 Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // 时间戳 RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // 请求ID unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ErrorResponse) Reset() { *x = ErrorResponse{} mi := &file_proto_errors_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ErrorResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ErrorResponse) ProtoMessage() {} func (x *ErrorResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_errors_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead. func (*ErrorResponse) Descriptor() ([]byte, []int) { return file_proto_errors_proto_rawDescGZIP(), []int{1} } func (x *ErrorResponse) GetSuccess() bool { if x != nil { return x.Success } return false } func (x *ErrorResponse) GetError() *ErrorInfo { if x != nil { return x.Error } return nil } func (x *ErrorResponse) GetMessage() string { if x != nil { return x.Message } return "" } func (x *ErrorResponse) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *ErrorResponse) GetRequestId() string { if x != nil { return x.RequestId } return "" } var File_proto_errors_proto protoreflect.FileDescriptor const file_proto_errors_proto_rawDesc = "" + "\n" + "\x12proto/errors.proto\x12\x14greatestworks.errors\"\xc9\x02\n" + "\tErrorInfo\x12\x1d\n" + "\n" + "error_code\x18\x01 \x01(\x05R\terrorCode\x12#\n" + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x12\x1d\n" + "\n" + "error_type\x18\x03 \x01(\tR\terrorType\x12\x18\n" + "\adetails\x18\x04 \x01(\tR\adetails\x12\x1c\n" + "\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\x12\x1d\n" + "\n" + "request_id\x18\x06 \x01(\tR\trequestId\x12F\n" + "\acontext\x18\a \x03(\v2,.greatestworks.errors.ErrorInfo.ContextEntryR\acontext\x1a:\n" + "\fContextEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb7\x01\n" + "\rErrorResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x125\n" + "\x05error\x18\x02 \x01(\v2\x1f.greatestworks.errors.ErrorInfoR\x05error\x12\x18\n" + "\amessage\x18\x03 \x01(\tR\amessage\x12\x1c\n" + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x1d\n" + "\n" + "request_id\x18\x05 \x01(\tR\trequestId*\xcb\x03\n" + "\x0fCommonErrorCode\x12!\n" + "\x1dCOMMON_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x0f\n" + "\vERR_SUCCESS\x10\x00\x12\x10\n" + "\vERR_UNKNOWN\x10\xe8\a\x12\x18\n" + "\x13ERR_INVALID_MESSAGE\x10\xe9\a\x12\x14\n" + "\x0fERR_AUTH_FAILED\x10\xea\a\x12\x19\n" + "\x14ERR_PLAYER_NOT_FOUND\x10\xeb\a\x12\x19\n" + "\x14ERR_BATTLE_NOT_FOUND\x10\xec\a\x12\x18\n" + "\x13ERR_UNKNOWN_MESSAGE\x10\xed\a\x12\x14\n" + "\x0fERR_SERVER_BUSY\x10\xee\a\x12\x17\n" + "\x12ERR_INVALID_PLAYER\x10\xef\a\x12\x1a\n" + "\x15ERR_PERMISSION_DENIED\x10\xf0\a\x12\x15\n" + "\x10ERR_RATE_LIMITED\x10\xf1\a\x12\x14\n" + "\x0fERR_MAINTENANCE\x10\xf2\a\x12\x18\n" + "\x13ERR_INVALID_REQUEST\x10\xf3\a\x12\x10\n" + "\vERR_TIMEOUT\x10\xf4\a\x12\x18\n" + "\x13ERR_CONNECTION_LOST\x10\xf5\a\x12\x16\n" + "\x11ERR_INVALID_TOKEN\x10\xf6\a\x12\x18\n" + "\x13ERR_SESSION_EXPIRED\x10\xf7\a\x1a\x02\x10\x01*\xbb\x04\n" + "\x0fBattleErrorCode\x12!\n" + "\x1dBATTLE_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x1b\n" + "\x16ERR_INVALID_CREATOR_ID\x10\xd1\x0f\x12\x1c\n" + "\x17ERR_INVALID_BATTLE_TYPE\x10\xd2\x0f\x12\x1a\n" + "\x15ERR_INVALID_BATTLE_ID\x10\xd3\x0f\x12\x1a\n" + "\x15ERR_INVALID_PLAYER_ID\x10\xd4\x0f\x12\x1a\n" + "\x15ERR_INVALID_TARGET_ID\x10\xd5\x0f\x12\x19\n" + "\x14ERR_INVALID_SKILL_ID\x10\xd6\x0f\x12\x15\n" + "\x10ERR_INVALID_TEAM\x10\xd7\x0f\x12\x1f\n" + "\x1aERR_BATTLE_ALREADY_STARTED\x10\xd8\x0f\x12\x1b\n" + "\x16ERR_BATTLE_NOT_STARTED\x10\xd9\x0f\x12\x1d\n" + "\x18ERR_PLAYER_NOT_IN_BATTLE\x10\xda\x0f\x12\x1a\n" + "\x15ERR_INSUFFICIENT_MANA\x10\xdb\x0f\x12\x1a\n" + "\x15ERR_SKILL_ON_COOLDOWN\x10\xdc\x0f\x12\x17\n" + "\x12ERR_INVALID_ACTION\x10\xdd\x0f\x12\x14\n" + "\x0fERR_BATTLE_FULL\x10\xde\x0f\x12\x15\n" + "\x10ERR_BATTLE_ENDED\x10\xdf\x0f\x12\x16\n" + "\x11ERR_NOT_YOUR_TURN\x10\xe0\x0f\x12\x19\n" + "\x14ERR_BATTLE_CANCELLED\x10\xe1\x0f\x12\x1d\n" + "\x18ERR_INVALID_BATTLE_STATE\x10\xe2\x0f\x12\x17\n" + "\x12ERR_BATTLE_TIMEOUT\x10\xe3\x0f*\xaf\x03\n" + "\fPetErrorCode\x12\x1e\n" + "\x1aPET_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x11ERR_PET_NOT_FOUND\x10\xb9\x17\x12\x1b\n" + "\x16ERR_PET_ALREADY_ACTIVE\x10\xba\x17\x12\x17\n" + "\x12ERR_PET_NOT_ACTIVE\x10\xbb\x17\x12\x1a\n" + "\x15ERR_PET_LEVEL_TOO_LOW\x10\xbc\x17\x12\x1b\n" + "\x16ERR_PET_EVOLUTION_FAIL\x10\xbd\x17\x12\x1d\n" + "\x18ERR_PET_INSUFFICIENT_EXP\x10\xbe\x17\x12\x1c\n" + "\x17ERR_PET_ALREADY_EVOLVED\x10\xbf\x17\x12\x1c\n" + "\x17ERR_PET_TRAINING_FAILED\x10\xc0\x17\x12\x1b\n" + "\x16ERR_PET_FEEDING_FAILED\x10\xc1\x17\x12\x1e\n" + "\x19ERR_PET_SKILL_NOT_LEARNED\x10\xc2\x17\x12 \n" + "\x1bERR_PET_INSUFFICIENT_ENERGY\x10\xc3\x17\x12\x11\n" + "\fERR_PET_SICK\x10\xc4\x17\x12\x11\n" + "\fERR_PET_DEAD\x10\xc5\x17\x12\x18\n" + "\x13ERR_PET_BOND_FAILED\x10\xc6\x17*\xc9\x03\n" + "\rItemErrorCode\x12\x1f\n" + "\x1bITEM_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x12ERR_ITEM_NOT_FOUND\x10\xa1\x1f\x12\x18\n" + "\x13ERR_ITEM_NOT_USABLE\x10\xa2\x1f\x12\x17\n" + "\x12ERR_INVENTORY_FULL\x10\xa3\x1f\x12\x1a\n" + "\x15ERR_INSUFFICIENT_ITEM\x10\xa4\x1f\x12\x1a\n" + "\x15ERR_ITEM_EQUIP_FAILED\x10\xa5\x1f\x12\x1c\n" + "\x17ERR_ITEM_UNEQUIP_FAILED\x10\xa6\x1f\x12\x1a\n" + "\x15ERR_ITEM_CRAFT_FAILED\x10\xa7\x1f\x12\x1c\n" + "\x17ERR_ITEM_ENHANCE_FAILED\x10\xa8\x1f\x12\x1a\n" + "\x15ERR_ITEM_TRADE_FAILED\x10\xa9\x1f\x12\x19\n" + "\x14ERR_ITEM_DROP_FAILED\x10\xaa\x1f\x12\x1b\n" + "\x16ERR_ITEM_PICKUP_FAILED\x10\xab\x1f\x12\x18\n" + "\x13ERR_ITEM_STACK_FULL\x10\xac\x1f\x12\x1b\n" + "\x16ERR_ITEM_NOT_TRADEABLE\x10\xad\x1f\x12\x13\n" + "\x0eERR_ITEM_BOUND\x10\xae\x1f\x12\x1b\n" + "\x16ERR_ITEM_LEVEL_TOO_LOW\x10\xaf\x1f*\xd0\x03\n" + "\x11BuildingErrorCode\x12#\n" + "\x1fBUILDING_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x1b\n" + "\x16ERR_BUILDING_NOT_FOUND\x10\x89'\x12 \n" + "\x1bERR_BUILDING_ALREADY_EXISTS\x10\x8a'\x12(\n" + "#ERR_BUILDING_INSUFFICIENT_RESOURCES\x10\x8b'\x12 \n" + "\x1bERR_BUILDING_UPGRADE_FAILED\x10\x8c'\x12 \n" + "\x1bERR_BUILDING_DESTROY_FAILED\x10\x8d'\x12 \n" + "\x1bERR_BUILDING_PRODUCE_FAILED\x10\x8e'\x12 \n" + "\x1bERR_BUILDING_COLLECT_FAILED\x10\x8f'\x12\x1f\n" + "\x1aERR_BUILDING_REPAIR_FAILED\x10\x90'\x12\x1f\n" + "\x1aERR_BUILDING_LEVEL_TOO_LOW\x10\x91'\x12 \n" + "\x1bERR_BUILDING_LEVEL_TOO_HIGH\x10\x92'\x12\x1b\n" + "\x16ERR_BUILDING_NOT_READY\x10\x93'\x12$\n" + "\x1fERR_BUILDING_UNDER_CONSTRUCTION\x10\x94'*\x89\x05\n" + "\x0fSocialErrorCode\x12!\n" + "\x1dSOCIAL_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x19\n" + "\x14ERR_FRIEND_NOT_FOUND\x10\xf1.\x12\x1e\n" + "\x19ERR_FRIEND_ALREADY_EXISTS\x10\xf2.\x12\x1e\n" + "\x19ERR_FRIEND_REQUEST_FAILED\x10\xf3.\x12\x1d\n" + "\x18ERR_FRIEND_ACCEPT_FAILED\x10\xf4.\x12\x1d\n" + "\x18ERR_FRIEND_REJECT_FAILED\x10\xf5.\x12\x1d\n" + "\x18ERR_FRIEND_REMOVE_FAILED\x10\xf6.\x12\x18\n" + "\x13ERR_GUILD_NOT_FOUND\x10\xf7.\x12\x1d\n" + "\x18ERR_GUILD_ALREADY_EXISTS\x10\xf8.\x12\x1c\n" + "\x17ERR_GUILD_CREATE_FAILED\x10\xf9.\x12\x1a\n" + "\x15ERR_GUILD_JOIN_FAILED\x10\xfa.\x12\x1b\n" + "\x16ERR_GUILD_LEAVE_FAILED\x10\xfb.\x12 \n" + "\x1bERR_GUILD_PERMISSION_DENIED\x10\xfc.\x12\x17\n" + "\x12ERR_TEAM_NOT_FOUND\x10\xfd.\x12\x1c\n" + "\x17ERR_TEAM_ALREADY_EXISTS\x10\xfe.\x12\x1b\n" + "\x16ERR_TEAM_CREATE_FAILED\x10\xff.\x12\x19\n" + "\x14ERR_TEAM_JOIN_FAILED\x10\x80/\x12\x1a\n" + "\x15ERR_TEAM_LEAVE_FAILED\x10\x81/\x12\x1c\n" + "\x17ERR_CHAT_MESSAGE_FAILED\x10\x82/\x12\x1f\n" + "\x1aERR_CHAT_CHANNEL_NOT_FOUND\x10\x83/\x12\x1f\n" + "\x1aERR_CHAT_PERMISSION_DENIED\x10\x84/*\xcb\x03\n" + "\x0eQuestErrorCode\x12 \n" + "\x1cQUEST_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x18\n" + "\x13ERR_QUEST_NOT_FOUND\x10\xd96\x12\x1f\n" + "\x1aERR_QUEST_ALREADY_ACCEPTED\x10\xda6\x12\x1b\n" + "\x16ERR_QUEST_NOT_ACCEPTED\x10\xdb6\x12 \n" + "\x1bERR_QUEST_ALREADY_COMPLETED\x10\xdc6\x12\x1c\n" + "\x17ERR_QUEST_NOT_COMPLETED\x10\xdd6\x12\x1c\n" + "\x17ERR_QUEST_ACCEPT_FAILED\x10\xde6\x12\x1e\n" + "\x19ERR_QUEST_COMPLETE_FAILED\x10\xdf6\x12\x1c\n" + "\x17ERR_QUEST_CANCEL_FAILED\x10\xe06\x12#\n" + "\x1eERR_QUEST_REQUIREMENTS_NOT_MET\x10\xe16\x12\x1c\n" + "\x17ERR_QUEST_REWARD_FAILED\x10\xe26\x12\x1c\n" + "\x17ERR_QUEST_LEVEL_TOO_LOW\x10\xe36\x12\x1d\n" + "\x18ERR_QUEST_LEVEL_TOO_HIGH\x10\xe46\x12#\n" + "\x1eERR_QUEST_PREREQUISITE_NOT_MET\x10\xe56*\x94\x04\n" + "\x0fSystemErrorCode\x12!\n" + "\x1dSYSTEM_ERROR_CODE_UNSPECIFIED\x10\x00\x12#\n" + "\x1eERR_DATABASE_CONNECTION_FAILED\x10\xc1>\x12\x1e\n" + "\x19ERR_DATABASE_QUERY_FAILED\x10\xc2>\x12\x1f\n" + "\x1aERR_DATABASE_UPDATE_FAILED\x10\xc3>\x12\x1f\n" + "\x1aERR_DATABASE_INSERT_FAILED\x10\xc4>\x12\x1f\n" + "\x1aERR_DATABASE_DELETE_FAILED\x10\xc5>\x12 \n" + "\x1bERR_CACHE_CONNECTION_FAILED\x10\xc6>\x12\x19\n" + "\x14ERR_CACHE_GET_FAILED\x10\xc7>\x12\x19\n" + "\x14ERR_CACHE_SET_FAILED\x10\xc8>\x12\x1c\n" + "\x17ERR_CACHE_DELETE_FAILED\x10\xc9>\x12 \n" + "\x1bERR_REDIS_CONNECTION_FAILED\x10\xca>\x12\x1f\n" + "\x1aERR_REDIS_OPERATION_FAILED\x10\xcb>\x12\"\n" + "\x1dERR_MONGODB_CONNECTION_FAILED\x10\xcc>\x12!\n" + "\x1cERR_MONGODB_OPERATION_FAILED\x10\xcd>\x12\x1b\n" + "\x16ERR_CONFIG_LOAD_FAILED\x10\xce>\x12\x19\n" + "\x14ERR_LOG_WRITE_FAILED\x10\xcf>B greatestworks.errors.ErrorInfo.ContextEntry 8, // 1: greatestworks.errors.ErrorResponse.error:type_name -> greatestworks.errors.ErrorInfo 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_proto_errors_proto_init() } func file_proto_errors_proto_init() { if File_proto_errors_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_errors_proto_rawDesc), len(file_proto_errors_proto_rawDesc)), NumEnums: 8, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proto_errors_proto_goTypes, DependencyIndexes: file_proto_errors_proto_depIdxs, EnumInfos: file_proto_errors_proto_enumTypes, MessageInfos: file_proto_errors_proto_msgTypes, }.Build() File_proto_errors_proto = out.File file_proto_errors_proto_goTypes = nil file_proto_errors_proto_depIdxs = nil } ================================================ FILE: internal/proto/gateway/gateway.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/gateway.proto package gateway import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 认证类型枚举 type AuthType int32 const ( AuthType_AUTH_TYPE_UNSPECIFIED AuthType = 0 AuthType_AUTH_TYPE_PASSWORD AuthType = 1 // 密码认证 AuthType_AUTH_TYPE_OAUTH AuthType = 2 // OAuth认证 AuthType_AUTH_TYPE_JWT AuthType = 3 // JWT认证 AuthType_AUTH_TYPE_GUEST AuthType = 4 // 游客认证 AuthType_AUTH_TYPE_FACEBOOK AuthType = 5 // Facebook登录 AuthType_AUTH_TYPE_GOOGLE AuthType = 6 // Google登录 AuthType_AUTH_TYPE_TWITTER AuthType = 7 // Twitter登录 AuthType_AUTH_TYPE_APPLE AuthType = 8 // Apple登录 ) // Enum value maps for AuthType. var ( AuthType_name = map[int32]string{ 0: "AUTH_TYPE_UNSPECIFIED", 1: "AUTH_TYPE_PASSWORD", 2: "AUTH_TYPE_OAUTH", 3: "AUTH_TYPE_JWT", 4: "AUTH_TYPE_GUEST", 5: "AUTH_TYPE_FACEBOOK", 6: "AUTH_TYPE_GOOGLE", 7: "AUTH_TYPE_TWITTER", 8: "AUTH_TYPE_APPLE", } AuthType_value = map[string]int32{ "AUTH_TYPE_UNSPECIFIED": 0, "AUTH_TYPE_PASSWORD": 1, "AUTH_TYPE_OAUTH": 2, "AUTH_TYPE_JWT": 3, "AUTH_TYPE_GUEST": 4, "AUTH_TYPE_FACEBOOK": 5, "AUTH_TYPE_GOOGLE": 6, "AUTH_TYPE_TWITTER": 7, "AUTH_TYPE_APPLE": 8, } ) func (x AuthType) Enum() *AuthType { p := new(AuthType) *p = x return p } func (x AuthType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AuthType) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[0].Descriptor() } func (AuthType) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[0] } func (x AuthType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AuthType.Descriptor instead. func (AuthType) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{0} } // 服务器类型枚举 type ServerType int32 const ( ServerType_SERVER_TYPE_UNSPECIFIED ServerType = 0 ServerType_SERVER_TYPE_GAME ServerType = 1 // 游戏服务器 ServerType_SERVER_TYPE_CHAT ServerType = 2 // 聊天服务器 ServerType_SERVER_TYPE_MATCH ServerType = 3 // 匹配服务器 ServerType_SERVER_TYPE_BATTLE ServerType = 4 // 战斗服务器 ServerType_SERVER_TYPE_SOCIAL ServerType = 5 // 社交服务器 ServerType_SERVER_TYPE_TEST ServerType = 6 // 测试服务器 ) // Enum value maps for ServerType. var ( ServerType_name = map[int32]string{ 0: "SERVER_TYPE_UNSPECIFIED", 1: "SERVER_TYPE_GAME", 2: "SERVER_TYPE_CHAT", 3: "SERVER_TYPE_MATCH", 4: "SERVER_TYPE_BATTLE", 5: "SERVER_TYPE_SOCIAL", 6: "SERVER_TYPE_TEST", } ServerType_value = map[string]int32{ "SERVER_TYPE_UNSPECIFIED": 0, "SERVER_TYPE_GAME": 1, "SERVER_TYPE_CHAT": 2, "SERVER_TYPE_MATCH": 3, "SERVER_TYPE_BATTLE": 4, "SERVER_TYPE_SOCIAL": 5, "SERVER_TYPE_TEST": 6, } ) func (x ServerType) Enum() *ServerType { p := new(ServerType) *p = x return p } func (x ServerType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ServerType) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[1].Descriptor() } func (ServerType) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[1] } func (x ServerType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ServerType.Descriptor instead. func (ServerType) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{1} } // 服务器状态枚举 type ServerStatus int32 const ( ServerStatus_SERVER_STATUS_UNSPECIFIED ServerStatus = 0 ServerStatus_SERVER_STATUS_ONLINE ServerStatus = 1 // 在线 ServerStatus_SERVER_STATUS_OFFLINE ServerStatus = 2 // 离线 ServerStatus_SERVER_STATUS_MAINTENANCE ServerStatus = 3 // 维护中 ServerStatus_SERVER_STATUS_FULL ServerStatus = 4 // 已满 ServerStatus_SERVER_STATUS_RESTRICTED ServerStatus = 5 // 受限制 ) // Enum value maps for ServerStatus. var ( ServerStatus_name = map[int32]string{ 0: "SERVER_STATUS_UNSPECIFIED", 1: "SERVER_STATUS_ONLINE", 2: "SERVER_STATUS_OFFLINE", 3: "SERVER_STATUS_MAINTENANCE", 4: "SERVER_STATUS_FULL", 5: "SERVER_STATUS_RESTRICTED", } ServerStatus_value = map[string]int32{ "SERVER_STATUS_UNSPECIFIED": 0, "SERVER_STATUS_ONLINE": 1, "SERVER_STATUS_OFFLINE": 2, "SERVER_STATUS_MAINTENANCE": 3, "SERVER_STATUS_FULL": 4, "SERVER_STATUS_RESTRICTED": 5, } ) func (x ServerStatus) Enum() *ServerStatus { p := new(ServerStatus) *p = x return p } func (x ServerStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ServerStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[2].Descriptor() } func (ServerStatus) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[2] } func (x ServerStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ServerStatus.Descriptor instead. func (ServerStatus) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{2} } // 连接类型枚举 type ConnectionType int32 const ( ConnectionType_CONNECTION_TYPE_UNSPECIFIED ConnectionType = 0 ConnectionType_CONNECTION_TYPE_WEBSOCKET ConnectionType = 1 // WebSocket连接 ConnectionType_CONNECTION_TYPE_TCP ConnectionType = 2 // TCP连接 ConnectionType_CONNECTION_TYPE_HTTP ConnectionType = 3 // HTTP连接 ConnectionType_CONNECTION_TYPE_GRPC ConnectionType = 4 // gRPC连接 ) // Enum value maps for ConnectionType. var ( ConnectionType_name = map[int32]string{ 0: "CONNECTION_TYPE_UNSPECIFIED", 1: "CONNECTION_TYPE_WEBSOCKET", 2: "CONNECTION_TYPE_TCP", 3: "CONNECTION_TYPE_HTTP", 4: "CONNECTION_TYPE_GRPC", } ConnectionType_value = map[string]int32{ "CONNECTION_TYPE_UNSPECIFIED": 0, "CONNECTION_TYPE_WEBSOCKET": 1, "CONNECTION_TYPE_TCP": 2, "CONNECTION_TYPE_HTTP": 3, "CONNECTION_TYPE_GRPC": 4, } ) func (x ConnectionType) Enum() *ConnectionType { p := new(ConnectionType) *p = x return p } func (x ConnectionType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ConnectionType) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[3].Descriptor() } func (ConnectionType) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[3] } func (x ConnectionType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ConnectionType.Descriptor instead. func (ConnectionType) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{3} } // 用户等级枚举 type UserLevel int32 const ( UserLevel_USER_LEVEL_UNSPECIFIED UserLevel = 0 UserLevel_USER_LEVEL_GUEST UserLevel = 1 // 游客 UserLevel_USER_LEVEL_REGISTERED UserLevel = 2 // 注册用户 UserLevel_USER_LEVEL_VERIFIED UserLevel = 3 // 认证用户 UserLevel_USER_LEVEL_PREMIUM UserLevel = 4 // 高级用户 UserLevel_USER_LEVEL_VIP UserLevel = 5 // VIP用户 UserLevel_USER_LEVEL_ADMIN UserLevel = 6 // 管理员 ) // Enum value maps for UserLevel. var ( UserLevel_name = map[int32]string{ 0: "USER_LEVEL_UNSPECIFIED", 1: "USER_LEVEL_GUEST", 2: "USER_LEVEL_REGISTERED", 3: "USER_LEVEL_VERIFIED", 4: "USER_LEVEL_PREMIUM", 5: "USER_LEVEL_VIP", 6: "USER_LEVEL_ADMIN", } UserLevel_value = map[string]int32{ "USER_LEVEL_UNSPECIFIED": 0, "USER_LEVEL_GUEST": 1, "USER_LEVEL_REGISTERED": 2, "USER_LEVEL_VERIFIED": 3, "USER_LEVEL_PREMIUM": 4, "USER_LEVEL_VIP": 5, "USER_LEVEL_ADMIN": 6, } ) func (x UserLevel) Enum() *UserLevel { p := new(UserLevel) *p = x return p } func (x UserLevel) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (UserLevel) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[4].Descriptor() } func (UserLevel) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[4] } func (x UserLevel) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use UserLevel.Descriptor instead. func (UserLevel) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{4} } // 服务健康状态枚举 type ServiceHealth int32 const ( ServiceHealth_SERVICE_HEALTH_UNSPECIFIED ServiceHealth = 0 ServiceHealth_SERVICE_HEALTH_HEALTHY ServiceHealth = 1 // 健康 ServiceHealth_SERVICE_HEALTH_DEGRADED ServiceHealth = 2 // 降级 ServiceHealth_SERVICE_HEALTH_UNHEALTHY ServiceHealth = 3 // 不健康 ServiceHealth_SERVICE_HEALTH_CRITICAL ServiceHealth = 4 // 严重 ) // Enum value maps for ServiceHealth. var ( ServiceHealth_name = map[int32]string{ 0: "SERVICE_HEALTH_UNSPECIFIED", 1: "SERVICE_HEALTH_HEALTHY", 2: "SERVICE_HEALTH_DEGRADED", 3: "SERVICE_HEALTH_UNHEALTHY", 4: "SERVICE_HEALTH_CRITICAL", } ServiceHealth_value = map[string]int32{ "SERVICE_HEALTH_UNSPECIFIED": 0, "SERVICE_HEALTH_HEALTHY": 1, "SERVICE_HEALTH_DEGRADED": 2, "SERVICE_HEALTH_UNHEALTHY": 3, "SERVICE_HEALTH_CRITICAL": 4, } ) func (x ServiceHealth) Enum() *ServiceHealth { p := new(ServiceHealth) *p = x return p } func (x ServiceHealth) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ServiceHealth) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[5].Descriptor() } func (ServiceHealth) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[5] } func (x ServiceHealth) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ServiceHealth.Descriptor instead. func (ServiceHealth) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{5} } // 会话状态枚举 type SessionStatus int32 const ( SessionStatus_SESSION_STATUS_UNSPECIFIED SessionStatus = 0 SessionStatus_SESSION_STATUS_ACTIVE SessionStatus = 1 // 活跃 SessionStatus_SESSION_STATUS_IDLE SessionStatus = 2 // 空闲 SessionStatus_SESSION_STATUS_EXPIRED SessionStatus = 3 // 已过期 SessionStatus_SESSION_STATUS_TERMINATED SessionStatus = 4 // 已终止 SessionStatus_SESSION_STATUS_SUSPENDED SessionStatus = 5 // 已暂停 ) // Enum value maps for SessionStatus. var ( SessionStatus_name = map[int32]string{ 0: "SESSION_STATUS_UNSPECIFIED", 1: "SESSION_STATUS_ACTIVE", 2: "SESSION_STATUS_IDLE", 3: "SESSION_STATUS_EXPIRED", 4: "SESSION_STATUS_TERMINATED", 5: "SESSION_STATUS_SUSPENDED", } SessionStatus_value = map[string]int32{ "SESSION_STATUS_UNSPECIFIED": 0, "SESSION_STATUS_ACTIVE": 1, "SESSION_STATUS_IDLE": 2, "SESSION_STATUS_EXPIRED": 3, "SESSION_STATUS_TERMINATED": 4, "SESSION_STATUS_SUSPENDED": 5, } ) func (x SessionStatus) Enum() *SessionStatus { p := new(SessionStatus) *p = x return p } func (x SessionStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SessionStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_gateway_proto_enumTypes[6].Descriptor() } func (SessionStatus) Type() protoreflect.EnumType { return &file_proto_gateway_proto_enumTypes[6] } func (x SessionStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SessionStatus.Descriptor instead. func (SessionStatus) EnumDescriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{6} } // 认证请求 type AuthenticateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` ClientVersion string `protobuf:"bytes,4,opt,name=client_version,json=clientVersion,proto3" json:"client_version,omitempty"` DeviceInfo string `protobuf:"bytes,5,opt,name=device_info,json=deviceInfo,proto3" json:"device_info,omitempty"` AuthType AuthType `protobuf:"varint,6,opt,name=auth_type,json=authType,proto3,enum=greatestworks.gateway.AuthType" json:"auth_type,omitempty"` ThirdPartyToken string `protobuf:"bytes,7,opt,name=third_party_token,json=thirdPartyToken,proto3" json:"third_party_token,omitempty"` // 第三方登录token Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *AuthenticateRequest) Reset() { *x = AuthenticateRequest{} mi := &file_proto_gateway_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *AuthenticateRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AuthenticateRequest) ProtoMessage() {} func (x *AuthenticateRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AuthenticateRequest.ProtoReflect.Descriptor instead. func (*AuthenticateRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{0} } func (x *AuthenticateRequest) GetUsername() string { if x != nil { return x.Username } return "" } func (x *AuthenticateRequest) GetPassword() string { if x != nil { return x.Password } return "" } func (x *AuthenticateRequest) GetClientId() string { if x != nil { return x.ClientId } return "" } func (x *AuthenticateRequest) GetClientVersion() string { if x != nil { return x.ClientVersion } return "" } func (x *AuthenticateRequest) GetDeviceInfo() string { if x != nil { return x.DeviceInfo } return "" } func (x *AuthenticateRequest) GetAuthType() AuthType { if x != nil { return x.AuthType } return AuthType_AUTH_TYPE_UNSPECIFIED } func (x *AuthenticateRequest) GetThirdPartyToken() string { if x != nil { return x.ThirdPartyToken } return "" } func (x *AuthenticateRequest) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 认证响应 type AuthenticateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` AccessToken string `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` ExpiresIn int64 `protobuf:"varint,4,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` UserId string `protobuf:"bytes,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` SessionId string `protobuf:"bytes,6,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` UserProfile *UserProfile `protobuf:"bytes,7,opt,name=user_profile,json=userProfile,proto3" json:"user_profile,omitempty"` Permissions []string `protobuf:"bytes,8,rep,name=permissions,proto3" json:"permissions,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *AuthenticateResponse) Reset() { *x = AuthenticateResponse{} mi := &file_proto_gateway_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *AuthenticateResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AuthenticateResponse) ProtoMessage() {} func (x *AuthenticateResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AuthenticateResponse.ProtoReflect.Descriptor instead. func (*AuthenticateResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{1} } func (x *AuthenticateResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *AuthenticateResponse) GetAccessToken() string { if x != nil { return x.AccessToken } return "" } func (x *AuthenticateResponse) GetRefreshToken() string { if x != nil { return x.RefreshToken } return "" } func (x *AuthenticateResponse) GetExpiresIn() int64 { if x != nil { return x.ExpiresIn } return 0 } func (x *AuthenticateResponse) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *AuthenticateResponse) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *AuthenticateResponse) GetUserProfile() *UserProfile { if x != nil { return x.UserProfile } return nil } func (x *AuthenticateResponse) GetPermissions() []string { if x != nil { return x.Permissions } return nil } // 刷新Token请求 type RefreshTokenRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RefreshTokenRequest) Reset() { *x = RefreshTokenRequest{} mi := &file_proto_gateway_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RefreshTokenRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*RefreshTokenRequest) ProtoMessage() {} func (x *RefreshTokenRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RefreshTokenRequest.ProtoReflect.Descriptor instead. func (*RefreshTokenRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{2} } func (x *RefreshTokenRequest) GetRefreshToken() string { if x != nil { return x.RefreshToken } return "" } func (x *RefreshTokenRequest) GetUserId() string { if x != nil { return x.UserId } return "" } // 刷新Token响应 type RefreshTokenResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` AccessToken string `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` ExpiresIn int64 `protobuf:"varint,4,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RefreshTokenResponse) Reset() { *x = RefreshTokenResponse{} mi := &file_proto_gateway_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RefreshTokenResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*RefreshTokenResponse) ProtoMessage() {} func (x *RefreshTokenResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RefreshTokenResponse.ProtoReflect.Descriptor instead. func (*RefreshTokenResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{3} } func (x *RefreshTokenResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *RefreshTokenResponse) GetAccessToken() string { if x != nil { return x.AccessToken } return "" } func (x *RefreshTokenResponse) GetRefreshToken() string { if x != nil { return x.RefreshToken } return "" } func (x *RefreshTokenResponse) GetExpiresIn() int64 { if x != nil { return x.ExpiresIn } return 0 } // 登出请求 type LogoutRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LogoutRequest) Reset() { *x = LogoutRequest{} mi := &file_proto_gateway_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LogoutRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogoutRequest) ProtoMessage() {} func (x *LogoutRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. func (*LogoutRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{4} } func (x *LogoutRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *LogoutRequest) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *LogoutRequest) GetAccessToken() string { if x != nil { return x.AccessToken } return "" } // 登出响应 type LogoutResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LogoutResponse) Reset() { *x = LogoutResponse{} mi := &file_proto_gateway_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LogoutResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogoutResponse) ProtoMessage() {} func (x *LogoutResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead. func (*LogoutResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{5} } func (x *LogoutResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 获取服务器列表请求 type GetServerListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Region string `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` ServerType ServerType `protobuf:"varint,2,opt,name=server_type,json=serverType,proto3,enum=greatestworks.gateway.ServerType" json:"server_type,omitempty"` OnlyAvailable bool `protobuf:"varint,3,opt,name=only_available,json=onlyAvailable,proto3" json:"only_available,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetServerListRequest) Reset() { *x = GetServerListRequest{} mi := &file_proto_gateway_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetServerListRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetServerListRequest) ProtoMessage() {} func (x *GetServerListRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetServerListRequest.ProtoReflect.Descriptor instead. func (*GetServerListRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{6} } func (x *GetServerListRequest) GetRegion() string { if x != nil { return x.Region } return "" } func (x *GetServerListRequest) GetServerType() ServerType { if x != nil { return x.ServerType } return ServerType_SERVER_TYPE_UNSPECIFIED } func (x *GetServerListRequest) GetOnlyAvailable() bool { if x != nil { return x.OnlyAvailable } return false } // 获取服务器列表响应 type GetServerListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Servers []*ServerInfo `protobuf:"bytes,2,rep,name=servers,proto3" json:"servers,omitempty"` RecommendedServerId string `protobuf:"bytes,3,opt,name=recommended_server_id,json=recommendedServerId,proto3" json:"recommended_server_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetServerListResponse) Reset() { *x = GetServerListResponse{} mi := &file_proto_gateway_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetServerListResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetServerListResponse) ProtoMessage() {} func (x *GetServerListResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetServerListResponse.ProtoReflect.Descriptor instead. func (*GetServerListResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{7} } func (x *GetServerListResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetServerListResponse) GetServers() []*ServerInfo { if x != nil { return x.Servers } return nil } func (x *GetServerListResponse) GetRecommendedServerId() string { if x != nil { return x.RecommendedServerId } return "" } // 选择服务器请求 type SelectServerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ServerId string `protobuf:"bytes,2,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"` CharacterId string `protobuf:"bytes,3,opt,name=character_id,json=characterId,proto3" json:"character_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SelectServerRequest) Reset() { *x = SelectServerRequest{} mi := &file_proto_gateway_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SelectServerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SelectServerRequest) ProtoMessage() {} func (x *SelectServerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SelectServerRequest.ProtoReflect.Descriptor instead. func (*SelectServerRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{8} } func (x *SelectServerRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *SelectServerRequest) GetServerId() string { if x != nil { return x.ServerId } return "" } func (x *SelectServerRequest) GetCharacterId() string { if x != nil { return x.CharacterId } return "" } // 选择服务器响应 type SelectServerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ServerInfo *ServerInfo `protobuf:"bytes,2,opt,name=server_info,json=serverInfo,proto3" json:"server_info,omitempty"` ConnectionToken string `protobuf:"bytes,3,opt,name=connection_token,json=connectionToken,proto3" json:"connection_token,omitempty"` ServiceEndpoints []string `protobuf:"bytes,4,rep,name=service_endpoints,json=serviceEndpoints,proto3" json:"service_endpoints,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SelectServerResponse) Reset() { *x = SelectServerResponse{} mi := &file_proto_gateway_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SelectServerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SelectServerResponse) ProtoMessage() {} func (x *SelectServerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SelectServerResponse.ProtoReflect.Descriptor instead. func (*SelectServerResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{9} } func (x *SelectServerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SelectServerResponse) GetServerInfo() *ServerInfo { if x != nil { return x.ServerInfo } return nil } func (x *SelectServerResponse) GetConnectionToken() string { if x != nil { return x.ConnectionToken } return "" } func (x *SelectServerResponse) GetServiceEndpoints() []string { if x != nil { return x.ServiceEndpoints } return nil } // 路由请求消息 type RouteRequestMessage struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` MethodName string `protobuf:"bytes,3,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` Payload []byte `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"` UserId string `protobuf:"bytes,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` SessionId string `protobuf:"bytes,6,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` Headers map[string]string `protobuf:"bytes,7,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Timeout int32 `protobuf:"varint,8,opt,name=timeout,proto3" json:"timeout,omitempty"` // 超时时间(秒) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RouteRequestMessage) Reset() { *x = RouteRequestMessage{} mi := &file_proto_gateway_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RouteRequestMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*RouteRequestMessage) ProtoMessage() {} func (x *RouteRequestMessage) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RouteRequestMessage.ProtoReflect.Descriptor instead. func (*RouteRequestMessage) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{10} } func (x *RouteRequestMessage) GetRequestId() string { if x != nil { return x.RequestId } return "" } func (x *RouteRequestMessage) GetServiceName() string { if x != nil { return x.ServiceName } return "" } func (x *RouteRequestMessage) GetMethodName() string { if x != nil { return x.MethodName } return "" } func (x *RouteRequestMessage) GetPayload() []byte { if x != nil { return x.Payload } return nil } func (x *RouteRequestMessage) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *RouteRequestMessage) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *RouteRequestMessage) GetHeaders() map[string]string { if x != nil { return x.Headers } return nil } func (x *RouteRequestMessage) GetTimeout() int32 { if x != nil { return x.Timeout } return 0 } // 路由响应消息 type RouteResponseMessage struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` ErrorMessage string `protobuf:"bytes,4,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` StatusCode int32 `protobuf:"varint,5,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` Headers map[string]string `protobuf:"bytes,6,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` ProcessingTime int64 `protobuf:"varint,7,opt,name=processing_time,json=processingTime,proto3" json:"processing_time,omitempty"` // 处理时间(毫秒) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RouteResponseMessage) Reset() { *x = RouteResponseMessage{} mi := &file_proto_gateway_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RouteResponseMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*RouteResponseMessage) ProtoMessage() {} func (x *RouteResponseMessage) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RouteResponseMessage.ProtoReflect.Descriptor instead. func (*RouteResponseMessage) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{11} } func (x *RouteResponseMessage) GetRequestId() string { if x != nil { return x.RequestId } return "" } func (x *RouteResponseMessage) GetSuccess() bool { if x != nil { return x.Success } return false } func (x *RouteResponseMessage) GetPayload() []byte { if x != nil { return x.Payload } return nil } func (x *RouteResponseMessage) GetErrorMessage() string { if x != nil { return x.ErrorMessage } return "" } func (x *RouteResponseMessage) GetStatusCode() int32 { if x != nil { return x.StatusCode } return 0 } func (x *RouteResponseMessage) GetHeaders() map[string]string { if x != nil { return x.Headers } return nil } func (x *RouteResponseMessage) GetProcessingTime() int64 { if x != nil { return x.ProcessingTime } return 0 } // 连接请求 type ConnectionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` ConnectionType ConnectionType `protobuf:"varint,4,opt,name=connection_type,json=connectionType,proto3,enum=greatestworks.gateway.ConnectionType" json:"connection_type,omitempty"` ClientVersion string `protobuf:"bytes,5,opt,name=client_version,json=clientVersion,proto3" json:"client_version,omitempty"` ConnectionParams map[string]string `protobuf:"bytes,6,rep,name=connection_params,json=connectionParams,proto3" json:"connection_params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ConnectionRequest) Reset() { *x = ConnectionRequest{} mi := &file_proto_gateway_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ConnectionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ConnectionRequest) ProtoMessage() {} func (x *ConnectionRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ConnectionRequest.ProtoReflect.Descriptor instead. func (*ConnectionRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{12} } func (x *ConnectionRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *ConnectionRequest) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *ConnectionRequest) GetAccessToken() string { if x != nil { return x.AccessToken } return "" } func (x *ConnectionRequest) GetConnectionType() ConnectionType { if x != nil { return x.ConnectionType } return ConnectionType_CONNECTION_TYPE_UNSPECIFIED } func (x *ConnectionRequest) GetClientVersion() string { if x != nil { return x.ClientVersion } return "" } func (x *ConnectionRequest) GetConnectionParams() map[string]string { if x != nil { return x.ConnectionParams } return nil } // 连接响应 type ConnectionResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` WebsocketUrl string `protobuf:"bytes,3,opt,name=websocket_url,json=websocketUrl,proto3" json:"websocket_url,omitempty"` SupportedProtocols []string `protobuf:"bytes,4,rep,name=supported_protocols,json=supportedProtocols,proto3" json:"supported_protocols,omitempty"` HeartbeatInterval int32 `protobuf:"varint,5,opt,name=heartbeat_interval,json=heartbeatInterval,proto3" json:"heartbeat_interval,omitempty"` // 心跳间隔(秒) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ConnectionResponse) Reset() { *x = ConnectionResponse{} mi := &file_proto_gateway_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ConnectionResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ConnectionResponse) ProtoMessage() {} func (x *ConnectionResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ConnectionResponse.ProtoReflect.Descriptor instead. func (*ConnectionResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{13} } func (x *ConnectionResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *ConnectionResponse) GetConnectionId() string { if x != nil { return x.ConnectionId } return "" } func (x *ConnectionResponse) GetWebsocketUrl() string { if x != nil { return x.WebsocketUrl } return "" } func (x *ConnectionResponse) GetSupportedProtocols() []string { if x != nil { return x.SupportedProtocols } return nil } func (x *ConnectionResponse) GetHeartbeatInterval() int32 { if x != nil { return x.HeartbeatInterval } return 0 } // 心跳请求 type HeartbeatRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` StatusInfo map[string]string `protobuf:"bytes,4,rep,name=status_info,json=statusInfo,proto3" json:"status_info,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HeartbeatRequest) Reset() { *x = HeartbeatRequest{} mi := &file_proto_gateway_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HeartbeatRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HeartbeatRequest) ProtoMessage() {} func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. func (*HeartbeatRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{14} } func (x *HeartbeatRequest) GetConnectionId() string { if x != nil { return x.ConnectionId } return "" } func (x *HeartbeatRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *HeartbeatRequest) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *HeartbeatRequest) GetStatusInfo() map[string]string { if x != nil { return x.StatusInfo } return nil } // 心跳响应 type HeartbeatResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ServerTimestamp int64 `protobuf:"varint,2,opt,name=server_timestamp,json=serverTimestamp,proto3" json:"server_timestamp,omitempty"` NextHeartbeatInterval int32 `protobuf:"varint,3,opt,name=next_heartbeat_interval,json=nextHeartbeatInterval,proto3" json:"next_heartbeat_interval,omitempty"` GatewayStatus *GatewayStatus `protobuf:"bytes,4,opt,name=gateway_status,json=gatewayStatus,proto3" json:"gateway_status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HeartbeatResponse) Reset() { *x = HeartbeatResponse{} mi := &file_proto_gateway_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HeartbeatResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*HeartbeatResponse) ProtoMessage() {} func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. func (*HeartbeatResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{15} } func (x *HeartbeatResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *HeartbeatResponse) GetServerTimestamp() int64 { if x != nil { return x.ServerTimestamp } return 0 } func (x *HeartbeatResponse) GetNextHeartbeatInterval() int32 { if x != nil { return x.NextHeartbeatInterval } return 0 } func (x *HeartbeatResponse) GetGatewayStatus() *GatewayStatus { if x != nil { return x.GatewayStatus } return nil } // 获取网关状态请求 type GetGatewayStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` AdminToken string `protobuf:"bytes,1,opt,name=admin_token,json=adminToken,proto3" json:"admin_token,omitempty"` IncludeMetrics bool `protobuf:"varint,2,opt,name=include_metrics,json=includeMetrics,proto3" json:"include_metrics,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetGatewayStatusRequest) Reset() { *x = GetGatewayStatusRequest{} mi := &file_proto_gateway_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetGatewayStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetGatewayStatusRequest) ProtoMessage() {} func (x *GetGatewayStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetGatewayStatusRequest.ProtoReflect.Descriptor instead. func (*GetGatewayStatusRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{16} } func (x *GetGatewayStatusRequest) GetAdminToken() string { if x != nil { return x.AdminToken } return "" } func (x *GetGatewayStatusRequest) GetIncludeMetrics() bool { if x != nil { return x.IncludeMetrics } return false } // 获取网关状态响应 type GetGatewayStatusResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Status *GatewayStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` Metrics *GatewayMetrics `protobuf:"bytes,3,opt,name=metrics,proto3" json:"metrics,omitempty"` BackendServices []*ServiceStatus `protobuf:"bytes,4,rep,name=backend_services,json=backendServices,proto3" json:"backend_services,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetGatewayStatusResponse) Reset() { *x = GetGatewayStatusResponse{} mi := &file_proto_gateway_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetGatewayStatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetGatewayStatusResponse) ProtoMessage() {} func (x *GetGatewayStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetGatewayStatusResponse.ProtoReflect.Descriptor instead. func (*GetGatewayStatusResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{17} } func (x *GetGatewayStatusResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetGatewayStatusResponse) GetStatus() *GatewayStatus { if x != nil { return x.Status } return nil } func (x *GetGatewayStatusResponse) GetMetrics() *GatewayMetrics { if x != nil { return x.Metrics } return nil } func (x *GetGatewayStatusResponse) GetBackendServices() []*ServiceStatus { if x != nil { return x.BackendServices } return nil } // 限流检查请求 type RateLimitRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` IpAddress string `protobuf:"bytes,2,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"` Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` // 请求的资源 Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` // 请求的操作 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RateLimitRequest) Reset() { *x = RateLimitRequest{} mi := &file_proto_gateway_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RateLimitRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*RateLimitRequest) ProtoMessage() {} func (x *RateLimitRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RateLimitRequest.ProtoReflect.Descriptor instead. func (*RateLimitRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{18} } func (x *RateLimitRequest) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *RateLimitRequest) GetIpAddress() string { if x != nil { return x.IpAddress } return "" } func (x *RateLimitRequest) GetResource() string { if x != nil { return x.Resource } return "" } func (x *RateLimitRequest) GetAction() string { if x != nil { return x.Action } return "" } // 限流检查响应 type RateLimitResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` RemainingRequests int32 `protobuf:"varint,2,opt,name=remaining_requests,json=remainingRequests,proto3" json:"remaining_requests,omitempty"` ResetTime int64 `protobuf:"varint,3,opt,name=reset_time,json=resetTime,proto3" json:"reset_time,omitempty"` // 限制重置时间 LimitType string `protobuf:"bytes,4,opt,name=limit_type,json=limitType,proto3" json:"limit_type,omitempty"` ErrorMessage string `protobuf:"bytes,5,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RateLimitResponse) Reset() { *x = RateLimitResponse{} mi := &file_proto_gateway_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RateLimitResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*RateLimitResponse) ProtoMessage() {} func (x *RateLimitResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RateLimitResponse.ProtoReflect.Descriptor instead. func (*RateLimitResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{19} } func (x *RateLimitResponse) GetAllowed() bool { if x != nil { return x.Allowed } return false } func (x *RateLimitResponse) GetRemainingRequests() int32 { if x != nil { return x.RemainingRequests } return 0 } func (x *RateLimitResponse) GetResetTime() int64 { if x != nil { return x.ResetTime } return 0 } func (x *RateLimitResponse) GetLimitType() string { if x != nil { return x.LimitType } return "" } func (x *RateLimitResponse) GetErrorMessage() string { if x != nil { return x.ErrorMessage } return "" } // 获取会话信息请求 type GetSessionInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSessionInfoRequest) Reset() { *x = GetSessionInfoRequest{} mi := &file_proto_gateway_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSessionInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSessionInfoRequest) ProtoMessage() {} func (x *GetSessionInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSessionInfoRequest.ProtoReflect.Descriptor instead. func (*GetSessionInfoRequest) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{20} } func (x *GetSessionInfoRequest) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *GetSessionInfoRequest) GetUserId() string { if x != nil { return x.UserId } return "" } // 获取会话信息响应 type GetSessionInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` SessionInfo *SessionInfo `protobuf:"bytes,2,opt,name=session_info,json=sessionInfo,proto3" json:"session_info,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSessionInfoResponse) Reset() { *x = GetSessionInfoResponse{} mi := &file_proto_gateway_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSessionInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSessionInfoResponse) ProtoMessage() {} func (x *GetSessionInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSessionInfoResponse.ProtoReflect.Descriptor instead. func (*GetSessionInfoResponse) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{21} } func (x *GetSessionInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetSessionInfoResponse) GetSessionInfo() *SessionInfo { if x != nil { return x.SessionInfo } return nil } // 服务器信息 type ServerInfo struct { state protoimpl.MessageState `protogen:"open.v1"` ServerId string `protobuf:"bytes,1,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Region string `protobuf:"bytes,3,opt,name=region,proto3" json:"region,omitempty"` ServerType ServerType `protobuf:"varint,4,opt,name=server_type,json=serverType,proto3,enum=greatestworks.gateway.ServerType" json:"server_type,omitempty"` Status ServerStatus `protobuf:"varint,5,opt,name=status,proto3,enum=greatestworks.gateway.ServerStatus" json:"status,omitempty"` CurrentPlayers int32 `protobuf:"varint,6,opt,name=current_players,json=currentPlayers,proto3" json:"current_players,omitempty"` MaxPlayers int32 `protobuf:"varint,7,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` LoadPercentage float32 `protobuf:"fixed32,8,opt,name=load_percentage,json=loadPercentage,proto3" json:"load_percentage,omitempty"` Ping int32 `protobuf:"varint,9,opt,name=ping,proto3" json:"ping,omitempty"` Version string `protobuf:"bytes,10,opt,name=version,proto3" json:"version,omitempty"` IsRecommended bool `protobuf:"varint,11,opt,name=is_recommended,json=isRecommended,proto3" json:"is_recommended,omitempty"` IsNew bool `protobuf:"varint,12,opt,name=is_new,json=isNew,proto3" json:"is_new,omitempty"` LastUpdate int64 `protobuf:"varint,13,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` Features map[string]string `protobuf:"bytes,14,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 服务器特性 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ServerInfo) Reset() { *x = ServerInfo{} mi := &file_proto_gateway_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ServerInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerInfo) ProtoMessage() {} func (x *ServerInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerInfo.ProtoReflect.Descriptor instead. func (*ServerInfo) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{22} } func (x *ServerInfo) GetServerId() string { if x != nil { return x.ServerId } return "" } func (x *ServerInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *ServerInfo) GetRegion() string { if x != nil { return x.Region } return "" } func (x *ServerInfo) GetServerType() ServerType { if x != nil { return x.ServerType } return ServerType_SERVER_TYPE_UNSPECIFIED } func (x *ServerInfo) GetStatus() ServerStatus { if x != nil { return x.Status } return ServerStatus_SERVER_STATUS_UNSPECIFIED } func (x *ServerInfo) GetCurrentPlayers() int32 { if x != nil { return x.CurrentPlayers } return 0 } func (x *ServerInfo) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *ServerInfo) GetLoadPercentage() float32 { if x != nil { return x.LoadPercentage } return 0 } func (x *ServerInfo) GetPing() int32 { if x != nil { return x.Ping } return 0 } func (x *ServerInfo) GetVersion() string { if x != nil { return x.Version } return "" } func (x *ServerInfo) GetIsRecommended() bool { if x != nil { return x.IsRecommended } return false } func (x *ServerInfo) GetIsNew() bool { if x != nil { return x.IsNew } return false } func (x *ServerInfo) GetLastUpdate() int64 { if x != nil { return x.LastUpdate } return 0 } func (x *ServerInfo) GetFeatures() map[string]string { if x != nil { return x.Features } return nil } // 用户档案 type UserProfile struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` DisplayName string `protobuf:"bytes,4,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` AvatarUrl string `protobuf:"bytes,5,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"` UserLevel UserLevel `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3,enum=greatestworks.gateway.UserLevel" json:"user_level,omitempty"` IsPremium bool `protobuf:"varint,7,opt,name=is_premium,json=isPremium,proto3" json:"is_premium,omitempty"` CreatedAt int64 `protobuf:"varint,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastLogin int64 `protobuf:"varint,9,opt,name=last_login,json=lastLogin,proto3" json:"last_login,omitempty"` PreferredLanguage string `protobuf:"bytes,10,opt,name=preferred_language,json=preferredLanguage,proto3" json:"preferred_language,omitempty"` Timezone string `protobuf:"bytes,11,opt,name=timezone,proto3" json:"timezone,omitempty"` Preferences map[string]string `protobuf:"bytes,12,rep,name=preferences,proto3" json:"preferences,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UserProfile) Reset() { *x = UserProfile{} mi := &file_proto_gateway_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UserProfile) String() string { return protoimpl.X.MessageStringOf(x) } func (*UserProfile) ProtoMessage() {} func (x *UserProfile) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UserProfile.ProtoReflect.Descriptor instead. func (*UserProfile) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{23} } func (x *UserProfile) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *UserProfile) GetUsername() string { if x != nil { return x.Username } return "" } func (x *UserProfile) GetEmail() string { if x != nil { return x.Email } return "" } func (x *UserProfile) GetDisplayName() string { if x != nil { return x.DisplayName } return "" } func (x *UserProfile) GetAvatarUrl() string { if x != nil { return x.AvatarUrl } return "" } func (x *UserProfile) GetUserLevel() UserLevel { if x != nil { return x.UserLevel } return UserLevel_USER_LEVEL_UNSPECIFIED } func (x *UserProfile) GetIsPremium() bool { if x != nil { return x.IsPremium } return false } func (x *UserProfile) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *UserProfile) GetLastLogin() int64 { if x != nil { return x.LastLogin } return 0 } func (x *UserProfile) GetPreferredLanguage() string { if x != nil { return x.PreferredLanguage } return "" } func (x *UserProfile) GetTimezone() string { if x != nil { return x.Timezone } return "" } func (x *UserProfile) GetPreferences() map[string]string { if x != nil { return x.Preferences } return nil } // 网关状态 type GatewayStatus struct { state protoimpl.MessageState `protogen:"open.v1"` IsHealthy bool `protobuf:"varint,1,opt,name=is_healthy,json=isHealthy,proto3" json:"is_healthy,omitempty"` Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` Uptime int64 `protobuf:"varint,3,opt,name=uptime,proto3" json:"uptime,omitempty"` // 运行时间(秒) ActiveConnections int32 `protobuf:"varint,4,opt,name=active_connections,json=activeConnections,proto3" json:"active_connections,omitempty"` TotalRequests int32 `protobuf:"varint,5,opt,name=total_requests,json=totalRequests,proto3" json:"total_requests,omitempty"` CpuUsage float32 `protobuf:"fixed32,6,opt,name=cpu_usage,json=cpuUsage,proto3" json:"cpu_usage,omitempty"` MemoryUsage float32 `protobuf:"fixed32,7,opt,name=memory_usage,json=memoryUsage,proto3" json:"memory_usage,omitempty"` ErrorRate int32 `protobuf:"varint,8,opt,name=error_rate,json=errorRate,proto3" json:"error_rate,omitempty"` // 错误率(每万次请求) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GatewayStatus) Reset() { *x = GatewayStatus{} mi := &file_proto_gateway_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GatewayStatus) String() string { return protoimpl.X.MessageStringOf(x) } func (*GatewayStatus) ProtoMessage() {} func (x *GatewayStatus) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GatewayStatus.ProtoReflect.Descriptor instead. func (*GatewayStatus) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{24} } func (x *GatewayStatus) GetIsHealthy() bool { if x != nil { return x.IsHealthy } return false } func (x *GatewayStatus) GetVersion() string { if x != nil { return x.Version } return "" } func (x *GatewayStatus) GetUptime() int64 { if x != nil { return x.Uptime } return 0 } func (x *GatewayStatus) GetActiveConnections() int32 { if x != nil { return x.ActiveConnections } return 0 } func (x *GatewayStatus) GetTotalRequests() int32 { if x != nil { return x.TotalRequests } return 0 } func (x *GatewayStatus) GetCpuUsage() float32 { if x != nil { return x.CpuUsage } return 0 } func (x *GatewayStatus) GetMemoryUsage() float32 { if x != nil { return x.MemoryUsage } return 0 } func (x *GatewayStatus) GetErrorRate() int32 { if x != nil { return x.ErrorRate } return 0 } // 网关指标 type GatewayMetrics struct { state protoimpl.MessageState `protogen:"open.v1"` TotalRequests int64 `protobuf:"varint,1,opt,name=total_requests,json=totalRequests,proto3" json:"total_requests,omitempty"` SuccessfulRequests int64 `protobuf:"varint,2,opt,name=successful_requests,json=successfulRequests,proto3" json:"successful_requests,omitempty"` FailedRequests int64 `protobuf:"varint,3,opt,name=failed_requests,json=failedRequests,proto3" json:"failed_requests,omitempty"` AverageResponseTime float32 `protobuf:"fixed32,4,opt,name=average_response_time,json=averageResponseTime,proto3" json:"average_response_time,omitempty"` // 平均响应时间(毫秒) ActiveUsers int32 `protobuf:"varint,5,opt,name=active_users,json=activeUsers,proto3" json:"active_users,omitempty"` PeakConcurrentUsers int32 `protobuf:"varint,6,opt,name=peak_concurrent_users,json=peakConcurrentUsers,proto3" json:"peak_concurrent_users,omitempty"` RequestsPerService map[string]int64 `protobuf:"bytes,7,rep,name=requests_per_service,json=requestsPerService,proto3" json:"requests_per_service,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // 各服务请求数 ResponseTimesPerService map[string]float32 `protobuf:"bytes,8,rep,name=response_times_per_service,json=responseTimesPerService,proto3" json:"response_times_per_service,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` // 各服务响应时间 BandwidthIn int64 `protobuf:"varint,9,opt,name=bandwidth_in,json=bandwidthIn,proto3" json:"bandwidth_in,omitempty"` // 入站带宽(字节) BandwidthOut int64 `protobuf:"varint,10,opt,name=bandwidth_out,json=bandwidthOut,proto3" json:"bandwidth_out,omitempty"` // 出站带宽(字节) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GatewayMetrics) Reset() { *x = GatewayMetrics{} mi := &file_proto_gateway_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GatewayMetrics) String() string { return protoimpl.X.MessageStringOf(x) } func (*GatewayMetrics) ProtoMessage() {} func (x *GatewayMetrics) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GatewayMetrics.ProtoReflect.Descriptor instead. func (*GatewayMetrics) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{25} } func (x *GatewayMetrics) GetTotalRequests() int64 { if x != nil { return x.TotalRequests } return 0 } func (x *GatewayMetrics) GetSuccessfulRequests() int64 { if x != nil { return x.SuccessfulRequests } return 0 } func (x *GatewayMetrics) GetFailedRequests() int64 { if x != nil { return x.FailedRequests } return 0 } func (x *GatewayMetrics) GetAverageResponseTime() float32 { if x != nil { return x.AverageResponseTime } return 0 } func (x *GatewayMetrics) GetActiveUsers() int32 { if x != nil { return x.ActiveUsers } return 0 } func (x *GatewayMetrics) GetPeakConcurrentUsers() int32 { if x != nil { return x.PeakConcurrentUsers } return 0 } func (x *GatewayMetrics) GetRequestsPerService() map[string]int64 { if x != nil { return x.RequestsPerService } return nil } func (x *GatewayMetrics) GetResponseTimesPerService() map[string]float32 { if x != nil { return x.ResponseTimesPerService } return nil } func (x *GatewayMetrics) GetBandwidthIn() int64 { if x != nil { return x.BandwidthIn } return 0 } func (x *GatewayMetrics) GetBandwidthOut() int64 { if x != nil { return x.BandwidthOut } return 0 } // 服务状态 type ServiceStatus struct { state protoimpl.MessageState `protogen:"open.v1"` ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` ServiceUrl string `protobuf:"bytes,2,opt,name=service_url,json=serviceUrl,proto3" json:"service_url,omitempty"` Health ServiceHealth `protobuf:"varint,3,opt,name=health,proto3,enum=greatestworks.gateway.ServiceHealth" json:"health,omitempty"` ResponseTime float32 `protobuf:"fixed32,4,opt,name=response_time,json=responseTime,proto3" json:"response_time,omitempty"` // 平均响应时间 ActiveConnections int32 `protobuf:"varint,5,opt,name=active_connections,json=activeConnections,proto3" json:"active_connections,omitempty"` LastCheck int64 `protobuf:"varint,6,opt,name=last_check,json=lastCheck,proto3" json:"last_check,omitempty"` Version string `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty"` Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ServiceStatus) Reset() { *x = ServiceStatus{} mi := &file_proto_gateway_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ServiceStatus) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServiceStatus) ProtoMessage() {} func (x *ServiceStatus) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServiceStatus.ProtoReflect.Descriptor instead. func (*ServiceStatus) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{26} } func (x *ServiceStatus) GetServiceName() string { if x != nil { return x.ServiceName } return "" } func (x *ServiceStatus) GetServiceUrl() string { if x != nil { return x.ServiceUrl } return "" } func (x *ServiceStatus) GetHealth() ServiceHealth { if x != nil { return x.Health } return ServiceHealth_SERVICE_HEALTH_UNSPECIFIED } func (x *ServiceStatus) GetResponseTime() float32 { if x != nil { return x.ResponseTime } return 0 } func (x *ServiceStatus) GetActiveConnections() int32 { if x != nil { return x.ActiveConnections } return 0 } func (x *ServiceStatus) GetLastCheck() int64 { if x != nil { return x.LastCheck } return 0 } func (x *ServiceStatus) GetVersion() string { if x != nil { return x.Version } return "" } func (x *ServiceStatus) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 会话信息 type SessionInfo struct { state protoimpl.MessageState `protogen:"open.v1"` SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` CreatedAt int64 `protobuf:"varint,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` ExpiresAt int64 `protobuf:"varint,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` LastActivity int64 `protobuf:"varint,6,opt,name=last_activity,json=lastActivity,proto3" json:"last_activity,omitempty"` ClientIp string `protobuf:"bytes,7,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` UserAgent string `protobuf:"bytes,8,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` CurrentServer string `protobuf:"bytes,9,opt,name=current_server,json=currentServer,proto3" json:"current_server,omitempty"` Status SessionStatus `protobuf:"varint,10,opt,name=status,proto3,enum=greatestworks.gateway.SessionStatus" json:"status,omitempty"` SessionData map[string]string `protobuf:"bytes,11,rep,name=session_data,json=sessionData,proto3" json:"session_data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SessionInfo) Reset() { *x = SessionInfo{} mi := &file_proto_gateway_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SessionInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*SessionInfo) ProtoMessage() {} func (x *SessionInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_gateway_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SessionInfo.ProtoReflect.Descriptor instead. func (*SessionInfo) Descriptor() ([]byte, []int) { return file_proto_gateway_proto_rawDescGZIP(), []int{27} } func (x *SessionInfo) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *SessionInfo) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *SessionInfo) GetAccessToken() string { if x != nil { return x.AccessToken } return "" } func (x *SessionInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *SessionInfo) GetExpiresAt() int64 { if x != nil { return x.ExpiresAt } return 0 } func (x *SessionInfo) GetLastActivity() int64 { if x != nil { return x.LastActivity } return 0 } func (x *SessionInfo) GetClientIp() string { if x != nil { return x.ClientIp } return "" } func (x *SessionInfo) GetUserAgent() string { if x != nil { return x.UserAgent } return "" } func (x *SessionInfo) GetCurrentServer() string { if x != nil { return x.CurrentServer } return "" } func (x *SessionInfo) GetStatus() SessionStatus { if x != nil { return x.Status } return SessionStatus_SESSION_STATUS_UNSPECIFIED } func (x *SessionInfo) GetSessionData() map[string]string { if x != nil { return x.SessionData } return nil } var File_proto_gateway_proto protoreflect.FileDescriptor const file_proto_gateway_proto_rawDesc = "" + "\n" + "\x13proto/gateway.proto\x12\x15greatestworks.gateway\x1a\x12proto/common.proto\"\xaf\x03\n" + "\x13AuthenticateRequest\x12\x1a\n" + "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" + "\bpassword\x18\x02 \x01(\tR\bpassword\x12\x1b\n" + "\tclient_id\x18\x03 \x01(\tR\bclientId\x12%\n" + "\x0eclient_version\x18\x04 \x01(\tR\rclientVersion\x12\x1f\n" + "\vdevice_info\x18\x05 \x01(\tR\n" + "deviceInfo\x12<\n" + "\tauth_type\x18\x06 \x01(\x0e2\x1f.greatestworks.gateway.AuthTypeR\bauthType\x12*\n" + "\x11third_party_token\x18\a \x01(\tR\x0fthirdPartyToken\x12T\n" + "\bmetadata\x18\b \x03(\v28.greatestworks.gateway.AuthenticateRequest.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdc\x02\n" + "\x14AuthenticateResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12!\n" + "\faccess_token\x18\x02 \x01(\tR\vaccessToken\x12#\n" + "\rrefresh_token\x18\x03 \x01(\tR\frefreshToken\x12\x1d\n" + "\n" + "expires_in\x18\x04 \x01(\x03R\texpiresIn\x12\x17\n" + "\auser_id\x18\x05 \x01(\tR\x06userId\x12\x1d\n" + "\n" + "session_id\x18\x06 \x01(\tR\tsessionId\x12E\n" + "\fuser_profile\x18\a \x01(\v2\".greatestworks.gateway.UserProfileR\vuserProfile\x12 \n" + "\vpermissions\x18\b \x03(\tR\vpermissions\"S\n" + "\x13RefreshTokenRequest\x12#\n" + "\rrefresh_token\x18\x01 \x01(\tR\frefreshToken\x12\x17\n" + "\auser_id\x18\x02 \x01(\tR\x06userId\"\xbb\x01\n" + "\x14RefreshTokenResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12!\n" + "\faccess_token\x18\x02 \x01(\tR\vaccessToken\x12#\n" + "\rrefresh_token\x18\x03 \x01(\tR\frefreshToken\x12\x1d\n" + "\n" + "expires_in\x18\x04 \x01(\x03R\texpiresIn\"j\n" + "\rLogoutRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1d\n" + "\n" + "session_id\x18\x02 \x01(\tR\tsessionId\x12!\n" + "\faccess_token\x18\x03 \x01(\tR\vaccessToken\"N\n" + "\x0eLogoutResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x99\x01\n" + "\x14GetServerListRequest\x12\x16\n" + "\x06region\x18\x01 \x01(\tR\x06region\x12B\n" + "\vserver_type\x18\x02 \x01(\x0e2!.greatestworks.gateway.ServerTypeR\n" + "serverType\x12%\n" + "\x0eonly_available\x18\x03 \x01(\bR\ronlyAvailable\"\xc6\x01\n" + "\x15GetServerListResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12;\n" + "\aservers\x18\x02 \x03(\v2!.greatestworks.gateway.ServerInfoR\aservers\x122\n" + "\x15recommended_server_id\x18\x03 \x01(\tR\x13recommendedServerId\"n\n" + "\x13SelectServerRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1b\n" + "\tserver_id\x18\x02 \x01(\tR\bserverId\x12!\n" + "\fcharacter_id\x18\x03 \x01(\tR\vcharacterId\"\xf0\x01\n" + "\x14SelectServerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12B\n" + "\vserver_info\x18\x02 \x01(\v2!.greatestworks.gateway.ServerInfoR\n" + "serverInfo\x12)\n" + "\x10connection_token\x18\x03 \x01(\tR\x0fconnectionToken\x12+\n" + "\x11service_endpoints\x18\x04 \x03(\tR\x10serviceEndpoints\"\xf3\x02\n" + "\x13RouteRequestMessage\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12!\n" + "\fservice_name\x18\x02 \x01(\tR\vserviceName\x12\x1f\n" + "\vmethod_name\x18\x03 \x01(\tR\n" + "methodName\x12\x18\n" + "\apayload\x18\x04 \x01(\fR\apayload\x12\x17\n" + "\auser_id\x18\x05 \x01(\tR\x06userId\x12\x1d\n" + "\n" + "session_id\x18\x06 \x01(\tR\tsessionId\x12Q\n" + "\aheaders\x18\a \x03(\v27.greatestworks.gateway.RouteRequestMessage.HeadersEntryR\aheaders\x12\x18\n" + "\atimeout\x18\b \x01(\x05R\atimeout\x1a:\n" + "\fHeadersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x02\n" + "\x14RouteResponseMessage\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x18\n" + "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x18\n" + "\apayload\x18\x03 \x01(\fR\apayload\x12#\n" + "\rerror_message\x18\x04 \x01(\tR\ferrorMessage\x12\x1f\n" + "\vstatus_code\x18\x05 \x01(\x05R\n" + "statusCode\x12R\n" + "\aheaders\x18\x06 \x03(\v28.greatestworks.gateway.RouteResponseMessage.HeadersEntryR\aheaders\x12'\n" + "\x0fprocessing_time\x18\a \x01(\x03R\x0eprocessingTime\x1a:\n" + "\fHeadersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x97\x03\n" + "\x11ConnectionRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1d\n" + "\n" + "session_id\x18\x02 \x01(\tR\tsessionId\x12!\n" + "\faccess_token\x18\x03 \x01(\tR\vaccessToken\x12N\n" + "\x0fconnection_type\x18\x04 \x01(\x0e2%.greatestworks.gateway.ConnectionTypeR\x0econnectionType\x12%\n" + "\x0eclient_version\x18\x05 \x01(\tR\rclientVersion\x12k\n" + "\x11connection_params\x18\x06 \x03(\v2>.greatestworks.gateway.ConnectionRequest.ConnectionParamsEntryR\x10connectionParams\x1aC\n" + "\x15ConnectionParamsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xfc\x01\n" + "\x12ConnectionResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12#\n" + "\rconnection_id\x18\x02 \x01(\tR\fconnectionId\x12#\n" + "\rwebsocket_url\x18\x03 \x01(\tR\fwebsocketUrl\x12/\n" + "\x13supported_protocols\x18\x04 \x03(\tR\x12supportedProtocols\x12-\n" + "\x12heartbeat_interval\x18\x05 \x01(\x05R\x11heartbeatInterval\"\x87\x02\n" + "\x10HeartbeatRequest\x12#\n" + "\rconnection_id\x18\x01 \x01(\tR\fconnectionId\x12\x17\n" + "\auser_id\x18\x02 \x01(\tR\x06userId\x12\x1c\n" + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12X\n" + "\vstatus_info\x18\x04 \x03(\v27.greatestworks.gateway.HeartbeatRequest.StatusInfoEntryR\n" + "statusInfo\x1a=\n" + "\x0fStatusInfoEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x81\x02\n" + "\x11HeartbeatResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12)\n" + "\x10server_timestamp\x18\x02 \x01(\x03R\x0fserverTimestamp\x126\n" + "\x17next_heartbeat_interval\x18\x03 \x01(\x05R\x15nextHeartbeatInterval\x12K\n" + "\x0egateway_status\x18\x04 \x01(\v2$.greatestworks.gateway.GatewayStatusR\rgatewayStatus\"c\n" + "\x17GetGatewayStatusRequest\x12\x1f\n" + "\vadmin_token\x18\x01 \x01(\tR\n" + "adminToken\x12'\n" + "\x0finclude_metrics\x18\x02 \x01(\bR\x0eincludeMetrics\"\xa8\x02\n" + "\x18GetGatewayStatusResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12<\n" + "\x06status\x18\x02 \x01(\v2$.greatestworks.gateway.GatewayStatusR\x06status\x12?\n" + "\ametrics\x18\x03 \x01(\v2%.greatestworks.gateway.GatewayMetricsR\ametrics\x12O\n" + "\x10backend_services\x18\x04 \x03(\v2$.greatestworks.gateway.ServiceStatusR\x0fbackendServices\"~\n" + "\x10RateLimitRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1d\n" + "\n" + "ip_address\x18\x02 \x01(\tR\tipAddress\x12\x1a\n" + "\bresource\x18\x03 \x01(\tR\bresource\x12\x16\n" + "\x06action\x18\x04 \x01(\tR\x06action\"\xbf\x01\n" + "\x11RateLimitResponse\x12\x18\n" + "\aallowed\x18\x01 \x01(\bR\aallowed\x12-\n" + "\x12remaining_requests\x18\x02 \x01(\x05R\x11remainingRequests\x12\x1d\n" + "\n" + "reset_time\x18\x03 \x01(\x03R\tresetTime\x12\x1d\n" + "\n" + "limit_type\x18\x04 \x01(\tR\tlimitType\x12#\n" + "\rerror_message\x18\x05 \x01(\tR\ferrorMessage\"O\n" + "\x15GetSessionInfoRequest\x12\x1d\n" + "\n" + "session_id\x18\x01 \x01(\tR\tsessionId\x12\x17\n" + "\auser_id\x18\x02 \x01(\tR\x06userId\"\x9d\x01\n" + "\x16GetSessionInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12E\n" + "\fsession_info\x18\x02 \x01(\v2\".greatestworks.gateway.SessionInfoR\vsessionInfo\"\xe0\x04\n" + "\n" + "ServerInfo\x12\x1b\n" + "\tserver_id\x18\x01 \x01(\tR\bserverId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" + "\x06region\x18\x03 \x01(\tR\x06region\x12B\n" + "\vserver_type\x18\x04 \x01(\x0e2!.greatestworks.gateway.ServerTypeR\n" + "serverType\x12;\n" + "\x06status\x18\x05 \x01(\x0e2#.greatestworks.gateway.ServerStatusR\x06status\x12'\n" + "\x0fcurrent_players\x18\x06 \x01(\x05R\x0ecurrentPlayers\x12\x1f\n" + "\vmax_players\x18\a \x01(\x05R\n" + "maxPlayers\x12'\n" + "\x0fload_percentage\x18\b \x01(\x02R\x0eloadPercentage\x12\x12\n" + "\x04ping\x18\t \x01(\x05R\x04ping\x12\x18\n" + "\aversion\x18\n" + " \x01(\tR\aversion\x12%\n" + "\x0eis_recommended\x18\v \x01(\bR\risRecommended\x12\x15\n" + "\x06is_new\x18\f \x01(\bR\x05isNew\x12\x1f\n" + "\vlast_update\x18\r \x01(\x03R\n" + "lastUpdate\x12K\n" + "\bfeatures\x18\x0e \x03(\v2/.greatestworks.gateway.ServerInfo.FeaturesEntryR\bfeatures\x1a;\n" + "\rFeaturesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9a\x04\n" + "\vUserProfile\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x1a\n" + "\busername\x18\x02 \x01(\tR\busername\x12\x14\n" + "\x05email\x18\x03 \x01(\tR\x05email\x12!\n" + "\fdisplay_name\x18\x04 \x01(\tR\vdisplayName\x12\x1d\n" + "\n" + "avatar_url\x18\x05 \x01(\tR\tavatarUrl\x12?\n" + "\n" + "user_level\x18\x06 \x01(\x0e2 .greatestworks.gateway.UserLevelR\tuserLevel\x12\x1d\n" + "\n" + "is_premium\x18\a \x01(\bR\tisPremium\x12\x1d\n" + "\n" + "created_at\x18\b \x01(\x03R\tcreatedAt\x12\x1d\n" + "\n" + "last_login\x18\t \x01(\x03R\tlastLogin\x12-\n" + "\x12preferred_language\x18\n" + " \x01(\tR\x11preferredLanguage\x12\x1a\n" + "\btimezone\x18\v \x01(\tR\btimezone\x12U\n" + "\vpreferences\x18\f \x03(\v23.greatestworks.gateway.UserProfile.PreferencesEntryR\vpreferences\x1a>\n" + "\x10PreferencesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x95\x02\n" + "\rGatewayStatus\x12\x1d\n" + "\n" + "is_healthy\x18\x01 \x01(\bR\tisHealthy\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12\x16\n" + "\x06uptime\x18\x03 \x01(\x03R\x06uptime\x12-\n" + "\x12active_connections\x18\x04 \x01(\x05R\x11activeConnections\x12%\n" + "\x0etotal_requests\x18\x05 \x01(\x05R\rtotalRequests\x12\x1b\n" + "\tcpu_usage\x18\x06 \x01(\x02R\bcpuUsage\x12!\n" + "\fmemory_usage\x18\a \x01(\x02R\vmemoryUsage\x12\x1d\n" + "\n" + "error_rate\x18\b \x01(\x05R\terrorRate\"\xe9\x05\n" + "\x0eGatewayMetrics\x12%\n" + "\x0etotal_requests\x18\x01 \x01(\x03R\rtotalRequests\x12/\n" + "\x13successful_requests\x18\x02 \x01(\x03R\x12successfulRequests\x12'\n" + "\x0ffailed_requests\x18\x03 \x01(\x03R\x0efailedRequests\x122\n" + "\x15average_response_time\x18\x04 \x01(\x02R\x13averageResponseTime\x12!\n" + "\factive_users\x18\x05 \x01(\x05R\vactiveUsers\x122\n" + "\x15peak_concurrent_users\x18\x06 \x01(\x05R\x13peakConcurrentUsers\x12o\n" + "\x14requests_per_service\x18\a \x03(\v2=.greatestworks.gateway.GatewayMetrics.RequestsPerServiceEntryR\x12requestsPerService\x12\x7f\n" + "\x1aresponse_times_per_service\x18\b \x03(\v2B.greatestworks.gateway.GatewayMetrics.ResponseTimesPerServiceEntryR\x17responseTimesPerService\x12!\n" + "\fbandwidth_in\x18\t \x01(\x03R\vbandwidthIn\x12#\n" + "\rbandwidth_out\x18\n" + " \x01(\x03R\fbandwidthOut\x1aE\n" + "\x17RequestsPerServiceEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\x1aJ\n" + "\x1cResponseTimesPerServiceEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\x02R\x05value:\x028\x01\"\xab\x03\n" + "\rServiceStatus\x12!\n" + "\fservice_name\x18\x01 \x01(\tR\vserviceName\x12\x1f\n" + "\vservice_url\x18\x02 \x01(\tR\n" + "serviceUrl\x12<\n" + "\x06health\x18\x03 \x01(\x0e2$.greatestworks.gateway.ServiceHealthR\x06health\x12#\n" + "\rresponse_time\x18\x04 \x01(\x02R\fresponseTime\x12-\n" + "\x12active_connections\x18\x05 \x01(\x05R\x11activeConnections\x12\x1d\n" + "\n" + "last_check\x18\x06 \x01(\x03R\tlastCheck\x12\x18\n" + "\aversion\x18\a \x01(\tR\aversion\x12N\n" + "\bmetadata\x18\b \x03(\v22.greatestworks.gateway.ServiceStatus.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x84\x04\n" + "\vSessionInfo\x12\x1d\n" + "\n" + "session_id\x18\x01 \x01(\tR\tsessionId\x12\x17\n" + "\auser_id\x18\x02 \x01(\tR\x06userId\x12!\n" + "\faccess_token\x18\x03 \x01(\tR\vaccessToken\x12\x1d\n" + "\n" + "created_at\x18\x04 \x01(\x03R\tcreatedAt\x12\x1d\n" + "\n" + "expires_at\x18\x05 \x01(\x03R\texpiresAt\x12#\n" + "\rlast_activity\x18\x06 \x01(\x03R\flastActivity\x12\x1b\n" + "\tclient_ip\x18\a \x01(\tR\bclientIp\x12\x1d\n" + "\n" + "user_agent\x18\b \x01(\tR\tuserAgent\x12%\n" + "\x0ecurrent_server\x18\t \x01(\tR\rcurrentServer\x12<\n" + "\x06status\x18\n" + " \x01(\x0e2$.greatestworks.gateway.SessionStatusR\x06status\x12V\n" + "\fsession_data\x18\v \x03(\v23.greatestworks.gateway.SessionInfo.SessionDataEntryR\vsessionData\x1a>\n" + "\x10SessionDataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*\xd4\x01\n" + "\bAuthType\x12\x19\n" + "\x15AUTH_TYPE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12AUTH_TYPE_PASSWORD\x10\x01\x12\x13\n" + "\x0fAUTH_TYPE_OAUTH\x10\x02\x12\x11\n" + "\rAUTH_TYPE_JWT\x10\x03\x12\x13\n" + "\x0fAUTH_TYPE_GUEST\x10\x04\x12\x16\n" + "\x12AUTH_TYPE_FACEBOOK\x10\x05\x12\x14\n" + "\x10AUTH_TYPE_GOOGLE\x10\x06\x12\x15\n" + "\x11AUTH_TYPE_TWITTER\x10\a\x12\x13\n" + "\x0fAUTH_TYPE_APPLE\x10\b*\xb2\x01\n" + "\n" + "ServerType\x12\x1b\n" + "\x17SERVER_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10SERVER_TYPE_GAME\x10\x01\x12\x14\n" + "\x10SERVER_TYPE_CHAT\x10\x02\x12\x15\n" + "\x11SERVER_TYPE_MATCH\x10\x03\x12\x16\n" + "\x12SERVER_TYPE_BATTLE\x10\x04\x12\x16\n" + "\x12SERVER_TYPE_SOCIAL\x10\x05\x12\x14\n" + "\x10SERVER_TYPE_TEST\x10\x06*\xb7\x01\n" + "\fServerStatus\x12\x1d\n" + "\x19SERVER_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14SERVER_STATUS_ONLINE\x10\x01\x12\x19\n" + "\x15SERVER_STATUS_OFFLINE\x10\x02\x12\x1d\n" + "\x19SERVER_STATUS_MAINTENANCE\x10\x03\x12\x16\n" + "\x12SERVER_STATUS_FULL\x10\x04\x12\x1c\n" + "\x18SERVER_STATUS_RESTRICTED\x10\x05*\x9d\x01\n" + "\x0eConnectionType\x12\x1f\n" + "\x1bCONNECTION_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19CONNECTION_TYPE_WEBSOCKET\x10\x01\x12\x17\n" + "\x13CONNECTION_TYPE_TCP\x10\x02\x12\x18\n" + "\x14CONNECTION_TYPE_HTTP\x10\x03\x12\x18\n" + "\x14CONNECTION_TYPE_GRPC\x10\x04*\xb3\x01\n" + "\tUserLevel\x12\x1a\n" + "\x16USER_LEVEL_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10USER_LEVEL_GUEST\x10\x01\x12\x19\n" + "\x15USER_LEVEL_REGISTERED\x10\x02\x12\x17\n" + "\x13USER_LEVEL_VERIFIED\x10\x03\x12\x16\n" + "\x12USER_LEVEL_PREMIUM\x10\x04\x12\x12\n" + "\x0eUSER_LEVEL_VIP\x10\x05\x12\x14\n" + "\x10USER_LEVEL_ADMIN\x10\x06*\xa3\x01\n" + "\rServiceHealth\x12\x1e\n" + "\x1aSERVICE_HEALTH_UNSPECIFIED\x10\x00\x12\x1a\n" + "\x16SERVICE_HEALTH_HEALTHY\x10\x01\x12\x1b\n" + "\x17SERVICE_HEALTH_DEGRADED\x10\x02\x12\x1c\n" + "\x18SERVICE_HEALTH_UNHEALTHY\x10\x03\x12\x1b\n" + "\x17SERVICE_HEALTH_CRITICAL\x10\x04*\xbc\x01\n" + "\rSessionStatus\x12\x1e\n" + "\x1aSESSION_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15SESSION_STATUS_ACTIVE\x10\x01\x12\x17\n" + "\x13SESSION_STATUS_IDLE\x10\x02\x12\x1a\n" + "\x16SESSION_STATUS_EXPIRED\x10\x03\x12\x1d\n" + "\x19SESSION_STATUS_TERMINATED\x10\x04\x12\x1c\n" + "\x18SESSION_STATUS_SUSPENDED\x10\x052\x8c\t\n" + "\x0eGatewayService\x12g\n" + "\fAuthenticate\x12*.greatestworks.gateway.AuthenticateRequest\x1a+.greatestworks.gateway.AuthenticateResponse\x12g\n" + "\fRefreshToken\x12*.greatestworks.gateway.RefreshTokenRequest\x1a+.greatestworks.gateway.RefreshTokenResponse\x12U\n" + "\x06Logout\x12$.greatestworks.gateway.LogoutRequest\x1a%.greatestworks.gateway.LogoutResponse\x12j\n" + "\rGetServerList\x12+.greatestworks.gateway.GetServerListRequest\x1a,.greatestworks.gateway.GetServerListResponse\x12g\n" + "\fSelectServer\x12*.greatestworks.gateway.SelectServerRequest\x1a+.greatestworks.gateway.SelectServerResponse\x12g\n" + "\fRouteRequest\x12*.greatestworks.gateway.RouteRequestMessage\x1a+.greatestworks.gateway.RouteResponseMessage\x12j\n" + "\x13EstablishConnection\x12(.greatestworks.gateway.ConnectionRequest\x1a).greatestworks.gateway.ConnectionResponse\x12^\n" + "\tHeartbeat\x12'.greatestworks.gateway.HeartbeatRequest\x1a(.greatestworks.gateway.HeartbeatResponse\x12s\n" + "\x10GetGatewayStatus\x12..greatestworks.gateway.GetGatewayStatusRequest\x1a/.greatestworks.gateway.GetGatewayStatusResponse\x12c\n" + "\x0eRateLimitCheck\x12'.greatestworks.gateway.RateLimitRequest\x1a(.greatestworks.gateway.RateLimitResponse\x12m\n" + "\x0eGetSessionInfo\x12,.greatestworks.gateway.GetSessionInfoRequest\x1a-.greatestworks.gateway.GetSessionInfoResponseB>Z$greatestworks/internal/proto/gateway\xaa\x02\x15GreatestWorks.Gatewayb\x06proto3" var ( file_proto_gateway_proto_rawDescOnce sync.Once file_proto_gateway_proto_rawDescData []byte ) func file_proto_gateway_proto_rawDescGZIP() []byte { file_proto_gateway_proto_rawDescOnce.Do(func() { file_proto_gateway_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_gateway_proto_rawDesc), len(file_proto_gateway_proto_rawDesc))) }) return file_proto_gateway_proto_rawDescData } var file_proto_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 7) var file_proto_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 39) var file_proto_gateway_proto_goTypes = []any{ (AuthType)(0), // 0: greatestworks.gateway.AuthType (ServerType)(0), // 1: greatestworks.gateway.ServerType (ServerStatus)(0), // 2: greatestworks.gateway.ServerStatus (ConnectionType)(0), // 3: greatestworks.gateway.ConnectionType (UserLevel)(0), // 4: greatestworks.gateway.UserLevel (ServiceHealth)(0), // 5: greatestworks.gateway.ServiceHealth (SessionStatus)(0), // 6: greatestworks.gateway.SessionStatus (*AuthenticateRequest)(nil), // 7: greatestworks.gateway.AuthenticateRequest (*AuthenticateResponse)(nil), // 8: greatestworks.gateway.AuthenticateResponse (*RefreshTokenRequest)(nil), // 9: greatestworks.gateway.RefreshTokenRequest (*RefreshTokenResponse)(nil), // 10: greatestworks.gateway.RefreshTokenResponse (*LogoutRequest)(nil), // 11: greatestworks.gateway.LogoutRequest (*LogoutResponse)(nil), // 12: greatestworks.gateway.LogoutResponse (*GetServerListRequest)(nil), // 13: greatestworks.gateway.GetServerListRequest (*GetServerListResponse)(nil), // 14: greatestworks.gateway.GetServerListResponse (*SelectServerRequest)(nil), // 15: greatestworks.gateway.SelectServerRequest (*SelectServerResponse)(nil), // 16: greatestworks.gateway.SelectServerResponse (*RouteRequestMessage)(nil), // 17: greatestworks.gateway.RouteRequestMessage (*RouteResponseMessage)(nil), // 18: greatestworks.gateway.RouteResponseMessage (*ConnectionRequest)(nil), // 19: greatestworks.gateway.ConnectionRequest (*ConnectionResponse)(nil), // 20: greatestworks.gateway.ConnectionResponse (*HeartbeatRequest)(nil), // 21: greatestworks.gateway.HeartbeatRequest (*HeartbeatResponse)(nil), // 22: greatestworks.gateway.HeartbeatResponse (*GetGatewayStatusRequest)(nil), // 23: greatestworks.gateway.GetGatewayStatusRequest (*GetGatewayStatusResponse)(nil), // 24: greatestworks.gateway.GetGatewayStatusResponse (*RateLimitRequest)(nil), // 25: greatestworks.gateway.RateLimitRequest (*RateLimitResponse)(nil), // 26: greatestworks.gateway.RateLimitResponse (*GetSessionInfoRequest)(nil), // 27: greatestworks.gateway.GetSessionInfoRequest (*GetSessionInfoResponse)(nil), // 28: greatestworks.gateway.GetSessionInfoResponse (*ServerInfo)(nil), // 29: greatestworks.gateway.ServerInfo (*UserProfile)(nil), // 30: greatestworks.gateway.UserProfile (*GatewayStatus)(nil), // 31: greatestworks.gateway.GatewayStatus (*GatewayMetrics)(nil), // 32: greatestworks.gateway.GatewayMetrics (*ServiceStatus)(nil), // 33: greatestworks.gateway.ServiceStatus (*SessionInfo)(nil), // 34: greatestworks.gateway.SessionInfo nil, // 35: greatestworks.gateway.AuthenticateRequest.MetadataEntry nil, // 36: greatestworks.gateway.RouteRequestMessage.HeadersEntry nil, // 37: greatestworks.gateway.RouteResponseMessage.HeadersEntry nil, // 38: greatestworks.gateway.ConnectionRequest.ConnectionParamsEntry nil, // 39: greatestworks.gateway.HeartbeatRequest.StatusInfoEntry nil, // 40: greatestworks.gateway.ServerInfo.FeaturesEntry nil, // 41: greatestworks.gateway.UserProfile.PreferencesEntry nil, // 42: greatestworks.gateway.GatewayMetrics.RequestsPerServiceEntry nil, // 43: greatestworks.gateway.GatewayMetrics.ResponseTimesPerServiceEntry nil, // 44: greatestworks.gateway.ServiceStatus.MetadataEntry nil, // 45: greatestworks.gateway.SessionInfo.SessionDataEntry (*common.CommonResponse)(nil), // 46: greatestworks.common.CommonResponse } var file_proto_gateway_proto_depIdxs = []int32{ 0, // 0: greatestworks.gateway.AuthenticateRequest.auth_type:type_name -> greatestworks.gateway.AuthType 35, // 1: greatestworks.gateway.AuthenticateRequest.metadata:type_name -> greatestworks.gateway.AuthenticateRequest.MetadataEntry 46, // 2: greatestworks.gateway.AuthenticateResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 3: greatestworks.gateway.AuthenticateResponse.user_profile:type_name -> greatestworks.gateway.UserProfile 46, // 4: greatestworks.gateway.RefreshTokenResponse.common:type_name -> greatestworks.common.CommonResponse 46, // 5: greatestworks.gateway.LogoutResponse.common:type_name -> greatestworks.common.CommonResponse 1, // 6: greatestworks.gateway.GetServerListRequest.server_type:type_name -> greatestworks.gateway.ServerType 46, // 7: greatestworks.gateway.GetServerListResponse.common:type_name -> greatestworks.common.CommonResponse 29, // 8: greatestworks.gateway.GetServerListResponse.servers:type_name -> greatestworks.gateway.ServerInfo 46, // 9: greatestworks.gateway.SelectServerResponse.common:type_name -> greatestworks.common.CommonResponse 29, // 10: greatestworks.gateway.SelectServerResponse.server_info:type_name -> greatestworks.gateway.ServerInfo 36, // 11: greatestworks.gateway.RouteRequestMessage.headers:type_name -> greatestworks.gateway.RouteRequestMessage.HeadersEntry 37, // 12: greatestworks.gateway.RouteResponseMessage.headers:type_name -> greatestworks.gateway.RouteResponseMessage.HeadersEntry 3, // 13: greatestworks.gateway.ConnectionRequest.connection_type:type_name -> greatestworks.gateway.ConnectionType 38, // 14: greatestworks.gateway.ConnectionRequest.connection_params:type_name -> greatestworks.gateway.ConnectionRequest.ConnectionParamsEntry 46, // 15: greatestworks.gateway.ConnectionResponse.common:type_name -> greatestworks.common.CommonResponse 39, // 16: greatestworks.gateway.HeartbeatRequest.status_info:type_name -> greatestworks.gateway.HeartbeatRequest.StatusInfoEntry 46, // 17: greatestworks.gateway.HeartbeatResponse.common:type_name -> greatestworks.common.CommonResponse 31, // 18: greatestworks.gateway.HeartbeatResponse.gateway_status:type_name -> greatestworks.gateway.GatewayStatus 46, // 19: greatestworks.gateway.GetGatewayStatusResponse.common:type_name -> greatestworks.common.CommonResponse 31, // 20: greatestworks.gateway.GetGatewayStatusResponse.status:type_name -> greatestworks.gateway.GatewayStatus 32, // 21: greatestworks.gateway.GetGatewayStatusResponse.metrics:type_name -> greatestworks.gateway.GatewayMetrics 33, // 22: greatestworks.gateway.GetGatewayStatusResponse.backend_services:type_name -> greatestworks.gateway.ServiceStatus 46, // 23: greatestworks.gateway.GetSessionInfoResponse.common:type_name -> greatestworks.common.CommonResponse 34, // 24: greatestworks.gateway.GetSessionInfoResponse.session_info:type_name -> greatestworks.gateway.SessionInfo 1, // 25: greatestworks.gateway.ServerInfo.server_type:type_name -> greatestworks.gateway.ServerType 2, // 26: greatestworks.gateway.ServerInfo.status:type_name -> greatestworks.gateway.ServerStatus 40, // 27: greatestworks.gateway.ServerInfo.features:type_name -> greatestworks.gateway.ServerInfo.FeaturesEntry 4, // 28: greatestworks.gateway.UserProfile.user_level:type_name -> greatestworks.gateway.UserLevel 41, // 29: greatestworks.gateway.UserProfile.preferences:type_name -> greatestworks.gateway.UserProfile.PreferencesEntry 42, // 30: greatestworks.gateway.GatewayMetrics.requests_per_service:type_name -> greatestworks.gateway.GatewayMetrics.RequestsPerServiceEntry 43, // 31: greatestworks.gateway.GatewayMetrics.response_times_per_service:type_name -> greatestworks.gateway.GatewayMetrics.ResponseTimesPerServiceEntry 5, // 32: greatestworks.gateway.ServiceStatus.health:type_name -> greatestworks.gateway.ServiceHealth 44, // 33: greatestworks.gateway.ServiceStatus.metadata:type_name -> greatestworks.gateway.ServiceStatus.MetadataEntry 6, // 34: greatestworks.gateway.SessionInfo.status:type_name -> greatestworks.gateway.SessionStatus 45, // 35: greatestworks.gateway.SessionInfo.session_data:type_name -> greatestworks.gateway.SessionInfo.SessionDataEntry 7, // 36: greatestworks.gateway.GatewayService.Authenticate:input_type -> greatestworks.gateway.AuthenticateRequest 9, // 37: greatestworks.gateway.GatewayService.RefreshToken:input_type -> greatestworks.gateway.RefreshTokenRequest 11, // 38: greatestworks.gateway.GatewayService.Logout:input_type -> greatestworks.gateway.LogoutRequest 13, // 39: greatestworks.gateway.GatewayService.GetServerList:input_type -> greatestworks.gateway.GetServerListRequest 15, // 40: greatestworks.gateway.GatewayService.SelectServer:input_type -> greatestworks.gateway.SelectServerRequest 17, // 41: greatestworks.gateway.GatewayService.RouteRequest:input_type -> greatestworks.gateway.RouteRequestMessage 19, // 42: greatestworks.gateway.GatewayService.EstablishConnection:input_type -> greatestworks.gateway.ConnectionRequest 21, // 43: greatestworks.gateway.GatewayService.Heartbeat:input_type -> greatestworks.gateway.HeartbeatRequest 23, // 44: greatestworks.gateway.GatewayService.GetGatewayStatus:input_type -> greatestworks.gateway.GetGatewayStatusRequest 25, // 45: greatestworks.gateway.GatewayService.RateLimitCheck:input_type -> greatestworks.gateway.RateLimitRequest 27, // 46: greatestworks.gateway.GatewayService.GetSessionInfo:input_type -> greatestworks.gateway.GetSessionInfoRequest 8, // 47: greatestworks.gateway.GatewayService.Authenticate:output_type -> greatestworks.gateway.AuthenticateResponse 10, // 48: greatestworks.gateway.GatewayService.RefreshToken:output_type -> greatestworks.gateway.RefreshTokenResponse 12, // 49: greatestworks.gateway.GatewayService.Logout:output_type -> greatestworks.gateway.LogoutResponse 14, // 50: greatestworks.gateway.GatewayService.GetServerList:output_type -> greatestworks.gateway.GetServerListResponse 16, // 51: greatestworks.gateway.GatewayService.SelectServer:output_type -> greatestworks.gateway.SelectServerResponse 18, // 52: greatestworks.gateway.GatewayService.RouteRequest:output_type -> greatestworks.gateway.RouteResponseMessage 20, // 53: greatestworks.gateway.GatewayService.EstablishConnection:output_type -> greatestworks.gateway.ConnectionResponse 22, // 54: greatestworks.gateway.GatewayService.Heartbeat:output_type -> greatestworks.gateway.HeartbeatResponse 24, // 55: greatestworks.gateway.GatewayService.GetGatewayStatus:output_type -> greatestworks.gateway.GetGatewayStatusResponse 26, // 56: greatestworks.gateway.GatewayService.RateLimitCheck:output_type -> greatestworks.gateway.RateLimitResponse 28, // 57: greatestworks.gateway.GatewayService.GetSessionInfo:output_type -> greatestworks.gateway.GetSessionInfoResponse 47, // [47:58] is the sub-list for method output_type 36, // [36:47] is the sub-list for method input_type 36, // [36:36] is the sub-list for extension type_name 36, // [36:36] is the sub-list for extension extendee 0, // [0:36] is the sub-list for field type_name } func init() { file_proto_gateway_proto_init() } func file_proto_gateway_proto_init() { if File_proto_gateway_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_gateway_proto_rawDesc), len(file_proto_gateway_proto_rawDesc)), NumEnums: 7, NumMessages: 39, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_gateway_proto_goTypes, DependencyIndexes: file_proto_gateway_proto_depIdxs, EnumInfos: file_proto_gateway_proto_enumTypes, MessageInfos: file_proto_gateway_proto_msgTypes, }.Build() File_proto_gateway_proto = out.File file_proto_gateway_proto_goTypes = nil file_proto_gateway_proto_depIdxs = nil } ================================================ FILE: internal/proto/mail/mail.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/mail.proto package mail import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 邮件类型枚举 type MailType int32 const ( MailType_MAIL_TYPE_UNSPECIFIED MailType = 0 MailType_MAIL_TYPE_SYSTEM MailType = 1 // 系统邮件 MailType_MAIL_TYPE_PLAYER MailType = 2 // 玩家邮件 MailType_MAIL_TYPE_REWARD MailType = 3 // 奖励邮件 MailType_MAIL_TYPE_NOTIFICATION MailType = 4 // 通知邮件 MailType_MAIL_TYPE_PROMOTION MailType = 5 // 推广邮件 MailType_MAIL_TYPE_ANNOUNCEMENT MailType = 6 // 公告邮件 MailType_MAIL_TYPE_GIFT MailType = 7 // 礼品邮件 MailType_MAIL_TYPE_COMPENSATION MailType = 8 // 补偿邮件 MailType_MAIL_TYPE_BATTLE_REPORT MailType = 9 // 战斗报告 MailType_MAIL_TYPE_GUILD MailType = 10 // 公会邮件 ) // Enum value maps for MailType. var ( MailType_name = map[int32]string{ 0: "MAIL_TYPE_UNSPECIFIED", 1: "MAIL_TYPE_SYSTEM", 2: "MAIL_TYPE_PLAYER", 3: "MAIL_TYPE_REWARD", 4: "MAIL_TYPE_NOTIFICATION", 5: "MAIL_TYPE_PROMOTION", 6: "MAIL_TYPE_ANNOUNCEMENT", 7: "MAIL_TYPE_GIFT", 8: "MAIL_TYPE_COMPENSATION", 9: "MAIL_TYPE_BATTLE_REPORT", 10: "MAIL_TYPE_GUILD", } MailType_value = map[string]int32{ "MAIL_TYPE_UNSPECIFIED": 0, "MAIL_TYPE_SYSTEM": 1, "MAIL_TYPE_PLAYER": 2, "MAIL_TYPE_REWARD": 3, "MAIL_TYPE_NOTIFICATION": 4, "MAIL_TYPE_PROMOTION": 5, "MAIL_TYPE_ANNOUNCEMENT": 6, "MAIL_TYPE_GIFT": 7, "MAIL_TYPE_COMPENSATION": 8, "MAIL_TYPE_BATTLE_REPORT": 9, "MAIL_TYPE_GUILD": 10, } ) func (x MailType) Enum() *MailType { p := new(MailType) *p = x return p } func (x MailType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MailType) Descriptor() protoreflect.EnumDescriptor { return file_proto_mail_proto_enumTypes[0].Descriptor() } func (MailType) Type() protoreflect.EnumType { return &file_proto_mail_proto_enumTypes[0] } func (x MailType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MailType.Descriptor instead. func (MailType) EnumDescriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{0} } // 邮件状态枚举 type MailStatus int32 const ( MailStatus_MAIL_STATUS_UNSPECIFIED MailStatus = 0 MailStatus_MAIL_STATUS_UNREAD MailStatus = 1 // 未读 MailStatus_MAIL_STATUS_READ MailStatus = 2 // 已读 MailStatus_MAIL_STATUS_ARCHIVED MailStatus = 3 // 已归档 MailStatus_MAIL_STATUS_DELETED MailStatus = 4 // 已删除 MailStatus_MAIL_STATUS_EXPIRED MailStatus = 5 // 已过期 ) // Enum value maps for MailStatus. var ( MailStatus_name = map[int32]string{ 0: "MAIL_STATUS_UNSPECIFIED", 1: "MAIL_STATUS_UNREAD", 2: "MAIL_STATUS_READ", 3: "MAIL_STATUS_ARCHIVED", 4: "MAIL_STATUS_DELETED", 5: "MAIL_STATUS_EXPIRED", } MailStatus_value = map[string]int32{ "MAIL_STATUS_UNSPECIFIED": 0, "MAIL_STATUS_UNREAD": 1, "MAIL_STATUS_READ": 2, "MAIL_STATUS_ARCHIVED": 3, "MAIL_STATUS_DELETED": 4, "MAIL_STATUS_EXPIRED": 5, } ) func (x MailStatus) Enum() *MailStatus { p := new(MailStatus) *p = x return p } func (x MailStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MailStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_mail_proto_enumTypes[1].Descriptor() } func (MailStatus) Type() protoreflect.EnumType { return &file_proto_mail_proto_enumTypes[1] } func (x MailStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MailStatus.Descriptor instead. func (MailStatus) EnumDescriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{1} } // 附件类型枚举 type AttachmentType int32 const ( AttachmentType_ATTACHMENT_TYPE_UNSPECIFIED AttachmentType = 0 AttachmentType_ATTACHMENT_TYPE_ITEM AttachmentType = 1 // 物品 AttachmentType_ATTACHMENT_TYPE_CURRENCY AttachmentType = 2 // 货币 AttachmentType_ATTACHMENT_TYPE_EXPERIENCE AttachmentType = 3 // 经验 AttachmentType_ATTACHMENT_TYPE_BUFF AttachmentType = 4 // 增益效果 AttachmentType_ATTACHMENT_TYPE_TITLE AttachmentType = 5 // 称号 AttachmentType_ATTACHMENT_TYPE_ACHIEVEMENT AttachmentType = 6 // 成就 ) // Enum value maps for AttachmentType. var ( AttachmentType_name = map[int32]string{ 0: "ATTACHMENT_TYPE_UNSPECIFIED", 1: "ATTACHMENT_TYPE_ITEM", 2: "ATTACHMENT_TYPE_CURRENCY", 3: "ATTACHMENT_TYPE_EXPERIENCE", 4: "ATTACHMENT_TYPE_BUFF", 5: "ATTACHMENT_TYPE_TITLE", 6: "ATTACHMENT_TYPE_ACHIEVEMENT", } AttachmentType_value = map[string]int32{ "ATTACHMENT_TYPE_UNSPECIFIED": 0, "ATTACHMENT_TYPE_ITEM": 1, "ATTACHMENT_TYPE_CURRENCY": 2, "ATTACHMENT_TYPE_EXPERIENCE": 3, "ATTACHMENT_TYPE_BUFF": 4, "ATTACHMENT_TYPE_TITLE": 5, "ATTACHMENT_TYPE_ACHIEVEMENT": 6, } ) func (x AttachmentType) Enum() *AttachmentType { p := new(AttachmentType) *p = x return p } func (x AttachmentType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AttachmentType) Descriptor() protoreflect.EnumDescriptor { return file_proto_mail_proto_enumTypes[2].Descriptor() } func (AttachmentType) Type() protoreflect.EnumType { return &file_proto_mail_proto_enumTypes[2] } func (x AttachmentType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AttachmentType.Descriptor instead. func (AttachmentType) EnumDescriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{2} } // 邮件标记类型枚举 type MailMarkType int32 const ( MailMarkType_MAIL_MARK_TYPE_UNSPECIFIED MailMarkType = 0 MailMarkType_MAIL_MARK_TYPE_READ MailMarkType = 1 // 标记为已读 MailMarkType_MAIL_MARK_TYPE_UNREAD MailMarkType = 2 // 标记为未读 MailMarkType_MAIL_MARK_TYPE_IMPORTANT MailMarkType = 3 // 标记为重要 MailMarkType_MAIL_MARK_TYPE_UNIMPORTANT MailMarkType = 4 // 取消重要标记 MailMarkType_MAIL_MARK_TYPE_FAVORITE MailMarkType = 5 // 标记为收藏 MailMarkType_MAIL_MARK_TYPE_UNFAVORITE MailMarkType = 6 // 取消收藏标记 ) // Enum value maps for MailMarkType. var ( MailMarkType_name = map[int32]string{ 0: "MAIL_MARK_TYPE_UNSPECIFIED", 1: "MAIL_MARK_TYPE_READ", 2: "MAIL_MARK_TYPE_UNREAD", 3: "MAIL_MARK_TYPE_IMPORTANT", 4: "MAIL_MARK_TYPE_UNIMPORTANT", 5: "MAIL_MARK_TYPE_FAVORITE", 6: "MAIL_MARK_TYPE_UNFAVORITE", } MailMarkType_value = map[string]int32{ "MAIL_MARK_TYPE_UNSPECIFIED": 0, "MAIL_MARK_TYPE_READ": 1, "MAIL_MARK_TYPE_UNREAD": 2, "MAIL_MARK_TYPE_IMPORTANT": 3, "MAIL_MARK_TYPE_UNIMPORTANT": 4, "MAIL_MARK_TYPE_FAVORITE": 5, "MAIL_MARK_TYPE_UNFAVORITE": 6, } ) func (x MailMarkType) Enum() *MailMarkType { p := new(MailMarkType) *p = x return p } func (x MailMarkType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MailMarkType) Descriptor() protoreflect.EnumDescriptor { return file_proto_mail_proto_enumTypes[3].Descriptor() } func (MailMarkType) Type() protoreflect.EnumType { return &file_proto_mail_proto_enumTypes[3] } func (x MailMarkType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MailMarkType.Descriptor instead. func (MailMarkType) EnumDescriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{3} } // 发送邮件请求 type SendMailRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SenderId string `protobuf:"bytes,1,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` RecipientIds []string `protobuf:"bytes,2,rep,name=recipient_ids,json=recipientIds,proto3" json:"recipient_ids,omitempty"` Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` MailType MailType `protobuf:"varint,5,opt,name=mail_type,json=mailType,proto3,enum=greatestworks.mail.MailType" json:"mail_type,omitempty"` Attachments []*MailAttachment `protobuf:"bytes,6,rep,name=attachments,proto3" json:"attachments,omitempty"` ExpireAt int64 `protobuf:"varint,7,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty"` // 过期时间,0表示不过期 IsSystemMail bool `protobuf:"varint,8,opt,name=is_system_mail,json=isSystemMail,proto3" json:"is_system_mail,omitempty"` Metadata map[string]string `protobuf:"bytes,9,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SendMailRequest) Reset() { *x = SendMailRequest{} mi := &file_proto_mail_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SendMailRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendMailRequest) ProtoMessage() {} func (x *SendMailRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendMailRequest.ProtoReflect.Descriptor instead. func (*SendMailRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{0} } func (x *SendMailRequest) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *SendMailRequest) GetRecipientIds() []string { if x != nil { return x.RecipientIds } return nil } func (x *SendMailRequest) GetSubject() string { if x != nil { return x.Subject } return "" } func (x *SendMailRequest) GetContent() string { if x != nil { return x.Content } return "" } func (x *SendMailRequest) GetMailType() MailType { if x != nil { return x.MailType } return MailType_MAIL_TYPE_UNSPECIFIED } func (x *SendMailRequest) GetAttachments() []*MailAttachment { if x != nil { return x.Attachments } return nil } func (x *SendMailRequest) GetExpireAt() int64 { if x != nil { return x.ExpireAt } return 0 } func (x *SendMailRequest) GetIsSystemMail() bool { if x != nil { return x.IsSystemMail } return false } func (x *SendMailRequest) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 发送邮件响应 type SendMailResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` MailIds []string `protobuf:"bytes,2,rep,name=mail_ids,json=mailIds,proto3" json:"mail_ids,omitempty"` // 为每个收件人创建的邮件ID unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SendMailResponse) Reset() { *x = SendMailResponse{} mi := &file_proto_mail_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SendMailResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendMailResponse) ProtoMessage() {} func (x *SendMailResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendMailResponse.ProtoReflect.Descriptor instead. func (*SendMailResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{1} } func (x *SendMailResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SendMailResponse) GetMailIds() []string { if x != nil { return x.MailIds } return nil } // 获取邮件列表请求 type GetMailListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Status MailStatus `protobuf:"varint,2,opt,name=status,proto3,enum=greatestworks.mail.MailStatus" json:"status,omitempty"` MailType MailType `protobuf:"varint,3,opt,name=mail_type,json=mailType,proto3,enum=greatestworks.mail.MailType" json:"mail_type,omitempty"` Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"` OnlyUnread bool `protobuf:"varint,6,opt,name=only_unread,json=onlyUnread,proto3" json:"only_unread,omitempty"` OnlyWithAttachments bool `protobuf:"varint,7,opt,name=only_with_attachments,json=onlyWithAttachments,proto3" json:"only_with_attachments,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetMailListRequest) Reset() { *x = GetMailListRequest{} mi := &file_proto_mail_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetMailListRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetMailListRequest) ProtoMessage() {} func (x *GetMailListRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetMailListRequest.ProtoReflect.Descriptor instead. func (*GetMailListRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{2} } func (x *GetMailListRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *GetMailListRequest) GetStatus() MailStatus { if x != nil { return x.Status } return MailStatus_MAIL_STATUS_UNSPECIFIED } func (x *GetMailListRequest) GetMailType() MailType { if x != nil { return x.MailType } return MailType_MAIL_TYPE_UNSPECIFIED } func (x *GetMailListRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetMailListRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } func (x *GetMailListRequest) GetOnlyUnread() bool { if x != nil { return x.OnlyUnread } return false } func (x *GetMailListRequest) GetOnlyWithAttachments() bool { if x != nil { return x.OnlyWithAttachments } return false } // 获取邮件列表响应 type GetMailListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Mails []*MailInfo `protobuf:"bytes,2,rep,name=mails,proto3" json:"mails,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` Stats *MailStats `protobuf:"bytes,4,opt,name=stats,proto3" json:"stats,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetMailListResponse) Reset() { *x = GetMailListResponse{} mi := &file_proto_mail_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetMailListResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetMailListResponse) ProtoMessage() {} func (x *GetMailListResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetMailListResponse.ProtoReflect.Descriptor instead. func (*GetMailListResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{3} } func (x *GetMailListResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetMailListResponse) GetMails() []*MailInfo { if x != nil { return x.Mails } return nil } func (x *GetMailListResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } func (x *GetMailListResponse) GetStats() *MailStats { if x != nil { return x.Stats } return nil } // 读取邮件请求 type ReadMailRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` MailId string `protobuf:"bytes,2,opt,name=mail_id,json=mailId,proto3" json:"mail_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ReadMailRequest) Reset() { *x = ReadMailRequest{} mi := &file_proto_mail_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ReadMailRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ReadMailRequest) ProtoMessage() {} func (x *ReadMailRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ReadMailRequest.ProtoReflect.Descriptor instead. func (*ReadMailRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{4} } func (x *ReadMailRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *ReadMailRequest) GetMailId() string { if x != nil { return x.MailId } return "" } // 读取邮件响应 type ReadMailResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Mail *MailDetail `protobuf:"bytes,2,opt,name=mail,proto3" json:"mail,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ReadMailResponse) Reset() { *x = ReadMailResponse{} mi := &file_proto_mail_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ReadMailResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ReadMailResponse) ProtoMessage() {} func (x *ReadMailResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ReadMailResponse.ProtoReflect.Descriptor instead. func (*ReadMailResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{5} } func (x *ReadMailResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *ReadMailResponse) GetMail() *MailDetail { if x != nil { return x.Mail } return nil } // 删除邮件请求 type DeleteMailRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` MailId string `protobuf:"bytes,2,opt,name=mail_id,json=mailId,proto3" json:"mail_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteMailRequest) Reset() { *x = DeleteMailRequest{} mi := &file_proto_mail_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteMailRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteMailRequest) ProtoMessage() {} func (x *DeleteMailRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteMailRequest.ProtoReflect.Descriptor instead. func (*DeleteMailRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{6} } func (x *DeleteMailRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *DeleteMailRequest) GetMailId() string { if x != nil { return x.MailId } return "" } // 删除邮件响应 type DeleteMailResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteMailResponse) Reset() { *x = DeleteMailResponse{} mi := &file_proto_mail_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteMailResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteMailResponse) ProtoMessage() {} func (x *DeleteMailResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteMailResponse.ProtoReflect.Descriptor instead. func (*DeleteMailResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{7} } func (x *DeleteMailResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 批量删除邮件请求 type BatchDeleteMailsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` MailIds []string `protobuf:"bytes,2,rep,name=mail_ids,json=mailIds,proto3" json:"mail_ids,omitempty"` DeleteAllRead bool `protobuf:"varint,3,opt,name=delete_all_read,json=deleteAllRead,proto3" json:"delete_all_read,omitempty"` // 删除所有已读邮件 DeleteExpired bool `protobuf:"varint,4,opt,name=delete_expired,json=deleteExpired,proto3" json:"delete_expired,omitempty"` // 删除过期邮件 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BatchDeleteMailsRequest) Reset() { *x = BatchDeleteMailsRequest{} mi := &file_proto_mail_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BatchDeleteMailsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*BatchDeleteMailsRequest) ProtoMessage() {} func (x *BatchDeleteMailsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BatchDeleteMailsRequest.ProtoReflect.Descriptor instead. func (*BatchDeleteMailsRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{8} } func (x *BatchDeleteMailsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *BatchDeleteMailsRequest) GetMailIds() []string { if x != nil { return x.MailIds } return nil } func (x *BatchDeleteMailsRequest) GetDeleteAllRead() bool { if x != nil { return x.DeleteAllRead } return false } func (x *BatchDeleteMailsRequest) GetDeleteExpired() bool { if x != nil { return x.DeleteExpired } return false } // 批量删除邮件响应 type BatchDeleteMailsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` DeletedCount int32 `protobuf:"varint,2,opt,name=deleted_count,json=deletedCount,proto3" json:"deleted_count,omitempty"` FailedMailIds []string `protobuf:"bytes,3,rep,name=failed_mail_ids,json=failedMailIds,proto3" json:"failed_mail_ids,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BatchDeleteMailsResponse) Reset() { *x = BatchDeleteMailsResponse{} mi := &file_proto_mail_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BatchDeleteMailsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*BatchDeleteMailsResponse) ProtoMessage() {} func (x *BatchDeleteMailsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BatchDeleteMailsResponse.ProtoReflect.Descriptor instead. func (*BatchDeleteMailsResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{9} } func (x *BatchDeleteMailsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *BatchDeleteMailsResponse) GetDeletedCount() int32 { if x != nil { return x.DeletedCount } return 0 } func (x *BatchDeleteMailsResponse) GetFailedMailIds() []string { if x != nil { return x.FailedMailIds } return nil } // 领取邮件附件请求 type ClaimAttachmentRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` MailId string `protobuf:"bytes,2,opt,name=mail_id,json=mailId,proto3" json:"mail_id,omitempty"` AttachmentIds []string `protobuf:"bytes,3,rep,name=attachment_ids,json=attachmentIds,proto3" json:"attachment_ids,omitempty"` // 空则领取所有附件 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ClaimAttachmentRequest) Reset() { *x = ClaimAttachmentRequest{} mi := &file_proto_mail_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ClaimAttachmentRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClaimAttachmentRequest) ProtoMessage() {} func (x *ClaimAttachmentRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClaimAttachmentRequest.ProtoReflect.Descriptor instead. func (*ClaimAttachmentRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{10} } func (x *ClaimAttachmentRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *ClaimAttachmentRequest) GetMailId() string { if x != nil { return x.MailId } return "" } func (x *ClaimAttachmentRequest) GetAttachmentIds() []string { if x != nil { return x.AttachmentIds } return nil } // 领取邮件附件响应 type ClaimAttachmentResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ClaimedAttachments []*ClaimedAttachment `protobuf:"bytes,2,rep,name=claimed_attachments,json=claimedAttachments,proto3" json:"claimed_attachments,omitempty"` FailedAttachmentIds []string `protobuf:"bytes,3,rep,name=failed_attachment_ids,json=failedAttachmentIds,proto3" json:"failed_attachment_ids,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ClaimAttachmentResponse) Reset() { *x = ClaimAttachmentResponse{} mi := &file_proto_mail_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ClaimAttachmentResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClaimAttachmentResponse) ProtoMessage() {} func (x *ClaimAttachmentResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClaimAttachmentResponse.ProtoReflect.Descriptor instead. func (*ClaimAttachmentResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{11} } func (x *ClaimAttachmentResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *ClaimAttachmentResponse) GetClaimedAttachments() []*ClaimedAttachment { if x != nil { return x.ClaimedAttachments } return nil } func (x *ClaimAttachmentResponse) GetFailedAttachmentIds() []string { if x != nil { return x.FailedAttachmentIds } return nil } // 批量领取附件请求 type BatchClaimAttachmentsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` MailIds []string `protobuf:"bytes,2,rep,name=mail_ids,json=mailIds,proto3" json:"mail_ids,omitempty"` ClaimAllAvailable bool `protobuf:"varint,3,opt,name=claim_all_available,json=claimAllAvailable,proto3" json:"claim_all_available,omitempty"` // 领取所有可领取的附件 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BatchClaimAttachmentsRequest) Reset() { *x = BatchClaimAttachmentsRequest{} mi := &file_proto_mail_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BatchClaimAttachmentsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*BatchClaimAttachmentsRequest) ProtoMessage() {} func (x *BatchClaimAttachmentsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BatchClaimAttachmentsRequest.ProtoReflect.Descriptor instead. func (*BatchClaimAttachmentsRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{12} } func (x *BatchClaimAttachmentsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *BatchClaimAttachmentsRequest) GetMailIds() []string { if x != nil { return x.MailIds } return nil } func (x *BatchClaimAttachmentsRequest) GetClaimAllAvailable() bool { if x != nil { return x.ClaimAllAvailable } return false } // 批量领取附件响应 type BatchClaimAttachmentsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ClaimedAttachments []*ClaimedAttachment `protobuf:"bytes,2,rep,name=claimed_attachments,json=claimedAttachments,proto3" json:"claimed_attachments,omitempty"` TotalClaimed int32 `protobuf:"varint,3,opt,name=total_claimed,json=totalClaimed,proto3" json:"total_claimed,omitempty"` FailedMailIds []string `protobuf:"bytes,4,rep,name=failed_mail_ids,json=failedMailIds,proto3" json:"failed_mail_ids,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BatchClaimAttachmentsResponse) Reset() { *x = BatchClaimAttachmentsResponse{} mi := &file_proto_mail_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BatchClaimAttachmentsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*BatchClaimAttachmentsResponse) ProtoMessage() {} func (x *BatchClaimAttachmentsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BatchClaimAttachmentsResponse.ProtoReflect.Descriptor instead. func (*BatchClaimAttachmentsResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{13} } func (x *BatchClaimAttachmentsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *BatchClaimAttachmentsResponse) GetClaimedAttachments() []*ClaimedAttachment { if x != nil { return x.ClaimedAttachments } return nil } func (x *BatchClaimAttachmentsResponse) GetTotalClaimed() int32 { if x != nil { return x.TotalClaimed } return 0 } func (x *BatchClaimAttachmentsResponse) GetFailedMailIds() []string { if x != nil { return x.FailedMailIds } return nil } // 标记邮件请求 type MarkMailRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` MailIds []string `protobuf:"bytes,2,rep,name=mail_ids,json=mailIds,proto3" json:"mail_ids,omitempty"` MarkType MailMarkType `protobuf:"varint,3,opt,name=mark_type,json=markType,proto3,enum=greatestworks.mail.MailMarkType" json:"mark_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MarkMailRequest) Reset() { *x = MarkMailRequest{} mi := &file_proto_mail_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MarkMailRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*MarkMailRequest) ProtoMessage() {} func (x *MarkMailRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MarkMailRequest.ProtoReflect.Descriptor instead. func (*MarkMailRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{14} } func (x *MarkMailRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *MarkMailRequest) GetMailIds() []string { if x != nil { return x.MailIds } return nil } func (x *MarkMailRequest) GetMarkType() MailMarkType { if x != nil { return x.MarkType } return MailMarkType_MAIL_MARK_TYPE_UNSPECIFIED } // 标记邮件响应 type MarkMailResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` MarkedCount int32 `protobuf:"varint,2,opt,name=marked_count,json=markedCount,proto3" json:"marked_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MarkMailResponse) Reset() { *x = MarkMailResponse{} mi := &file_proto_mail_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MarkMailResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*MarkMailResponse) ProtoMessage() {} func (x *MarkMailResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MarkMailResponse.ProtoReflect.Descriptor instead. func (*MarkMailResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{15} } func (x *MarkMailResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *MarkMailResponse) GetMarkedCount() int32 { if x != nil { return x.MarkedCount } return 0 } // 搜索邮件请求 type SearchMailsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Keyword string `protobuf:"bytes,2,opt,name=keyword,proto3" json:"keyword,omitempty"` // 搜索关键词 SenderName string `protobuf:"bytes,3,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` // 发件人名称 MailType MailType `protobuf:"varint,4,opt,name=mail_type,json=mailType,proto3,enum=greatestworks.mail.MailType" json:"mail_type,omitempty"` StartDate int64 `protobuf:"varint,5,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` EndDate int64 `protobuf:"varint,6,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` Limit int32 `protobuf:"varint,7,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,8,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SearchMailsRequest) Reset() { *x = SearchMailsRequest{} mi := &file_proto_mail_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SearchMailsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchMailsRequest) ProtoMessage() {} func (x *SearchMailsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchMailsRequest.ProtoReflect.Descriptor instead. func (*SearchMailsRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{16} } func (x *SearchMailsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *SearchMailsRequest) GetKeyword() string { if x != nil { return x.Keyword } return "" } func (x *SearchMailsRequest) GetSenderName() string { if x != nil { return x.SenderName } return "" } func (x *SearchMailsRequest) GetMailType() MailType { if x != nil { return x.MailType } return MailType_MAIL_TYPE_UNSPECIFIED } func (x *SearchMailsRequest) GetStartDate() int64 { if x != nil { return x.StartDate } return 0 } func (x *SearchMailsRequest) GetEndDate() int64 { if x != nil { return x.EndDate } return 0 } func (x *SearchMailsRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *SearchMailsRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 搜索邮件响应 type SearchMailsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Mails []*MailInfo `protobuf:"bytes,2,rep,name=mails,proto3" json:"mails,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SearchMailsResponse) Reset() { *x = SearchMailsResponse{} mi := &file_proto_mail_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SearchMailsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchMailsResponse) ProtoMessage() {} func (x *SearchMailsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchMailsResponse.ProtoReflect.Descriptor instead. func (*SearchMailsResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{17} } func (x *SearchMailsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SearchMailsResponse) GetMails() []*MailInfo { if x != nil { return x.Mails } return nil } func (x *SearchMailsResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 获取邮件统计请求 type GetMailStatsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetMailStatsRequest) Reset() { *x = GetMailStatsRequest{} mi := &file_proto_mail_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetMailStatsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetMailStatsRequest) ProtoMessage() {} func (x *GetMailStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetMailStatsRequest.ProtoReflect.Descriptor instead. func (*GetMailStatsRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{18} } func (x *GetMailStatsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } // 获取邮件统计响应 type GetMailStatsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Stats *MailStats `protobuf:"bytes,2,opt,name=stats,proto3" json:"stats,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetMailStatsResponse) Reset() { *x = GetMailStatsResponse{} mi := &file_proto_mail_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetMailStatsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetMailStatsResponse) ProtoMessage() {} func (x *GetMailStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetMailStatsResponse.ProtoReflect.Descriptor instead. func (*GetMailStatsResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{19} } func (x *GetMailStatsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetMailStatsResponse) GetStats() *MailStats { if x != nil { return x.Stats } return nil } // 设置邮件配置请求 type SetMailConfigRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Config *MailConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetMailConfigRequest) Reset() { *x = SetMailConfigRequest{} mi := &file_proto_mail_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetMailConfigRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetMailConfigRequest) ProtoMessage() {} func (x *SetMailConfigRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetMailConfigRequest.ProtoReflect.Descriptor instead. func (*SetMailConfigRequest) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{20} } func (x *SetMailConfigRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *SetMailConfigRequest) GetConfig() *MailConfig { if x != nil { return x.Config } return nil } // 设置邮件配置响应 type SetMailConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Config *MailConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetMailConfigResponse) Reset() { *x = SetMailConfigResponse{} mi := &file_proto_mail_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetMailConfigResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetMailConfigResponse) ProtoMessage() {} func (x *SetMailConfigResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetMailConfigResponse.ProtoReflect.Descriptor instead. func (*SetMailConfigResponse) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{21} } func (x *SetMailConfigResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SetMailConfigResponse) GetConfig() *MailConfig { if x != nil { return x.Config } return nil } // 邮件信息 type MailInfo struct { state protoimpl.MessageState `protogen:"open.v1"` MailId string `protobuf:"bytes,1,opt,name=mail_id,json=mailId,proto3" json:"mail_id,omitempty"` SenderId string `protobuf:"bytes,2,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` SenderName string `protobuf:"bytes,3,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` RecipientId string `protobuf:"bytes,4,opt,name=recipient_id,json=recipientId,proto3" json:"recipient_id,omitempty"` Subject string `protobuf:"bytes,5,opt,name=subject,proto3" json:"subject,omitempty"` MailType MailType `protobuf:"varint,6,opt,name=mail_type,json=mailType,proto3,enum=greatestworks.mail.MailType" json:"mail_type,omitempty"` Status MailStatus `protobuf:"varint,7,opt,name=status,proto3,enum=greatestworks.mail.MailStatus" json:"status,omitempty"` HasAttachments bool `protobuf:"varint,8,opt,name=has_attachments,json=hasAttachments,proto3" json:"has_attachments,omitempty"` HasUnclaimedAttachments bool `protobuf:"varint,9,opt,name=has_unclaimed_attachments,json=hasUnclaimedAttachments,proto3" json:"has_unclaimed_attachments,omitempty"` SentAt int64 `protobuf:"varint,10,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"` ReadAt int64 `protobuf:"varint,11,opt,name=read_at,json=readAt,proto3" json:"read_at,omitempty"` ExpireAt int64 `protobuf:"varint,12,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty"` IsImportant bool `protobuf:"varint,13,opt,name=is_important,json=isImportant,proto3" json:"is_important,omitempty"` IsFavorite bool `protobuf:"varint,14,opt,name=is_favorite,json=isFavorite,proto3" json:"is_favorite,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MailInfo) Reset() { *x = MailInfo{} mi := &file_proto_mail_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MailInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*MailInfo) ProtoMessage() {} func (x *MailInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MailInfo.ProtoReflect.Descriptor instead. func (*MailInfo) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{22} } func (x *MailInfo) GetMailId() string { if x != nil { return x.MailId } return "" } func (x *MailInfo) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *MailInfo) GetSenderName() string { if x != nil { return x.SenderName } return "" } func (x *MailInfo) GetRecipientId() string { if x != nil { return x.RecipientId } return "" } func (x *MailInfo) GetSubject() string { if x != nil { return x.Subject } return "" } func (x *MailInfo) GetMailType() MailType { if x != nil { return x.MailType } return MailType_MAIL_TYPE_UNSPECIFIED } func (x *MailInfo) GetStatus() MailStatus { if x != nil { return x.Status } return MailStatus_MAIL_STATUS_UNSPECIFIED } func (x *MailInfo) GetHasAttachments() bool { if x != nil { return x.HasAttachments } return false } func (x *MailInfo) GetHasUnclaimedAttachments() bool { if x != nil { return x.HasUnclaimedAttachments } return false } func (x *MailInfo) GetSentAt() int64 { if x != nil { return x.SentAt } return 0 } func (x *MailInfo) GetReadAt() int64 { if x != nil { return x.ReadAt } return 0 } func (x *MailInfo) GetExpireAt() int64 { if x != nil { return x.ExpireAt } return 0 } func (x *MailInfo) GetIsImportant() bool { if x != nil { return x.IsImportant } return false } func (x *MailInfo) GetIsFavorite() bool { if x != nil { return x.IsFavorite } return false } // 邮件详情 type MailDetail struct { state protoimpl.MessageState `protogen:"open.v1"` MailId string `protobuf:"bytes,1,opt,name=mail_id,json=mailId,proto3" json:"mail_id,omitempty"` SenderId string `protobuf:"bytes,2,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` SenderName string `protobuf:"bytes,3,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` RecipientId string `protobuf:"bytes,4,opt,name=recipient_id,json=recipientId,proto3" json:"recipient_id,omitempty"` Subject string `protobuf:"bytes,5,opt,name=subject,proto3" json:"subject,omitempty"` Content string `protobuf:"bytes,6,opt,name=content,proto3" json:"content,omitempty"` MailType MailType `protobuf:"varint,7,opt,name=mail_type,json=mailType,proto3,enum=greatestworks.mail.MailType" json:"mail_type,omitempty"` Status MailStatus `protobuf:"varint,8,opt,name=status,proto3,enum=greatestworks.mail.MailStatus" json:"status,omitempty"` Attachments []*MailAttachment `protobuf:"bytes,9,rep,name=attachments,proto3" json:"attachments,omitempty"` SentAt int64 `protobuf:"varint,10,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"` ReadAt int64 `protobuf:"varint,11,opt,name=read_at,json=readAt,proto3" json:"read_at,omitempty"` ExpireAt int64 `protobuf:"varint,12,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty"` IsImportant bool `protobuf:"varint,13,opt,name=is_important,json=isImportant,proto3" json:"is_important,omitempty"` IsFavorite bool `protobuf:"varint,14,opt,name=is_favorite,json=isFavorite,proto3" json:"is_favorite,omitempty"` Metadata map[string]string `protobuf:"bytes,15,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MailDetail) Reset() { *x = MailDetail{} mi := &file_proto_mail_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MailDetail) String() string { return protoimpl.X.MessageStringOf(x) } func (*MailDetail) ProtoMessage() {} func (x *MailDetail) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MailDetail.ProtoReflect.Descriptor instead. func (*MailDetail) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{23} } func (x *MailDetail) GetMailId() string { if x != nil { return x.MailId } return "" } func (x *MailDetail) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *MailDetail) GetSenderName() string { if x != nil { return x.SenderName } return "" } func (x *MailDetail) GetRecipientId() string { if x != nil { return x.RecipientId } return "" } func (x *MailDetail) GetSubject() string { if x != nil { return x.Subject } return "" } func (x *MailDetail) GetContent() string { if x != nil { return x.Content } return "" } func (x *MailDetail) GetMailType() MailType { if x != nil { return x.MailType } return MailType_MAIL_TYPE_UNSPECIFIED } func (x *MailDetail) GetStatus() MailStatus { if x != nil { return x.Status } return MailStatus_MAIL_STATUS_UNSPECIFIED } func (x *MailDetail) GetAttachments() []*MailAttachment { if x != nil { return x.Attachments } return nil } func (x *MailDetail) GetSentAt() int64 { if x != nil { return x.SentAt } return 0 } func (x *MailDetail) GetReadAt() int64 { if x != nil { return x.ReadAt } return 0 } func (x *MailDetail) GetExpireAt() int64 { if x != nil { return x.ExpireAt } return 0 } func (x *MailDetail) GetIsImportant() bool { if x != nil { return x.IsImportant } return false } func (x *MailDetail) GetIsFavorite() bool { if x != nil { return x.IsFavorite } return false } func (x *MailDetail) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 邮件附件 type MailAttachment struct { state protoimpl.MessageState `protogen:"open.v1"` AttachmentId string `protobuf:"bytes,1,opt,name=attachment_id,json=attachmentId,proto3" json:"attachment_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` AttachmentType AttachmentType `protobuf:"varint,3,opt,name=attachment_type,json=attachmentType,proto3,enum=greatestworks.mail.AttachmentType" json:"attachment_type,omitempty"` ItemId string `protobuf:"bytes,4,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` // 物品ID Quantity int32 `protobuf:"varint,5,opt,name=quantity,proto3" json:"quantity,omitempty"` // 数量 GoldAmount int32 `protobuf:"varint,6,opt,name=gold_amount,json=goldAmount,proto3" json:"gold_amount,omitempty"` // 金币数量 DiamondAmount int32 `protobuf:"varint,7,opt,name=diamond_amount,json=diamondAmount,proto3" json:"diamond_amount,omitempty"` // 钻石数量 IsClaimed bool `protobuf:"varint,8,opt,name=is_claimed,json=isClaimed,proto3" json:"is_claimed,omitempty"` ClaimedAt int64 `protobuf:"varint,9,opt,name=claimed_at,json=claimedAt,proto3" json:"claimed_at,omitempty"` Properties map[string]string `protobuf:"bytes,10,rep,name=properties,proto3" json:"properties,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MailAttachment) Reset() { *x = MailAttachment{} mi := &file_proto_mail_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MailAttachment) String() string { return protoimpl.X.MessageStringOf(x) } func (*MailAttachment) ProtoMessage() {} func (x *MailAttachment) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MailAttachment.ProtoReflect.Descriptor instead. func (*MailAttachment) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{24} } func (x *MailAttachment) GetAttachmentId() string { if x != nil { return x.AttachmentId } return "" } func (x *MailAttachment) GetName() string { if x != nil { return x.Name } return "" } func (x *MailAttachment) GetAttachmentType() AttachmentType { if x != nil { return x.AttachmentType } return AttachmentType_ATTACHMENT_TYPE_UNSPECIFIED } func (x *MailAttachment) GetItemId() string { if x != nil { return x.ItemId } return "" } func (x *MailAttachment) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } func (x *MailAttachment) GetGoldAmount() int32 { if x != nil { return x.GoldAmount } return 0 } func (x *MailAttachment) GetDiamondAmount() int32 { if x != nil { return x.DiamondAmount } return 0 } func (x *MailAttachment) GetIsClaimed() bool { if x != nil { return x.IsClaimed } return false } func (x *MailAttachment) GetClaimedAt() int64 { if x != nil { return x.ClaimedAt } return 0 } func (x *MailAttachment) GetProperties() map[string]string { if x != nil { return x.Properties } return nil } // 已领取附件 type ClaimedAttachment struct { state protoimpl.MessageState `protogen:"open.v1"` AttachmentId string `protobuf:"bytes,1,opt,name=attachment_id,json=attachmentId,proto3" json:"attachment_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` AttachmentType AttachmentType `protobuf:"varint,3,opt,name=attachment_type,json=attachmentType,proto3,enum=greatestworks.mail.AttachmentType" json:"attachment_type,omitempty"` Quantity int32 `protobuf:"varint,4,opt,name=quantity,proto3" json:"quantity,omitempty"` Success bool `protobuf:"varint,5,opt,name=success,proto3" json:"success,omitempty"` ErrorMessage string `protobuf:"bytes,6,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ClaimedAttachment) Reset() { *x = ClaimedAttachment{} mi := &file_proto_mail_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ClaimedAttachment) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClaimedAttachment) ProtoMessage() {} func (x *ClaimedAttachment) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClaimedAttachment.ProtoReflect.Descriptor instead. func (*ClaimedAttachment) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{25} } func (x *ClaimedAttachment) GetAttachmentId() string { if x != nil { return x.AttachmentId } return "" } func (x *ClaimedAttachment) GetName() string { if x != nil { return x.Name } return "" } func (x *ClaimedAttachment) GetAttachmentType() AttachmentType { if x != nil { return x.AttachmentType } return AttachmentType_ATTACHMENT_TYPE_UNSPECIFIED } func (x *ClaimedAttachment) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } func (x *ClaimedAttachment) GetSuccess() bool { if x != nil { return x.Success } return false } func (x *ClaimedAttachment) GetErrorMessage() string { if x != nil { return x.ErrorMessage } return "" } // 邮件统计 type MailStats struct { state protoimpl.MessageState `protogen:"open.v1"` TotalMails int32 `protobuf:"varint,1,opt,name=total_mails,json=totalMails,proto3" json:"total_mails,omitempty"` UnreadMails int32 `protobuf:"varint,2,opt,name=unread_mails,json=unreadMails,proto3" json:"unread_mails,omitempty"` MailsWithAttachments int32 `protobuf:"varint,3,opt,name=mails_with_attachments,json=mailsWithAttachments,proto3" json:"mails_with_attachments,omitempty"` MailsWithUnclaimedAttachments int32 `protobuf:"varint,4,opt,name=mails_with_unclaimed_attachments,json=mailsWithUnclaimedAttachments,proto3" json:"mails_with_unclaimed_attachments,omitempty"` ImportantMails int32 `protobuf:"varint,5,opt,name=important_mails,json=importantMails,proto3" json:"important_mails,omitempty"` FavoriteMails int32 `protobuf:"varint,6,opt,name=favorite_mails,json=favoriteMails,proto3" json:"favorite_mails,omitempty"` SystemMails int32 `protobuf:"varint,7,opt,name=system_mails,json=systemMails,proto3" json:"system_mails,omitempty"` PlayerMails int32 `protobuf:"varint,8,opt,name=player_mails,json=playerMails,proto3" json:"player_mails,omitempty"` OldestMailDate int64 `protobuf:"varint,9,opt,name=oldest_mail_date,json=oldestMailDate,proto3" json:"oldest_mail_date,omitempty"` NewestMailDate int64 `protobuf:"varint,10,opt,name=newest_mail_date,json=newestMailDate,proto3" json:"newest_mail_date,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MailStats) Reset() { *x = MailStats{} mi := &file_proto_mail_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MailStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*MailStats) ProtoMessage() {} func (x *MailStats) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MailStats.ProtoReflect.Descriptor instead. func (*MailStats) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{26} } func (x *MailStats) GetTotalMails() int32 { if x != nil { return x.TotalMails } return 0 } func (x *MailStats) GetUnreadMails() int32 { if x != nil { return x.UnreadMails } return 0 } func (x *MailStats) GetMailsWithAttachments() int32 { if x != nil { return x.MailsWithAttachments } return 0 } func (x *MailStats) GetMailsWithUnclaimedAttachments() int32 { if x != nil { return x.MailsWithUnclaimedAttachments } return 0 } func (x *MailStats) GetImportantMails() int32 { if x != nil { return x.ImportantMails } return 0 } func (x *MailStats) GetFavoriteMails() int32 { if x != nil { return x.FavoriteMails } return 0 } func (x *MailStats) GetSystemMails() int32 { if x != nil { return x.SystemMails } return 0 } func (x *MailStats) GetPlayerMails() int32 { if x != nil { return x.PlayerMails } return 0 } func (x *MailStats) GetOldestMailDate() int64 { if x != nil { return x.OldestMailDate } return 0 } func (x *MailStats) GetNewestMailDate() int64 { if x != nil { return x.NewestMailDate } return 0 } // 邮件配置 type MailConfig struct { state protoimpl.MessageState `protogen:"open.v1"` AutoDeleteReadMails bool `protobuf:"varint,1,opt,name=auto_delete_read_mails,json=autoDeleteReadMails,proto3" json:"auto_delete_read_mails,omitempty"` AutoDeleteDays int32 `protobuf:"varint,2,opt,name=auto_delete_days,json=autoDeleteDays,proto3" json:"auto_delete_days,omitempty"` // 自动删除天数 NotifyNewMail bool `protobuf:"varint,3,opt,name=notify_new_mail,json=notifyNewMail,proto3" json:"notify_new_mail,omitempty"` NotifySystemMail bool `protobuf:"varint,4,opt,name=notify_system_mail,json=notifySystemMail,proto3" json:"notify_system_mail,omitempty"` AutoClaimAttachments bool `protobuf:"varint,5,opt,name=auto_claim_attachments,json=autoClaimAttachments,proto3" json:"auto_claim_attachments,omitempty"` MaxMailsPerPage int32 `protobuf:"varint,6,opt,name=max_mails_per_page,json=maxMailsPerPage,proto3" json:"max_mails_per_page,omitempty"` BlockedMailTypes []MailType `protobuf:"varint,7,rep,packed,name=blocked_mail_types,json=blockedMailTypes,proto3,enum=greatestworks.mail.MailType" json:"blocked_mail_types,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MailConfig) Reset() { *x = MailConfig{} mi := &file_proto_mail_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MailConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*MailConfig) ProtoMessage() {} func (x *MailConfig) ProtoReflect() protoreflect.Message { mi := &file_proto_mail_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MailConfig.ProtoReflect.Descriptor instead. func (*MailConfig) Descriptor() ([]byte, []int) { return file_proto_mail_proto_rawDescGZIP(), []int{27} } func (x *MailConfig) GetAutoDeleteReadMails() bool { if x != nil { return x.AutoDeleteReadMails } return false } func (x *MailConfig) GetAutoDeleteDays() int32 { if x != nil { return x.AutoDeleteDays } return 0 } func (x *MailConfig) GetNotifyNewMail() bool { if x != nil { return x.NotifyNewMail } return false } func (x *MailConfig) GetNotifySystemMail() bool { if x != nil { return x.NotifySystemMail } return false } func (x *MailConfig) GetAutoClaimAttachments() bool { if x != nil { return x.AutoClaimAttachments } return false } func (x *MailConfig) GetMaxMailsPerPage() int32 { if x != nil { return x.MaxMailsPerPage } return 0 } func (x *MailConfig) GetBlockedMailTypes() []MailType { if x != nil { return x.BlockedMailTypes } return nil } var File_proto_mail_proto protoreflect.FileDescriptor const file_proto_mail_proto_rawDesc = "" + "\n" + "\x10proto/mail.proto\x12\x12greatestworks.mail\x1a\x12proto/common.proto\"\xd7\x03\n" + "\x0fSendMailRequest\x12\x1b\n" + "\tsender_id\x18\x01 \x01(\tR\bsenderId\x12#\n" + "\rrecipient_ids\x18\x02 \x03(\tR\frecipientIds\x12\x18\n" + "\asubject\x18\x03 \x01(\tR\asubject\x12\x18\n" + "\acontent\x18\x04 \x01(\tR\acontent\x129\n" + "\tmail_type\x18\x05 \x01(\x0e2\x1c.greatestworks.mail.MailTypeR\bmailType\x12D\n" + "\vattachments\x18\x06 \x03(\v2\".greatestworks.mail.MailAttachmentR\vattachments\x12\x1b\n" + "\texpire_at\x18\a \x01(\x03R\bexpireAt\x12$\n" + "\x0eis_system_mail\x18\b \x01(\bR\fisSystemMail\x12M\n" + "\bmetadata\x18\t \x03(\v21.greatestworks.mail.SendMailRequest.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"k\n" + "\x10SendMailResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x19\n" + "\bmail_ids\x18\x02 \x03(\tR\amailIds\"\xa7\x02\n" + "\x12GetMailListRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x126\n" + "\x06status\x18\x02 \x01(\x0e2\x1e.greatestworks.mail.MailStatusR\x06status\x129\n" + "\tmail_type\x18\x03 \x01(\x0e2\x1c.greatestworks.mail.MailTypeR\bmailType\x12\x14\n" + "\x05limit\x18\x04 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x05 \x01(\x05R\x06offset\x12\x1f\n" + "\vonly_unread\x18\x06 \x01(\bR\n" + "onlyUnread\x122\n" + "\x15only_with_attachments\x18\a \x01(\bR\x13onlyWithAttachments\"\x82\x02\n" + "\x13GetMailListResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x05mails\x18\x02 \x03(\v2\x1c.greatestworks.mail.MailInfoR\x05mails\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\x123\n" + "\x05stats\x18\x04 \x01(\v2\x1d.greatestworks.mail.MailStatsR\x05stats\"G\n" + "\x0fReadMailRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\amail_id\x18\x02 \x01(\tR\x06mailId\"\x84\x01\n" + "\x10ReadMailResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x04mail\x18\x02 \x01(\v2\x1e.greatestworks.mail.MailDetailR\x04mail\"I\n" + "\x11DeleteMailRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\amail_id\x18\x02 \x01(\tR\x06mailId\"R\n" + "\x12DeleteMailResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\xa0\x01\n" + "\x17BatchDeleteMailsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bmail_ids\x18\x02 \x03(\tR\amailIds\x12&\n" + "\x0fdelete_all_read\x18\x03 \x01(\bR\rdeleteAllRead\x12%\n" + "\x0edelete_expired\x18\x04 \x01(\bR\rdeleteExpired\"\xa5\x01\n" + "\x18BatchDeleteMailsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12#\n" + "\rdeleted_count\x18\x02 \x01(\x05R\fdeletedCount\x12&\n" + "\x0ffailed_mail_ids\x18\x03 \x03(\tR\rfailedMailIds\"u\n" + "\x16ClaimAttachmentRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\amail_id\x18\x02 \x01(\tR\x06mailId\x12%\n" + "\x0eattachment_ids\x18\x03 \x03(\tR\rattachmentIds\"\xe3\x01\n" + "\x17ClaimAttachmentResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12V\n" + "\x13claimed_attachments\x18\x02 \x03(\v2%.greatestworks.mail.ClaimedAttachmentR\x12claimedAttachments\x122\n" + "\x15failed_attachment_ids\x18\x03 \x03(\tR\x13failedAttachmentIds\"\x86\x01\n" + "\x1cBatchClaimAttachmentsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bmail_ids\x18\x02 \x03(\tR\amailIds\x12.\n" + "\x13claim_all_available\x18\x03 \x01(\bR\x11claimAllAvailable\"\x82\x02\n" + "\x1dBatchClaimAttachmentsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12V\n" + "\x13claimed_attachments\x18\x02 \x03(\v2%.greatestworks.mail.ClaimedAttachmentR\x12claimedAttachments\x12#\n" + "\rtotal_claimed\x18\x03 \x01(\x05R\ftotalClaimed\x12&\n" + "\x0ffailed_mail_ids\x18\x04 \x03(\tR\rfailedMailIds\"\x88\x01\n" + "\x0fMarkMailRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bmail_ids\x18\x02 \x03(\tR\amailIds\x12=\n" + "\tmark_type\x18\x03 \x01(\x0e2 .greatestworks.mail.MailMarkTypeR\bmarkType\"s\n" + "\x10MarkMailResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12!\n" + "\fmarked_count\x18\x02 \x01(\x05R\vmarkedCount\"\x8f\x02\n" + "\x12SearchMailsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x18\n" + "\akeyword\x18\x02 \x01(\tR\akeyword\x12\x1f\n" + "\vsender_name\x18\x03 \x01(\tR\n" + "senderName\x129\n" + "\tmail_type\x18\x04 \x01(\x0e2\x1c.greatestworks.mail.MailTypeR\bmailType\x12\x1d\n" + "\n" + "start_date\x18\x05 \x01(\x03R\tstartDate\x12\x19\n" + "\bend_date\x18\x06 \x01(\x03R\aendDate\x12\x14\n" + "\x05limit\x18\a \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\b \x01(\x05R\x06offset\"\xcd\x01\n" + "\x13SearchMailsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x05mails\x18\x02 \x03(\v2\x1c.greatestworks.mail.MailInfoR\x05mails\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"2\n" + "\x13GetMailStatsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\"\x89\x01\n" + "\x14GetMailStatsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x123\n" + "\x05stats\x18\x02 \x01(\v2\x1d.greatestworks.mail.MailStatsR\x05stats\"k\n" + "\x14SetMailConfigRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x126\n" + "\x06config\x18\x02 \x01(\v2\x1e.greatestworks.mail.MailConfigR\x06config\"\x8d\x01\n" + "\x15SetMailConfigResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x126\n" + "\x06config\x18\x02 \x01(\v2\x1e.greatestworks.mail.MailConfigR\x06config\"\x89\x04\n" + "\bMailInfo\x12\x17\n" + "\amail_id\x18\x01 \x01(\tR\x06mailId\x12\x1b\n" + "\tsender_id\x18\x02 \x01(\tR\bsenderId\x12\x1f\n" + "\vsender_name\x18\x03 \x01(\tR\n" + "senderName\x12!\n" + "\frecipient_id\x18\x04 \x01(\tR\vrecipientId\x12\x18\n" + "\asubject\x18\x05 \x01(\tR\asubject\x129\n" + "\tmail_type\x18\x06 \x01(\x0e2\x1c.greatestworks.mail.MailTypeR\bmailType\x126\n" + "\x06status\x18\a \x01(\x0e2\x1e.greatestworks.mail.MailStatusR\x06status\x12'\n" + "\x0fhas_attachments\x18\b \x01(\bR\x0ehasAttachments\x12:\n" + "\x19has_unclaimed_attachments\x18\t \x01(\bR\x17hasUnclaimedAttachments\x12\x17\n" + "\asent_at\x18\n" + " \x01(\x03R\x06sentAt\x12\x17\n" + "\aread_at\x18\v \x01(\x03R\x06readAt\x12\x1b\n" + "\texpire_at\x18\f \x01(\x03R\bexpireAt\x12!\n" + "\fis_important\x18\r \x01(\bR\visImportant\x12\x1f\n" + "\vis_favorite\x18\x0e \x01(\bR\n" + "isFavorite\"\x8d\x05\n" + "\n" + "MailDetail\x12\x17\n" + "\amail_id\x18\x01 \x01(\tR\x06mailId\x12\x1b\n" + "\tsender_id\x18\x02 \x01(\tR\bsenderId\x12\x1f\n" + "\vsender_name\x18\x03 \x01(\tR\n" + "senderName\x12!\n" + "\frecipient_id\x18\x04 \x01(\tR\vrecipientId\x12\x18\n" + "\asubject\x18\x05 \x01(\tR\asubject\x12\x18\n" + "\acontent\x18\x06 \x01(\tR\acontent\x129\n" + "\tmail_type\x18\a \x01(\x0e2\x1c.greatestworks.mail.MailTypeR\bmailType\x126\n" + "\x06status\x18\b \x01(\x0e2\x1e.greatestworks.mail.MailStatusR\x06status\x12D\n" + "\vattachments\x18\t \x03(\v2\".greatestworks.mail.MailAttachmentR\vattachments\x12\x17\n" + "\asent_at\x18\n" + " \x01(\x03R\x06sentAt\x12\x17\n" + "\aread_at\x18\v \x01(\x03R\x06readAt\x12\x1b\n" + "\texpire_at\x18\f \x01(\x03R\bexpireAt\x12!\n" + "\fis_important\x18\r \x01(\bR\visImportant\x12\x1f\n" + "\vis_favorite\x18\x0e \x01(\bR\n" + "isFavorite\x12H\n" + "\bmetadata\x18\x0f \x03(\v2,.greatestworks.mail.MailDetail.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe4\x03\n" + "\x0eMailAttachment\x12#\n" + "\rattachment_id\x18\x01 \x01(\tR\fattachmentId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12K\n" + "\x0fattachment_type\x18\x03 \x01(\x0e2\".greatestworks.mail.AttachmentTypeR\x0eattachmentType\x12\x17\n" + "\aitem_id\x18\x04 \x01(\tR\x06itemId\x12\x1a\n" + "\bquantity\x18\x05 \x01(\x05R\bquantity\x12\x1f\n" + "\vgold_amount\x18\x06 \x01(\x05R\n" + "goldAmount\x12%\n" + "\x0ediamond_amount\x18\a \x01(\x05R\rdiamondAmount\x12\x1d\n" + "\n" + "is_claimed\x18\b \x01(\bR\tisClaimed\x12\x1d\n" + "\n" + "claimed_at\x18\t \x01(\x03R\tclaimedAt\x12R\n" + "\n" + "properties\x18\n" + " \x03(\v22.greatestworks.mail.MailAttachment.PropertiesEntryR\n" + "properties\x1a=\n" + "\x0fPropertiesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf4\x01\n" + "\x11ClaimedAttachment\x12#\n" + "\rattachment_id\x18\x01 \x01(\tR\fattachmentId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12K\n" + "\x0fattachment_type\x18\x03 \x01(\x0e2\".greatestworks.mail.AttachmentTypeR\x0eattachmentType\x12\x1a\n" + "\bquantity\x18\x04 \x01(\x05R\bquantity\x12\x18\n" + "\asuccess\x18\x05 \x01(\bR\asuccess\x12#\n" + "\rerror_message\x18\x06 \x01(\tR\ferrorMessage\"\xb8\x03\n" + "\tMailStats\x12\x1f\n" + "\vtotal_mails\x18\x01 \x01(\x05R\n" + "totalMails\x12!\n" + "\funread_mails\x18\x02 \x01(\x05R\vunreadMails\x124\n" + "\x16mails_with_attachments\x18\x03 \x01(\x05R\x14mailsWithAttachments\x12G\n" + " mails_with_unclaimed_attachments\x18\x04 \x01(\x05R\x1dmailsWithUnclaimedAttachments\x12'\n" + "\x0fimportant_mails\x18\x05 \x01(\x05R\x0eimportantMails\x12%\n" + "\x0efavorite_mails\x18\x06 \x01(\x05R\rfavoriteMails\x12!\n" + "\fsystem_mails\x18\a \x01(\x05R\vsystemMails\x12!\n" + "\fplayer_mails\x18\b \x01(\x05R\vplayerMails\x12(\n" + "\x10oldest_mail_date\x18\t \x01(\x03R\x0eoldestMailDate\x12(\n" + "\x10newest_mail_date\x18\n" + " \x01(\x03R\x0enewestMailDate\"\xf0\x02\n" + "\n" + "MailConfig\x123\n" + "\x16auto_delete_read_mails\x18\x01 \x01(\bR\x13autoDeleteReadMails\x12(\n" + "\x10auto_delete_days\x18\x02 \x01(\x05R\x0eautoDeleteDays\x12&\n" + "\x0fnotify_new_mail\x18\x03 \x01(\bR\rnotifyNewMail\x12,\n" + "\x12notify_system_mail\x18\x04 \x01(\bR\x10notifySystemMail\x124\n" + "\x16auto_claim_attachments\x18\x05 \x01(\bR\x14autoClaimAttachments\x12+\n" + "\x12max_mails_per_page\x18\x06 \x01(\x05R\x0fmaxMailsPerPage\x12J\n" + "\x12blocked_mail_types\x18\a \x03(\x0e2\x1c.greatestworks.mail.MailTypeR\x10blockedMailTypes*\x9a\x02\n" + "\bMailType\x12\x19\n" + "\x15MAIL_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10MAIL_TYPE_SYSTEM\x10\x01\x12\x14\n" + "\x10MAIL_TYPE_PLAYER\x10\x02\x12\x14\n" + "\x10MAIL_TYPE_REWARD\x10\x03\x12\x1a\n" + "\x16MAIL_TYPE_NOTIFICATION\x10\x04\x12\x17\n" + "\x13MAIL_TYPE_PROMOTION\x10\x05\x12\x1a\n" + "\x16MAIL_TYPE_ANNOUNCEMENT\x10\x06\x12\x12\n" + "\x0eMAIL_TYPE_GIFT\x10\a\x12\x1a\n" + "\x16MAIL_TYPE_COMPENSATION\x10\b\x12\x1b\n" + "\x17MAIL_TYPE_BATTLE_REPORT\x10\t\x12\x13\n" + "\x0fMAIL_TYPE_GUILD\x10\n" + "*\xa3\x01\n" + "\n" + "MailStatus\x12\x1b\n" + "\x17MAIL_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12MAIL_STATUS_UNREAD\x10\x01\x12\x14\n" + "\x10MAIL_STATUS_READ\x10\x02\x12\x18\n" + "\x14MAIL_STATUS_ARCHIVED\x10\x03\x12\x17\n" + "\x13MAIL_STATUS_DELETED\x10\x04\x12\x17\n" + "\x13MAIL_STATUS_EXPIRED\x10\x05*\xdf\x01\n" + "\x0eAttachmentType\x12\x1f\n" + "\x1bATTACHMENT_TYPE_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14ATTACHMENT_TYPE_ITEM\x10\x01\x12\x1c\n" + "\x18ATTACHMENT_TYPE_CURRENCY\x10\x02\x12\x1e\n" + "\x1aATTACHMENT_TYPE_EXPERIENCE\x10\x03\x12\x18\n" + "\x14ATTACHMENT_TYPE_BUFF\x10\x04\x12\x19\n" + "\x15ATTACHMENT_TYPE_TITLE\x10\x05\x12\x1f\n" + "\x1bATTACHMENT_TYPE_ACHIEVEMENT\x10\x06*\xdc\x01\n" + "\fMailMarkType\x12\x1e\n" + "\x1aMAIL_MARK_TYPE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13MAIL_MARK_TYPE_READ\x10\x01\x12\x19\n" + "\x15MAIL_MARK_TYPE_UNREAD\x10\x02\x12\x1c\n" + "\x18MAIL_MARK_TYPE_IMPORTANT\x10\x03\x12\x1e\n" + "\x1aMAIL_MARK_TYPE_UNIMPORTANT\x10\x04\x12\x1b\n" + "\x17MAIL_MARK_TYPE_FAVORITE\x10\x05\x12\x1d\n" + "\x19MAIL_MARK_TYPE_UNFAVORITE\x10\x062\xd1\b\n" + "\vMailService\x12U\n" + "\bSendMail\x12#.greatestworks.mail.SendMailRequest\x1a$.greatestworks.mail.SendMailResponse\x12^\n" + "\vGetMailList\x12&.greatestworks.mail.GetMailListRequest\x1a'.greatestworks.mail.GetMailListResponse\x12U\n" + "\bReadMail\x12#.greatestworks.mail.ReadMailRequest\x1a$.greatestworks.mail.ReadMailResponse\x12[\n" + "\n" + "DeleteMail\x12%.greatestworks.mail.DeleteMailRequest\x1a&.greatestworks.mail.DeleteMailResponse\x12m\n" + "\x10BatchDeleteMails\x12+.greatestworks.mail.BatchDeleteMailsRequest\x1a,.greatestworks.mail.BatchDeleteMailsResponse\x12j\n" + "\x0fClaimAttachment\x12*.greatestworks.mail.ClaimAttachmentRequest\x1a+.greatestworks.mail.ClaimAttachmentResponse\x12|\n" + "\x15BatchClaimAttachments\x120.greatestworks.mail.BatchClaimAttachmentsRequest\x1a1.greatestworks.mail.BatchClaimAttachmentsResponse\x12U\n" + "\bMarkMail\x12#.greatestworks.mail.MarkMailRequest\x1a$.greatestworks.mail.MarkMailResponse\x12^\n" + "\vSearchMails\x12&.greatestworks.mail.SearchMailsRequest\x1a'.greatestworks.mail.SearchMailsResponse\x12a\n" + "\fGetMailStats\x12'.greatestworks.mail.GetMailStatsRequest\x1a(.greatestworks.mail.GetMailStatsResponse\x12d\n" + "\rSetMailConfig\x12(.greatestworks.mail.SetMailConfigRequest\x1a).greatestworks.mail.SetMailConfigResponseB8Z!greatestworks/internal/proto/mail\xaa\x02\x12GreatestWorks.Mailb\x06proto3" var ( file_proto_mail_proto_rawDescOnce sync.Once file_proto_mail_proto_rawDescData []byte ) func file_proto_mail_proto_rawDescGZIP() []byte { file_proto_mail_proto_rawDescOnce.Do(func() { file_proto_mail_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_mail_proto_rawDesc), len(file_proto_mail_proto_rawDesc))) }) return file_proto_mail_proto_rawDescData } var file_proto_mail_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_proto_mail_proto_msgTypes = make([]protoimpl.MessageInfo, 31) var file_proto_mail_proto_goTypes = []any{ (MailType)(0), // 0: greatestworks.mail.MailType (MailStatus)(0), // 1: greatestworks.mail.MailStatus (AttachmentType)(0), // 2: greatestworks.mail.AttachmentType (MailMarkType)(0), // 3: greatestworks.mail.MailMarkType (*SendMailRequest)(nil), // 4: greatestworks.mail.SendMailRequest (*SendMailResponse)(nil), // 5: greatestworks.mail.SendMailResponse (*GetMailListRequest)(nil), // 6: greatestworks.mail.GetMailListRequest (*GetMailListResponse)(nil), // 7: greatestworks.mail.GetMailListResponse (*ReadMailRequest)(nil), // 8: greatestworks.mail.ReadMailRequest (*ReadMailResponse)(nil), // 9: greatestworks.mail.ReadMailResponse (*DeleteMailRequest)(nil), // 10: greatestworks.mail.DeleteMailRequest (*DeleteMailResponse)(nil), // 11: greatestworks.mail.DeleteMailResponse (*BatchDeleteMailsRequest)(nil), // 12: greatestworks.mail.BatchDeleteMailsRequest (*BatchDeleteMailsResponse)(nil), // 13: greatestworks.mail.BatchDeleteMailsResponse (*ClaimAttachmentRequest)(nil), // 14: greatestworks.mail.ClaimAttachmentRequest (*ClaimAttachmentResponse)(nil), // 15: greatestworks.mail.ClaimAttachmentResponse (*BatchClaimAttachmentsRequest)(nil), // 16: greatestworks.mail.BatchClaimAttachmentsRequest (*BatchClaimAttachmentsResponse)(nil), // 17: greatestworks.mail.BatchClaimAttachmentsResponse (*MarkMailRequest)(nil), // 18: greatestworks.mail.MarkMailRequest (*MarkMailResponse)(nil), // 19: greatestworks.mail.MarkMailResponse (*SearchMailsRequest)(nil), // 20: greatestworks.mail.SearchMailsRequest (*SearchMailsResponse)(nil), // 21: greatestworks.mail.SearchMailsResponse (*GetMailStatsRequest)(nil), // 22: greatestworks.mail.GetMailStatsRequest (*GetMailStatsResponse)(nil), // 23: greatestworks.mail.GetMailStatsResponse (*SetMailConfigRequest)(nil), // 24: greatestworks.mail.SetMailConfigRequest (*SetMailConfigResponse)(nil), // 25: greatestworks.mail.SetMailConfigResponse (*MailInfo)(nil), // 26: greatestworks.mail.MailInfo (*MailDetail)(nil), // 27: greatestworks.mail.MailDetail (*MailAttachment)(nil), // 28: greatestworks.mail.MailAttachment (*ClaimedAttachment)(nil), // 29: greatestworks.mail.ClaimedAttachment (*MailStats)(nil), // 30: greatestworks.mail.MailStats (*MailConfig)(nil), // 31: greatestworks.mail.MailConfig nil, // 32: greatestworks.mail.SendMailRequest.MetadataEntry nil, // 33: greatestworks.mail.MailDetail.MetadataEntry nil, // 34: greatestworks.mail.MailAttachment.PropertiesEntry (*common.CommonResponse)(nil), // 35: greatestworks.common.CommonResponse (*common.PaginationInfo)(nil), // 36: greatestworks.common.PaginationInfo } var file_proto_mail_proto_depIdxs = []int32{ 0, // 0: greatestworks.mail.SendMailRequest.mail_type:type_name -> greatestworks.mail.MailType 28, // 1: greatestworks.mail.SendMailRequest.attachments:type_name -> greatestworks.mail.MailAttachment 32, // 2: greatestworks.mail.SendMailRequest.metadata:type_name -> greatestworks.mail.SendMailRequest.MetadataEntry 35, // 3: greatestworks.mail.SendMailResponse.common:type_name -> greatestworks.common.CommonResponse 1, // 4: greatestworks.mail.GetMailListRequest.status:type_name -> greatestworks.mail.MailStatus 0, // 5: greatestworks.mail.GetMailListRequest.mail_type:type_name -> greatestworks.mail.MailType 35, // 6: greatestworks.mail.GetMailListResponse.common:type_name -> greatestworks.common.CommonResponse 26, // 7: greatestworks.mail.GetMailListResponse.mails:type_name -> greatestworks.mail.MailInfo 36, // 8: greatestworks.mail.GetMailListResponse.pagination:type_name -> greatestworks.common.PaginationInfo 30, // 9: greatestworks.mail.GetMailListResponse.stats:type_name -> greatestworks.mail.MailStats 35, // 10: greatestworks.mail.ReadMailResponse.common:type_name -> greatestworks.common.CommonResponse 27, // 11: greatestworks.mail.ReadMailResponse.mail:type_name -> greatestworks.mail.MailDetail 35, // 12: greatestworks.mail.DeleteMailResponse.common:type_name -> greatestworks.common.CommonResponse 35, // 13: greatestworks.mail.BatchDeleteMailsResponse.common:type_name -> greatestworks.common.CommonResponse 35, // 14: greatestworks.mail.ClaimAttachmentResponse.common:type_name -> greatestworks.common.CommonResponse 29, // 15: greatestworks.mail.ClaimAttachmentResponse.claimed_attachments:type_name -> greatestworks.mail.ClaimedAttachment 35, // 16: greatestworks.mail.BatchClaimAttachmentsResponse.common:type_name -> greatestworks.common.CommonResponse 29, // 17: greatestworks.mail.BatchClaimAttachmentsResponse.claimed_attachments:type_name -> greatestworks.mail.ClaimedAttachment 3, // 18: greatestworks.mail.MarkMailRequest.mark_type:type_name -> greatestworks.mail.MailMarkType 35, // 19: greatestworks.mail.MarkMailResponse.common:type_name -> greatestworks.common.CommonResponse 0, // 20: greatestworks.mail.SearchMailsRequest.mail_type:type_name -> greatestworks.mail.MailType 35, // 21: greatestworks.mail.SearchMailsResponse.common:type_name -> greatestworks.common.CommonResponse 26, // 22: greatestworks.mail.SearchMailsResponse.mails:type_name -> greatestworks.mail.MailInfo 36, // 23: greatestworks.mail.SearchMailsResponse.pagination:type_name -> greatestworks.common.PaginationInfo 35, // 24: greatestworks.mail.GetMailStatsResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 25: greatestworks.mail.GetMailStatsResponse.stats:type_name -> greatestworks.mail.MailStats 31, // 26: greatestworks.mail.SetMailConfigRequest.config:type_name -> greatestworks.mail.MailConfig 35, // 27: greatestworks.mail.SetMailConfigResponse.common:type_name -> greatestworks.common.CommonResponse 31, // 28: greatestworks.mail.SetMailConfigResponse.config:type_name -> greatestworks.mail.MailConfig 0, // 29: greatestworks.mail.MailInfo.mail_type:type_name -> greatestworks.mail.MailType 1, // 30: greatestworks.mail.MailInfo.status:type_name -> greatestworks.mail.MailStatus 0, // 31: greatestworks.mail.MailDetail.mail_type:type_name -> greatestworks.mail.MailType 1, // 32: greatestworks.mail.MailDetail.status:type_name -> greatestworks.mail.MailStatus 28, // 33: greatestworks.mail.MailDetail.attachments:type_name -> greatestworks.mail.MailAttachment 33, // 34: greatestworks.mail.MailDetail.metadata:type_name -> greatestworks.mail.MailDetail.MetadataEntry 2, // 35: greatestworks.mail.MailAttachment.attachment_type:type_name -> greatestworks.mail.AttachmentType 34, // 36: greatestworks.mail.MailAttachment.properties:type_name -> greatestworks.mail.MailAttachment.PropertiesEntry 2, // 37: greatestworks.mail.ClaimedAttachment.attachment_type:type_name -> greatestworks.mail.AttachmentType 0, // 38: greatestworks.mail.MailConfig.blocked_mail_types:type_name -> greatestworks.mail.MailType 4, // 39: greatestworks.mail.MailService.SendMail:input_type -> greatestworks.mail.SendMailRequest 6, // 40: greatestworks.mail.MailService.GetMailList:input_type -> greatestworks.mail.GetMailListRequest 8, // 41: greatestworks.mail.MailService.ReadMail:input_type -> greatestworks.mail.ReadMailRequest 10, // 42: greatestworks.mail.MailService.DeleteMail:input_type -> greatestworks.mail.DeleteMailRequest 12, // 43: greatestworks.mail.MailService.BatchDeleteMails:input_type -> greatestworks.mail.BatchDeleteMailsRequest 14, // 44: greatestworks.mail.MailService.ClaimAttachment:input_type -> greatestworks.mail.ClaimAttachmentRequest 16, // 45: greatestworks.mail.MailService.BatchClaimAttachments:input_type -> greatestworks.mail.BatchClaimAttachmentsRequest 18, // 46: greatestworks.mail.MailService.MarkMail:input_type -> greatestworks.mail.MarkMailRequest 20, // 47: greatestworks.mail.MailService.SearchMails:input_type -> greatestworks.mail.SearchMailsRequest 22, // 48: greatestworks.mail.MailService.GetMailStats:input_type -> greatestworks.mail.GetMailStatsRequest 24, // 49: greatestworks.mail.MailService.SetMailConfig:input_type -> greatestworks.mail.SetMailConfigRequest 5, // 50: greatestworks.mail.MailService.SendMail:output_type -> greatestworks.mail.SendMailResponse 7, // 51: greatestworks.mail.MailService.GetMailList:output_type -> greatestworks.mail.GetMailListResponse 9, // 52: greatestworks.mail.MailService.ReadMail:output_type -> greatestworks.mail.ReadMailResponse 11, // 53: greatestworks.mail.MailService.DeleteMail:output_type -> greatestworks.mail.DeleteMailResponse 13, // 54: greatestworks.mail.MailService.BatchDeleteMails:output_type -> greatestworks.mail.BatchDeleteMailsResponse 15, // 55: greatestworks.mail.MailService.ClaimAttachment:output_type -> greatestworks.mail.ClaimAttachmentResponse 17, // 56: greatestworks.mail.MailService.BatchClaimAttachments:output_type -> greatestworks.mail.BatchClaimAttachmentsResponse 19, // 57: greatestworks.mail.MailService.MarkMail:output_type -> greatestworks.mail.MarkMailResponse 21, // 58: greatestworks.mail.MailService.SearchMails:output_type -> greatestworks.mail.SearchMailsResponse 23, // 59: greatestworks.mail.MailService.GetMailStats:output_type -> greatestworks.mail.GetMailStatsResponse 25, // 60: greatestworks.mail.MailService.SetMailConfig:output_type -> greatestworks.mail.SetMailConfigResponse 50, // [50:61] is the sub-list for method output_type 39, // [39:50] is the sub-list for method input_type 39, // [39:39] is the sub-list for extension type_name 39, // [39:39] is the sub-list for extension extendee 0, // [0:39] is the sub-list for field type_name } func init() { file_proto_mail_proto_init() } func file_proto_mail_proto_init() { if File_proto_mail_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_mail_proto_rawDesc), len(file_proto_mail_proto_rawDesc)), NumEnums: 4, NumMessages: 31, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_mail_proto_goTypes, DependencyIndexes: file_proto_mail_proto_depIdxs, EnumInfos: file_proto_mail_proto_enumTypes, MessageInfos: file_proto_mail_proto_msgTypes, }.Build() File_proto_mail_proto = out.File file_proto_mail_proto_goTypes = nil file_proto_mail_proto_depIdxs = nil } ================================================ FILE: internal/proto/messages/messages.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/messages.proto package messages import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 消息号枚举 - 系统消息 (0x0000 - 0x00FF) type SystemMessageID int32 const ( SystemMessageID_SYSTEM_MESSAGE_ID_UNSPECIFIED SystemMessageID = 0 SystemMessageID_MSG_HEARTBEAT SystemMessageID = 1 // 心跳消息 SystemMessageID_MSG_HANDSHAKE SystemMessageID = 2 // 握手消息 SystemMessageID_MSG_AUTH SystemMessageID = 3 // 认证消息 SystemMessageID_MSG_DISCONNECT SystemMessageID = 4 // 断开连接 SystemMessageID_MSG_ERROR SystemMessageID = 5 // 错误消息 SystemMessageID_MSG_PING SystemMessageID = 6 // Ping消息 SystemMessageID_MSG_PONG SystemMessageID = 7 // Pong消息 SystemMessageID_MSG_SYSTEM_INFO SystemMessageID = 8 // 系统信息 SystemMessageID_MSG_SERVER_STATUS SystemMessageID = 9 // 服务器状态 SystemMessageID_MSG_MAINTENANCE SystemMessageID = 10 // 维护通知 ) // Enum value maps for SystemMessageID. var ( SystemMessageID_name = map[int32]string{ 0: "SYSTEM_MESSAGE_ID_UNSPECIFIED", 1: "MSG_HEARTBEAT", 2: "MSG_HANDSHAKE", 3: "MSG_AUTH", 4: "MSG_DISCONNECT", 5: "MSG_ERROR", 6: "MSG_PING", 7: "MSG_PONG", 8: "MSG_SYSTEM_INFO", 9: "MSG_SERVER_STATUS", 10: "MSG_MAINTENANCE", } SystemMessageID_value = map[string]int32{ "SYSTEM_MESSAGE_ID_UNSPECIFIED": 0, "MSG_HEARTBEAT": 1, "MSG_HANDSHAKE": 2, "MSG_AUTH": 3, "MSG_DISCONNECT": 4, "MSG_ERROR": 5, "MSG_PING": 6, "MSG_PONG": 7, "MSG_SYSTEM_INFO": 8, "MSG_SERVER_STATUS": 9, "MSG_MAINTENANCE": 10, } ) func (x SystemMessageID) Enum() *SystemMessageID { p := new(SystemMessageID) *p = x return p } func (x SystemMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SystemMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[0].Descriptor() } func (SystemMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[0] } func (x SystemMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SystemMessageID.Descriptor instead. func (SystemMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{0} } // 消息号枚举 - 玩家相关消息 (0x0100 - 0x01FF) type PlayerMessageID int32 const ( PlayerMessageID_PLAYER_MESSAGE_ID_UNSPECIFIED PlayerMessageID = 0 PlayerMessageID_MSG_PLAYER_LOGIN PlayerMessageID = 257 // 玩家登录 PlayerMessageID_MSG_PLAYER_LOGOUT PlayerMessageID = 258 // 玩家登出 PlayerMessageID_MSG_PLAYER_INFO PlayerMessageID = 259 // 玩家信息 PlayerMessageID_MSG_PLAYER_MOVE PlayerMessageID = 260 // 玩家移动 PlayerMessageID_MSG_PLAYER_CREATE PlayerMessageID = 261 // 创建玩家 PlayerMessageID_MSG_PLAYER_UPDATE PlayerMessageID = 262 // 更新玩家 PlayerMessageID_MSG_PLAYER_DELETE PlayerMessageID = 263 // 删除玩家 PlayerMessageID_MSG_PLAYER_STATUS PlayerMessageID = 264 // 玩家状态 PlayerMessageID_MSG_PLAYER_STATS PlayerMessageID = 265 // 玩家属性 PlayerMessageID_MSG_PLAYER_LEVEL PlayerMessageID = 266 // 玩家升级 PlayerMessageID_MSG_PLAYER_EXP_GAIN PlayerMessageID = 267 // 玩家经验获得 PlayerMessageID_MSG_PLAYER_SYNC PlayerMessageID = 268 // 玩家同步 PlayerMessageID_MSG_PLAYER_BAN PlayerMessageID = 269 // 玩家封禁 PlayerMessageID_MSG_PLAYER_UNBAN PlayerMessageID = 270 // 玩家解封 PlayerMessageID_MSG_PLAYER_GM_UPDATE PlayerMessageID = 271 // GM更新玩家 ) // Enum value maps for PlayerMessageID. var ( PlayerMessageID_name = map[int32]string{ 0: "PLAYER_MESSAGE_ID_UNSPECIFIED", 257: "MSG_PLAYER_LOGIN", 258: "MSG_PLAYER_LOGOUT", 259: "MSG_PLAYER_INFO", 260: "MSG_PLAYER_MOVE", 261: "MSG_PLAYER_CREATE", 262: "MSG_PLAYER_UPDATE", 263: "MSG_PLAYER_DELETE", 264: "MSG_PLAYER_STATUS", 265: "MSG_PLAYER_STATS", 266: "MSG_PLAYER_LEVEL", 267: "MSG_PLAYER_EXP_GAIN", 268: "MSG_PLAYER_SYNC", 269: "MSG_PLAYER_BAN", 270: "MSG_PLAYER_UNBAN", 271: "MSG_PLAYER_GM_UPDATE", } PlayerMessageID_value = map[string]int32{ "PLAYER_MESSAGE_ID_UNSPECIFIED": 0, "MSG_PLAYER_LOGIN": 257, "MSG_PLAYER_LOGOUT": 258, "MSG_PLAYER_INFO": 259, "MSG_PLAYER_MOVE": 260, "MSG_PLAYER_CREATE": 261, "MSG_PLAYER_UPDATE": 262, "MSG_PLAYER_DELETE": 263, "MSG_PLAYER_STATUS": 264, "MSG_PLAYER_STATS": 265, "MSG_PLAYER_LEVEL": 266, "MSG_PLAYER_EXP_GAIN": 267, "MSG_PLAYER_SYNC": 268, "MSG_PLAYER_BAN": 269, "MSG_PLAYER_UNBAN": 270, "MSG_PLAYER_GM_UPDATE": 271, } ) func (x PlayerMessageID) Enum() *PlayerMessageID { p := new(PlayerMessageID) *p = x return p } func (x PlayerMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[1].Descriptor() } func (PlayerMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[1] } func (x PlayerMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerMessageID.Descriptor instead. func (PlayerMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{1} } // 消息号枚举 - 战斗相关消息 (0x0200 - 0x02FF) type BattleMessageID int32 const ( BattleMessageID_BATTLE_MESSAGE_ID_UNSPECIFIED BattleMessageID = 0 BattleMessageID_MSG_CREATE_BATTLE BattleMessageID = 513 // 创建战斗 BattleMessageID_MSG_JOIN_BATTLE BattleMessageID = 514 // 加入战斗 BattleMessageID_MSG_LEAVE_BATTLE BattleMessageID = 515 // 离开战斗 BattleMessageID_MSG_START_BATTLE BattleMessageID = 516 // 开始战斗 BattleMessageID_MSG_END_BATTLE BattleMessageID = 517 // 结束战斗 BattleMessageID_MSG_BATTLE_ACTION BattleMessageID = 518 // 战斗行动 BattleMessageID_MSG_BATTLE_RESULT BattleMessageID = 519 // 战斗结果 BattleMessageID_MSG_BATTLE_STATUS BattleMessageID = 520 // 战斗状态 BattleMessageID_MSG_SKILL_CAST BattleMessageID = 521 // 技能释放 BattleMessageID_MSG_DAMAGE_DEALT BattleMessageID = 522 // 伤害计算 BattleMessageID_MSG_BATTLE_ROUND BattleMessageID = 523 // 战斗回合 BattleMessageID_MSG_BATTLE_SYNC BattleMessageID = 524 // 战斗同步 BattleMessageID_MSG_BATTLE_SPECTATE BattleMessageID = 525 // 观战 BattleMessageID_MSG_BATTLE_REPLAY BattleMessageID = 526 // 战斗回放 ) // Enum value maps for BattleMessageID. var ( BattleMessageID_name = map[int32]string{ 0: "BATTLE_MESSAGE_ID_UNSPECIFIED", 513: "MSG_CREATE_BATTLE", 514: "MSG_JOIN_BATTLE", 515: "MSG_LEAVE_BATTLE", 516: "MSG_START_BATTLE", 517: "MSG_END_BATTLE", 518: "MSG_BATTLE_ACTION", 519: "MSG_BATTLE_RESULT", 520: "MSG_BATTLE_STATUS", 521: "MSG_SKILL_CAST", 522: "MSG_DAMAGE_DEALT", 523: "MSG_BATTLE_ROUND", 524: "MSG_BATTLE_SYNC", 525: "MSG_BATTLE_SPECTATE", 526: "MSG_BATTLE_REPLAY", } BattleMessageID_value = map[string]int32{ "BATTLE_MESSAGE_ID_UNSPECIFIED": 0, "MSG_CREATE_BATTLE": 513, "MSG_JOIN_BATTLE": 514, "MSG_LEAVE_BATTLE": 515, "MSG_START_BATTLE": 516, "MSG_END_BATTLE": 517, "MSG_BATTLE_ACTION": 518, "MSG_BATTLE_RESULT": 519, "MSG_BATTLE_STATUS": 520, "MSG_SKILL_CAST": 521, "MSG_DAMAGE_DEALT": 522, "MSG_BATTLE_ROUND": 523, "MSG_BATTLE_SYNC": 524, "MSG_BATTLE_SPECTATE": 525, "MSG_BATTLE_REPLAY": 526, } ) func (x BattleMessageID) Enum() *BattleMessageID { p := new(BattleMessageID) *p = x return p } func (x BattleMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[2].Descriptor() } func (BattleMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[2] } func (x BattleMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleMessageID.Descriptor instead. func (BattleMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{2} } // 消息号枚举 - 宠物相关消息 (0x0300 - 0x03FF) type PetMessageID int32 const ( PetMessageID_PET_MESSAGE_ID_UNSPECIFIED PetMessageID = 0 PetMessageID_MSG_PET_SUMMON PetMessageID = 769 // 召唤宠物 PetMessageID_MSG_PET_DISMISS PetMessageID = 770 // 收回宠物 PetMessageID_MSG_PET_INFO PetMessageID = 771 // 宠物信息 PetMessageID_MSG_PET_MOVE PetMessageID = 772 // 宠物移动 PetMessageID_MSG_PET_ACTION PetMessageID = 773 // 宠物行动 PetMessageID_MSG_PET_LEVEL_UP PetMessageID = 774 // 宠物升级 PetMessageID_MSG_PET_EVOLUTION PetMessageID = 775 // 宠物进化 PetMessageID_MSG_PET_TRAIN PetMessageID = 776 // 宠物训练 PetMessageID_MSG_PET_FEED PetMessageID = 777 // 宠物喂养 PetMessageID_MSG_PET_STATUS PetMessageID = 778 // 宠物状态 PetMessageID_MSG_PET_SKILL_LEARN PetMessageID = 779 // 宠物技能学习 PetMessageID_MSG_PET_SKILL_FORGET PetMessageID = 780 // 宠物技能遗忘 PetMessageID_MSG_PET_BOND PetMessageID = 781 // 宠物羁绊 PetMessageID_MSG_PET_SYNTHESIS PetMessageID = 782 // 宠物合成 PetMessageID_MSG_PET_SKIN_EQUIP PetMessageID = 783 // 宠物皮肤装备 ) // Enum value maps for PetMessageID. var ( PetMessageID_name = map[int32]string{ 0: "PET_MESSAGE_ID_UNSPECIFIED", 769: "MSG_PET_SUMMON", 770: "MSG_PET_DISMISS", 771: "MSG_PET_INFO", 772: "MSG_PET_MOVE", 773: "MSG_PET_ACTION", 774: "MSG_PET_LEVEL_UP", 775: "MSG_PET_EVOLUTION", 776: "MSG_PET_TRAIN", 777: "MSG_PET_FEED", 778: "MSG_PET_STATUS", 779: "MSG_PET_SKILL_LEARN", 780: "MSG_PET_SKILL_FORGET", 781: "MSG_PET_BOND", 782: "MSG_PET_SYNTHESIS", 783: "MSG_PET_SKIN_EQUIP", } PetMessageID_value = map[string]int32{ "PET_MESSAGE_ID_UNSPECIFIED": 0, "MSG_PET_SUMMON": 769, "MSG_PET_DISMISS": 770, "MSG_PET_INFO": 771, "MSG_PET_MOVE": 772, "MSG_PET_ACTION": 773, "MSG_PET_LEVEL_UP": 774, "MSG_PET_EVOLUTION": 775, "MSG_PET_TRAIN": 776, "MSG_PET_FEED": 777, "MSG_PET_STATUS": 778, "MSG_PET_SKILL_LEARN": 779, "MSG_PET_SKILL_FORGET": 780, "MSG_PET_BOND": 781, "MSG_PET_SYNTHESIS": 782, "MSG_PET_SKIN_EQUIP": 783, } ) func (x PetMessageID) Enum() *PetMessageID { p := new(PetMessageID) *p = x return p } func (x PetMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[3].Descriptor() } func (PetMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[3] } func (x PetMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetMessageID.Descriptor instead. func (PetMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{3} } // 消息号枚举 - 建筑相关消息 (0x0400 - 0x04FF) type BuildingMessageID int32 const ( BuildingMessageID_BUILDING_MESSAGE_ID_UNSPECIFIED BuildingMessageID = 0 BuildingMessageID_MSG_BUILDING_CREATE BuildingMessageID = 1025 // 创建建筑 BuildingMessageID_MSG_BUILDING_UPGRADE BuildingMessageID = 1026 // 升级建筑 BuildingMessageID_MSG_BUILDING_DESTROY BuildingMessageID = 1027 // 摧毁建筑 BuildingMessageID_MSG_BUILDING_INFO BuildingMessageID = 1028 // 建筑信息 BuildingMessageID_MSG_BUILDING_PRODUCE BuildingMessageID = 1029 // 建筑生产 BuildingMessageID_MSG_BUILDING_COLLECT BuildingMessageID = 1030 // 收集资源 BuildingMessageID_MSG_BUILDING_REPAIR BuildingMessageID = 1031 // 修复建筑 BuildingMessageID_MSG_BUILDING_STATUS BuildingMessageID = 1032 // 建筑状态 BuildingMessageID_MSG_BUILDING_SYNC BuildingMessageID = 1033 // 建筑同步 BuildingMessageID_MSG_BUILDING_MOVE BuildingMessageID = 1034 // 建筑移动 BuildingMessageID_MSG_BUILDING_ROTATE BuildingMessageID = 1035 // 建筑旋转 BuildingMessageID_MSG_BUILDING_UPGRADE_CANCEL BuildingMessageID = 1036 // 取消升级 ) // Enum value maps for BuildingMessageID. var ( BuildingMessageID_name = map[int32]string{ 0: "BUILDING_MESSAGE_ID_UNSPECIFIED", 1025: "MSG_BUILDING_CREATE", 1026: "MSG_BUILDING_UPGRADE", 1027: "MSG_BUILDING_DESTROY", 1028: "MSG_BUILDING_INFO", 1029: "MSG_BUILDING_PRODUCE", 1030: "MSG_BUILDING_COLLECT", 1031: "MSG_BUILDING_REPAIR", 1032: "MSG_BUILDING_STATUS", 1033: "MSG_BUILDING_SYNC", 1034: "MSG_BUILDING_MOVE", 1035: "MSG_BUILDING_ROTATE", 1036: "MSG_BUILDING_UPGRADE_CANCEL", } BuildingMessageID_value = map[string]int32{ "BUILDING_MESSAGE_ID_UNSPECIFIED": 0, "MSG_BUILDING_CREATE": 1025, "MSG_BUILDING_UPGRADE": 1026, "MSG_BUILDING_DESTROY": 1027, "MSG_BUILDING_INFO": 1028, "MSG_BUILDING_PRODUCE": 1029, "MSG_BUILDING_COLLECT": 1030, "MSG_BUILDING_REPAIR": 1031, "MSG_BUILDING_STATUS": 1032, "MSG_BUILDING_SYNC": 1033, "MSG_BUILDING_MOVE": 1034, "MSG_BUILDING_ROTATE": 1035, "MSG_BUILDING_UPGRADE_CANCEL": 1036, } ) func (x BuildingMessageID) Enum() *BuildingMessageID { p := new(BuildingMessageID) *p = x return p } func (x BuildingMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BuildingMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[4].Descriptor() } func (BuildingMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[4] } func (x BuildingMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BuildingMessageID.Descriptor instead. func (BuildingMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{4} } // 消息号枚举 - 社交相关消息 (0x0500 - 0x05FF) type SocialMessageID int32 const ( SocialMessageID_SOCIAL_MESSAGE_ID_UNSPECIFIED SocialMessageID = 0 SocialMessageID_MSG_CHAT_MESSAGE SocialMessageID = 1281 // 聊天消息 SocialMessageID_MSG_FRIEND_REQUEST SocialMessageID = 1282 // 好友请求 SocialMessageID_MSG_FRIEND_ACCEPT SocialMessageID = 1283 // 接受好友 SocialMessageID_MSG_FRIEND_REJECT SocialMessageID = 1284 // 拒绝好友 SocialMessageID_MSG_FRIEND_REMOVE SocialMessageID = 1285 // 删除好友 SocialMessageID_MSG_FRIEND_LIST SocialMessageID = 1286 // 好友列表 SocialMessageID_MSG_GUILD_CREATE SocialMessageID = 1287 // 创建公会 SocialMessageID_MSG_GUILD_JOIN SocialMessageID = 1288 // 加入公会 SocialMessageID_MSG_GUILD_LEAVE SocialMessageID = 1289 // 离开公会 SocialMessageID_MSG_GUILD_INFO SocialMessageID = 1290 // 公会信息 SocialMessageID_MSG_TEAM_CREATE SocialMessageID = 1291 // 创建队伍 SocialMessageID_MSG_TEAM_JOIN SocialMessageID = 1292 // 加入队伍 SocialMessageID_MSG_TEAM_LEAVE SocialMessageID = 1293 // 离开队伍 SocialMessageID_MSG_TEAM_INFO SocialMessageID = 1294 // 队伍信息 SocialMessageID_MSG_MAIL_SEND SocialMessageID = 1295 // 发送邮件 SocialMessageID_MSG_MAIL_RECEIVE SocialMessageID = 1296 // 接收邮件 SocialMessageID_MSG_MAIL_DELETE SocialMessageID = 1297 // 删除邮件 SocialMessageID_MSG_MAIL_READ SocialMessageID = 1298 // 阅读邮件 ) // Enum value maps for SocialMessageID. var ( SocialMessageID_name = map[int32]string{ 0: "SOCIAL_MESSAGE_ID_UNSPECIFIED", 1281: "MSG_CHAT_MESSAGE", 1282: "MSG_FRIEND_REQUEST", 1283: "MSG_FRIEND_ACCEPT", 1284: "MSG_FRIEND_REJECT", 1285: "MSG_FRIEND_REMOVE", 1286: "MSG_FRIEND_LIST", 1287: "MSG_GUILD_CREATE", 1288: "MSG_GUILD_JOIN", 1289: "MSG_GUILD_LEAVE", 1290: "MSG_GUILD_INFO", 1291: "MSG_TEAM_CREATE", 1292: "MSG_TEAM_JOIN", 1293: "MSG_TEAM_LEAVE", 1294: "MSG_TEAM_INFO", 1295: "MSG_MAIL_SEND", 1296: "MSG_MAIL_RECEIVE", 1297: "MSG_MAIL_DELETE", 1298: "MSG_MAIL_READ", } SocialMessageID_value = map[string]int32{ "SOCIAL_MESSAGE_ID_UNSPECIFIED": 0, "MSG_CHAT_MESSAGE": 1281, "MSG_FRIEND_REQUEST": 1282, "MSG_FRIEND_ACCEPT": 1283, "MSG_FRIEND_REJECT": 1284, "MSG_FRIEND_REMOVE": 1285, "MSG_FRIEND_LIST": 1286, "MSG_GUILD_CREATE": 1287, "MSG_GUILD_JOIN": 1288, "MSG_GUILD_LEAVE": 1289, "MSG_GUILD_INFO": 1290, "MSG_TEAM_CREATE": 1291, "MSG_TEAM_JOIN": 1292, "MSG_TEAM_LEAVE": 1293, "MSG_TEAM_INFO": 1294, "MSG_MAIL_SEND": 1295, "MSG_MAIL_RECEIVE": 1296, "MSG_MAIL_DELETE": 1297, "MSG_MAIL_READ": 1298, } ) func (x SocialMessageID) Enum() *SocialMessageID { p := new(SocialMessageID) *p = x return p } func (x SocialMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SocialMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[5].Descriptor() } func (SocialMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[5] } func (x SocialMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SocialMessageID.Descriptor instead. func (SocialMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{5} } // 消息号枚举 - 物品相关消息 (0x0600 - 0x06FF) type ItemMessageID int32 const ( ItemMessageID_ITEM_MESSAGE_ID_UNSPECIFIED ItemMessageID = 0 ItemMessageID_MSG_ITEM_USE ItemMessageID = 1537 // 使用物品 ItemMessageID_MSG_ITEM_EQUIP ItemMessageID = 1538 // 装备物品 ItemMessageID_MSG_ITEM_UNEQUIP ItemMessageID = 1539 // 卸下装备 ItemMessageID_MSG_ITEM_DROP ItemMessageID = 1540 // 丢弃物品 ItemMessageID_MSG_ITEM_PICKUP ItemMessageID = 1541 // 拾取物品 ItemMessageID_MSG_ITEM_TRADE ItemMessageID = 1542 // 交易物品 ItemMessageID_MSG_INVENTORY_INFO ItemMessageID = 1543 // 背包信息 ItemMessageID_MSG_ITEM_INFO ItemMessageID = 1544 // 物品信息 ItemMessageID_MSG_ITEM_CRAFT ItemMessageID = 1545 // 制作物品 ItemMessageID_MSG_ITEM_ENHANCE ItemMessageID = 1546 // 强化物品 ItemMessageID_MSG_ITEM_DISASSEMBLE ItemMessageID = 1547 // 分解物品 ItemMessageID_MSG_ITEM_SELL ItemMessageID = 1548 // 出售物品 ItemMessageID_MSG_ITEM_BUY ItemMessageID = 1549 // 购买物品 ItemMessageID_MSG_ITEM_STACK ItemMessageID = 1550 // 物品堆叠 ItemMessageID_MSG_ITEM_SPLIT ItemMessageID = 1551 // 物品拆分 ) // Enum value maps for ItemMessageID. var ( ItemMessageID_name = map[int32]string{ 0: "ITEM_MESSAGE_ID_UNSPECIFIED", 1537: "MSG_ITEM_USE", 1538: "MSG_ITEM_EQUIP", 1539: "MSG_ITEM_UNEQUIP", 1540: "MSG_ITEM_DROP", 1541: "MSG_ITEM_PICKUP", 1542: "MSG_ITEM_TRADE", 1543: "MSG_INVENTORY_INFO", 1544: "MSG_ITEM_INFO", 1545: "MSG_ITEM_CRAFT", 1546: "MSG_ITEM_ENHANCE", 1547: "MSG_ITEM_DISASSEMBLE", 1548: "MSG_ITEM_SELL", 1549: "MSG_ITEM_BUY", 1550: "MSG_ITEM_STACK", 1551: "MSG_ITEM_SPLIT", } ItemMessageID_value = map[string]int32{ "ITEM_MESSAGE_ID_UNSPECIFIED": 0, "MSG_ITEM_USE": 1537, "MSG_ITEM_EQUIP": 1538, "MSG_ITEM_UNEQUIP": 1539, "MSG_ITEM_DROP": 1540, "MSG_ITEM_PICKUP": 1541, "MSG_ITEM_TRADE": 1542, "MSG_INVENTORY_INFO": 1543, "MSG_ITEM_INFO": 1544, "MSG_ITEM_CRAFT": 1545, "MSG_ITEM_ENHANCE": 1546, "MSG_ITEM_DISASSEMBLE": 1547, "MSG_ITEM_SELL": 1548, "MSG_ITEM_BUY": 1549, "MSG_ITEM_STACK": 1550, "MSG_ITEM_SPLIT": 1551, } ) func (x ItemMessageID) Enum() *ItemMessageID { p := new(ItemMessageID) *p = x return p } func (x ItemMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ItemMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[6].Descriptor() } func (ItemMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[6] } func (x ItemMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ItemMessageID.Descriptor instead. func (ItemMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{6} } // 消息号枚举 - 任务相关消息 (0x0700 - 0x07FF) type QuestMessageID int32 const ( QuestMessageID_QUEST_MESSAGE_ID_UNSPECIFIED QuestMessageID = 0 QuestMessageID_MSG_QUEST_ACCEPT QuestMessageID = 1793 // 接受任务 QuestMessageID_MSG_QUEST_COMPLETE QuestMessageID = 1794 // 完成任务 QuestMessageID_MSG_QUEST_CANCEL QuestMessageID = 1795 // 取消任务 QuestMessageID_MSG_QUEST_PROGRESS QuestMessageID = 1796 // 任务进度 QuestMessageID_MSG_QUEST_LIST QuestMessageID = 1797 // 任务列表 QuestMessageID_MSG_QUEST_INFO QuestMessageID = 1798 // 任务信息 QuestMessageID_MSG_QUEST_REWARD QuestMessageID = 1799 // 任务奖励 QuestMessageID_MSG_QUEST_UPDATE QuestMessageID = 1800 // 任务更新 QuestMessageID_MSG_QUEST_ABANDON QuestMessageID = 1801 // 放弃任务 QuestMessageID_MSG_QUEST_SHARE QuestMessageID = 1802 // 分享任务 QuestMessageID_MSG_QUEST_TRACK QuestMessageID = 1803 // 追踪任务 QuestMessageID_MSG_QUEST_UNTrack QuestMessageID = 1804 // 取消追踪 ) // Enum value maps for QuestMessageID. var ( QuestMessageID_name = map[int32]string{ 0: "QUEST_MESSAGE_ID_UNSPECIFIED", 1793: "MSG_QUEST_ACCEPT", 1794: "MSG_QUEST_COMPLETE", 1795: "MSG_QUEST_CANCEL", 1796: "MSG_QUEST_PROGRESS", 1797: "MSG_QUEST_LIST", 1798: "MSG_QUEST_INFO", 1799: "MSG_QUEST_REWARD", 1800: "MSG_QUEST_UPDATE", 1801: "MSG_QUEST_ABANDON", 1802: "MSG_QUEST_SHARE", 1803: "MSG_QUEST_TRACK", 1804: "MSG_QUEST_UNTrack", } QuestMessageID_value = map[string]int32{ "QUEST_MESSAGE_ID_UNSPECIFIED": 0, "MSG_QUEST_ACCEPT": 1793, "MSG_QUEST_COMPLETE": 1794, "MSG_QUEST_CANCEL": 1795, "MSG_QUEST_PROGRESS": 1796, "MSG_QUEST_LIST": 1797, "MSG_QUEST_INFO": 1798, "MSG_QUEST_REWARD": 1799, "MSG_QUEST_UPDATE": 1800, "MSG_QUEST_ABANDON": 1801, "MSG_QUEST_SHARE": 1802, "MSG_QUEST_TRACK": 1803, "MSG_QUEST_UNTrack": 1804, } ) func (x QuestMessageID) Enum() *QuestMessageID { p := new(QuestMessageID) *p = x return p } func (x QuestMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QuestMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[7].Descriptor() } func (QuestMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[7] } func (x QuestMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QuestMessageID.Descriptor instead. func (QuestMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{7} } // 消息号枚举 - 查询相关消息 (0x0800 - 0x08FF) type QueryMessageID int32 const ( QueryMessageID_QUERY_MESSAGE_ID_UNSPECIFIED QueryMessageID = 0 QueryMessageID_MSG_GET_PLAYER_INFO QueryMessageID = 2049 // 获取玩家信息 QueryMessageID_MSG_GET_ONLINE_PLAYERS QueryMessageID = 2050 // 获取在线玩家 QueryMessageID_MSG_GET_BATTLE_INFO QueryMessageID = 2051 // 获取战斗信息 QueryMessageID_MSG_GET_RANKING_LIST QueryMessageID = 2052 // 获取排行榜 QueryMessageID_MSG_GET_SERVER_INFO QueryMessageID = 2053 // 获取服务器信息 QueryMessageID_MSG_GET_PET_INFO QueryMessageID = 2054 // 获取宠物信息 QueryMessageID_MSG_GET_BUILDING_INFO QueryMessageID = 2055 // 获取建筑信息 QueryMessageID_MSG_GET_ITEM_INFO QueryMessageID = 2056 // 获取物品信息 QueryMessageID_MSG_GET_QUEST_INFO QueryMessageID = 2057 // 获取任务信息 QueryMessageID_MSG_GET_GUILD_INFO QueryMessageID = 2058 // 获取公会信息 QueryMessageID_MSG_GET_TEAM_INFO QueryMessageID = 2059 // 获取队伍信息 QueryMessageID_MSG_GET_FRIEND_INFO QueryMessageID = 2060 // 获取好友信息 QueryMessageID_MSG_GET_MAIL_INFO QueryMessageID = 2061 // 获取邮件信息 QueryMessageID_MSG_GET_STATS QueryMessageID = 2062 // 获取统计信息 ) // Enum value maps for QueryMessageID. var ( QueryMessageID_name = map[int32]string{ 0: "QUERY_MESSAGE_ID_UNSPECIFIED", 2049: "MSG_GET_PLAYER_INFO", 2050: "MSG_GET_ONLINE_PLAYERS", 2051: "MSG_GET_BATTLE_INFO", 2052: "MSG_GET_RANKING_LIST", 2053: "MSG_GET_SERVER_INFO", 2054: "MSG_GET_PET_INFO", 2055: "MSG_GET_BUILDING_INFO", 2056: "MSG_GET_ITEM_INFO", 2057: "MSG_GET_QUEST_INFO", 2058: "MSG_GET_GUILD_INFO", 2059: "MSG_GET_TEAM_INFO", 2060: "MSG_GET_FRIEND_INFO", 2061: "MSG_GET_MAIL_INFO", 2062: "MSG_GET_STATS", } QueryMessageID_value = map[string]int32{ "QUERY_MESSAGE_ID_UNSPECIFIED": 0, "MSG_GET_PLAYER_INFO": 2049, "MSG_GET_ONLINE_PLAYERS": 2050, "MSG_GET_BATTLE_INFO": 2051, "MSG_GET_RANKING_LIST": 2052, "MSG_GET_SERVER_INFO": 2053, "MSG_GET_PET_INFO": 2054, "MSG_GET_BUILDING_INFO": 2055, "MSG_GET_ITEM_INFO": 2056, "MSG_GET_QUEST_INFO": 2057, "MSG_GET_GUILD_INFO": 2058, "MSG_GET_TEAM_INFO": 2059, "MSG_GET_FRIEND_INFO": 2060, "MSG_GET_MAIL_INFO": 2061, "MSG_GET_STATS": 2062, } ) func (x QueryMessageID) Enum() *QueryMessageID { p := new(QueryMessageID) *p = x return p } func (x QueryMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QueryMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[8].Descriptor() } func (QueryMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[8] } func (x QueryMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QueryMessageID.Descriptor instead. func (QueryMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{8} } // 消息号枚举 - 系统管理消息 (0x0900 - 0x09FF) type AdminMessageID int32 const ( AdminMessageID_ADMIN_MESSAGE_ID_UNSPECIFIED AdminMessageID = 0 AdminMessageID_MSG_ADMIN_BAN_PLAYER AdminMessageID = 2305 // 封禁玩家 AdminMessageID_MSG_ADMIN_UNBAN_PLAYER AdminMessageID = 2306 // 解封玩家 AdminMessageID_MSG_ADMIN_KICK_PLAYER AdminMessageID = 2307 // 踢出玩家 AdminMessageID_MSG_ADMIN_MUTE_PLAYER AdminMessageID = 2308 // 禁言玩家 AdminMessageID_MSG_ADMIN_UNMUTE_PLAYER AdminMessageID = 2309 // 取消禁言 AdminMessageID_MSG_ADMIN_GIVE_ITEM AdminMessageID = 2310 // 给予物品 AdminMessageID_MSG_ADMIN_TAKE_ITEM AdminMessageID = 2311 // 收回物品 AdminMessageID_MSG_ADMIN_SET_LEVEL AdminMessageID = 2312 // 设置等级 AdminMessageID_MSG_ADMIN_SET_EXP AdminMessageID = 2313 // 设置经验 AdminMessageID_MSG_ADMIN_SET_GOLD AdminMessageID = 2314 // 设置金币 AdminMessageID_MSG_ADMIN_TELEPORT AdminMessageID = 2315 // 传送玩家 AdminMessageID_MSG_ADMIN_ANNOUNCE AdminMessageID = 2316 // 公告 AdminMessageID_MSG_ADMIN_RELOAD_CONFIG AdminMessageID = 2317 // 重载配置 AdminMessageID_MSG_ADMIN_SHUTDOWN AdminMessageID = 2318 // 关闭服务器 AdminMessageID_MSG_ADMIN_RESTART AdminMessageID = 2319 // 重启服务器 ) // Enum value maps for AdminMessageID. var ( AdminMessageID_name = map[int32]string{ 0: "ADMIN_MESSAGE_ID_UNSPECIFIED", 2305: "MSG_ADMIN_BAN_PLAYER", 2306: "MSG_ADMIN_UNBAN_PLAYER", 2307: "MSG_ADMIN_KICK_PLAYER", 2308: "MSG_ADMIN_MUTE_PLAYER", 2309: "MSG_ADMIN_UNMUTE_PLAYER", 2310: "MSG_ADMIN_GIVE_ITEM", 2311: "MSG_ADMIN_TAKE_ITEM", 2312: "MSG_ADMIN_SET_LEVEL", 2313: "MSG_ADMIN_SET_EXP", 2314: "MSG_ADMIN_SET_GOLD", 2315: "MSG_ADMIN_TELEPORT", 2316: "MSG_ADMIN_ANNOUNCE", 2317: "MSG_ADMIN_RELOAD_CONFIG", 2318: "MSG_ADMIN_SHUTDOWN", 2319: "MSG_ADMIN_RESTART", } AdminMessageID_value = map[string]int32{ "ADMIN_MESSAGE_ID_UNSPECIFIED": 0, "MSG_ADMIN_BAN_PLAYER": 2305, "MSG_ADMIN_UNBAN_PLAYER": 2306, "MSG_ADMIN_KICK_PLAYER": 2307, "MSG_ADMIN_MUTE_PLAYER": 2308, "MSG_ADMIN_UNMUTE_PLAYER": 2309, "MSG_ADMIN_GIVE_ITEM": 2310, "MSG_ADMIN_TAKE_ITEM": 2311, "MSG_ADMIN_SET_LEVEL": 2312, "MSG_ADMIN_SET_EXP": 2313, "MSG_ADMIN_SET_GOLD": 2314, "MSG_ADMIN_TELEPORT": 2315, "MSG_ADMIN_ANNOUNCE": 2316, "MSG_ADMIN_RELOAD_CONFIG": 2317, "MSG_ADMIN_SHUTDOWN": 2318, "MSG_ADMIN_RESTART": 2319, } ) func (x AdminMessageID) Enum() *AdminMessageID { p := new(AdminMessageID) *p = x return p } func (x AdminMessageID) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AdminMessageID) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[9].Descriptor() } func (AdminMessageID) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[9] } func (x AdminMessageID) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AdminMessageID.Descriptor instead. func (AdminMessageID) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{9} } // 消息标志位枚举 type MessageFlag int32 const ( MessageFlag_MESSAGE_FLAG_UNSPECIFIED MessageFlag = 0 MessageFlag_MESSAGE_FLAG_REQUEST MessageFlag = 1 // 请求消息 MessageFlag_MESSAGE_FLAG_RESPONSE MessageFlag = 2 // 响应消息 MessageFlag_MESSAGE_FLAG_ERROR MessageFlag = 4 // 错误消息 MessageFlag_MESSAGE_FLAG_ASYNC MessageFlag = 8 // 异步消息 MessageFlag_MESSAGE_FLAG_BROADCAST MessageFlag = 16 // 广播消息 MessageFlag_MESSAGE_FLAG_ENCRYPTED MessageFlag = 32 // 加密消息 MessageFlag_MESSAGE_FLAG_COMPRESSED MessageFlag = 64 // 压缩消息 MessageFlag_MESSAGE_FLAG_PRIORITY MessageFlag = 128 // 高优先级消息 MessageFlag_MESSAGE_FLAG_RELIABLE MessageFlag = 256 // 可靠消息 MessageFlag_MESSAGE_FLAG_ORDERED MessageFlag = 512 // 有序消息 ) // Enum value maps for MessageFlag. var ( MessageFlag_name = map[int32]string{ 0: "MESSAGE_FLAG_UNSPECIFIED", 1: "MESSAGE_FLAG_REQUEST", 2: "MESSAGE_FLAG_RESPONSE", 4: "MESSAGE_FLAG_ERROR", 8: "MESSAGE_FLAG_ASYNC", 16: "MESSAGE_FLAG_BROADCAST", 32: "MESSAGE_FLAG_ENCRYPTED", 64: "MESSAGE_FLAG_COMPRESSED", 128: "MESSAGE_FLAG_PRIORITY", 256: "MESSAGE_FLAG_RELIABLE", 512: "MESSAGE_FLAG_ORDERED", } MessageFlag_value = map[string]int32{ "MESSAGE_FLAG_UNSPECIFIED": 0, "MESSAGE_FLAG_REQUEST": 1, "MESSAGE_FLAG_RESPONSE": 2, "MESSAGE_FLAG_ERROR": 4, "MESSAGE_FLAG_ASYNC": 8, "MESSAGE_FLAG_BROADCAST": 16, "MESSAGE_FLAG_ENCRYPTED": 32, "MESSAGE_FLAG_COMPRESSED": 64, "MESSAGE_FLAG_PRIORITY": 128, "MESSAGE_FLAG_RELIABLE": 256, "MESSAGE_FLAG_ORDERED": 512, } ) func (x MessageFlag) Enum() *MessageFlag { p := new(MessageFlag) *p = x return p } func (x MessageFlag) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MessageFlag) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[10].Descriptor() } func (MessageFlag) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[10] } func (x MessageFlag) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MessageFlag.Descriptor instead. func (MessageFlag) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{10} } // 消息优先级枚举 type MessagePriority int32 const ( MessagePriority_MESSAGE_PRIORITY_UNSPECIFIED MessagePriority = 0 MessagePriority_MESSAGE_PRIORITY_LOW MessagePriority = 1 // 低优先级 MessagePriority_MESSAGE_PRIORITY_NORMAL MessagePriority = 2 // 普通优先级 MessagePriority_MESSAGE_PRIORITY_HIGH MessagePriority = 3 // 高优先级 MessagePriority_MESSAGE_PRIORITY_URGENT MessagePriority = 4 // 紧急优先级 MessagePriority_MESSAGE_PRIORITY_CRITICAL MessagePriority = 5 // 关键优先级 ) // Enum value maps for MessagePriority. var ( MessagePriority_name = map[int32]string{ 0: "MESSAGE_PRIORITY_UNSPECIFIED", 1: "MESSAGE_PRIORITY_LOW", 2: "MESSAGE_PRIORITY_NORMAL", 3: "MESSAGE_PRIORITY_HIGH", 4: "MESSAGE_PRIORITY_URGENT", 5: "MESSAGE_PRIORITY_CRITICAL", } MessagePriority_value = map[string]int32{ "MESSAGE_PRIORITY_UNSPECIFIED": 0, "MESSAGE_PRIORITY_LOW": 1, "MESSAGE_PRIORITY_NORMAL": 2, "MESSAGE_PRIORITY_HIGH": 3, "MESSAGE_PRIORITY_URGENT": 4, "MESSAGE_PRIORITY_CRITICAL": 5, } ) func (x MessagePriority) Enum() *MessagePriority { p := new(MessagePriority) *p = x return p } func (x MessagePriority) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MessagePriority) Descriptor() protoreflect.EnumDescriptor { return file_proto_messages_proto_enumTypes[11].Descriptor() } func (MessagePriority) Type() protoreflect.EnumType { return &file_proto_messages_proto_enumTypes[11] } func (x MessagePriority) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MessagePriority.Descriptor instead. func (MessagePriority) EnumDescriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{11} } // 消息头结构 type MessageHeader struct { state protoimpl.MessageState `protogen:"open.v1"` Magic uint32 `protobuf:"varint,1,opt,name=magic,proto3" json:"magic,omitempty"` // 魔数标识 (0x47574B53 "GWKS") MessageId uint32 `protobuf:"varint,2,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` // 消息ID MessageType uint32 `protobuf:"varint,3,opt,name=message_type,json=messageType,proto3" json:"message_type,omitempty"` // 消息类型 Flags uint32 `protobuf:"varint,4,opt,name=flags,proto3" json:"flags,omitempty"` // 标志位 PlayerId uint64 `protobuf:"varint,5,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` // 玩家ID Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // 时间戳 Sequence uint32 `protobuf:"varint,7,opt,name=sequence,proto3" json:"sequence,omitempty"` // 序列号 Length uint32 `protobuf:"varint,8,opt,name=length,proto3" json:"length,omitempty"` // 消息体长度 RequestId string `protobuf:"bytes,9,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // 请求ID SessionId string `protobuf:"bytes,10,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // 会话ID unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MessageHeader) Reset() { *x = MessageHeader{} mi := &file_proto_messages_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MessageHeader) String() string { return protoimpl.X.MessageStringOf(x) } func (*MessageHeader) ProtoMessage() {} func (x *MessageHeader) ProtoReflect() protoreflect.Message { mi := &file_proto_messages_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MessageHeader.ProtoReflect.Descriptor instead. func (*MessageHeader) Descriptor() ([]byte, []int) { return file_proto_messages_proto_rawDescGZIP(), []int{0} } func (x *MessageHeader) GetMagic() uint32 { if x != nil { return x.Magic } return 0 } func (x *MessageHeader) GetMessageId() uint32 { if x != nil { return x.MessageId } return 0 } func (x *MessageHeader) GetMessageType() uint32 { if x != nil { return x.MessageType } return 0 } func (x *MessageHeader) GetFlags() uint32 { if x != nil { return x.Flags } return 0 } func (x *MessageHeader) GetPlayerId() uint64 { if x != nil { return x.PlayerId } return 0 } func (x *MessageHeader) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *MessageHeader) GetSequence() uint32 { if x != nil { return x.Sequence } return 0 } func (x *MessageHeader) GetLength() uint32 { if x != nil { return x.Length } return 0 } func (x *MessageHeader) GetRequestId() string { if x != nil { return x.RequestId } return "" } func (x *MessageHeader) GetSessionId() string { if x != nil { return x.SessionId } return "" } var File_proto_messages_proto protoreflect.FileDescriptor const file_proto_messages_proto_rawDesc = "" + "\n" + "\x14proto/messages.proto\x12\x16greatestworks.messages\"\xaa\x02\n" + "\rMessageHeader\x12\x14\n" + "\x05magic\x18\x01 \x01(\rR\x05magic\x12\x1d\n" + "\n" + "message_id\x18\x02 \x01(\rR\tmessageId\x12!\n" + "\fmessage_type\x18\x03 \x01(\rR\vmessageType\x12\x14\n" + "\x05flags\x18\x04 \x01(\rR\x05flags\x12\x1b\n" + "\tplayer_id\x18\x05 \x01(\x04R\bplayerId\x12\x1c\n" + "\ttimestamp\x18\x06 \x01(\x03R\ttimestamp\x12\x1a\n" + "\bsequence\x18\a \x01(\rR\bsequence\x12\x16\n" + "\x06length\x18\b \x01(\rR\x06length\x12\x1d\n" + "\n" + "request_id\x18\t \x01(\tR\trequestId\x12\x1d\n" + "\n" + "session_id\x18\n" + " \x01(\tR\tsessionId*\xe8\x01\n" + "\x0fSystemMessageID\x12!\n" + "\x1dSYSTEM_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x11\n" + "\rMSG_HEARTBEAT\x10\x01\x12\x11\n" + "\rMSG_HANDSHAKE\x10\x02\x12\f\n" + "\bMSG_AUTH\x10\x03\x12\x12\n" + "\x0eMSG_DISCONNECT\x10\x04\x12\r\n" + "\tMSG_ERROR\x10\x05\x12\f\n" + "\bMSG_PING\x10\x06\x12\f\n" + "\bMSG_PONG\x10\a\x12\x13\n" + "\x0fMSG_SYSTEM_INFO\x10\b\x12\x15\n" + "\x11MSG_SERVER_STATUS\x10\t\x12\x13\n" + "\x0fMSG_MAINTENANCE\x10\n" + "*\x94\x03\n" + "\x0fPlayerMessageID\x12!\n" + "\x1dPLAYER_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x15\n" + "\x10MSG_PLAYER_LOGIN\x10\x81\x02\x12\x16\n" + "\x11MSG_PLAYER_LOGOUT\x10\x82\x02\x12\x14\n" + "\x0fMSG_PLAYER_INFO\x10\x83\x02\x12\x14\n" + "\x0fMSG_PLAYER_MOVE\x10\x84\x02\x12\x16\n" + "\x11MSG_PLAYER_CREATE\x10\x85\x02\x12\x16\n" + "\x11MSG_PLAYER_UPDATE\x10\x86\x02\x12\x16\n" + "\x11MSG_PLAYER_DELETE\x10\x87\x02\x12\x16\n" + "\x11MSG_PLAYER_STATUS\x10\x88\x02\x12\x15\n" + "\x10MSG_PLAYER_STATS\x10\x89\x02\x12\x15\n" + "\x10MSG_PLAYER_LEVEL\x10\x8a\x02\x12\x18\n" + "\x13MSG_PLAYER_EXP_GAIN\x10\x8b\x02\x12\x14\n" + "\x0fMSG_PLAYER_SYNC\x10\x8c\x02\x12\x13\n" + "\x0eMSG_PLAYER_BAN\x10\x8d\x02\x12\x15\n" + "\x10MSG_PLAYER_UNBAN\x10\x8e\x02\x12\x19\n" + "\x14MSG_PLAYER_GM_UPDATE\x10\x8f\x02*\xf8\x02\n" + "\x0fBattleMessageID\x12!\n" + "\x1dBATTLE_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x16\n" + "\x11MSG_CREATE_BATTLE\x10\x81\x04\x12\x14\n" + "\x0fMSG_JOIN_BATTLE\x10\x82\x04\x12\x15\n" + "\x10MSG_LEAVE_BATTLE\x10\x83\x04\x12\x15\n" + "\x10MSG_START_BATTLE\x10\x84\x04\x12\x13\n" + "\x0eMSG_END_BATTLE\x10\x85\x04\x12\x16\n" + "\x11MSG_BATTLE_ACTION\x10\x86\x04\x12\x16\n" + "\x11MSG_BATTLE_RESULT\x10\x87\x04\x12\x16\n" + "\x11MSG_BATTLE_STATUS\x10\x88\x04\x12\x13\n" + "\x0eMSG_SKILL_CAST\x10\x89\x04\x12\x15\n" + "\x10MSG_DAMAGE_DEALT\x10\x8a\x04\x12\x15\n" + "\x10MSG_BATTLE_ROUND\x10\x8b\x04\x12\x14\n" + "\x0fMSG_BATTLE_SYNC\x10\x8c\x04\x12\x18\n" + "\x13MSG_BATTLE_SPECTATE\x10\x8d\x04\x12\x16\n" + "\x11MSG_BATTLE_REPLAY\x10\x8e\x04*\xf8\x02\n" + "\fPetMessageID\x12\x1e\n" + "\x1aPET_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0eMSG_PET_SUMMON\x10\x81\x06\x12\x14\n" + "\x0fMSG_PET_DISMISS\x10\x82\x06\x12\x11\n" + "\fMSG_PET_INFO\x10\x83\x06\x12\x11\n" + "\fMSG_PET_MOVE\x10\x84\x06\x12\x13\n" + "\x0eMSG_PET_ACTION\x10\x85\x06\x12\x15\n" + "\x10MSG_PET_LEVEL_UP\x10\x86\x06\x12\x16\n" + "\x11MSG_PET_EVOLUTION\x10\x87\x06\x12\x12\n" + "\rMSG_PET_TRAIN\x10\x88\x06\x12\x11\n" + "\fMSG_PET_FEED\x10\x89\x06\x12\x13\n" + "\x0eMSG_PET_STATUS\x10\x8a\x06\x12\x18\n" + "\x13MSG_PET_SKILL_LEARN\x10\x8b\x06\x12\x19\n" + "\x14MSG_PET_SKILL_FORGET\x10\x8c\x06\x12\x11\n" + "\fMSG_PET_BOND\x10\x8d\x06\x12\x16\n" + "\x11MSG_PET_SYNTHESIS\x10\x8e\x06\x12\x17\n" + "\x12MSG_PET_SKIN_EQUIP\x10\x8f\x06*\xf6\x02\n" + "\x11BuildingMessageID\x12#\n" + "\x1fBUILDING_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x18\n" + "\x13MSG_BUILDING_CREATE\x10\x81\b\x12\x19\n" + "\x14MSG_BUILDING_UPGRADE\x10\x82\b\x12\x19\n" + "\x14MSG_BUILDING_DESTROY\x10\x83\b\x12\x16\n" + "\x11MSG_BUILDING_INFO\x10\x84\b\x12\x19\n" + "\x14MSG_BUILDING_PRODUCE\x10\x85\b\x12\x19\n" + "\x14MSG_BUILDING_COLLECT\x10\x86\b\x12\x18\n" + "\x13MSG_BUILDING_REPAIR\x10\x87\b\x12\x18\n" + "\x13MSG_BUILDING_STATUS\x10\x88\b\x12\x16\n" + "\x11MSG_BUILDING_SYNC\x10\x89\b\x12\x16\n" + "\x11MSG_BUILDING_MOVE\x10\x8a\b\x12\x18\n" + "\x13MSG_BUILDING_ROTATE\x10\x8b\b\x12 \n" + "\x1bMSG_BUILDING_UPGRADE_CANCEL\x10\x8c\b*\xc1\x03\n" + "\x0fSocialMessageID\x12!\n" + "\x1dSOCIAL_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x15\n" + "\x10MSG_CHAT_MESSAGE\x10\x81\n" + "\x12\x17\n" + "\x12MSG_FRIEND_REQUEST\x10\x82\n" + "\x12\x16\n" + "\x11MSG_FRIEND_ACCEPT\x10\x83\n" + "\x12\x16\n" + "\x11MSG_FRIEND_REJECT\x10\x84\n" + "\x12\x16\n" + "\x11MSG_FRIEND_REMOVE\x10\x85\n" + "\x12\x14\n" + "\x0fMSG_FRIEND_LIST\x10\x86\n" + "\x12\x15\n" + "\x10MSG_GUILD_CREATE\x10\x87\n" + "\x12\x13\n" + "\x0eMSG_GUILD_JOIN\x10\x88\n" + "\x12\x14\n" + "\x0fMSG_GUILD_LEAVE\x10\x89\n" + "\x12\x13\n" + "\x0eMSG_GUILD_INFO\x10\x8a\n" + "\x12\x14\n" + "\x0fMSG_TEAM_CREATE\x10\x8b\n" + "\x12\x12\n" + "\rMSG_TEAM_JOIN\x10\x8c\n" + "\x12\x13\n" + "\x0eMSG_TEAM_LEAVE\x10\x8d\n" + "\x12\x12\n" + "\rMSG_TEAM_INFO\x10\x8e\n" + "\x12\x12\n" + "\rMSG_MAIL_SEND\x10\x8f\n" + "\x12\x15\n" + "\x10MSG_MAIL_RECEIVE\x10\x90\n" + "\x12\x14\n" + "\x0fMSG_MAIL_DELETE\x10\x91\n" + "\x12\x12\n" + "\rMSG_MAIL_READ\x10\x92\n" + "*\xf3\x02\n" + "\rItemMessageID\x12\x1f\n" + "\x1bITEM_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x11\n" + "\fMSG_ITEM_USE\x10\x81\f\x12\x13\n" + "\x0eMSG_ITEM_EQUIP\x10\x82\f\x12\x15\n" + "\x10MSG_ITEM_UNEQUIP\x10\x83\f\x12\x12\n" + "\rMSG_ITEM_DROP\x10\x84\f\x12\x14\n" + "\x0fMSG_ITEM_PICKUP\x10\x85\f\x12\x13\n" + "\x0eMSG_ITEM_TRADE\x10\x86\f\x12\x17\n" + "\x12MSG_INVENTORY_INFO\x10\x87\f\x12\x12\n" + "\rMSG_ITEM_INFO\x10\x88\f\x12\x13\n" + "\x0eMSG_ITEM_CRAFT\x10\x89\f\x12\x15\n" + "\x10MSG_ITEM_ENHANCE\x10\x8a\f\x12\x19\n" + "\x14MSG_ITEM_DISASSEMBLE\x10\x8b\f\x12\x12\n" + "\rMSG_ITEM_SELL\x10\x8c\f\x12\x11\n" + "\fMSG_ITEM_BUY\x10\x8d\f\x12\x13\n" + "\x0eMSG_ITEM_STACK\x10\x8e\f\x12\x13\n" + "\x0eMSG_ITEM_SPLIT\x10\x8f\f*\xc6\x02\n" + "\x0eQuestMessageID\x12 \n" + "\x1cQUEST_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x15\n" + "\x10MSG_QUEST_ACCEPT\x10\x81\x0e\x12\x17\n" + "\x12MSG_QUEST_COMPLETE\x10\x82\x0e\x12\x15\n" + "\x10MSG_QUEST_CANCEL\x10\x83\x0e\x12\x17\n" + "\x12MSG_QUEST_PROGRESS\x10\x84\x0e\x12\x13\n" + "\x0eMSG_QUEST_LIST\x10\x85\x0e\x12\x13\n" + "\x0eMSG_QUEST_INFO\x10\x86\x0e\x12\x15\n" + "\x10MSG_QUEST_REWARD\x10\x87\x0e\x12\x15\n" + "\x10MSG_QUEST_UPDATE\x10\x88\x0e\x12\x16\n" + "\x11MSG_QUEST_ABANDON\x10\x89\x0e\x12\x14\n" + "\x0fMSG_QUEST_SHARE\x10\x8a\x0e\x12\x14\n" + "\x0fMSG_QUEST_TRACK\x10\x8b\x0e\x12\x16\n" + "\x11MSG_QUEST_UNTrack\x10\x8c\x0e*\x93\x03\n" + "\x0eQueryMessageID\x12 \n" + "\x1cQUERY_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x18\n" + "\x13MSG_GET_PLAYER_INFO\x10\x81\x10\x12\x1b\n" + "\x16MSG_GET_ONLINE_PLAYERS\x10\x82\x10\x12\x18\n" + "\x13MSG_GET_BATTLE_INFO\x10\x83\x10\x12\x19\n" + "\x14MSG_GET_RANKING_LIST\x10\x84\x10\x12\x18\n" + "\x13MSG_GET_SERVER_INFO\x10\x85\x10\x12\x15\n" + "\x10MSG_GET_PET_INFO\x10\x86\x10\x12\x1a\n" + "\x15MSG_GET_BUILDING_INFO\x10\x87\x10\x12\x16\n" + "\x11MSG_GET_ITEM_INFO\x10\x88\x10\x12\x17\n" + "\x12MSG_GET_QUEST_INFO\x10\x89\x10\x12\x17\n" + "\x12MSG_GET_GUILD_INFO\x10\x8a\x10\x12\x16\n" + "\x11MSG_GET_TEAM_INFO\x10\x8b\x10\x12\x18\n" + "\x13MSG_GET_FRIEND_INFO\x10\x8c\x10\x12\x16\n" + "\x11MSG_GET_MAIL_INFO\x10\x8d\x10\x12\x12\n" + "\rMSG_GET_STATS\x10\x8e\x10*\xc0\x03\n" + "\x0eAdminMessageID\x12 \n" + "\x1cADMIN_MESSAGE_ID_UNSPECIFIED\x10\x00\x12\x19\n" + "\x14MSG_ADMIN_BAN_PLAYER\x10\x81\x12\x12\x1b\n" + "\x16MSG_ADMIN_UNBAN_PLAYER\x10\x82\x12\x12\x1a\n" + "\x15MSG_ADMIN_KICK_PLAYER\x10\x83\x12\x12\x1a\n" + "\x15MSG_ADMIN_MUTE_PLAYER\x10\x84\x12\x12\x1c\n" + "\x17MSG_ADMIN_UNMUTE_PLAYER\x10\x85\x12\x12\x18\n" + "\x13MSG_ADMIN_GIVE_ITEM\x10\x86\x12\x12\x18\n" + "\x13MSG_ADMIN_TAKE_ITEM\x10\x87\x12\x12\x18\n" + "\x13MSG_ADMIN_SET_LEVEL\x10\x88\x12\x12\x16\n" + "\x11MSG_ADMIN_SET_EXP\x10\x89\x12\x12\x17\n" + "\x12MSG_ADMIN_SET_GOLD\x10\x8a\x12\x12\x17\n" + "\x12MSG_ADMIN_TELEPORT\x10\x8b\x12\x12\x17\n" + "\x12MSG_ADMIN_ANNOUNCE\x10\x8c\x12\x12\x1c\n" + "\x17MSG_ADMIN_RELOAD_CONFIG\x10\x8d\x12\x12\x17\n" + "\x12MSG_ADMIN_SHUTDOWN\x10\x8e\x12\x12\x16\n" + "\x11MSG_ADMIN_RESTART\x10\x8f\x12*\xb8\x02\n" + "\vMessageFlag\x12\x1c\n" + "\x18MESSAGE_FLAG_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14MESSAGE_FLAG_REQUEST\x10\x01\x12\x19\n" + "\x15MESSAGE_FLAG_RESPONSE\x10\x02\x12\x16\n" + "\x12MESSAGE_FLAG_ERROR\x10\x04\x12\x16\n" + "\x12MESSAGE_FLAG_ASYNC\x10\b\x12\x1a\n" + "\x16MESSAGE_FLAG_BROADCAST\x10\x10\x12\x1a\n" + "\x16MESSAGE_FLAG_ENCRYPTED\x10 \x12\x1b\n" + "\x17MESSAGE_FLAG_COMPRESSED\x10@\x12\x1a\n" + "\x15MESSAGE_FLAG_PRIORITY\x10\x80\x01\x12\x1a\n" + "\x15MESSAGE_FLAG_RELIABLE\x10\x80\x02\x12\x19\n" + "\x14MESSAGE_FLAG_ORDERED\x10\x80\x04*\xc1\x01\n" + "\x0fMessagePriority\x12 \n" + "\x1cMESSAGE_PRIORITY_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14MESSAGE_PRIORITY_LOW\x10\x01\x12\x1b\n" + "\x17MESSAGE_PRIORITY_NORMAL\x10\x02\x12\x19\n" + "\x15MESSAGE_PRIORITY_HIGH\x10\x03\x12\x1b\n" + "\x17MESSAGE_PRIORITY_URGENT\x10\x04\x12\x1d\n" + "\x19MESSAGE_PRIORITY_CRITICAL\x10\x05B@Z%greatestworks/internal/proto/messages\xaa\x02\x16GreatestWorks.Messagesb\x06proto3" var ( file_proto_messages_proto_rawDescOnce sync.Once file_proto_messages_proto_rawDescData []byte ) func file_proto_messages_proto_rawDescGZIP() []byte { file_proto_messages_proto_rawDescOnce.Do(func() { file_proto_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_messages_proto_rawDesc), len(file_proto_messages_proto_rawDesc))) }) return file_proto_messages_proto_rawDescData } var file_proto_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 12) var file_proto_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proto_messages_proto_goTypes = []any{ (SystemMessageID)(0), // 0: greatestworks.messages.SystemMessageID (PlayerMessageID)(0), // 1: greatestworks.messages.PlayerMessageID (BattleMessageID)(0), // 2: greatestworks.messages.BattleMessageID (PetMessageID)(0), // 3: greatestworks.messages.PetMessageID (BuildingMessageID)(0), // 4: greatestworks.messages.BuildingMessageID (SocialMessageID)(0), // 5: greatestworks.messages.SocialMessageID (ItemMessageID)(0), // 6: greatestworks.messages.ItemMessageID (QuestMessageID)(0), // 7: greatestworks.messages.QuestMessageID (QueryMessageID)(0), // 8: greatestworks.messages.QueryMessageID (AdminMessageID)(0), // 9: greatestworks.messages.AdminMessageID (MessageFlag)(0), // 10: greatestworks.messages.MessageFlag (MessagePriority)(0), // 11: greatestworks.messages.MessagePriority (*MessageHeader)(nil), // 12: greatestworks.messages.MessageHeader } var file_proto_messages_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_proto_messages_proto_init() } func file_proto_messages_proto_init() { if File_proto_messages_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_messages_proto_rawDesc), len(file_proto_messages_proto_rawDesc)), NumEnums: 12, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proto_messages_proto_goTypes, DependencyIndexes: file_proto_messages_proto_depIdxs, EnumInfos: file_proto_messages_proto_enumTypes, MessageInfos: file_proto_messages_proto_msgTypes, }.Build() File_proto_messages_proto = out.File file_proto_messages_proto_goTypes = nil file_proto_messages_proto_depIdxs = nil } ================================================ FILE: internal/proto/pet/pet.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/pet.proto package pet import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 宠物稀有度枚举 type PetRarity int32 const ( PetRarity_PET_RARITY_UNSPECIFIED PetRarity = 0 PetRarity_PET_RARITY_COMMON PetRarity = 1 // 普通 PetRarity_PET_RARITY_UNCOMMON PetRarity = 2 // 不常见 PetRarity_PET_RARITY_RARE PetRarity = 3 // 稀有 PetRarity_PET_RARITY_EPIC PetRarity = 4 // 史诗 PetRarity_PET_RARITY_LEGENDARY PetRarity = 5 // 传说 PetRarity_PET_RARITY_MYTHIC PetRarity = 6 // 神话 ) // Enum value maps for PetRarity. var ( PetRarity_name = map[int32]string{ 0: "PET_RARITY_UNSPECIFIED", 1: "PET_RARITY_COMMON", 2: "PET_RARITY_UNCOMMON", 3: "PET_RARITY_RARE", 4: "PET_RARITY_EPIC", 5: "PET_RARITY_LEGENDARY", 6: "PET_RARITY_MYTHIC", } PetRarity_value = map[string]int32{ "PET_RARITY_UNSPECIFIED": 0, "PET_RARITY_COMMON": 1, "PET_RARITY_UNCOMMON": 2, "PET_RARITY_RARE": 3, "PET_RARITY_EPIC": 4, "PET_RARITY_LEGENDARY": 5, "PET_RARITY_MYTHIC": 6, } ) func (x PetRarity) Enum() *PetRarity { p := new(PetRarity) *p = x return p } func (x PetRarity) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetRarity) Descriptor() protoreflect.EnumDescriptor { return file_proto_pet_proto_enumTypes[0].Descriptor() } func (PetRarity) Type() protoreflect.EnumType { return &file_proto_pet_proto_enumTypes[0] } func (x PetRarity) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetRarity.Descriptor instead. func (PetRarity) EnumDescriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{0} } // 宠物品质枚举 type PetQuality int32 const ( PetQuality_PET_QUALITY_UNSPECIFIED PetQuality = 0 PetQuality_PET_QUALITY_POOR PetQuality = 1 // 劣质 PetQuality_PET_QUALITY_FAIR PetQuality = 2 // 一般 PetQuality_PET_QUALITY_GOOD PetQuality = 3 // 良好 PetQuality_PET_QUALITY_EXCELLENT PetQuality = 4 // 优秀 PetQuality_PET_QUALITY_PERFECT PetQuality = 5 // 完美 ) // Enum value maps for PetQuality. var ( PetQuality_name = map[int32]string{ 0: "PET_QUALITY_UNSPECIFIED", 1: "PET_QUALITY_POOR", 2: "PET_QUALITY_FAIR", 3: "PET_QUALITY_GOOD", 4: "PET_QUALITY_EXCELLENT", 5: "PET_QUALITY_PERFECT", } PetQuality_value = map[string]int32{ "PET_QUALITY_UNSPECIFIED": 0, "PET_QUALITY_POOR": 1, "PET_QUALITY_FAIR": 2, "PET_QUALITY_GOOD": 3, "PET_QUALITY_EXCELLENT": 4, "PET_QUALITY_PERFECT": 5, } ) func (x PetQuality) Enum() *PetQuality { p := new(PetQuality) *p = x return p } func (x PetQuality) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetQuality) Descriptor() protoreflect.EnumDescriptor { return file_proto_pet_proto_enumTypes[1].Descriptor() } func (PetQuality) Type() protoreflect.EnumType { return &file_proto_pet_proto_enumTypes[1] } func (x PetQuality) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetQuality.Descriptor instead. func (PetQuality) EnumDescriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{1} } // 宠物技能类型枚举 type PetSkillType int32 const ( PetSkillType_PET_SKILL_TYPE_UNSPECIFIED PetSkillType = 0 PetSkillType_PET_SKILL_TYPE_ATTACK PetSkillType = 1 // 攻击技能 PetSkillType_PET_SKILL_TYPE_DEFENSE PetSkillType = 2 // 防御技能 PetSkillType_PET_SKILL_TYPE_HEAL PetSkillType = 3 // 治疗技能 PetSkillType_PET_SKILL_TYPE_BUFF PetSkillType = 4 // 增益技能 PetSkillType_PET_SKILL_TYPE_DEBUFF PetSkillType = 5 // 减益技能 PetSkillType_PET_SKILL_TYPE_PASSIVE PetSkillType = 6 // 被动技能 PetSkillType_PET_SKILL_TYPE_ULTIMATE PetSkillType = 7 // 终极技能 ) // Enum value maps for PetSkillType. var ( PetSkillType_name = map[int32]string{ 0: "PET_SKILL_TYPE_UNSPECIFIED", 1: "PET_SKILL_TYPE_ATTACK", 2: "PET_SKILL_TYPE_DEFENSE", 3: "PET_SKILL_TYPE_HEAL", 4: "PET_SKILL_TYPE_BUFF", 5: "PET_SKILL_TYPE_DEBUFF", 6: "PET_SKILL_TYPE_PASSIVE", 7: "PET_SKILL_TYPE_ULTIMATE", } PetSkillType_value = map[string]int32{ "PET_SKILL_TYPE_UNSPECIFIED": 0, "PET_SKILL_TYPE_ATTACK": 1, "PET_SKILL_TYPE_DEFENSE": 2, "PET_SKILL_TYPE_HEAL": 3, "PET_SKILL_TYPE_BUFF": 4, "PET_SKILL_TYPE_DEBUFF": 5, "PET_SKILL_TYPE_PASSIVE": 6, "PET_SKILL_TYPE_ULTIMATE": 7, } ) func (x PetSkillType) Enum() *PetSkillType { p := new(PetSkillType) *p = x return p } func (x PetSkillType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetSkillType) Descriptor() protoreflect.EnumDescriptor { return file_proto_pet_proto_enumTypes[2].Descriptor() } func (PetSkillType) Type() protoreflect.EnumType { return &file_proto_pet_proto_enumTypes[2] } func (x PetSkillType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetSkillType.Descriptor instead. func (PetSkillType) EnumDescriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{2} } // 宠物心情枚举 type PetMood int32 const ( PetMood_PET_MOOD_UNSPECIFIED PetMood = 0 PetMood_PET_MOOD_VERY_HAPPY PetMood = 1 // 非常开心 PetMood_PET_MOOD_HAPPY PetMood = 2 // 开心 PetMood_PET_MOOD_NORMAL PetMood = 3 // 正常 PetMood_PET_MOOD_SAD PetMood = 4 // 难过 PetMood_PET_MOOD_VERY_SAD PetMood = 5 // 非常难过 PetMood_PET_MOOD_ANGRY PetMood = 6 // 愤怒 PetMood_PET_MOOD_SICK PetMood = 7 // 生病 ) // Enum value maps for PetMood. var ( PetMood_name = map[int32]string{ 0: "PET_MOOD_UNSPECIFIED", 1: "PET_MOOD_VERY_HAPPY", 2: "PET_MOOD_HAPPY", 3: "PET_MOOD_NORMAL", 4: "PET_MOOD_SAD", 5: "PET_MOOD_VERY_SAD", 6: "PET_MOOD_ANGRY", 7: "PET_MOOD_SICK", } PetMood_value = map[string]int32{ "PET_MOOD_UNSPECIFIED": 0, "PET_MOOD_VERY_HAPPY": 1, "PET_MOOD_HAPPY": 2, "PET_MOOD_NORMAL": 3, "PET_MOOD_SAD": 4, "PET_MOOD_VERY_SAD": 5, "PET_MOOD_ANGRY": 6, "PET_MOOD_SICK": 7, } ) func (x PetMood) Enum() *PetMood { p := new(PetMood) *p = x return p } func (x PetMood) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetMood) Descriptor() protoreflect.EnumDescriptor { return file_proto_pet_proto_enumTypes[3].Descriptor() } func (PetMood) Type() protoreflect.EnumType { return &file_proto_pet_proto_enumTypes[3] } func (x PetMood) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetMood.Descriptor instead. func (PetMood) EnumDescriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{3} } // 宠物训练类型枚举 type PetTrainingType int32 const ( PetTrainingType_PET_TRAINING_TYPE_UNSPECIFIED PetTrainingType = 0 PetTrainingType_PET_TRAINING_TYPE_STRENGTH PetTrainingType = 1 // 力量训练 PetTrainingType_PET_TRAINING_TYPE_AGILITY PetTrainingType = 2 // 敏捷训练 PetTrainingType_PET_TRAINING_TYPE_INTELLIGENCE PetTrainingType = 3 // 智力训练 PetTrainingType_PET_TRAINING_TYPE_ENDURANCE PetTrainingType = 4 // 耐力训练 PetTrainingType_PET_TRAINING_TYPE_BALANCED PetTrainingType = 5 // 平衡训练 ) // Enum value maps for PetTrainingType. var ( PetTrainingType_name = map[int32]string{ 0: "PET_TRAINING_TYPE_UNSPECIFIED", 1: "PET_TRAINING_TYPE_STRENGTH", 2: "PET_TRAINING_TYPE_AGILITY", 3: "PET_TRAINING_TYPE_INTELLIGENCE", 4: "PET_TRAINING_TYPE_ENDURANCE", 5: "PET_TRAINING_TYPE_BALANCED", } PetTrainingType_value = map[string]int32{ "PET_TRAINING_TYPE_UNSPECIFIED": 0, "PET_TRAINING_TYPE_STRENGTH": 1, "PET_TRAINING_TYPE_AGILITY": 2, "PET_TRAINING_TYPE_INTELLIGENCE": 3, "PET_TRAINING_TYPE_ENDURANCE": 4, "PET_TRAINING_TYPE_BALANCED": 5, } ) func (x PetTrainingType) Enum() *PetTrainingType { p := new(PetTrainingType) *p = x return p } func (x PetTrainingType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetTrainingType) Descriptor() protoreflect.EnumDescriptor { return file_proto_pet_proto_enumTypes[4].Descriptor() } func (PetTrainingType) Type() protoreflect.EnumType { return &file_proto_pet_proto_enumTypes[4] } func (x PetTrainingType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetTrainingType.Descriptor instead. func (PetTrainingType) EnumDescriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{4} } // 宠物训练强度枚举 type PetTrainingIntensity int32 const ( PetTrainingIntensity_PET_TRAINING_INTENSITY_UNSPECIFIED PetTrainingIntensity = 0 PetTrainingIntensity_PET_TRAINING_INTENSITY_LIGHT PetTrainingIntensity = 1 // 轻度训练 PetTrainingIntensity_PET_TRAINING_INTENSITY_MODERATE PetTrainingIntensity = 2 // 中度训练 PetTrainingIntensity_PET_TRAINING_INTENSITY_INTENSE PetTrainingIntensity = 3 // 强度训练 PetTrainingIntensity_PET_TRAINING_INTENSITY_EXTREME PetTrainingIntensity = 4 // 极限训练 ) // Enum value maps for PetTrainingIntensity. var ( PetTrainingIntensity_name = map[int32]string{ 0: "PET_TRAINING_INTENSITY_UNSPECIFIED", 1: "PET_TRAINING_INTENSITY_LIGHT", 2: "PET_TRAINING_INTENSITY_MODERATE", 3: "PET_TRAINING_INTENSITY_INTENSE", 4: "PET_TRAINING_INTENSITY_EXTREME", } PetTrainingIntensity_value = map[string]int32{ "PET_TRAINING_INTENSITY_UNSPECIFIED": 0, "PET_TRAINING_INTENSITY_LIGHT": 1, "PET_TRAINING_INTENSITY_MODERATE": 2, "PET_TRAINING_INTENSITY_INTENSE": 3, "PET_TRAINING_INTENSITY_EXTREME": 4, } ) func (x PetTrainingIntensity) Enum() *PetTrainingIntensity { p := new(PetTrainingIntensity) *p = x return p } func (x PetTrainingIntensity) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetTrainingIntensity) Descriptor() protoreflect.EnumDescriptor { return file_proto_pet_proto_enumTypes[5].Descriptor() } func (PetTrainingIntensity) Type() protoreflect.EnumType { return &file_proto_pet_proto_enumTypes[5] } func (x PetTrainingIntensity) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetTrainingIntensity.Descriptor instead. func (PetTrainingIntensity) EnumDescriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{5} } // 创建宠物请求 type CreatePetRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SpeciesId string `protobuf:"bytes,2,opt,name=species_id,json=speciesId,proto3" json:"species_id,omitempty"` Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` InitialLevel int32 `protobuf:"varint,4,opt,name=initial_level,json=initialLevel,proto3" json:"initial_level,omitempty"` Rarity string `protobuf:"bytes,5,opt,name=rarity,proto3" json:"rarity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePetRequest) Reset() { *x = CreatePetRequest{} mi := &file_proto_pet_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePetRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePetRequest) ProtoMessage() {} func (x *CreatePetRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePetRequest.ProtoReflect.Descriptor instead. func (*CreatePetRequest) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{0} } func (x *CreatePetRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *CreatePetRequest) GetSpeciesId() string { if x != nil { return x.SpeciesId } return "" } func (x *CreatePetRequest) GetName() string { if x != nil { return x.Name } return "" } func (x *CreatePetRequest) GetInitialLevel() int32 { if x != nil { return x.InitialLevel } return 0 } func (x *CreatePetRequest) GetRarity() string { if x != nil { return x.Rarity } return "" } // 创建宠物响应 type CreatePetResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Pet *PetInfo `protobuf:"bytes,2,opt,name=pet,proto3" json:"pet,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePetResponse) Reset() { *x = CreatePetResponse{} mi := &file_proto_pet_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePetResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePetResponse) ProtoMessage() {} func (x *CreatePetResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePetResponse.ProtoReflect.Descriptor instead. func (*CreatePetResponse) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{1} } func (x *CreatePetResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *CreatePetResponse) GetPet() *PetInfo { if x != nil { return x.Pet } return nil } // 获取宠物信息请求 type GetPetInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PetId string `protobuf:"bytes,1,opt,name=pet_id,json=petId,proto3" json:"pet_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPetInfoRequest) Reset() { *x = GetPetInfoRequest{} mi := &file_proto_pet_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPetInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPetInfoRequest) ProtoMessage() {} func (x *GetPetInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPetInfoRequest.ProtoReflect.Descriptor instead. func (*GetPetInfoRequest) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{2} } func (x *GetPetInfoRequest) GetPetId() string { if x != nil { return x.PetId } return "" } // 获取宠物信息响应 type GetPetInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Pet *PetInfo `protobuf:"bytes,2,opt,name=pet,proto3" json:"pet,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPetInfoResponse) Reset() { *x = GetPetInfoResponse{} mi := &file_proto_pet_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPetInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPetInfoResponse) ProtoMessage() {} func (x *GetPetInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPetInfoResponse.ProtoReflect.Descriptor instead. func (*GetPetInfoResponse) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{3} } func (x *GetPetInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetPetInfoResponse) GetPet() *PetInfo { if x != nil { return x.Pet } return nil } // 更新宠物信息请求 type UpdatePetRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PetId string `protobuf:"bytes,1,opt,name=pet_id,json=petId,proto3" json:"pet_id,omitempty"` Updates map[string]string `protobuf:"bytes,2,rep,name=updates,proto3" json:"updates,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePetRequest) Reset() { *x = UpdatePetRequest{} mi := &file_proto_pet_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePetRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetRequest) ProtoMessage() {} func (x *UpdatePetRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePetRequest.ProtoReflect.Descriptor instead. func (*UpdatePetRequest) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{4} } func (x *UpdatePetRequest) GetPetId() string { if x != nil { return x.PetId } return "" } func (x *UpdatePetRequest) GetUpdates() map[string]string { if x != nil { return x.Updates } return nil } // 更新宠物信息响应 type UpdatePetResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Pet *PetInfo `protobuf:"bytes,2,opt,name=pet,proto3" json:"pet,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePetResponse) Reset() { *x = UpdatePetResponse{} mi := &file_proto_pet_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePetResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePetResponse) ProtoMessage() {} func (x *UpdatePetResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePetResponse.ProtoReflect.Descriptor instead. func (*UpdatePetResponse) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{5} } func (x *UpdatePetResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *UpdatePetResponse) GetPet() *PetInfo { if x != nil { return x.Pet } return nil } // 宠物升级请求 type LevelUpPetRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PetId string `protobuf:"bytes,1,opt,name=pet_id,json=petId,proto3" json:"pet_id,omitempty"` ExperiencePoints int32 `protobuf:"varint,2,opt,name=experience_points,json=experiencePoints,proto3" json:"experience_points,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LevelUpPetRequest) Reset() { *x = LevelUpPetRequest{} mi := &file_proto_pet_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LevelUpPetRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LevelUpPetRequest) ProtoMessage() {} func (x *LevelUpPetRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LevelUpPetRequest.ProtoReflect.Descriptor instead. func (*LevelUpPetRequest) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{6} } func (x *LevelUpPetRequest) GetPetId() string { if x != nil { return x.PetId } return "" } func (x *LevelUpPetRequest) GetExperiencePoints() int32 { if x != nil { return x.ExperiencePoints } return 0 } // 宠物升级响应 type LevelUpPetResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Pet *PetInfo `protobuf:"bytes,2,opt,name=pet,proto3" json:"pet,omitempty"` LeveledUp bool `protobuf:"varint,3,opt,name=leveled_up,json=leveledUp,proto3" json:"leveled_up,omitempty"` NewLevel int32 `protobuf:"varint,4,opt,name=new_level,json=newLevel,proto3" json:"new_level,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LevelUpPetResponse) Reset() { *x = LevelUpPetResponse{} mi := &file_proto_pet_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LevelUpPetResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LevelUpPetResponse) ProtoMessage() {} func (x *LevelUpPetResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LevelUpPetResponse.ProtoReflect.Descriptor instead. func (*LevelUpPetResponse) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{7} } func (x *LevelUpPetResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *LevelUpPetResponse) GetPet() *PetInfo { if x != nil { return x.Pet } return nil } func (x *LevelUpPetResponse) GetLeveledUp() bool { if x != nil { return x.LeveledUp } return false } func (x *LevelUpPetResponse) GetNewLevel() int32 { if x != nil { return x.NewLevel } return 0 } // 宠物进化请求 type EvolvePetRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PetId string `protobuf:"bytes,1,opt,name=pet_id,json=petId,proto3" json:"pet_id,omitempty"` TargetSpeciesId string `protobuf:"bytes,2,opt,name=target_species_id,json=targetSpeciesId,proto3" json:"target_species_id,omitempty"` RequiredItems []string `protobuf:"bytes,3,rep,name=required_items,json=requiredItems,proto3" json:"required_items,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *EvolvePetRequest) Reset() { *x = EvolvePetRequest{} mi := &file_proto_pet_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *EvolvePetRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EvolvePetRequest) ProtoMessage() {} func (x *EvolvePetRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EvolvePetRequest.ProtoReflect.Descriptor instead. func (*EvolvePetRequest) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{8} } func (x *EvolvePetRequest) GetPetId() string { if x != nil { return x.PetId } return "" } func (x *EvolvePetRequest) GetTargetSpeciesId() string { if x != nil { return x.TargetSpeciesId } return "" } func (x *EvolvePetRequest) GetRequiredItems() []string { if x != nil { return x.RequiredItems } return nil } // 宠物进化响应 type EvolvePetResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Pet *PetInfo `protobuf:"bytes,2,opt,name=pet,proto3" json:"pet,omitempty"` Evolved bool `protobuf:"varint,3,opt,name=evolved,proto3" json:"evolved,omitempty"` NewSpecies string `protobuf:"bytes,4,opt,name=new_species,json=newSpecies,proto3" json:"new_species,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *EvolvePetResponse) Reset() { *x = EvolvePetResponse{} mi := &file_proto_pet_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *EvolvePetResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*EvolvePetResponse) ProtoMessage() {} func (x *EvolvePetResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EvolvePetResponse.ProtoReflect.Descriptor instead. func (*EvolvePetResponse) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{9} } func (x *EvolvePetResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *EvolvePetResponse) GetPet() *PetInfo { if x != nil { return x.Pet } return nil } func (x *EvolvePetResponse) GetEvolved() bool { if x != nil { return x.Evolved } return false } func (x *EvolvePetResponse) GetNewSpecies() string { if x != nil { return x.NewSpecies } return "" } // 获取玩家宠物列表请求 type GetPlayerPetsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPlayerPetsRequest) Reset() { *x = GetPlayerPetsRequest{} mi := &file_proto_pet_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPlayerPetsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPlayerPetsRequest) ProtoMessage() {} func (x *GetPlayerPetsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPlayerPetsRequest.ProtoReflect.Descriptor instead. func (*GetPlayerPetsRequest) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{10} } func (x *GetPlayerPetsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *GetPlayerPetsRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetPlayerPetsRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 获取玩家宠物列表响应 type GetPlayerPetsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Pets []*PetInfo `protobuf:"bytes,2,rep,name=pets,proto3" json:"pets,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPlayerPetsResponse) Reset() { *x = GetPlayerPetsResponse{} mi := &file_proto_pet_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPlayerPetsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPlayerPetsResponse) ProtoMessage() {} func (x *GetPlayerPetsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPlayerPetsResponse.ProtoReflect.Descriptor instead. func (*GetPlayerPetsResponse) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{11} } func (x *GetPlayerPetsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetPlayerPetsResponse) GetPets() []*PetInfo { if x != nil { return x.Pets } return nil } func (x *GetPlayerPetsResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 宠物信息 type PetInfo struct { state protoimpl.MessageState `protogen:"open.v1"` PetId string `protobuf:"bytes,1,opt,name=pet_id,json=petId,proto3" json:"pet_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SpeciesId string `protobuf:"bytes,3,opt,name=species_id,json=speciesId,proto3" json:"species_id,omitempty"` Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` Level int32 `protobuf:"varint,5,opt,name=level,proto3" json:"level,omitempty"` Experience int32 `protobuf:"varint,6,opt,name=experience,proto3" json:"experience,omitempty"` MaxExperience int32 `protobuf:"varint,7,opt,name=max_experience,json=maxExperience,proto3" json:"max_experience,omitempty"` Rarity string `protobuf:"bytes,8,opt,name=rarity,proto3" json:"rarity,omitempty"` Stats *PetStats `protobuf:"bytes,9,opt,name=stats,proto3" json:"stats,omitempty"` Skills *PetSkills `protobuf:"bytes,10,opt,name=skills,proto3" json:"skills,omitempty"` CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastFed int64 `protobuf:"varint,12,opt,name=last_fed,json=lastFed,proto3" json:"last_fed,omitempty"` IsActive bool `protobuf:"varint,13,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PetInfo) Reset() { *x = PetInfo{} mi := &file_proto_pet_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PetInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*PetInfo) ProtoMessage() {} func (x *PetInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PetInfo.ProtoReflect.Descriptor instead. func (*PetInfo) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{12} } func (x *PetInfo) GetPetId() string { if x != nil { return x.PetId } return "" } func (x *PetInfo) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *PetInfo) GetSpeciesId() string { if x != nil { return x.SpeciesId } return "" } func (x *PetInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *PetInfo) GetLevel() int32 { if x != nil { return x.Level } return 0 } func (x *PetInfo) GetExperience() int32 { if x != nil { return x.Experience } return 0 } func (x *PetInfo) GetMaxExperience() int32 { if x != nil { return x.MaxExperience } return 0 } func (x *PetInfo) GetRarity() string { if x != nil { return x.Rarity } return "" } func (x *PetInfo) GetStats() *PetStats { if x != nil { return x.Stats } return nil } func (x *PetInfo) GetSkills() *PetSkills { if x != nil { return x.Skills } return nil } func (x *PetInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *PetInfo) GetLastFed() int64 { if x != nil { return x.LastFed } return 0 } func (x *PetInfo) GetIsActive() bool { if x != nil { return x.IsActive } return false } // 宠物属性 type PetStats struct { state protoimpl.MessageState `protogen:"open.v1"` Health int32 `protobuf:"varint,1,opt,name=health,proto3" json:"health,omitempty"` MaxHealth int32 `protobuf:"varint,2,opt,name=max_health,json=maxHealth,proto3" json:"max_health,omitempty"` Attack int32 `protobuf:"varint,3,opt,name=attack,proto3" json:"attack,omitempty"` Defense int32 `protobuf:"varint,4,opt,name=defense,proto3" json:"defense,omitempty"` Speed int32 `protobuf:"varint,5,opt,name=speed,proto3" json:"speed,omitempty"` Intelligence int32 `protobuf:"varint,6,opt,name=intelligence,proto3" json:"intelligence,omitempty"` Loyalty int32 `protobuf:"varint,7,opt,name=loyalty,proto3" json:"loyalty,omitempty"` Happiness int32 `protobuf:"varint,8,opt,name=happiness,proto3" json:"happiness,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PetStats) Reset() { *x = PetStats{} mi := &file_proto_pet_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PetStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*PetStats) ProtoMessage() {} func (x *PetStats) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PetStats.ProtoReflect.Descriptor instead. func (*PetStats) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{13} } func (x *PetStats) GetHealth() int32 { if x != nil { return x.Health } return 0 } func (x *PetStats) GetMaxHealth() int32 { if x != nil { return x.MaxHealth } return 0 } func (x *PetStats) GetAttack() int32 { if x != nil { return x.Attack } return 0 } func (x *PetStats) GetDefense() int32 { if x != nil { return x.Defense } return 0 } func (x *PetStats) GetSpeed() int32 { if x != nil { return x.Speed } return 0 } func (x *PetStats) GetIntelligence() int32 { if x != nil { return x.Intelligence } return 0 } func (x *PetStats) GetLoyalty() int32 { if x != nil { return x.Loyalty } return 0 } func (x *PetStats) GetHappiness() int32 { if x != nil { return x.Happiness } return 0 } // 宠物技能 type PetSkills struct { state protoimpl.MessageState `protogen:"open.v1"` Skills []*PetSkill `protobuf:"bytes,1,rep,name=skills,proto3" json:"skills,omitempty"` SkillPoints int32 `protobuf:"varint,2,opt,name=skill_points,json=skillPoints,proto3" json:"skill_points,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PetSkills) Reset() { *x = PetSkills{} mi := &file_proto_pet_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PetSkills) String() string { return protoimpl.X.MessageStringOf(x) } func (*PetSkills) ProtoMessage() {} func (x *PetSkills) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PetSkills.ProtoReflect.Descriptor instead. func (*PetSkills) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{14} } func (x *PetSkills) GetSkills() []*PetSkill { if x != nil { return x.Skills } return nil } func (x *PetSkills) GetSkillPoints() int32 { if x != nil { return x.SkillPoints } return 0 } // 宠物技能详情 type PetSkill struct { state protoimpl.MessageState `protogen:"open.v1"` SkillId string `protobuf:"bytes,1,opt,name=skill_id,json=skillId,proto3" json:"skill_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` Level int32 `protobuf:"varint,4,opt,name=level,proto3" json:"level,omitempty"` MaxLevel int32 `protobuf:"varint,5,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` SkillType PetSkillType `protobuf:"varint,6,opt,name=skill_type,json=skillType,proto3,enum=greatestworks.pet.PetSkillType" json:"skill_type,omitempty"` Cooldown int32 `protobuf:"varint,7,opt,name=cooldown,proto3" json:"cooldown,omitempty"` ManaCost int32 `protobuf:"varint,8,opt,name=mana_cost,json=manaCost,proto3" json:"mana_cost,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PetSkill) Reset() { *x = PetSkill{} mi := &file_proto_pet_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PetSkill) String() string { return protoimpl.X.MessageStringOf(x) } func (*PetSkill) ProtoMessage() {} func (x *PetSkill) ProtoReflect() protoreflect.Message { mi := &file_proto_pet_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PetSkill.ProtoReflect.Descriptor instead. func (*PetSkill) Descriptor() ([]byte, []int) { return file_proto_pet_proto_rawDescGZIP(), []int{15} } func (x *PetSkill) GetSkillId() string { if x != nil { return x.SkillId } return "" } func (x *PetSkill) GetName() string { if x != nil { return x.Name } return "" } func (x *PetSkill) GetDescription() string { if x != nil { return x.Description } return "" } func (x *PetSkill) GetLevel() int32 { if x != nil { return x.Level } return 0 } func (x *PetSkill) GetMaxLevel() int32 { if x != nil { return x.MaxLevel } return 0 } func (x *PetSkill) GetSkillType() PetSkillType { if x != nil { return x.SkillType } return PetSkillType_PET_SKILL_TYPE_UNSPECIFIED } func (x *PetSkill) GetCooldown() int32 { if x != nil { return x.Cooldown } return 0 } func (x *PetSkill) GetManaCost() int32 { if x != nil { return x.ManaCost } return 0 } var File_proto_pet_proto protoreflect.FileDescriptor const file_proto_pet_proto_rawDesc = "" + "\n" + "\x0fproto/pet.proto\x12\x11greatestworks.pet\x1a\x12proto/common.proto\"\x9f\x01\n" + "\x10CreatePetRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x1d\n" + "\n" + "species_id\x18\x02 \x01(\tR\tspeciesId\x12\x12\n" + "\x04name\x18\x03 \x01(\tR\x04name\x12#\n" + "\rinitial_level\x18\x04 \x01(\x05R\finitialLevel\x12\x16\n" + "\x06rarity\x18\x05 \x01(\tR\x06rarity\"\x7f\n" + "\x11CreatePetResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12,\n" + "\x03pet\x18\x02 \x01(\v2\x1a.greatestworks.pet.PetInfoR\x03pet\"*\n" + "\x11GetPetInfoRequest\x12\x15\n" + "\x06pet_id\x18\x01 \x01(\tR\x05petId\"\x80\x01\n" + "\x12GetPetInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12,\n" + "\x03pet\x18\x02 \x01(\v2\x1a.greatestworks.pet.PetInfoR\x03pet\"\xb1\x01\n" + "\x10UpdatePetRequest\x12\x15\n" + "\x06pet_id\x18\x01 \x01(\tR\x05petId\x12J\n" + "\aupdates\x18\x02 \x03(\v20.greatestworks.pet.UpdatePetRequest.UpdatesEntryR\aupdates\x1a:\n" + "\fUpdatesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x7f\n" + "\x11UpdatePetResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12,\n" + "\x03pet\x18\x02 \x01(\v2\x1a.greatestworks.pet.PetInfoR\x03pet\"W\n" + "\x11LevelUpPetRequest\x12\x15\n" + "\x06pet_id\x18\x01 \x01(\tR\x05petId\x12+\n" + "\x11experience_points\x18\x02 \x01(\x05R\x10experiencePoints\"\xbc\x01\n" + "\x12LevelUpPetResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12,\n" + "\x03pet\x18\x02 \x01(\v2\x1a.greatestworks.pet.PetInfoR\x03pet\x12\x1d\n" + "\n" + "leveled_up\x18\x03 \x01(\bR\tleveledUp\x12\x1b\n" + "\tnew_level\x18\x04 \x01(\x05R\bnewLevel\"|\n" + "\x10EvolvePetRequest\x12\x15\n" + "\x06pet_id\x18\x01 \x01(\tR\x05petId\x12*\n" + "\x11target_species_id\x18\x02 \x01(\tR\x0ftargetSpeciesId\x12%\n" + "\x0erequired_items\x18\x03 \x03(\tR\rrequiredItems\"\xba\x01\n" + "\x11EvolvePetResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12,\n" + "\x03pet\x18\x02 \x01(\v2\x1a.greatestworks.pet.PetInfoR\x03pet\x12\x18\n" + "\aevolved\x18\x03 \x01(\bR\aevolved\x12\x1f\n" + "\vnew_species\x18\x04 \x01(\tR\n" + "newSpecies\"a\n" + "\x14GetPlayerPetsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x14\n" + "\x05limit\x18\x02 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\x05R\x06offset\"\xcb\x01\n" + "\x15GetPlayerPetsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12.\n" + "\x04pets\x18\x02 \x03(\v2\x1a.greatestworks.pet.PetInfoR\x04pets\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\xa5\x03\n" + "\aPetInfo\x12\x15\n" + "\x06pet_id\x18\x01 \x01(\tR\x05petId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\x12\x1d\n" + "\n" + "species_id\x18\x03 \x01(\tR\tspeciesId\x12\x12\n" + "\x04name\x18\x04 \x01(\tR\x04name\x12\x14\n" + "\x05level\x18\x05 \x01(\x05R\x05level\x12\x1e\n" + "\n" + "experience\x18\x06 \x01(\x05R\n" + "experience\x12%\n" + "\x0emax_experience\x18\a \x01(\x05R\rmaxExperience\x12\x16\n" + "\x06rarity\x18\b \x01(\tR\x06rarity\x121\n" + "\x05stats\x18\t \x01(\v2\x1b.greatestworks.pet.PetStatsR\x05stats\x124\n" + "\x06skills\x18\n" + " \x01(\v2\x1c.greatestworks.pet.PetSkillsR\x06skills\x12\x1d\n" + "\n" + "created_at\x18\v \x01(\x03R\tcreatedAt\x12\x19\n" + "\blast_fed\x18\f \x01(\x03R\alastFed\x12\x1b\n" + "\tis_active\x18\r \x01(\bR\bisActive\"\xe5\x01\n" + "\bPetStats\x12\x16\n" + "\x06health\x18\x01 \x01(\x05R\x06health\x12\x1d\n" + "\n" + "max_health\x18\x02 \x01(\x05R\tmaxHealth\x12\x16\n" + "\x06attack\x18\x03 \x01(\x05R\x06attack\x12\x18\n" + "\adefense\x18\x04 \x01(\x05R\adefense\x12\x14\n" + "\x05speed\x18\x05 \x01(\x05R\x05speed\x12\"\n" + "\fintelligence\x18\x06 \x01(\x05R\fintelligence\x12\x18\n" + "\aloyalty\x18\a \x01(\x05R\aloyalty\x12\x1c\n" + "\thappiness\x18\b \x01(\x05R\thappiness\"c\n" + "\tPetSkills\x123\n" + "\x06skills\x18\x01 \x03(\v2\x1b.greatestworks.pet.PetSkillR\x06skills\x12!\n" + "\fskill_points\x18\x02 \x01(\x05R\vskillPoints\"\x87\x02\n" + "\bPetSkill\x12\x19\n" + "\bskill_id\x18\x01 \x01(\tR\askillId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x14\n" + "\x05level\x18\x04 \x01(\x05R\x05level\x12\x1b\n" + "\tmax_level\x18\x05 \x01(\x05R\bmaxLevel\x12>\n" + "\n" + "skill_type\x18\x06 \x01(\x0e2\x1f.greatestworks.pet.PetSkillTypeR\tskillType\x12\x1a\n" + "\bcooldown\x18\a \x01(\x05R\bcooldown\x12\x1b\n" + "\tmana_cost\x18\b \x01(\x05R\bmanaCost*\xb2\x01\n" + "\tPetRarity\x12\x1a\n" + "\x16PET_RARITY_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11PET_RARITY_COMMON\x10\x01\x12\x17\n" + "\x13PET_RARITY_UNCOMMON\x10\x02\x12\x13\n" + "\x0fPET_RARITY_RARE\x10\x03\x12\x13\n" + "\x0fPET_RARITY_EPIC\x10\x04\x12\x18\n" + "\x14PET_RARITY_LEGENDARY\x10\x05\x12\x15\n" + "\x11PET_RARITY_MYTHIC\x10\x06*\x9f\x01\n" + "\n" + "PetQuality\x12\x1b\n" + "\x17PET_QUALITY_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10PET_QUALITY_POOR\x10\x01\x12\x14\n" + "\x10PET_QUALITY_FAIR\x10\x02\x12\x14\n" + "\x10PET_QUALITY_GOOD\x10\x03\x12\x19\n" + "\x15PET_QUALITY_EXCELLENT\x10\x04\x12\x17\n" + "\x13PET_QUALITY_PERFECT\x10\x05*\xeb\x01\n" + "\fPetSkillType\x12\x1e\n" + "\x1aPET_SKILL_TYPE_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15PET_SKILL_TYPE_ATTACK\x10\x01\x12\x1a\n" + "\x16PET_SKILL_TYPE_DEFENSE\x10\x02\x12\x17\n" + "\x13PET_SKILL_TYPE_HEAL\x10\x03\x12\x17\n" + "\x13PET_SKILL_TYPE_BUFF\x10\x04\x12\x19\n" + "\x15PET_SKILL_TYPE_DEBUFF\x10\x05\x12\x1a\n" + "\x16PET_SKILL_TYPE_PASSIVE\x10\x06\x12\x1b\n" + "\x17PET_SKILL_TYPE_ULTIMATE\x10\a*\xb5\x01\n" + "\aPetMood\x12\x18\n" + "\x14PET_MOOD_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13PET_MOOD_VERY_HAPPY\x10\x01\x12\x12\n" + "\x0ePET_MOOD_HAPPY\x10\x02\x12\x13\n" + "\x0fPET_MOOD_NORMAL\x10\x03\x12\x10\n" + "\fPET_MOOD_SAD\x10\x04\x12\x15\n" + "\x11PET_MOOD_VERY_SAD\x10\x05\x12\x12\n" + "\x0ePET_MOOD_ANGRY\x10\x06\x12\x11\n" + "\rPET_MOOD_SICK\x10\a*\xd8\x01\n" + "\x0fPetTrainingType\x12!\n" + "\x1dPET_TRAINING_TYPE_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aPET_TRAINING_TYPE_STRENGTH\x10\x01\x12\x1d\n" + "\x19PET_TRAINING_TYPE_AGILITY\x10\x02\x12\"\n" + "\x1ePET_TRAINING_TYPE_INTELLIGENCE\x10\x03\x12\x1f\n" + "\x1bPET_TRAINING_TYPE_ENDURANCE\x10\x04\x12\x1e\n" + "\x1aPET_TRAINING_TYPE_BALANCED\x10\x05*\xcd\x01\n" + "\x14PetTrainingIntensity\x12&\n" + "\"PET_TRAINING_INTENSITY_UNSPECIFIED\x10\x00\x12 \n" + "\x1cPET_TRAINING_INTENSITY_LIGHT\x10\x01\x12#\n" + "\x1fPET_TRAINING_INTENSITY_MODERATE\x10\x02\x12\"\n" + "\x1ePET_TRAINING_INTENSITY_INTENSE\x10\x03\x12\"\n" + "\x1ePET_TRAINING_INTENSITY_EXTREME\x10\x042\xae\x04\n" + "\n" + "PetService\x12V\n" + "\tCreatePet\x12#.greatestworks.pet.CreatePetRequest\x1a$.greatestworks.pet.CreatePetResponse\x12Y\n" + "\n" + "GetPetInfo\x12$.greatestworks.pet.GetPetInfoRequest\x1a%.greatestworks.pet.GetPetInfoResponse\x12V\n" + "\tUpdatePet\x12#.greatestworks.pet.UpdatePetRequest\x1a$.greatestworks.pet.UpdatePetResponse\x12Y\n" + "\n" + "LevelUpPet\x12$.greatestworks.pet.LevelUpPetRequest\x1a%.greatestworks.pet.LevelUpPetResponse\x12V\n" + "\tEvolvePet\x12#.greatestworks.pet.EvolvePetRequest\x1a$.greatestworks.pet.EvolvePetResponse\x12b\n" + "\rGetPlayerPets\x12'.greatestworks.pet.GetPlayerPetsRequest\x1a(.greatestworks.pet.GetPlayerPetsResponseB6Z greatestworks/internal/proto/pet\xaa\x02\x11GreatestWorks.Petb\x06proto3" var ( file_proto_pet_proto_rawDescOnce sync.Once file_proto_pet_proto_rawDescData []byte ) func file_proto_pet_proto_rawDescGZIP() []byte { file_proto_pet_proto_rawDescOnce.Do(func() { file_proto_pet_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_pet_proto_rawDesc), len(file_proto_pet_proto_rawDesc))) }) return file_proto_pet_proto_rawDescData } var file_proto_pet_proto_enumTypes = make([]protoimpl.EnumInfo, 6) var file_proto_pet_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_proto_pet_proto_goTypes = []any{ (PetRarity)(0), // 0: greatestworks.pet.PetRarity (PetQuality)(0), // 1: greatestworks.pet.PetQuality (PetSkillType)(0), // 2: greatestworks.pet.PetSkillType (PetMood)(0), // 3: greatestworks.pet.PetMood (PetTrainingType)(0), // 4: greatestworks.pet.PetTrainingType (PetTrainingIntensity)(0), // 5: greatestworks.pet.PetTrainingIntensity (*CreatePetRequest)(nil), // 6: greatestworks.pet.CreatePetRequest (*CreatePetResponse)(nil), // 7: greatestworks.pet.CreatePetResponse (*GetPetInfoRequest)(nil), // 8: greatestworks.pet.GetPetInfoRequest (*GetPetInfoResponse)(nil), // 9: greatestworks.pet.GetPetInfoResponse (*UpdatePetRequest)(nil), // 10: greatestworks.pet.UpdatePetRequest (*UpdatePetResponse)(nil), // 11: greatestworks.pet.UpdatePetResponse (*LevelUpPetRequest)(nil), // 12: greatestworks.pet.LevelUpPetRequest (*LevelUpPetResponse)(nil), // 13: greatestworks.pet.LevelUpPetResponse (*EvolvePetRequest)(nil), // 14: greatestworks.pet.EvolvePetRequest (*EvolvePetResponse)(nil), // 15: greatestworks.pet.EvolvePetResponse (*GetPlayerPetsRequest)(nil), // 16: greatestworks.pet.GetPlayerPetsRequest (*GetPlayerPetsResponse)(nil), // 17: greatestworks.pet.GetPlayerPetsResponse (*PetInfo)(nil), // 18: greatestworks.pet.PetInfo (*PetStats)(nil), // 19: greatestworks.pet.PetStats (*PetSkills)(nil), // 20: greatestworks.pet.PetSkills (*PetSkill)(nil), // 21: greatestworks.pet.PetSkill nil, // 22: greatestworks.pet.UpdatePetRequest.UpdatesEntry (*common.CommonResponse)(nil), // 23: greatestworks.common.CommonResponse (*common.PaginationInfo)(nil), // 24: greatestworks.common.PaginationInfo } var file_proto_pet_proto_depIdxs = []int32{ 23, // 0: greatestworks.pet.CreatePetResponse.common:type_name -> greatestworks.common.CommonResponse 18, // 1: greatestworks.pet.CreatePetResponse.pet:type_name -> greatestworks.pet.PetInfo 23, // 2: greatestworks.pet.GetPetInfoResponse.common:type_name -> greatestworks.common.CommonResponse 18, // 3: greatestworks.pet.GetPetInfoResponse.pet:type_name -> greatestworks.pet.PetInfo 22, // 4: greatestworks.pet.UpdatePetRequest.updates:type_name -> greatestworks.pet.UpdatePetRequest.UpdatesEntry 23, // 5: greatestworks.pet.UpdatePetResponse.common:type_name -> greatestworks.common.CommonResponse 18, // 6: greatestworks.pet.UpdatePetResponse.pet:type_name -> greatestworks.pet.PetInfo 23, // 7: greatestworks.pet.LevelUpPetResponse.common:type_name -> greatestworks.common.CommonResponse 18, // 8: greatestworks.pet.LevelUpPetResponse.pet:type_name -> greatestworks.pet.PetInfo 23, // 9: greatestworks.pet.EvolvePetResponse.common:type_name -> greatestworks.common.CommonResponse 18, // 10: greatestworks.pet.EvolvePetResponse.pet:type_name -> greatestworks.pet.PetInfo 23, // 11: greatestworks.pet.GetPlayerPetsResponse.common:type_name -> greatestworks.common.CommonResponse 18, // 12: greatestworks.pet.GetPlayerPetsResponse.pets:type_name -> greatestworks.pet.PetInfo 24, // 13: greatestworks.pet.GetPlayerPetsResponse.pagination:type_name -> greatestworks.common.PaginationInfo 19, // 14: greatestworks.pet.PetInfo.stats:type_name -> greatestworks.pet.PetStats 20, // 15: greatestworks.pet.PetInfo.skills:type_name -> greatestworks.pet.PetSkills 21, // 16: greatestworks.pet.PetSkills.skills:type_name -> greatestworks.pet.PetSkill 2, // 17: greatestworks.pet.PetSkill.skill_type:type_name -> greatestworks.pet.PetSkillType 6, // 18: greatestworks.pet.PetService.CreatePet:input_type -> greatestworks.pet.CreatePetRequest 8, // 19: greatestworks.pet.PetService.GetPetInfo:input_type -> greatestworks.pet.GetPetInfoRequest 10, // 20: greatestworks.pet.PetService.UpdatePet:input_type -> greatestworks.pet.UpdatePetRequest 12, // 21: greatestworks.pet.PetService.LevelUpPet:input_type -> greatestworks.pet.LevelUpPetRequest 14, // 22: greatestworks.pet.PetService.EvolvePet:input_type -> greatestworks.pet.EvolvePetRequest 16, // 23: greatestworks.pet.PetService.GetPlayerPets:input_type -> greatestworks.pet.GetPlayerPetsRequest 7, // 24: greatestworks.pet.PetService.CreatePet:output_type -> greatestworks.pet.CreatePetResponse 9, // 25: greatestworks.pet.PetService.GetPetInfo:output_type -> greatestworks.pet.GetPetInfoResponse 11, // 26: greatestworks.pet.PetService.UpdatePet:output_type -> greatestworks.pet.UpdatePetResponse 13, // 27: greatestworks.pet.PetService.LevelUpPet:output_type -> greatestworks.pet.LevelUpPetResponse 15, // 28: greatestworks.pet.PetService.EvolvePet:output_type -> greatestworks.pet.EvolvePetResponse 17, // 29: greatestworks.pet.PetService.GetPlayerPets:output_type -> greatestworks.pet.GetPlayerPetsResponse 24, // [24:30] is the sub-list for method output_type 18, // [18:24] is the sub-list for method input_type 18, // [18:18] is the sub-list for extension type_name 18, // [18:18] is the sub-list for extension extendee 0, // [0:18] is the sub-list for field type_name } func init() { file_proto_pet_proto_init() } func file_proto_pet_proto_init() { if File_proto_pet_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_pet_proto_rawDesc), len(file_proto_pet_proto_rawDesc)), NumEnums: 6, NumMessages: 17, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_pet_proto_goTypes, DependencyIndexes: file_proto_pet_proto_depIdxs, EnumInfos: file_proto_pet_proto_enumTypes, MessageInfos: file_proto_pet_proto_msgTypes, }.Build() File_proto_pet_proto = out.File file_proto_pet_proto_goTypes = nil file_proto_pet_proto_depIdxs = nil } ================================================ FILE: internal/proto/player/player.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/player.proto package player import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 玩家性别枚举 type PlayerGender int32 const ( PlayerGender_PLAYER_GENDER_UNSPECIFIED PlayerGender = 0 PlayerGender_PLAYER_GENDER_MALE PlayerGender = 1 // 男性 PlayerGender_PLAYER_GENDER_FEMALE PlayerGender = 2 // 女性 PlayerGender_PLAYER_GENDER_OTHER PlayerGender = 3 // 其他 ) // Enum value maps for PlayerGender. var ( PlayerGender_name = map[int32]string{ 0: "PLAYER_GENDER_UNSPECIFIED", 1: "PLAYER_GENDER_MALE", 2: "PLAYER_GENDER_FEMALE", 3: "PLAYER_GENDER_OTHER", } PlayerGender_value = map[string]int32{ "PLAYER_GENDER_UNSPECIFIED": 0, "PLAYER_GENDER_MALE": 1, "PLAYER_GENDER_FEMALE": 2, "PLAYER_GENDER_OTHER": 3, } ) func (x PlayerGender) Enum() *PlayerGender { p := new(PlayerGender) *p = x return p } func (x PlayerGender) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerGender) Descriptor() protoreflect.EnumDescriptor { return file_proto_player_proto_enumTypes[0].Descriptor() } func (PlayerGender) Type() protoreflect.EnumType { return &file_proto_player_proto_enumTypes[0] } func (x PlayerGender) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerGender.Descriptor instead. func (PlayerGender) EnumDescriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{0} } // 玩家职业枚举 type PlayerClass int32 const ( PlayerClass_PLAYER_CLASS_UNSPECIFIED PlayerClass = 0 PlayerClass_PLAYER_CLASS_WARRIOR PlayerClass = 1 // 战士 PlayerClass_PLAYER_CLASS_MAGE PlayerClass = 2 // 法师 PlayerClass_PLAYER_CLASS_ARCHER PlayerClass = 3 // 弓箭手 PlayerClass_PLAYER_CLASS_ASSASSIN PlayerClass = 4 // 刺客 PlayerClass_PLAYER_CLASS_PRIEST PlayerClass = 5 // 牧师 PlayerClass_PLAYER_CLASS_PALADIN PlayerClass = 6 // 圣骑士 PlayerClass_PLAYER_CLASS_DRUID PlayerClass = 7 // 德鲁伊 PlayerClass_PLAYER_CLASS_NECROMANCER PlayerClass = 8 // 死灵法师 ) // Enum value maps for PlayerClass. var ( PlayerClass_name = map[int32]string{ 0: "PLAYER_CLASS_UNSPECIFIED", 1: "PLAYER_CLASS_WARRIOR", 2: "PLAYER_CLASS_MAGE", 3: "PLAYER_CLASS_ARCHER", 4: "PLAYER_CLASS_ASSASSIN", 5: "PLAYER_CLASS_PRIEST", 6: "PLAYER_CLASS_PALADIN", 7: "PLAYER_CLASS_DRUID", 8: "PLAYER_CLASS_NECROMANCER", } PlayerClass_value = map[string]int32{ "PLAYER_CLASS_UNSPECIFIED": 0, "PLAYER_CLASS_WARRIOR": 1, "PLAYER_CLASS_MAGE": 2, "PLAYER_CLASS_ARCHER": 3, "PLAYER_CLASS_ASSASSIN": 4, "PLAYER_CLASS_PRIEST": 5, "PLAYER_CLASS_PALADIN": 6, "PLAYER_CLASS_DRUID": 7, "PLAYER_CLASS_NECROMANCER": 8, } ) func (x PlayerClass) Enum() *PlayerClass { p := new(PlayerClass) *p = x return p } func (x PlayerClass) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerClass) Descriptor() protoreflect.EnumDescriptor { return file_proto_player_proto_enumTypes[1].Descriptor() } func (PlayerClass) Type() protoreflect.EnumType { return &file_proto_player_proto_enumTypes[1] } func (x PlayerClass) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerClass.Descriptor instead. func (PlayerClass) EnumDescriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{1} } // 玩家等级枚举 type PlayerLevel int32 const ( PlayerLevel_PLAYER_LEVEL_UNSPECIFIED PlayerLevel = 0 PlayerLevel_PLAYER_LEVEL_BEGINNER PlayerLevel = 1 // 新手 PlayerLevel_PLAYER_LEVEL_INTERMEDIATE PlayerLevel = 2 // 中级 PlayerLevel_PLAYER_LEVEL_ADVANCED PlayerLevel = 3 // 高级 PlayerLevel_PLAYER_LEVEL_EXPERT PlayerLevel = 4 // 专家 PlayerLevel_PLAYER_LEVEL_MASTER PlayerLevel = 5 // 大师 PlayerLevel_PLAYER_LEVEL_GRANDMASTER PlayerLevel = 6 // 宗师 ) // Enum value maps for PlayerLevel. var ( PlayerLevel_name = map[int32]string{ 0: "PLAYER_LEVEL_UNSPECIFIED", 1: "PLAYER_LEVEL_BEGINNER", 2: "PLAYER_LEVEL_INTERMEDIATE", 3: "PLAYER_LEVEL_ADVANCED", 4: "PLAYER_LEVEL_EXPERT", 5: "PLAYER_LEVEL_MASTER", 6: "PLAYER_LEVEL_GRANDMASTER", } PlayerLevel_value = map[string]int32{ "PLAYER_LEVEL_UNSPECIFIED": 0, "PLAYER_LEVEL_BEGINNER": 1, "PLAYER_LEVEL_INTERMEDIATE": 2, "PLAYER_LEVEL_ADVANCED": 3, "PLAYER_LEVEL_EXPERT": 4, "PLAYER_LEVEL_MASTER": 5, "PLAYER_LEVEL_GRANDMASTER": 6, } ) func (x PlayerLevel) Enum() *PlayerLevel { p := new(PlayerLevel) *p = x return p } func (x PlayerLevel) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerLevel) Descriptor() protoreflect.EnumDescriptor { return file_proto_player_proto_enumTypes[2].Descriptor() } func (PlayerLevel) Type() protoreflect.EnumType { return &file_proto_player_proto_enumTypes[2] } func (x PlayerLevel) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerLevel.Descriptor instead. func (PlayerLevel) EnumDescriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{2} } // 玩家状态枚举 type PlayerStatus int32 const ( PlayerStatus_PLAYER_STATUS_UNSPECIFIED PlayerStatus = 0 PlayerStatus_PLAYER_STATUS_OFFLINE PlayerStatus = 1 // 离线 PlayerStatus_PLAYER_STATUS_ONLINE PlayerStatus = 2 // 在线 PlayerStatus_PLAYER_STATUS_IN_BATTLE PlayerStatus = 3 // 战斗中 PlayerStatus_PLAYER_STATUS_IN_QUEUE PlayerStatus = 4 // 排队中 PlayerStatus_PLAYER_STATUS_AFK PlayerStatus = 5 // 离开 PlayerStatus_PLAYER_STATUS_BANNED PlayerStatus = 6 // 被封禁 ) // Enum value maps for PlayerStatus. var ( PlayerStatus_name = map[int32]string{ 0: "PLAYER_STATUS_UNSPECIFIED", 1: "PLAYER_STATUS_OFFLINE", 2: "PLAYER_STATUS_ONLINE", 3: "PLAYER_STATUS_IN_BATTLE", 4: "PLAYER_STATUS_IN_QUEUE", 5: "PLAYER_STATUS_AFK", 6: "PLAYER_STATUS_BANNED", } PlayerStatus_value = map[string]int32{ "PLAYER_STATUS_UNSPECIFIED": 0, "PLAYER_STATUS_OFFLINE": 1, "PLAYER_STATUS_ONLINE": 2, "PLAYER_STATUS_IN_BATTLE": 3, "PLAYER_STATUS_IN_QUEUE": 4, "PLAYER_STATUS_AFK": 5, "PLAYER_STATUS_BANNED": 6, } ) func (x PlayerStatus) Enum() *PlayerStatus { p := new(PlayerStatus) *p = x return p } func (x PlayerStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_player_proto_enumTypes[3].Descriptor() } func (PlayerStatus) Type() protoreflect.EnumType { return &file_proto_player_proto_enumTypes[3] } func (x PlayerStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerStatus.Descriptor instead. func (PlayerStatus) EnumDescriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{3} } // 创建玩家请求 type CreatePlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` InitialLevel int32 `protobuf:"varint,3,opt,name=initial_level,json=initialLevel,proto3" json:"initial_level,omitempty"` InitialPosition *common.Position `protobuf:"bytes,4,opt,name=initial_position,json=initialPosition,proto3" json:"initial_position,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePlayerRequest) Reset() { *x = CreatePlayerRequest{} mi := &file_proto_player_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePlayerRequest) ProtoMessage() {} func (x *CreatePlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePlayerRequest.ProtoReflect.Descriptor instead. func (*CreatePlayerRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{0} } func (x *CreatePlayerRequest) GetName() string { if x != nil { return x.Name } return "" } func (x *CreatePlayerRequest) GetAccountId() string { if x != nil { return x.AccountId } return "" } func (x *CreatePlayerRequest) GetInitialLevel() int32 { if x != nil { return x.InitialLevel } return 0 } func (x *CreatePlayerRequest) GetInitialPosition() *common.Position { if x != nil { return x.InitialPosition } return nil } // 创建玩家响应 type CreatePlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Player *common.PlayerBasicInfo `protobuf:"bytes,2,opt,name=player,proto3" json:"player,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePlayerResponse) Reset() { *x = CreatePlayerResponse{} mi := &file_proto_player_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePlayerResponse) ProtoMessage() {} func (x *CreatePlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePlayerResponse.ProtoReflect.Descriptor instead. func (*CreatePlayerResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{1} } func (x *CreatePlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *CreatePlayerResponse) GetPlayer() *common.PlayerBasicInfo { if x != nil { return x.Player } return nil } // 登录请求 type LoginRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` ClientVersion string `protobuf:"bytes,3,opt,name=client_version,json=clientVersion,proto3" json:"client_version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LoginRequest) Reset() { *x = LoginRequest{} mi := &file_proto_player_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LoginRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LoginRequest) ProtoMessage() {} func (x *LoginRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. func (*LoginRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{2} } func (x *LoginRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *LoginRequest) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *LoginRequest) GetClientVersion() string { if x != nil { return x.ClientVersion } return "" } // 登录响应 type LoginResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Player *common.PlayerBasicInfo `protobuf:"bytes,2,opt,name=player,proto3" json:"player,omitempty"` SessionToken string `protobuf:"bytes,3,opt,name=session_token,json=sessionToken,proto3" json:"session_token,omitempty"` LoginTime int64 `protobuf:"varint,4,opt,name=login_time,json=loginTime,proto3" json:"login_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LoginResponse) Reset() { *x = LoginResponse{} mi := &file_proto_player_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LoginResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LoginResponse) ProtoMessage() {} func (x *LoginResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. func (*LoginResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{3} } func (x *LoginResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *LoginResponse) GetPlayer() *common.PlayerBasicInfo { if x != nil { return x.Player } return nil } func (x *LoginResponse) GetSessionToken() string { if x != nil { return x.SessionToken } return "" } func (x *LoginResponse) GetLoginTime() int64 { if x != nil { return x.LoginTime } return 0 } // 登出请求 type LogoutRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LogoutRequest) Reset() { *x = LogoutRequest{} mi := &file_proto_player_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LogoutRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogoutRequest) ProtoMessage() {} func (x *LogoutRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. func (*LogoutRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{4} } func (x *LogoutRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *LogoutRequest) GetSessionId() string { if x != nil { return x.SessionId } return "" } // 登出响应 type LogoutResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` LogoutTime int64 `protobuf:"varint,2,opt,name=logout_time,json=logoutTime,proto3" json:"logout_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LogoutResponse) Reset() { *x = LogoutResponse{} mi := &file_proto_player_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LogoutResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LogoutResponse) ProtoMessage() {} func (x *LogoutResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead. func (*LogoutResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{5} } func (x *LogoutResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *LogoutResponse) GetLogoutTime() int64 { if x != nil { return x.LogoutTime } return 0 } // 获取玩家信息请求 type GetPlayerInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPlayerInfoRequest) Reset() { *x = GetPlayerInfoRequest{} mi := &file_proto_player_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPlayerInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPlayerInfoRequest) ProtoMessage() {} func (x *GetPlayerInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPlayerInfoRequest.ProtoReflect.Descriptor instead. func (*GetPlayerInfoRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{6} } func (x *GetPlayerInfoRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } // 获取玩家信息响应 type GetPlayerInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Player *common.PlayerBasicInfo `protobuf:"bytes,2,opt,name=player,proto3" json:"player,omitempty"` Stats *PlayerStats `protobuf:"bytes,3,opt,name=stats,proto3" json:"stats,omitempty"` Inventory *PlayerInventory `protobuf:"bytes,4,opt,name=inventory,proto3" json:"inventory,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPlayerInfoResponse) Reset() { *x = GetPlayerInfoResponse{} mi := &file_proto_player_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPlayerInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPlayerInfoResponse) ProtoMessage() {} func (x *GetPlayerInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPlayerInfoResponse.ProtoReflect.Descriptor instead. func (*GetPlayerInfoResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{7} } func (x *GetPlayerInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetPlayerInfoResponse) GetPlayer() *common.PlayerBasicInfo { if x != nil { return x.Player } return nil } func (x *GetPlayerInfoResponse) GetStats() *PlayerStats { if x != nil { return x.Stats } return nil } func (x *GetPlayerInfoResponse) GetInventory() *PlayerInventory { if x != nil { return x.Inventory } return nil } // 更新玩家信息请求 type UpdatePlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Updates map[string]string `protobuf:"bytes,2,rep,name=updates,proto3" json:"updates,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePlayerRequest) Reset() { *x = UpdatePlayerRequest{} mi := &file_proto_player_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePlayerRequest) ProtoMessage() {} func (x *UpdatePlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePlayerRequest.ProtoReflect.Descriptor instead. func (*UpdatePlayerRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{8} } func (x *UpdatePlayerRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *UpdatePlayerRequest) GetUpdates() map[string]string { if x != nil { return x.Updates } return nil } // 更新玩家信息响应 type UpdatePlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Player *common.PlayerBasicInfo `protobuf:"bytes,2,opt,name=player,proto3" json:"player,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePlayerResponse) Reset() { *x = UpdatePlayerResponse{} mi := &file_proto_player_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePlayerResponse) ProtoMessage() {} func (x *UpdatePlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePlayerResponse.ProtoReflect.Descriptor instead. func (*UpdatePlayerResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{9} } func (x *UpdatePlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *UpdatePlayerResponse) GetPlayer() *common.PlayerBasicInfo { if x != nil { return x.Player } return nil } // 移动玩家请求 type MovePlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` Position *common.Position `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` SceneId string `protobuf:"bytes,3,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MovePlayerRequest) Reset() { *x = MovePlayerRequest{} mi := &file_proto_player_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MovePlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*MovePlayerRequest) ProtoMessage() {} func (x *MovePlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MovePlayerRequest.ProtoReflect.Descriptor instead. func (*MovePlayerRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{10} } func (x *MovePlayerRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *MovePlayerRequest) GetPosition() *common.Position { if x != nil { return x.Position } return nil } func (x *MovePlayerRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } // 移动玩家响应 type MovePlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewPosition *common.Position `protobuf:"bytes,2,opt,name=new_position,json=newPosition,proto3" json:"new_position,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MovePlayerResponse) Reset() { *x = MovePlayerResponse{} mi := &file_proto_player_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MovePlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*MovePlayerResponse) ProtoMessage() {} func (x *MovePlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MovePlayerResponse.ProtoReflect.Descriptor instead. func (*MovePlayerResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{11} } func (x *MovePlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *MovePlayerResponse) GetNewPosition() *common.Position { if x != nil { return x.NewPosition } return nil } // 获取在线玩家列表请求 type GetOnlinePlayersRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Limit int32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetOnlinePlayersRequest) Reset() { *x = GetOnlinePlayersRequest{} mi := &file_proto_player_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetOnlinePlayersRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetOnlinePlayersRequest) ProtoMessage() {} func (x *GetOnlinePlayersRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetOnlinePlayersRequest.ProtoReflect.Descriptor instead. func (*GetOnlinePlayersRequest) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{12} } func (x *GetOnlinePlayersRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetOnlinePlayersRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 获取在线玩家列表响应 type GetOnlinePlayersResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Players []*common.PlayerBasicInfo `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetOnlinePlayersResponse) Reset() { *x = GetOnlinePlayersResponse{} mi := &file_proto_player_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetOnlinePlayersResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetOnlinePlayersResponse) ProtoMessage() {} func (x *GetOnlinePlayersResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetOnlinePlayersResponse.ProtoReflect.Descriptor instead. func (*GetOnlinePlayersResponse) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{13} } func (x *GetOnlinePlayersResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetOnlinePlayersResponse) GetPlayers() []*common.PlayerBasicInfo { if x != nil { return x.Players } return nil } func (x *GetOnlinePlayersResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 玩家属性 type PlayerStats struct { state protoimpl.MessageState `protogen:"open.v1"` Health int32 `protobuf:"varint,1,opt,name=health,proto3" json:"health,omitempty"` MaxHealth int32 `protobuf:"varint,2,opt,name=max_health,json=maxHealth,proto3" json:"max_health,omitempty"` Mana int32 `protobuf:"varint,3,opt,name=mana,proto3" json:"mana,omitempty"` MaxMana int32 `protobuf:"varint,4,opt,name=max_mana,json=maxMana,proto3" json:"max_mana,omitempty"` Attack int32 `protobuf:"varint,5,opt,name=attack,proto3" json:"attack,omitempty"` Defense int32 `protobuf:"varint,6,opt,name=defense,proto3" json:"defense,omitempty"` Speed int32 `protobuf:"varint,7,opt,name=speed,proto3" json:"speed,omitempty"` Gold int32 `protobuf:"varint,8,opt,name=gold,proto3" json:"gold,omitempty"` Diamonds int32 `protobuf:"varint,9,opt,name=diamonds,proto3" json:"diamonds,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PlayerStats) Reset() { *x = PlayerStats{} mi := &file_proto_player_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PlayerStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlayerStats) ProtoMessage() {} func (x *PlayerStats) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlayerStats.ProtoReflect.Descriptor instead. func (*PlayerStats) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{14} } func (x *PlayerStats) GetHealth() int32 { if x != nil { return x.Health } return 0 } func (x *PlayerStats) GetMaxHealth() int32 { if x != nil { return x.MaxHealth } return 0 } func (x *PlayerStats) GetMana() int32 { if x != nil { return x.Mana } return 0 } func (x *PlayerStats) GetMaxMana() int32 { if x != nil { return x.MaxMana } return 0 } func (x *PlayerStats) GetAttack() int32 { if x != nil { return x.Attack } return 0 } func (x *PlayerStats) GetDefense() int32 { if x != nil { return x.Defense } return 0 } func (x *PlayerStats) GetSpeed() int32 { if x != nil { return x.Speed } return 0 } func (x *PlayerStats) GetGold() int32 { if x != nil { return x.Gold } return 0 } func (x *PlayerStats) GetDiamonds() int32 { if x != nil { return x.Diamonds } return 0 } // 玩家背包 type PlayerInventory struct { state protoimpl.MessageState `protogen:"open.v1"` Capacity int32 `protobuf:"varint,1,opt,name=capacity,proto3" json:"capacity,omitempty"` UsedSlots int32 `protobuf:"varint,2,opt,name=used_slots,json=usedSlots,proto3" json:"used_slots,omitempty"` Items []*InventoryItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PlayerInventory) Reset() { *x = PlayerInventory{} mi := &file_proto_player_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PlayerInventory) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlayerInventory) ProtoMessage() {} func (x *PlayerInventory) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlayerInventory.ProtoReflect.Descriptor instead. func (*PlayerInventory) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{15} } func (x *PlayerInventory) GetCapacity() int32 { if x != nil { return x.Capacity } return 0 } func (x *PlayerInventory) GetUsedSlots() int32 { if x != nil { return x.UsedSlots } return 0 } func (x *PlayerInventory) GetItems() []*InventoryItem { if x != nil { return x.Items } return nil } // 背包物品 type InventoryItem struct { state protoimpl.MessageState `protogen:"open.v1"` ItemId string `protobuf:"bytes,1,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Quantity int32 `protobuf:"varint,3,opt,name=quantity,proto3" json:"quantity,omitempty"` MaxStack int32 `protobuf:"varint,4,opt,name=max_stack,json=maxStack,proto3" json:"max_stack,omitempty"` ItemType common.ItemType `protobuf:"varint,5,opt,name=item_type,json=itemType,proto3,enum=greatestworks.common.ItemType" json:"item_type,omitempty"` Rarity common.ItemRarity `protobuf:"varint,6,opt,name=rarity,proto3,enum=greatestworks.common.ItemRarity" json:"rarity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InventoryItem) Reset() { *x = InventoryItem{} mi := &file_proto_player_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InventoryItem) String() string { return protoimpl.X.MessageStringOf(x) } func (*InventoryItem) ProtoMessage() {} func (x *InventoryItem) ProtoReflect() protoreflect.Message { mi := &file_proto_player_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InventoryItem.ProtoReflect.Descriptor instead. func (*InventoryItem) Descriptor() ([]byte, []int) { return file_proto_player_proto_rawDescGZIP(), []int{16} } func (x *InventoryItem) GetItemId() string { if x != nil { return x.ItemId } return "" } func (x *InventoryItem) GetName() string { if x != nil { return x.Name } return "" } func (x *InventoryItem) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } func (x *InventoryItem) GetMaxStack() int32 { if x != nil { return x.MaxStack } return 0 } func (x *InventoryItem) GetItemType() common.ItemType { if x != nil { return x.ItemType } return common.ItemType(0) } func (x *InventoryItem) GetRarity() common.ItemRarity { if x != nil { return x.Rarity } return common.ItemRarity(0) } var File_proto_player_proto protoreflect.FileDescriptor const file_proto_player_proto_rawDesc = "" + "\n" + "\x12proto/player.proto\x12\x14greatestworks.player\x1a\x12proto/common.proto\"\xb8\x01\n" + "\x13CreatePlayerRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" + "\n" + "account_id\x18\x02 \x01(\tR\taccountId\x12#\n" + "\rinitial_level\x18\x03 \x01(\x05R\finitialLevel\x12I\n" + "\x10initial_position\x18\x04 \x01(\v2\x1e.greatestworks.common.PositionR\x0finitialPosition\"\x93\x01\n" + "\x14CreatePlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\x06player\x18\x02 \x01(\v2%.greatestworks.common.PlayerBasicInfoR\x06player\"q\n" + "\fLoginRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x1d\n" + "\n" + "session_id\x18\x02 \x01(\tR\tsessionId\x12%\n" + "\x0eclient_version\x18\x03 \x01(\tR\rclientVersion\"\xd0\x01\n" + "\rLoginResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\x06player\x18\x02 \x01(\v2%.greatestworks.common.PlayerBasicInfoR\x06player\x12#\n" + "\rsession_token\x18\x03 \x01(\tR\fsessionToken\x12\x1d\n" + "\n" + "login_time\x18\x04 \x01(\x03R\tloginTime\"K\n" + "\rLogoutRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x1d\n" + "\n" + "session_id\x18\x02 \x01(\tR\tsessionId\"o\n" + "\x0eLogoutResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1f\n" + "\vlogout_time\x18\x02 \x01(\x03R\n" + "logoutTime\"3\n" + "\x14GetPlayerInfoRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\"\x92\x02\n" + "\x15GetPlayerInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\x06player\x18\x02 \x01(\v2%.greatestworks.common.PlayerBasicInfoR\x06player\x127\n" + "\x05stats\x18\x03 \x01(\v2!.greatestworks.player.PlayerStatsR\x05stats\x12C\n" + "\tinventory\x18\x04 \x01(\v2%.greatestworks.player.PlayerInventoryR\tinventory\"\xc0\x01\n" + "\x13UpdatePlayerRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12P\n" + "\aupdates\x18\x02 \x03(\v26.greatestworks.player.UpdatePlayerRequest.UpdatesEntryR\aupdates\x1a:\n" + "\fUpdatesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x93\x01\n" + "\x14UpdatePlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\x06player\x18\x02 \x01(\v2%.greatestworks.common.PlayerBasicInfoR\x06player\"\x87\x01\n" + "\x11MovePlayerRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12:\n" + "\bposition\x18\x02 \x01(\v2\x1e.greatestworks.common.PositionR\bposition\x12\x19\n" + "\bscene_id\x18\x03 \x01(\tR\asceneId\"\x95\x01\n" + "\x12MovePlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12A\n" + "\fnew_position\x18\x02 \x01(\v2\x1e.greatestworks.common.PositionR\vnewPosition\"G\n" + "\x17GetOnlinePlayersRequest\x12\x14\n" + "\x05limit\x18\x01 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x02 \x01(\x05R\x06offset\"\xdf\x01\n" + "\x18GetOnlinePlayersResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12?\n" + "\aplayers\x18\x02 \x03(\v2%.greatestworks.common.PlayerBasicInfoR\aplayers\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\xeb\x01\n" + "\vPlayerStats\x12\x16\n" + "\x06health\x18\x01 \x01(\x05R\x06health\x12\x1d\n" + "\n" + "max_health\x18\x02 \x01(\x05R\tmaxHealth\x12\x12\n" + "\x04mana\x18\x03 \x01(\x05R\x04mana\x12\x19\n" + "\bmax_mana\x18\x04 \x01(\x05R\amaxMana\x12\x16\n" + "\x06attack\x18\x05 \x01(\x05R\x06attack\x12\x18\n" + "\adefense\x18\x06 \x01(\x05R\adefense\x12\x14\n" + "\x05speed\x18\a \x01(\x05R\x05speed\x12\x12\n" + "\x04gold\x18\b \x01(\x05R\x04gold\x12\x1a\n" + "\bdiamonds\x18\t \x01(\x05R\bdiamonds\"\x87\x01\n" + "\x0fPlayerInventory\x12\x1a\n" + "\bcapacity\x18\x01 \x01(\x05R\bcapacity\x12\x1d\n" + "\n" + "used_slots\x18\x02 \x01(\x05R\tusedSlots\x129\n" + "\x05items\x18\x03 \x03(\v2#.greatestworks.player.InventoryItemR\x05items\"\xec\x01\n" + "\rInventoryItem\x12\x17\n" + "\aitem_id\x18\x01 \x01(\tR\x06itemId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n" + "\bquantity\x18\x03 \x01(\x05R\bquantity\x12\x1b\n" + "\tmax_stack\x18\x04 \x01(\x05R\bmaxStack\x12;\n" + "\titem_type\x18\x05 \x01(\x0e2\x1e.greatestworks.common.ItemTypeR\bitemType\x128\n" + "\x06rarity\x18\x06 \x01(\x0e2 .greatestworks.common.ItemRarityR\x06rarity*x\n" + "\fPlayerGender\x12\x1d\n" + "\x19PLAYER_GENDER_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12PLAYER_GENDER_MALE\x10\x01\x12\x18\n" + "\x14PLAYER_GENDER_FEMALE\x10\x02\x12\x17\n" + "\x13PLAYER_GENDER_OTHER\x10\x03*\xf9\x01\n" + "\vPlayerClass\x12\x1c\n" + "\x18PLAYER_CLASS_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14PLAYER_CLASS_WARRIOR\x10\x01\x12\x15\n" + "\x11PLAYER_CLASS_MAGE\x10\x02\x12\x17\n" + "\x13PLAYER_CLASS_ARCHER\x10\x03\x12\x19\n" + "\x15PLAYER_CLASS_ASSASSIN\x10\x04\x12\x17\n" + "\x13PLAYER_CLASS_PRIEST\x10\x05\x12\x18\n" + "\x14PLAYER_CLASS_PALADIN\x10\x06\x12\x16\n" + "\x12PLAYER_CLASS_DRUID\x10\a\x12\x1c\n" + "\x18PLAYER_CLASS_NECROMANCER\x10\b*\xd0\x01\n" + "\vPlayerLevel\x12\x1c\n" + "\x18PLAYER_LEVEL_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15PLAYER_LEVEL_BEGINNER\x10\x01\x12\x1d\n" + "\x19PLAYER_LEVEL_INTERMEDIATE\x10\x02\x12\x19\n" + "\x15PLAYER_LEVEL_ADVANCED\x10\x03\x12\x17\n" + "\x13PLAYER_LEVEL_EXPERT\x10\x04\x12\x17\n" + "\x13PLAYER_LEVEL_MASTER\x10\x05\x12\x1c\n" + "\x18PLAYER_LEVEL_GRANDMASTER\x10\x06*\xcc\x01\n" + "\fPlayerStatus\x12\x1d\n" + "\x19PLAYER_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15PLAYER_STATUS_OFFLINE\x10\x01\x12\x18\n" + "\x14PLAYER_STATUS_ONLINE\x10\x02\x12\x1b\n" + "\x17PLAYER_STATUS_IN_BATTLE\x10\x03\x12\x1a\n" + "\x16PLAYER_STATUS_IN_QUEUE\x10\x04\x12\x15\n" + "\x11PLAYER_STATUS_AFK\x10\x05\x12\x18\n" + "\x14PLAYER_STATUS_BANNED\x10\x062\xc2\x05\n" + "\rPlayerService\x12e\n" + "\fCreatePlayer\x12).greatestworks.player.CreatePlayerRequest\x1a*.greatestworks.player.CreatePlayerResponse\x12P\n" + "\x05Login\x12\".greatestworks.player.LoginRequest\x1a#.greatestworks.player.LoginResponse\x12S\n" + "\x06Logout\x12#.greatestworks.player.LogoutRequest\x1a$.greatestworks.player.LogoutResponse\x12h\n" + "\rGetPlayerInfo\x12*.greatestworks.player.GetPlayerInfoRequest\x1a+.greatestworks.player.GetPlayerInfoResponse\x12e\n" + "\fUpdatePlayer\x12).greatestworks.player.UpdatePlayerRequest\x1a*.greatestworks.player.UpdatePlayerResponse\x12_\n" + "\n" + "MovePlayer\x12'.greatestworks.player.MovePlayerRequest\x1a(.greatestworks.player.MovePlayerResponse\x12q\n" + "\x10GetOnlinePlayers\x12-.greatestworks.player.GetOnlinePlayersRequest\x1a..greatestworks.player.GetOnlinePlayersResponseB greatestworks.common.Position 23, // 1: greatestworks.player.CreatePlayerResponse.common:type_name -> greatestworks.common.CommonResponse 24, // 2: greatestworks.player.CreatePlayerResponse.player:type_name -> greatestworks.common.PlayerBasicInfo 23, // 3: greatestworks.player.LoginResponse.common:type_name -> greatestworks.common.CommonResponse 24, // 4: greatestworks.player.LoginResponse.player:type_name -> greatestworks.common.PlayerBasicInfo 23, // 5: greatestworks.player.LogoutResponse.common:type_name -> greatestworks.common.CommonResponse 23, // 6: greatestworks.player.GetPlayerInfoResponse.common:type_name -> greatestworks.common.CommonResponse 24, // 7: greatestworks.player.GetPlayerInfoResponse.player:type_name -> greatestworks.common.PlayerBasicInfo 18, // 8: greatestworks.player.GetPlayerInfoResponse.stats:type_name -> greatestworks.player.PlayerStats 19, // 9: greatestworks.player.GetPlayerInfoResponse.inventory:type_name -> greatestworks.player.PlayerInventory 21, // 10: greatestworks.player.UpdatePlayerRequest.updates:type_name -> greatestworks.player.UpdatePlayerRequest.UpdatesEntry 23, // 11: greatestworks.player.UpdatePlayerResponse.common:type_name -> greatestworks.common.CommonResponse 24, // 12: greatestworks.player.UpdatePlayerResponse.player:type_name -> greatestworks.common.PlayerBasicInfo 22, // 13: greatestworks.player.MovePlayerRequest.position:type_name -> greatestworks.common.Position 23, // 14: greatestworks.player.MovePlayerResponse.common:type_name -> greatestworks.common.CommonResponse 22, // 15: greatestworks.player.MovePlayerResponse.new_position:type_name -> greatestworks.common.Position 23, // 16: greatestworks.player.GetOnlinePlayersResponse.common:type_name -> greatestworks.common.CommonResponse 24, // 17: greatestworks.player.GetOnlinePlayersResponse.players:type_name -> greatestworks.common.PlayerBasicInfo 25, // 18: greatestworks.player.GetOnlinePlayersResponse.pagination:type_name -> greatestworks.common.PaginationInfo 20, // 19: greatestworks.player.PlayerInventory.items:type_name -> greatestworks.player.InventoryItem 26, // 20: greatestworks.player.InventoryItem.item_type:type_name -> greatestworks.common.ItemType 27, // 21: greatestworks.player.InventoryItem.rarity:type_name -> greatestworks.common.ItemRarity 4, // 22: greatestworks.player.PlayerService.CreatePlayer:input_type -> greatestworks.player.CreatePlayerRequest 6, // 23: greatestworks.player.PlayerService.Login:input_type -> greatestworks.player.LoginRequest 8, // 24: greatestworks.player.PlayerService.Logout:input_type -> greatestworks.player.LogoutRequest 10, // 25: greatestworks.player.PlayerService.GetPlayerInfo:input_type -> greatestworks.player.GetPlayerInfoRequest 12, // 26: greatestworks.player.PlayerService.UpdatePlayer:input_type -> greatestworks.player.UpdatePlayerRequest 14, // 27: greatestworks.player.PlayerService.MovePlayer:input_type -> greatestworks.player.MovePlayerRequest 16, // 28: greatestworks.player.PlayerService.GetOnlinePlayers:input_type -> greatestworks.player.GetOnlinePlayersRequest 5, // 29: greatestworks.player.PlayerService.CreatePlayer:output_type -> greatestworks.player.CreatePlayerResponse 7, // 30: greatestworks.player.PlayerService.Login:output_type -> greatestworks.player.LoginResponse 9, // 31: greatestworks.player.PlayerService.Logout:output_type -> greatestworks.player.LogoutResponse 11, // 32: greatestworks.player.PlayerService.GetPlayerInfo:output_type -> greatestworks.player.GetPlayerInfoResponse 13, // 33: greatestworks.player.PlayerService.UpdatePlayer:output_type -> greatestworks.player.UpdatePlayerResponse 15, // 34: greatestworks.player.PlayerService.MovePlayer:output_type -> greatestworks.player.MovePlayerResponse 17, // 35: greatestworks.player.PlayerService.GetOnlinePlayers:output_type -> greatestworks.player.GetOnlinePlayersResponse 29, // [29:36] is the sub-list for method output_type 22, // [22:29] is the sub-list for method input_type 22, // [22:22] is the sub-list for extension type_name 22, // [22:22] is the sub-list for extension extendee 0, // [0:22] is the sub-list for field type_name } func init() { file_proto_player_proto_init() } func file_proto_player_proto_init() { if File_proto_player_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_player_proto_rawDesc), len(file_proto_player_proto_rawDesc)), NumEnums: 4, NumMessages: 18, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_player_proto_goTypes, DependencyIndexes: file_proto_player_proto_depIdxs, EnumInfos: file_proto_player_proto_enumTypes, MessageInfos: file_proto_player_proto_msgTypes, }.Build() File_proto_player_proto = out.File file_proto_player_proto_goTypes = nil file_proto_player_proto_depIdxs = nil } ================================================ FILE: internal/proto/protocol/protocol.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/protocol.proto package protocol import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 消息类型枚举 - 系统消息 (0x0000 - 0x00FF) type SystemMessageType int32 const ( SystemMessageType_SYSTEM_MESSAGE_UNSPECIFIED SystemMessageType = 0 SystemMessageType_MSG_HEARTBEAT SystemMessageType = 1 // 心跳消息 SystemMessageType_MSG_HANDSHAKE SystemMessageType = 2 // 握手消息 SystemMessageType_MSG_AUTH SystemMessageType = 3 // 认证消息 SystemMessageType_MSG_DISCONNECT SystemMessageType = 4 // 断开连接 SystemMessageType_MSG_ERROR SystemMessageType = 5 // 错误消息 SystemMessageType_MSG_PING SystemMessageType = 6 // Ping消息 SystemMessageType_MSG_PONG SystemMessageType = 7 // Pong消息 ) // Enum value maps for SystemMessageType. var ( SystemMessageType_name = map[int32]string{ 0: "SYSTEM_MESSAGE_UNSPECIFIED", 1: "MSG_HEARTBEAT", 2: "MSG_HANDSHAKE", 3: "MSG_AUTH", 4: "MSG_DISCONNECT", 5: "MSG_ERROR", 6: "MSG_PING", 7: "MSG_PONG", } SystemMessageType_value = map[string]int32{ "SYSTEM_MESSAGE_UNSPECIFIED": 0, "MSG_HEARTBEAT": 1, "MSG_HANDSHAKE": 2, "MSG_AUTH": 3, "MSG_DISCONNECT": 4, "MSG_ERROR": 5, "MSG_PING": 6, "MSG_PONG": 7, } ) func (x SystemMessageType) Enum() *SystemMessageType { p := new(SystemMessageType) *p = x return p } func (x SystemMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SystemMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[0].Descriptor() } func (SystemMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[0] } func (x SystemMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SystemMessageType.Descriptor instead. func (SystemMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{0} } // 消息类型枚举 - 玩家相关消息 (0x0100 - 0x01FF) type PlayerMessageType int32 const ( PlayerMessageType_PLAYER_MESSAGE_UNSPECIFIED PlayerMessageType = 0 PlayerMessageType_MSG_PLAYER_LOGIN PlayerMessageType = 257 // 玩家登录 PlayerMessageType_MSG_PLAYER_LOGOUT PlayerMessageType = 258 // 玩家登出 PlayerMessageType_MSG_PLAYER_INFO PlayerMessageType = 259 // 玩家信息 PlayerMessageType_MSG_PLAYER_MOVE PlayerMessageType = 260 // 玩家移动 PlayerMessageType_MSG_PLAYER_CREATE PlayerMessageType = 261 // 创建玩家 PlayerMessageType_MSG_PLAYER_UPDATE PlayerMessageType = 262 // 更新玩家 PlayerMessageType_MSG_PLAYER_DELETE PlayerMessageType = 263 // 删除玩家 PlayerMessageType_MSG_PLAYER_STATUS PlayerMessageType = 264 // 玩家状态 PlayerMessageType_MSG_PLAYER_STATS PlayerMessageType = 265 // 玩家属性 PlayerMessageType_MSG_PLAYER_LEVEL PlayerMessageType = 266 // 玩家升级 ) // Enum value maps for PlayerMessageType. var ( PlayerMessageType_name = map[int32]string{ 0: "PLAYER_MESSAGE_UNSPECIFIED", 257: "MSG_PLAYER_LOGIN", 258: "MSG_PLAYER_LOGOUT", 259: "MSG_PLAYER_INFO", 260: "MSG_PLAYER_MOVE", 261: "MSG_PLAYER_CREATE", 262: "MSG_PLAYER_UPDATE", 263: "MSG_PLAYER_DELETE", 264: "MSG_PLAYER_STATUS", 265: "MSG_PLAYER_STATS", 266: "MSG_PLAYER_LEVEL", } PlayerMessageType_value = map[string]int32{ "PLAYER_MESSAGE_UNSPECIFIED": 0, "MSG_PLAYER_LOGIN": 257, "MSG_PLAYER_LOGOUT": 258, "MSG_PLAYER_INFO": 259, "MSG_PLAYER_MOVE": 260, "MSG_PLAYER_CREATE": 261, "MSG_PLAYER_UPDATE": 262, "MSG_PLAYER_DELETE": 263, "MSG_PLAYER_STATUS": 264, "MSG_PLAYER_STATS": 265, "MSG_PLAYER_LEVEL": 266, } ) func (x PlayerMessageType) Enum() *PlayerMessageType { p := new(PlayerMessageType) *p = x return p } func (x PlayerMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[1].Descriptor() } func (PlayerMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[1] } func (x PlayerMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerMessageType.Descriptor instead. func (PlayerMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{1} } // 消息类型枚举 - 战斗相关消息 (0x0200 - 0x02FF) type BattleMessageType int32 const ( BattleMessageType_BATTLE_MESSAGE_UNSPECIFIED BattleMessageType = 0 BattleMessageType_MSG_CREATE_BATTLE BattleMessageType = 513 // 创建战斗 BattleMessageType_MSG_JOIN_BATTLE BattleMessageType = 514 // 加入战斗 BattleMessageType_MSG_LEAVE_BATTLE BattleMessageType = 515 // 离开战斗 BattleMessageType_MSG_START_BATTLE BattleMessageType = 516 // 开始战斗 BattleMessageType_MSG_END_BATTLE BattleMessageType = 517 // 结束战斗 BattleMessageType_MSG_BATTLE_ACTION BattleMessageType = 518 // 战斗行动 BattleMessageType_MSG_BATTLE_RESULT BattleMessageType = 519 // 战斗结果 BattleMessageType_MSG_BATTLE_STATUS BattleMessageType = 520 // 战斗状态 BattleMessageType_MSG_SKILL_CAST BattleMessageType = 521 // 技能释放 BattleMessageType_MSG_DAMAGE_DEALT BattleMessageType = 522 // 伤害计算 ) // Enum value maps for BattleMessageType. var ( BattleMessageType_name = map[int32]string{ 0: "BATTLE_MESSAGE_UNSPECIFIED", 513: "MSG_CREATE_BATTLE", 514: "MSG_JOIN_BATTLE", 515: "MSG_LEAVE_BATTLE", 516: "MSG_START_BATTLE", 517: "MSG_END_BATTLE", 518: "MSG_BATTLE_ACTION", 519: "MSG_BATTLE_RESULT", 520: "MSG_BATTLE_STATUS", 521: "MSG_SKILL_CAST", 522: "MSG_DAMAGE_DEALT", } BattleMessageType_value = map[string]int32{ "BATTLE_MESSAGE_UNSPECIFIED": 0, "MSG_CREATE_BATTLE": 513, "MSG_JOIN_BATTLE": 514, "MSG_LEAVE_BATTLE": 515, "MSG_START_BATTLE": 516, "MSG_END_BATTLE": 517, "MSG_BATTLE_ACTION": 518, "MSG_BATTLE_RESULT": 519, "MSG_BATTLE_STATUS": 520, "MSG_SKILL_CAST": 521, "MSG_DAMAGE_DEALT": 522, } ) func (x BattleMessageType) Enum() *BattleMessageType { p := new(BattleMessageType) *p = x return p } func (x BattleMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[2].Descriptor() } func (BattleMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[2] } func (x BattleMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleMessageType.Descriptor instead. func (BattleMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{2} } // 消息类型枚举 - 宠物相关消息 (0x0300 - 0x03FF) type PetMessageType int32 const ( PetMessageType_PET_MESSAGE_UNSPECIFIED PetMessageType = 0 PetMessageType_MSG_PET_SUMMON PetMessageType = 769 // 召唤宠物 PetMessageType_MSG_PET_DISMISS PetMessageType = 770 // 收回宠物 PetMessageType_MSG_PET_INFO PetMessageType = 771 // 宠物信息 PetMessageType_MSG_PET_MOVE PetMessageType = 772 // 宠物移动 PetMessageType_MSG_PET_ACTION PetMessageType = 773 // 宠物行动 PetMessageType_MSG_PET_LEVEL_UP PetMessageType = 774 // 宠物升级 PetMessageType_MSG_PET_EVOLUTION PetMessageType = 775 // 宠物进化 PetMessageType_MSG_PET_TRAIN PetMessageType = 776 // 宠物训练 PetMessageType_MSG_PET_FEED PetMessageType = 777 // 宠物喂养 PetMessageType_MSG_PET_STATUS PetMessageType = 778 // 宠物状态 ) // Enum value maps for PetMessageType. var ( PetMessageType_name = map[int32]string{ 0: "PET_MESSAGE_UNSPECIFIED", 769: "MSG_PET_SUMMON", 770: "MSG_PET_DISMISS", 771: "MSG_PET_INFO", 772: "MSG_PET_MOVE", 773: "MSG_PET_ACTION", 774: "MSG_PET_LEVEL_UP", 775: "MSG_PET_EVOLUTION", 776: "MSG_PET_TRAIN", 777: "MSG_PET_FEED", 778: "MSG_PET_STATUS", } PetMessageType_value = map[string]int32{ "PET_MESSAGE_UNSPECIFIED": 0, "MSG_PET_SUMMON": 769, "MSG_PET_DISMISS": 770, "MSG_PET_INFO": 771, "MSG_PET_MOVE": 772, "MSG_PET_ACTION": 773, "MSG_PET_LEVEL_UP": 774, "MSG_PET_EVOLUTION": 775, "MSG_PET_TRAIN": 776, "MSG_PET_FEED": 777, "MSG_PET_STATUS": 778, } ) func (x PetMessageType) Enum() *PetMessageType { p := new(PetMessageType) *p = x return p } func (x PetMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[3].Descriptor() } func (PetMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[3] } func (x PetMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetMessageType.Descriptor instead. func (PetMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{3} } // 消息类型枚举 - 建筑相关消息 (0x0400 - 0x04FF) type BuildingMessageType int32 const ( BuildingMessageType_BUILDING_MESSAGE_UNSPECIFIED BuildingMessageType = 0 BuildingMessageType_MSG_BUILDING_CREATE BuildingMessageType = 1025 // 创建建筑 BuildingMessageType_MSG_BUILDING_UPGRADE BuildingMessageType = 1026 // 升级建筑 BuildingMessageType_MSG_BUILDING_DESTROY BuildingMessageType = 1027 // 摧毁建筑 BuildingMessageType_MSG_BUILDING_INFO BuildingMessageType = 1028 // 建筑信息 BuildingMessageType_MSG_BUILDING_PRODUCE BuildingMessageType = 1029 // 建筑生产 BuildingMessageType_MSG_BUILDING_COLLECT BuildingMessageType = 1030 // 收集资源 BuildingMessageType_MSG_BUILDING_REPAIR BuildingMessageType = 1031 // 修复建筑 BuildingMessageType_MSG_BUILDING_STATUS BuildingMessageType = 1032 // 建筑状态 ) // Enum value maps for BuildingMessageType. var ( BuildingMessageType_name = map[int32]string{ 0: "BUILDING_MESSAGE_UNSPECIFIED", 1025: "MSG_BUILDING_CREATE", 1026: "MSG_BUILDING_UPGRADE", 1027: "MSG_BUILDING_DESTROY", 1028: "MSG_BUILDING_INFO", 1029: "MSG_BUILDING_PRODUCE", 1030: "MSG_BUILDING_COLLECT", 1031: "MSG_BUILDING_REPAIR", 1032: "MSG_BUILDING_STATUS", } BuildingMessageType_value = map[string]int32{ "BUILDING_MESSAGE_UNSPECIFIED": 0, "MSG_BUILDING_CREATE": 1025, "MSG_BUILDING_UPGRADE": 1026, "MSG_BUILDING_DESTROY": 1027, "MSG_BUILDING_INFO": 1028, "MSG_BUILDING_PRODUCE": 1029, "MSG_BUILDING_COLLECT": 1030, "MSG_BUILDING_REPAIR": 1031, "MSG_BUILDING_STATUS": 1032, } ) func (x BuildingMessageType) Enum() *BuildingMessageType { p := new(BuildingMessageType) *p = x return p } func (x BuildingMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BuildingMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[4].Descriptor() } func (BuildingMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[4] } func (x BuildingMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BuildingMessageType.Descriptor instead. func (BuildingMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{4} } // 消息类型枚举 - 社交相关消息 (0x0500 - 0x05FF) type SocialMessageType int32 const ( SocialMessageType_SOCIAL_MESSAGE_UNSPECIFIED SocialMessageType = 0 SocialMessageType_MSG_CHAT_MESSAGE SocialMessageType = 1281 // 聊天消息 SocialMessageType_MSG_FRIEND_REQUEST SocialMessageType = 1282 // 好友请求 SocialMessageType_MSG_FRIEND_ACCEPT SocialMessageType = 1283 // 接受好友 SocialMessageType_MSG_FRIEND_REJECT SocialMessageType = 1284 // 拒绝好友 SocialMessageType_MSG_FRIEND_REMOVE SocialMessageType = 1285 // 删除好友 SocialMessageType_MSG_FRIEND_LIST SocialMessageType = 1286 // 好友列表 SocialMessageType_MSG_GUILD_CREATE SocialMessageType = 1287 // 创建公会 SocialMessageType_MSG_GUILD_JOIN SocialMessageType = 1288 // 加入公会 SocialMessageType_MSG_GUILD_LEAVE SocialMessageType = 1289 // 离开公会 SocialMessageType_MSG_GUILD_INFO SocialMessageType = 1290 // 公会信息 SocialMessageType_MSG_TEAM_CREATE SocialMessageType = 1291 // 创建队伍 SocialMessageType_MSG_TEAM_JOIN SocialMessageType = 1292 // 加入队伍 SocialMessageType_MSG_TEAM_LEAVE SocialMessageType = 1293 // 离开队伍 SocialMessageType_MSG_TEAM_INFO SocialMessageType = 1294 // 队伍信息 ) // Enum value maps for SocialMessageType. var ( SocialMessageType_name = map[int32]string{ 0: "SOCIAL_MESSAGE_UNSPECIFIED", 1281: "MSG_CHAT_MESSAGE", 1282: "MSG_FRIEND_REQUEST", 1283: "MSG_FRIEND_ACCEPT", 1284: "MSG_FRIEND_REJECT", 1285: "MSG_FRIEND_REMOVE", 1286: "MSG_FRIEND_LIST", 1287: "MSG_GUILD_CREATE", 1288: "MSG_GUILD_JOIN", 1289: "MSG_GUILD_LEAVE", 1290: "MSG_GUILD_INFO", 1291: "MSG_TEAM_CREATE", 1292: "MSG_TEAM_JOIN", 1293: "MSG_TEAM_LEAVE", 1294: "MSG_TEAM_INFO", } SocialMessageType_value = map[string]int32{ "SOCIAL_MESSAGE_UNSPECIFIED": 0, "MSG_CHAT_MESSAGE": 1281, "MSG_FRIEND_REQUEST": 1282, "MSG_FRIEND_ACCEPT": 1283, "MSG_FRIEND_REJECT": 1284, "MSG_FRIEND_REMOVE": 1285, "MSG_FRIEND_LIST": 1286, "MSG_GUILD_CREATE": 1287, "MSG_GUILD_JOIN": 1288, "MSG_GUILD_LEAVE": 1289, "MSG_GUILD_INFO": 1290, "MSG_TEAM_CREATE": 1291, "MSG_TEAM_JOIN": 1292, "MSG_TEAM_LEAVE": 1293, "MSG_TEAM_INFO": 1294, } ) func (x SocialMessageType) Enum() *SocialMessageType { p := new(SocialMessageType) *p = x return p } func (x SocialMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SocialMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[5].Descriptor() } func (SocialMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[5] } func (x SocialMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SocialMessageType.Descriptor instead. func (SocialMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{5} } // 消息类型枚举 - 物品相关消息 (0x0600 - 0x06FF) type ItemMessageType int32 const ( ItemMessageType_ITEM_MESSAGE_UNSPECIFIED ItemMessageType = 0 ItemMessageType_MSG_ITEM_USE ItemMessageType = 1537 // 使用物品 ItemMessageType_MSG_ITEM_EQUIP ItemMessageType = 1538 // 装备物品 ItemMessageType_MSG_ITEM_UNEQUIP ItemMessageType = 1539 // 卸下装备 ItemMessageType_MSG_ITEM_DROP ItemMessageType = 1540 // 丢弃物品 ItemMessageType_MSG_ITEM_PICKUP ItemMessageType = 1541 // 拾取物品 ItemMessageType_MSG_ITEM_TRADE ItemMessageType = 1542 // 交易物品 ItemMessageType_MSG_INVENTORY_INFO ItemMessageType = 1543 // 背包信息 ItemMessageType_MSG_ITEM_INFO ItemMessageType = 1544 // 物品信息 ItemMessageType_MSG_ITEM_CRAFT ItemMessageType = 1545 // 制作物品 ItemMessageType_MSG_ITEM_ENHANCE ItemMessageType = 1546 // 强化物品 ) // Enum value maps for ItemMessageType. var ( ItemMessageType_name = map[int32]string{ 0: "ITEM_MESSAGE_UNSPECIFIED", 1537: "MSG_ITEM_USE", 1538: "MSG_ITEM_EQUIP", 1539: "MSG_ITEM_UNEQUIP", 1540: "MSG_ITEM_DROP", 1541: "MSG_ITEM_PICKUP", 1542: "MSG_ITEM_TRADE", 1543: "MSG_INVENTORY_INFO", 1544: "MSG_ITEM_INFO", 1545: "MSG_ITEM_CRAFT", 1546: "MSG_ITEM_ENHANCE", } ItemMessageType_value = map[string]int32{ "ITEM_MESSAGE_UNSPECIFIED": 0, "MSG_ITEM_USE": 1537, "MSG_ITEM_EQUIP": 1538, "MSG_ITEM_UNEQUIP": 1539, "MSG_ITEM_DROP": 1540, "MSG_ITEM_PICKUP": 1541, "MSG_ITEM_TRADE": 1542, "MSG_INVENTORY_INFO": 1543, "MSG_ITEM_INFO": 1544, "MSG_ITEM_CRAFT": 1545, "MSG_ITEM_ENHANCE": 1546, } ) func (x ItemMessageType) Enum() *ItemMessageType { p := new(ItemMessageType) *p = x return p } func (x ItemMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ItemMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[6].Descriptor() } func (ItemMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[6] } func (x ItemMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ItemMessageType.Descriptor instead. func (ItemMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{6} } // 消息类型枚举 - 任务相关消息 (0x0700 - 0x07FF) type QuestMessageType int32 const ( QuestMessageType_QUEST_MESSAGE_UNSPECIFIED QuestMessageType = 0 QuestMessageType_MSG_QUEST_ACCEPT QuestMessageType = 1793 // 接受任务 QuestMessageType_MSG_QUEST_COMPLETE QuestMessageType = 1794 // 完成任务 QuestMessageType_MSG_QUEST_CANCEL QuestMessageType = 1795 // 取消任务 QuestMessageType_MSG_QUEST_PROGRESS QuestMessageType = 1796 // 任务进度 QuestMessageType_MSG_QUEST_LIST QuestMessageType = 1797 // 任务列表 QuestMessageType_MSG_QUEST_INFO QuestMessageType = 1798 // 任务信息 QuestMessageType_MSG_QUEST_REWARD QuestMessageType = 1799 // 任务奖励 ) // Enum value maps for QuestMessageType. var ( QuestMessageType_name = map[int32]string{ 0: "QUEST_MESSAGE_UNSPECIFIED", 1793: "MSG_QUEST_ACCEPT", 1794: "MSG_QUEST_COMPLETE", 1795: "MSG_QUEST_CANCEL", 1796: "MSG_QUEST_PROGRESS", 1797: "MSG_QUEST_LIST", 1798: "MSG_QUEST_INFO", 1799: "MSG_QUEST_REWARD", } QuestMessageType_value = map[string]int32{ "QUEST_MESSAGE_UNSPECIFIED": 0, "MSG_QUEST_ACCEPT": 1793, "MSG_QUEST_COMPLETE": 1794, "MSG_QUEST_CANCEL": 1795, "MSG_QUEST_PROGRESS": 1796, "MSG_QUEST_LIST": 1797, "MSG_QUEST_INFO": 1798, "MSG_QUEST_REWARD": 1799, } ) func (x QuestMessageType) Enum() *QuestMessageType { p := new(QuestMessageType) *p = x return p } func (x QuestMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QuestMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[7].Descriptor() } func (QuestMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[7] } func (x QuestMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QuestMessageType.Descriptor instead. func (QuestMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{7} } // 消息类型枚举 - 查询相关消息 (0x0800 - 0x08FF) type QueryMessageType int32 const ( QueryMessageType_QUERY_MESSAGE_UNSPECIFIED QueryMessageType = 0 QueryMessageType_MSG_GET_PLAYER_INFO QueryMessageType = 2049 // 获取玩家信息 QueryMessageType_MSG_GET_ONLINE_PLAYERS QueryMessageType = 2050 // 获取在线玩家 QueryMessageType_MSG_GET_BATTLE_INFO QueryMessageType = 2051 // 获取战斗信息 QueryMessageType_MSG_GET_RANKING_LIST QueryMessageType = 2052 // 获取排行榜 QueryMessageType_MSG_GET_SERVER_INFO QueryMessageType = 2053 // 获取服务器信息 ) // Enum value maps for QueryMessageType. var ( QueryMessageType_name = map[int32]string{ 0: "QUERY_MESSAGE_UNSPECIFIED", 2049: "MSG_GET_PLAYER_INFO", 2050: "MSG_GET_ONLINE_PLAYERS", 2051: "MSG_GET_BATTLE_INFO", 2052: "MSG_GET_RANKING_LIST", 2053: "MSG_GET_SERVER_INFO", } QueryMessageType_value = map[string]int32{ "QUERY_MESSAGE_UNSPECIFIED": 0, "MSG_GET_PLAYER_INFO": 2049, "MSG_GET_ONLINE_PLAYERS": 2050, "MSG_GET_BATTLE_INFO": 2051, "MSG_GET_RANKING_LIST": 2052, "MSG_GET_SERVER_INFO": 2053, } ) func (x QueryMessageType) Enum() *QueryMessageType { p := new(QueryMessageType) *p = x return p } func (x QueryMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (QueryMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[8].Descriptor() } func (QueryMessageType) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[8] } func (x QueryMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use QueryMessageType.Descriptor instead. func (QueryMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{8} } // 错误码枚举 type ErrorCode int32 const ( ErrorCode_ERROR_CODE_UNSPECIFIED ErrorCode = 0 // 通用错误 (0-999) ErrorCode_ERR_SUCCESS ErrorCode = 0 // 成功 ErrorCode_ERR_UNKNOWN ErrorCode = 1000 // 未知错误 ErrorCode_ERR_INVALID_MESSAGE ErrorCode = 1001 // 无效消息 ErrorCode_ERR_INVALID_PLAYER ErrorCode = 1002 // 无效玩家 ErrorCode_ERR_PLAYER_NOT_FOUND ErrorCode = 1003 // 玩家未找到 ErrorCode_ERR_PLAYER_OFFLINE ErrorCode = 1004 // 玩家离线 ErrorCode_ERR_AUTH_FAILED ErrorCode = 1005 // 认证失败 ErrorCode_ERR_PERMISSION_DENIED ErrorCode = 1006 // 权限不足 ErrorCode_ERR_RATE_LIMITED ErrorCode = 1007 // 请求过于频繁 ErrorCode_ERR_SERVER_BUSY ErrorCode = 1008 // 服务器繁忙 ErrorCode_ERR_MAINTENANCE ErrorCode = 1009 // 服务器维护 // 战斗相关错误 (2000-2999) ErrorCode_ERR_BATTLE_NOT_FOUND ErrorCode = 2001 // 战斗未找到 ErrorCode_ERR_BATTLE_FULL ErrorCode = 2002 // 战斗已满 ErrorCode_ERR_BATTLE_STARTED ErrorCode = 2003 // 战斗已开始 ErrorCode_ERR_BATTLE_ENDED ErrorCode = 2004 // 战斗已结束 ErrorCode_ERR_INVALID_ACTION ErrorCode = 2005 // 无效行动 ErrorCode_ERR_NOT_YOUR_TURN ErrorCode = 2006 // 不是你的回合 ErrorCode_ERR_SKILL_COOLDOWN ErrorCode = 2007 // 技能冷却中 ErrorCode_ERR_INSUFFICIENT_MP ErrorCode = 2008 // MP不足 // 宠物相关错误 (3000-3999) ErrorCode_ERR_PET_NOT_FOUND ErrorCode = 3001 // 宠物未找到 ErrorCode_ERR_PET_ALREADY_ACTIVE ErrorCode = 3002 // 宠物已激活 ErrorCode_ERR_PET_NOT_ACTIVE ErrorCode = 3003 // 宠物未激活 ErrorCode_ERR_PET_LEVEL_TOO_LOW ErrorCode = 3004 // 宠物等级过低 ErrorCode_ERR_PET_EVOLUTION_FAIL ErrorCode = 3005 // 宠物进化失败 // 物品相关错误 (4000-4999) ErrorCode_ERR_ITEM_NOT_FOUND ErrorCode = 4001 // 物品未找到 ErrorCode_ERR_ITEM_NOT_USABLE ErrorCode = 4002 // 物品不可使用 ErrorCode_ERR_INVENTORY_FULL ErrorCode = 4003 // 背包已满 ErrorCode_ERR_INSUFFICIENT_ITEM ErrorCode = 4004 // 物品数量不足 ErrorCode_ERR_ITEM_EQUIP_FAILED ErrorCode = 4005 // 装备失败 ) // Enum value maps for ErrorCode. var ( ErrorCode_name = map[int32]string{ 0: "ERROR_CODE_UNSPECIFIED", // Duplicate value: 0: "ERR_SUCCESS", 1000: "ERR_UNKNOWN", 1001: "ERR_INVALID_MESSAGE", 1002: "ERR_INVALID_PLAYER", 1003: "ERR_PLAYER_NOT_FOUND", 1004: "ERR_PLAYER_OFFLINE", 1005: "ERR_AUTH_FAILED", 1006: "ERR_PERMISSION_DENIED", 1007: "ERR_RATE_LIMITED", 1008: "ERR_SERVER_BUSY", 1009: "ERR_MAINTENANCE", 2001: "ERR_BATTLE_NOT_FOUND", 2002: "ERR_BATTLE_FULL", 2003: "ERR_BATTLE_STARTED", 2004: "ERR_BATTLE_ENDED", 2005: "ERR_INVALID_ACTION", 2006: "ERR_NOT_YOUR_TURN", 2007: "ERR_SKILL_COOLDOWN", 2008: "ERR_INSUFFICIENT_MP", 3001: "ERR_PET_NOT_FOUND", 3002: "ERR_PET_ALREADY_ACTIVE", 3003: "ERR_PET_NOT_ACTIVE", 3004: "ERR_PET_LEVEL_TOO_LOW", 3005: "ERR_PET_EVOLUTION_FAIL", 4001: "ERR_ITEM_NOT_FOUND", 4002: "ERR_ITEM_NOT_USABLE", 4003: "ERR_INVENTORY_FULL", 4004: "ERR_INSUFFICIENT_ITEM", 4005: "ERR_ITEM_EQUIP_FAILED", } ErrorCode_value = map[string]int32{ "ERROR_CODE_UNSPECIFIED": 0, "ERR_SUCCESS": 0, "ERR_UNKNOWN": 1000, "ERR_INVALID_MESSAGE": 1001, "ERR_INVALID_PLAYER": 1002, "ERR_PLAYER_NOT_FOUND": 1003, "ERR_PLAYER_OFFLINE": 1004, "ERR_AUTH_FAILED": 1005, "ERR_PERMISSION_DENIED": 1006, "ERR_RATE_LIMITED": 1007, "ERR_SERVER_BUSY": 1008, "ERR_MAINTENANCE": 1009, "ERR_BATTLE_NOT_FOUND": 2001, "ERR_BATTLE_FULL": 2002, "ERR_BATTLE_STARTED": 2003, "ERR_BATTLE_ENDED": 2004, "ERR_INVALID_ACTION": 2005, "ERR_NOT_YOUR_TURN": 2006, "ERR_SKILL_COOLDOWN": 2007, "ERR_INSUFFICIENT_MP": 2008, "ERR_PET_NOT_FOUND": 3001, "ERR_PET_ALREADY_ACTIVE": 3002, "ERR_PET_NOT_ACTIVE": 3003, "ERR_PET_LEVEL_TOO_LOW": 3004, "ERR_PET_EVOLUTION_FAIL": 3005, "ERR_ITEM_NOT_FOUND": 4001, "ERR_ITEM_NOT_USABLE": 4002, "ERR_INVENTORY_FULL": 4003, "ERR_INSUFFICIENT_ITEM": 4004, "ERR_ITEM_EQUIP_FAILED": 4005, } ) func (x ErrorCode) Enum() *ErrorCode { p := new(ErrorCode) *p = x return p } func (x ErrorCode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ErrorCode) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[9].Descriptor() } func (ErrorCode) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[9] } func (x ErrorCode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ErrorCode.Descriptor instead. func (ErrorCode) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{9} } // 玩家状态枚举 type PlayerStatus int32 const ( PlayerStatus_PLAYER_STATUS_UNSPECIFIED PlayerStatus = 0 PlayerStatus_PLAYER_STATUS_OFFLINE PlayerStatus = 1 // 离线 PlayerStatus_PLAYER_STATUS_ONLINE PlayerStatus = 2 // 在线 PlayerStatus_PLAYER_STATUS_IN_BATTLE PlayerStatus = 3 // 战斗中 PlayerStatus_PLAYER_STATUS_IN_QUEUE PlayerStatus = 4 // 排队中 PlayerStatus_PLAYER_STATUS_AFK PlayerStatus = 5 // 离开 PlayerStatus_PLAYER_STATUS_BANNED PlayerStatus = 6 // 被封禁 ) // Enum value maps for PlayerStatus. var ( PlayerStatus_name = map[int32]string{ 0: "PLAYER_STATUS_UNSPECIFIED", 1: "PLAYER_STATUS_OFFLINE", 2: "PLAYER_STATUS_ONLINE", 3: "PLAYER_STATUS_IN_BATTLE", 4: "PLAYER_STATUS_IN_QUEUE", 5: "PLAYER_STATUS_AFK", 6: "PLAYER_STATUS_BANNED", } PlayerStatus_value = map[string]int32{ "PLAYER_STATUS_UNSPECIFIED": 0, "PLAYER_STATUS_OFFLINE": 1, "PLAYER_STATUS_ONLINE": 2, "PLAYER_STATUS_IN_BATTLE": 3, "PLAYER_STATUS_IN_QUEUE": 4, "PLAYER_STATUS_AFK": 5, "PLAYER_STATUS_BANNED": 6, } ) func (x PlayerStatus) Enum() *PlayerStatus { p := new(PlayerStatus) *p = x return p } func (x PlayerStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[10].Descriptor() } func (PlayerStatus) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[10] } func (x PlayerStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerStatus.Descriptor instead. func (PlayerStatus) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{10} } // 战斗状态枚举 type BattleStatus int32 const ( BattleStatus_BATTLE_STATUS_UNSPECIFIED BattleStatus = 0 BattleStatus_BATTLE_STATUS_WAITING BattleStatus = 1 // 等待中 BattleStatus_BATTLE_STATUS_STARTING BattleStatus = 2 // 开始中 BattleStatus_BATTLE_STATUS_ACTIVE BattleStatus = 3 // 进行中 BattleStatus_BATTLE_STATUS_ENDING BattleStatus = 4 // 结束中 BattleStatus_BATTLE_STATUS_FINISHED BattleStatus = 5 // 已结束 BattleStatus_BATTLE_STATUS_CANCELLED BattleStatus = 6 // 已取消 ) // Enum value maps for BattleStatus. var ( BattleStatus_name = map[int32]string{ 0: "BATTLE_STATUS_UNSPECIFIED", 1: "BATTLE_STATUS_WAITING", 2: "BATTLE_STATUS_STARTING", 3: "BATTLE_STATUS_ACTIVE", 4: "BATTLE_STATUS_ENDING", 5: "BATTLE_STATUS_FINISHED", 6: "BATTLE_STATUS_CANCELLED", } BattleStatus_value = map[string]int32{ "BATTLE_STATUS_UNSPECIFIED": 0, "BATTLE_STATUS_WAITING": 1, "BATTLE_STATUS_STARTING": 2, "BATTLE_STATUS_ACTIVE": 3, "BATTLE_STATUS_ENDING": 4, "BATTLE_STATUS_FINISHED": 5, "BATTLE_STATUS_CANCELLED": 6, } ) func (x BattleStatus) Enum() *BattleStatus { p := new(BattleStatus) *p = x return p } func (x BattleStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (BattleStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[11].Descriptor() } func (BattleStatus) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[11] } func (x BattleStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use BattleStatus.Descriptor instead. func (BattleStatus) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{11} } // 宠物状态枚举 type PetStatus int32 const ( PetStatus_PET_STATUS_UNSPECIFIED PetStatus = 0 PetStatus_PET_STATUS_IDLE PetStatus = 1 // 空闲 PetStatus_PET_STATUS_ACTIVE PetStatus = 2 // 活跃 PetStatus_PET_STATUS_BATTLE PetStatus = 3 // 战斗中 PetStatus_PET_STATUS_TRAINING PetStatus = 4 // 训练中 PetStatus_PET_STATUS_SLEEPING PetStatus = 5 // 睡眠中 PetStatus_PET_STATUS_SICK PetStatus = 6 // 生病 PetStatus_PET_STATUS_EVOLVING PetStatus = 7 // 进化中 ) // Enum value maps for PetStatus. var ( PetStatus_name = map[int32]string{ 0: "PET_STATUS_UNSPECIFIED", 1: "PET_STATUS_IDLE", 2: "PET_STATUS_ACTIVE", 3: "PET_STATUS_BATTLE", 4: "PET_STATUS_TRAINING", 5: "PET_STATUS_SLEEPING", 6: "PET_STATUS_SICK", 7: "PET_STATUS_EVOLVING", } PetStatus_value = map[string]int32{ "PET_STATUS_UNSPECIFIED": 0, "PET_STATUS_IDLE": 1, "PET_STATUS_ACTIVE": 2, "PET_STATUS_BATTLE": 3, "PET_STATUS_TRAINING": 4, "PET_STATUS_SLEEPING": 5, "PET_STATUS_SICK": 6, "PET_STATUS_EVOLVING": 7, } ) func (x PetStatus) Enum() *PetStatus { p := new(PetStatus) *p = x return p } func (x PetStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[12].Descriptor() } func (PetStatus) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[12] } func (x PetStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetStatus.Descriptor instead. func (PetStatus) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{12} } // 物品稀有度枚举 type ItemRarity int32 const ( ItemRarity_ITEM_RARITY_UNSPECIFIED ItemRarity = 0 ItemRarity_ITEM_RARITY_COMMON ItemRarity = 1 // 普通 ItemRarity_ITEM_RARITY_UNCOMMON ItemRarity = 2 // 不常见 ItemRarity_ITEM_RARITY_RARE ItemRarity = 3 // 稀有 ItemRarity_ITEM_RARITY_EPIC ItemRarity = 4 // 史诗 ItemRarity_ITEM_RARITY_LEGENDARY ItemRarity = 5 // 传说 ItemRarity_ITEM_RARITY_MYTHIC ItemRarity = 6 // 神话 ) // Enum value maps for ItemRarity. var ( ItemRarity_name = map[int32]string{ 0: "ITEM_RARITY_UNSPECIFIED", 1: "ITEM_RARITY_COMMON", 2: "ITEM_RARITY_UNCOMMON", 3: "ITEM_RARITY_RARE", 4: "ITEM_RARITY_EPIC", 5: "ITEM_RARITY_LEGENDARY", 6: "ITEM_RARITY_MYTHIC", } ItemRarity_value = map[string]int32{ "ITEM_RARITY_UNSPECIFIED": 0, "ITEM_RARITY_COMMON": 1, "ITEM_RARITY_UNCOMMON": 2, "ITEM_RARITY_RARE": 3, "ITEM_RARITY_EPIC": 4, "ITEM_RARITY_LEGENDARY": 5, "ITEM_RARITY_MYTHIC": 6, } ) func (x ItemRarity) Enum() *ItemRarity { p := new(ItemRarity) *p = x return p } func (x ItemRarity) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ItemRarity) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[13].Descriptor() } func (ItemRarity) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[13] } func (x ItemRarity) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ItemRarity.Descriptor instead. func (ItemRarity) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{13} } // 宠物稀有度枚举 type PetRarity int32 const ( PetRarity_PET_RARITY_UNSPECIFIED PetRarity = 0 PetRarity_PET_RARITY_COMMON PetRarity = 1 // 普通 PetRarity_PET_RARITY_UNCOMMON PetRarity = 2 // 不常见 PetRarity_PET_RARITY_RARE PetRarity = 3 // 稀有 PetRarity_PET_RARITY_EPIC PetRarity = 4 // 史诗 PetRarity_PET_RARITY_LEGENDARY PetRarity = 5 // 传说 PetRarity_PET_RARITY_MYTHIC PetRarity = 6 // 神话 ) // Enum value maps for PetRarity. var ( PetRarity_name = map[int32]string{ 0: "PET_RARITY_UNSPECIFIED", 1: "PET_RARITY_COMMON", 2: "PET_RARITY_UNCOMMON", 3: "PET_RARITY_RARE", 4: "PET_RARITY_EPIC", 5: "PET_RARITY_LEGENDARY", 6: "PET_RARITY_MYTHIC", } PetRarity_value = map[string]int32{ "PET_RARITY_UNSPECIFIED": 0, "PET_RARITY_COMMON": 1, "PET_RARITY_UNCOMMON": 2, "PET_RARITY_RARE": 3, "PET_RARITY_EPIC": 4, "PET_RARITY_LEGENDARY": 5, "PET_RARITY_MYTHIC": 6, } ) func (x PetRarity) Enum() *PetRarity { p := new(PetRarity) *p = x return p } func (x PetRarity) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PetRarity) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[14].Descriptor() } func (PetRarity) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[14] } func (x PetRarity) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PetRarity.Descriptor instead. func (PetRarity) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{14} } // 消息标志位枚举 type MessageFlag int32 const ( MessageFlag_MESSAGE_FLAG_UNSPECIFIED MessageFlag = 0 MessageFlag_MESSAGE_FLAG_REQUEST MessageFlag = 1 // 请求消息 MessageFlag_MESSAGE_FLAG_RESPONSE MessageFlag = 2 // 响应消息 MessageFlag_MESSAGE_FLAG_ERROR MessageFlag = 4 // 错误消息 MessageFlag_MESSAGE_FLAG_ASYNC MessageFlag = 8 // 异步消息 MessageFlag_MESSAGE_FLAG_BROADCAST MessageFlag = 16 // 广播消息 MessageFlag_MESSAGE_FLAG_ENCRYPTED MessageFlag = 32 // 加密消息 MessageFlag_MESSAGE_FLAG_COMPRESSED MessageFlag = 64 // 压缩消息 ) // Enum value maps for MessageFlag. var ( MessageFlag_name = map[int32]string{ 0: "MESSAGE_FLAG_UNSPECIFIED", 1: "MESSAGE_FLAG_REQUEST", 2: "MESSAGE_FLAG_RESPONSE", 4: "MESSAGE_FLAG_ERROR", 8: "MESSAGE_FLAG_ASYNC", 16: "MESSAGE_FLAG_BROADCAST", 32: "MESSAGE_FLAG_ENCRYPTED", 64: "MESSAGE_FLAG_COMPRESSED", } MessageFlag_value = map[string]int32{ "MESSAGE_FLAG_UNSPECIFIED": 0, "MESSAGE_FLAG_REQUEST": 1, "MESSAGE_FLAG_RESPONSE": 2, "MESSAGE_FLAG_ERROR": 4, "MESSAGE_FLAG_ASYNC": 8, "MESSAGE_FLAG_BROADCAST": 16, "MESSAGE_FLAG_ENCRYPTED": 32, "MESSAGE_FLAG_COMPRESSED": 64, } ) func (x MessageFlag) Enum() *MessageFlag { p := new(MessageFlag) *p = x return p } func (x MessageFlag) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MessageFlag) Descriptor() protoreflect.EnumDescriptor { return file_proto_protocol_proto_enumTypes[15].Descriptor() } func (MessageFlag) Type() protoreflect.EnumType { return &file_proto_protocol_proto_enumTypes[15] } func (x MessageFlag) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MessageFlag.Descriptor instead. func (MessageFlag) EnumDescriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{15} } // 协议常量 type ProtocolConstants struct { state protoimpl.MessageState `protogen:"open.v1"` // 消息魔数 MessageMagic uint32 `protobuf:"varint,1,opt,name=message_magic,json=messageMagic,proto3" json:"message_magic,omitempty"` // 0x47574B53 "GWKS" - GreatestWorks // 消息头大小 MessageHeaderSize uint32 `protobuf:"varint,2,opt,name=message_header_size,json=messageHeaderSize,proto3" json:"message_header_size,omitempty"` // 32字节 // 最大消息体大小 MaxMessageBodySize uint32 `protobuf:"varint,3,opt,name=max_message_body_size,json=maxMessageBodySize,proto3" json:"max_message_body_size,omitempty"` // 1MB // 心跳间隔(秒) HeartbeatInterval uint32 `protobuf:"varint,4,opt,name=heartbeat_interval,json=heartbeatInterval,proto3" json:"heartbeat_interval,omitempty"` // 30秒 // 连接超时(秒) ConnectionTimeout uint32 `protobuf:"varint,5,opt,name=connection_timeout,json=connectionTimeout,proto3" json:"connection_timeout,omitempty"` // 300秒 // 重连间隔(秒) ReconnectInterval uint32 `protobuf:"varint,6,opt,name=reconnect_interval,json=reconnectInterval,proto3" json:"reconnect_interval,omitempty"` // 5秒 // 最大重连次数 MaxReconnectAttempts uint32 `protobuf:"varint,7,opt,name=max_reconnect_attempts,json=maxReconnectAttempts,proto3" json:"max_reconnect_attempts,omitempty"` // 5次 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ProtocolConstants) Reset() { *x = ProtocolConstants{} mi := &file_proto_protocol_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ProtocolConstants) String() string { return protoimpl.X.MessageStringOf(x) } func (*ProtocolConstants) ProtoMessage() {} func (x *ProtocolConstants) ProtoReflect() protoreflect.Message { mi := &file_proto_protocol_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ProtocolConstants.ProtoReflect.Descriptor instead. func (*ProtocolConstants) Descriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{0} } func (x *ProtocolConstants) GetMessageMagic() uint32 { if x != nil { return x.MessageMagic } return 0 } func (x *ProtocolConstants) GetMessageHeaderSize() uint32 { if x != nil { return x.MessageHeaderSize } return 0 } func (x *ProtocolConstants) GetMaxMessageBodySize() uint32 { if x != nil { return x.MaxMessageBodySize } return 0 } func (x *ProtocolConstants) GetHeartbeatInterval() uint32 { if x != nil { return x.HeartbeatInterval } return 0 } func (x *ProtocolConstants) GetConnectionTimeout() uint32 { if x != nil { return x.ConnectionTimeout } return 0 } func (x *ProtocolConstants) GetReconnectInterval() uint32 { if x != nil { return x.ReconnectInterval } return 0 } func (x *ProtocolConstants) GetMaxReconnectAttempts() uint32 { if x != nil { return x.MaxReconnectAttempts } return 0 } // 消息头结构 type MessageHeader struct { state protoimpl.MessageState `protogen:"open.v1"` Magic uint32 `protobuf:"varint,1,opt,name=magic,proto3" json:"magic,omitempty"` // 魔数标识 MessageId uint32 `protobuf:"varint,2,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` // 消息ID(用于请求响应匹配) MessageType uint32 `protobuf:"varint,3,opt,name=message_type,json=messageType,proto3" json:"message_type,omitempty"` // 消息类型 Flags uint32 `protobuf:"varint,4,opt,name=flags,proto3" json:"flags,omitempty"` // 标志位 PlayerId uint64 `protobuf:"varint,5,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` // 玩家ID Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // 时间戳 Sequence uint32 `protobuf:"varint,7,opt,name=sequence,proto3" json:"sequence,omitempty"` // 序列号 Length uint32 `protobuf:"varint,8,opt,name=length,proto3" json:"length,omitempty"` // 消息体长度 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MessageHeader) Reset() { *x = MessageHeader{} mi := &file_proto_protocol_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MessageHeader) String() string { return protoimpl.X.MessageStringOf(x) } func (*MessageHeader) ProtoMessage() {} func (x *MessageHeader) ProtoReflect() protoreflect.Message { mi := &file_proto_protocol_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MessageHeader.ProtoReflect.Descriptor instead. func (*MessageHeader) Descriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{1} } func (x *MessageHeader) GetMagic() uint32 { if x != nil { return x.Magic } return 0 } func (x *MessageHeader) GetMessageId() uint32 { if x != nil { return x.MessageId } return 0 } func (x *MessageHeader) GetMessageType() uint32 { if x != nil { return x.MessageType } return 0 } func (x *MessageHeader) GetFlags() uint32 { if x != nil { return x.Flags } return 0 } func (x *MessageHeader) GetPlayerId() uint64 { if x != nil { return x.PlayerId } return 0 } func (x *MessageHeader) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *MessageHeader) GetSequence() uint32 { if x != nil { return x.Sequence } return 0 } func (x *MessageHeader) GetLength() uint32 { if x != nil { return x.Length } return 0 } // 基础响应结构 type BaseResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` ErrorCode ErrorCode `protobuf:"varint,3,opt,name=error_code,json=errorCode,proto3,enum=greatestworks.protocol.ErrorCode" json:"error_code,omitempty"` Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *BaseResponse) Reset() { *x = BaseResponse{} mi := &file_proto_protocol_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *BaseResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*BaseResponse) ProtoMessage() {} func (x *BaseResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_protocol_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BaseResponse.ProtoReflect.Descriptor instead. func (*BaseResponse) Descriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{2} } func (x *BaseResponse) GetSuccess() bool { if x != nil { return x.Success } return false } func (x *BaseResponse) GetMessage() string { if x != nil { return x.Message } return "" } func (x *BaseResponse) GetErrorCode() ErrorCode { if x != nil { return x.ErrorCode } return ErrorCode_ERROR_CODE_UNSPECIFIED } func (x *BaseResponse) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *BaseResponse) GetRequestId() string { if x != nil { return x.RequestId } return "" } // 错误响应结构 type ErrorResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Base *BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` ErrorType string `protobuf:"bytes,2,opt,name=error_type,json=errorType,proto3" json:"error_type,omitempty"` Details string `protobuf:"bytes,3,opt,name=details,proto3" json:"details,omitempty"` ErrorTime int64 `protobuf:"varint,4,opt,name=error_time,json=errorTime,proto3" json:"error_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ErrorResponse) Reset() { *x = ErrorResponse{} mi := &file_proto_protocol_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ErrorResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ErrorResponse) ProtoMessage() {} func (x *ErrorResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_protocol_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead. func (*ErrorResponse) Descriptor() ([]byte, []int) { return file_proto_protocol_proto_rawDescGZIP(), []int{3} } func (x *ErrorResponse) GetBase() *BaseResponse { if x != nil { return x.Base } return nil } func (x *ErrorResponse) GetErrorType() string { if x != nil { return x.ErrorType } return "" } func (x *ErrorResponse) GetDetails() string { if x != nil { return x.Details } return "" } func (x *ErrorResponse) GetErrorTime() int64 { if x != nil { return x.ErrorTime } return 0 } var File_proto_protocol_proto protoreflect.FileDescriptor const file_proto_protocol_proto_rawDesc = "" + "\n" + "\x14proto/protocol.proto\x12\x16greatestworks.protocol\"\xde\x02\n" + "\x11ProtocolConstants\x12#\n" + "\rmessage_magic\x18\x01 \x01(\rR\fmessageMagic\x12.\n" + "\x13message_header_size\x18\x02 \x01(\rR\x11messageHeaderSize\x121\n" + "\x15max_message_body_size\x18\x03 \x01(\rR\x12maxMessageBodySize\x12-\n" + "\x12heartbeat_interval\x18\x04 \x01(\rR\x11heartbeatInterval\x12-\n" + "\x12connection_timeout\x18\x05 \x01(\rR\x11connectionTimeout\x12-\n" + "\x12reconnect_interval\x18\x06 \x01(\rR\x11reconnectInterval\x124\n" + "\x16max_reconnect_attempts\x18\a \x01(\rR\x14maxReconnectAttempts\"\xec\x01\n" + "\rMessageHeader\x12\x14\n" + "\x05magic\x18\x01 \x01(\rR\x05magic\x12\x1d\n" + "\n" + "message_id\x18\x02 \x01(\rR\tmessageId\x12!\n" + "\fmessage_type\x18\x03 \x01(\rR\vmessageType\x12\x14\n" + "\x05flags\x18\x04 \x01(\rR\x05flags\x12\x1b\n" + "\tplayer_id\x18\x05 \x01(\x04R\bplayerId\x12\x1c\n" + "\ttimestamp\x18\x06 \x01(\x03R\ttimestamp\x12\x1a\n" + "\bsequence\x18\a \x01(\rR\bsequence\x12\x16\n" + "\x06length\x18\b \x01(\rR\x06length\"\xc1\x01\n" + "\fBaseResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12@\n" + "\n" + "error_code\x18\x03 \x01(\x0e2!.greatestworks.protocol.ErrorCodeR\terrorCode\x12\x1c\n" + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x1d\n" + "\n" + "request_id\x18\x05 \x01(\tR\trequestId\"\xa1\x01\n" + "\rErrorResponse\x128\n" + "\x04base\x18\x01 \x01(\v2$.greatestworks.protocol.BaseResponseR\x04base\x12\x1d\n" + "\n" + "error_type\x18\x02 \x01(\tR\terrorType\x12\x18\n" + "\adetails\x18\x03 \x01(\tR\adetails\x12\x1d\n" + "\n" + "error_time\x18\x04 \x01(\x03R\terrorTime*\xa6\x01\n" + "\x11SystemMessageType\x12\x1e\n" + "\x1aSYSTEM_MESSAGE_UNSPECIFIED\x10\x00\x12\x11\n" + "\rMSG_HEARTBEAT\x10\x01\x12\x11\n" + "\rMSG_HANDSHAKE\x10\x02\x12\f\n" + "\bMSG_AUTH\x10\x03\x12\x12\n" + "\x0eMSG_DISCONNECT\x10\x04\x12\r\n" + "\tMSG_ERROR\x10\x05\x12\f\n" + "\bMSG_PING\x10\x06\x12\f\n" + "\bMSG_PONG\x10\a*\x9c\x02\n" + "\x11PlayerMessageType\x12\x1e\n" + "\x1aPLAYER_MESSAGE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x10MSG_PLAYER_LOGIN\x10\x81\x02\x12\x16\n" + "\x11MSG_PLAYER_LOGOUT\x10\x82\x02\x12\x14\n" + "\x0fMSG_PLAYER_INFO\x10\x83\x02\x12\x14\n" + "\x0fMSG_PLAYER_MOVE\x10\x84\x02\x12\x16\n" + "\x11MSG_PLAYER_CREATE\x10\x85\x02\x12\x16\n" + "\x11MSG_PLAYER_UPDATE\x10\x86\x02\x12\x16\n" + "\x11MSG_PLAYER_DELETE\x10\x87\x02\x12\x16\n" + "\x11MSG_PLAYER_STATUS\x10\x88\x02\x12\x15\n" + "\x10MSG_PLAYER_STATS\x10\x89\x02\x12\x15\n" + "\x10MSG_PLAYER_LEVEL\x10\x8a\x02*\x98\x02\n" + "\x11BattleMessageType\x12\x1e\n" + "\x1aBATTLE_MESSAGE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x11MSG_CREATE_BATTLE\x10\x81\x04\x12\x14\n" + "\x0fMSG_JOIN_BATTLE\x10\x82\x04\x12\x15\n" + "\x10MSG_LEAVE_BATTLE\x10\x83\x04\x12\x15\n" + "\x10MSG_START_BATTLE\x10\x84\x04\x12\x13\n" + "\x0eMSG_END_BATTLE\x10\x85\x04\x12\x16\n" + "\x11MSG_BATTLE_ACTION\x10\x86\x04\x12\x16\n" + "\x11MSG_BATTLE_RESULT\x10\x87\x04\x12\x16\n" + "\x11MSG_BATTLE_STATUS\x10\x88\x04\x12\x13\n" + "\x0eMSG_SKILL_CAST\x10\x89\x04\x12\x15\n" + "\x10MSG_DAMAGE_DEALT\x10\x8a\x04*\xfe\x01\n" + "\x0ePetMessageType\x12\x1b\n" + "\x17PET_MESSAGE_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0eMSG_PET_SUMMON\x10\x81\x06\x12\x14\n" + "\x0fMSG_PET_DISMISS\x10\x82\x06\x12\x11\n" + "\fMSG_PET_INFO\x10\x83\x06\x12\x11\n" + "\fMSG_PET_MOVE\x10\x84\x06\x12\x13\n" + "\x0eMSG_PET_ACTION\x10\x85\x06\x12\x15\n" + "\x10MSG_PET_LEVEL_UP\x10\x86\x06\x12\x16\n" + "\x11MSG_PET_EVOLUTION\x10\x87\x06\x12\x12\n" + "\rMSG_PET_TRAIN\x10\x88\x06\x12\x11\n" + "\fMSG_PET_FEED\x10\x89\x06\x12\x13\n" + "\x0eMSG_PET_STATUS\x10\x8a\x06*\x89\x02\n" + "\x13BuildingMessageType\x12 \n" + "\x1cBUILDING_MESSAGE_UNSPECIFIED\x10\x00\x12\x18\n" + "\x13MSG_BUILDING_CREATE\x10\x81\b\x12\x19\n" + "\x14MSG_BUILDING_UPGRADE\x10\x82\b\x12\x19\n" + "\x14MSG_BUILDING_DESTROY\x10\x83\b\x12\x16\n" + "\x11MSG_BUILDING_INFO\x10\x84\b\x12\x19\n" + "\x14MSG_BUILDING_PRODUCE\x10\x85\b\x12\x19\n" + "\x14MSG_BUILDING_COLLECT\x10\x86\b\x12\x18\n" + "\x13MSG_BUILDING_REPAIR\x10\x87\b\x12\x18\n" + "\x13MSG_BUILDING_STATUS\x10\x88\b*\xeb\x02\n" + "\x11SocialMessageType\x12\x1e\n" + "\x1aSOCIAL_MESSAGE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x10MSG_CHAT_MESSAGE\x10\x81\n" + "\x12\x17\n" + "\x12MSG_FRIEND_REQUEST\x10\x82\n" + "\x12\x16\n" + "\x11MSG_FRIEND_ACCEPT\x10\x83\n" + "\x12\x16\n" + "\x11MSG_FRIEND_REJECT\x10\x84\n" + "\x12\x16\n" + "\x11MSG_FRIEND_REMOVE\x10\x85\n" + "\x12\x14\n" + "\x0fMSG_FRIEND_LIST\x10\x86\n" + "\x12\x15\n" + "\x10MSG_GUILD_CREATE\x10\x87\n" + "\x12\x13\n" + "\x0eMSG_GUILD_JOIN\x10\x88\n" + "\x12\x14\n" + "\x0fMSG_GUILD_LEAVE\x10\x89\n" + "\x12\x13\n" + "\x0eMSG_GUILD_INFO\x10\x8a\n" + "\x12\x14\n" + "\x0fMSG_TEAM_CREATE\x10\x8b\n" + "\x12\x12\n" + "\rMSG_TEAM_JOIN\x10\x8c\n" + "\x12\x13\n" + "\x0eMSG_TEAM_LEAVE\x10\x8d\n" + "\x12\x12\n" + "\rMSG_TEAM_INFO\x10\x8e\n" + "*\x86\x02\n" + "\x0fItemMessageType\x12\x1c\n" + "\x18ITEM_MESSAGE_UNSPECIFIED\x10\x00\x12\x11\n" + "\fMSG_ITEM_USE\x10\x81\f\x12\x13\n" + "\x0eMSG_ITEM_EQUIP\x10\x82\f\x12\x15\n" + "\x10MSG_ITEM_UNEQUIP\x10\x83\f\x12\x12\n" + "\rMSG_ITEM_DROP\x10\x84\f\x12\x14\n" + "\x0fMSG_ITEM_PICKUP\x10\x85\f\x12\x13\n" + "\x0eMSG_ITEM_TRADE\x10\x86\f\x12\x17\n" + "\x12MSG_INVENTORY_INFO\x10\x87\f\x12\x12\n" + "\rMSG_ITEM_INFO\x10\x88\f\x12\x13\n" + "\x0eMSG_ITEM_CRAFT\x10\x89\f\x12\x15\n" + "\x10MSG_ITEM_ENHANCE\x10\x8a\f*\xd2\x01\n" + "\x10QuestMessageType\x12\x1d\n" + "\x19QUEST_MESSAGE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x10MSG_QUEST_ACCEPT\x10\x81\x0e\x12\x17\n" + "\x12MSG_QUEST_COMPLETE\x10\x82\x0e\x12\x15\n" + "\x10MSG_QUEST_CANCEL\x10\x83\x0e\x12\x17\n" + "\x12MSG_QUEST_PROGRESS\x10\x84\x0e\x12\x13\n" + "\x0eMSG_QUEST_LIST\x10\x85\x0e\x12\x13\n" + "\x0eMSG_QUEST_INFO\x10\x86\x0e\x12\x15\n" + "\x10MSG_QUEST_REWARD\x10\x87\x0e*\xb7\x01\n" + "\x10QueryMessageType\x12\x1d\n" + "\x19QUERY_MESSAGE_UNSPECIFIED\x10\x00\x12\x18\n" + "\x13MSG_GET_PLAYER_INFO\x10\x81\x10\x12\x1b\n" + "\x16MSG_GET_ONLINE_PLAYERS\x10\x82\x10\x12\x18\n" + "\x13MSG_GET_BATTLE_INFO\x10\x83\x10\x12\x19\n" + "\x14MSG_GET_RANKING_LIST\x10\x84\x10\x12\x18\n" + "\x13MSG_GET_SERVER_INFO\x10\x85\x10*\xfa\x05\n" + "\tErrorCode\x12\x1a\n" + "\x16ERROR_CODE_UNSPECIFIED\x10\x00\x12\x0f\n" + "\vERR_SUCCESS\x10\x00\x12\x10\n" + "\vERR_UNKNOWN\x10\xe8\a\x12\x18\n" + "\x13ERR_INVALID_MESSAGE\x10\xe9\a\x12\x17\n" + "\x12ERR_INVALID_PLAYER\x10\xea\a\x12\x19\n" + "\x14ERR_PLAYER_NOT_FOUND\x10\xeb\a\x12\x17\n" + "\x12ERR_PLAYER_OFFLINE\x10\xec\a\x12\x14\n" + "\x0fERR_AUTH_FAILED\x10\xed\a\x12\x1a\n" + "\x15ERR_PERMISSION_DENIED\x10\xee\a\x12\x15\n" + "\x10ERR_RATE_LIMITED\x10\xef\a\x12\x14\n" + "\x0fERR_SERVER_BUSY\x10\xf0\a\x12\x14\n" + "\x0fERR_MAINTENANCE\x10\xf1\a\x12\x19\n" + "\x14ERR_BATTLE_NOT_FOUND\x10\xd1\x0f\x12\x14\n" + "\x0fERR_BATTLE_FULL\x10\xd2\x0f\x12\x17\n" + "\x12ERR_BATTLE_STARTED\x10\xd3\x0f\x12\x15\n" + "\x10ERR_BATTLE_ENDED\x10\xd4\x0f\x12\x17\n" + "\x12ERR_INVALID_ACTION\x10\xd5\x0f\x12\x16\n" + "\x11ERR_NOT_YOUR_TURN\x10\xd6\x0f\x12\x17\n" + "\x12ERR_SKILL_COOLDOWN\x10\xd7\x0f\x12\x18\n" + "\x13ERR_INSUFFICIENT_MP\x10\xd8\x0f\x12\x16\n" + "\x11ERR_PET_NOT_FOUND\x10\xb9\x17\x12\x1b\n" + "\x16ERR_PET_ALREADY_ACTIVE\x10\xba\x17\x12\x17\n" + "\x12ERR_PET_NOT_ACTIVE\x10\xbb\x17\x12\x1a\n" + "\x15ERR_PET_LEVEL_TOO_LOW\x10\xbc\x17\x12\x1b\n" + "\x16ERR_PET_EVOLUTION_FAIL\x10\xbd\x17\x12\x17\n" + "\x12ERR_ITEM_NOT_FOUND\x10\xa1\x1f\x12\x18\n" + "\x13ERR_ITEM_NOT_USABLE\x10\xa2\x1f\x12\x17\n" + "\x12ERR_INVENTORY_FULL\x10\xa3\x1f\x12\x1a\n" + "\x15ERR_INSUFFICIENT_ITEM\x10\xa4\x1f\x12\x1a\n" + "\x15ERR_ITEM_EQUIP_FAILED\x10\xa5\x1f\x1a\x02\x10\x01*\xcc\x01\n" + "\fPlayerStatus\x12\x1d\n" + "\x19PLAYER_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15PLAYER_STATUS_OFFLINE\x10\x01\x12\x18\n" + "\x14PLAYER_STATUS_ONLINE\x10\x02\x12\x1b\n" + "\x17PLAYER_STATUS_IN_BATTLE\x10\x03\x12\x1a\n" + "\x16PLAYER_STATUS_IN_QUEUE\x10\x04\x12\x15\n" + "\x11PLAYER_STATUS_AFK\x10\x05\x12\x18\n" + "\x14PLAYER_STATUS_BANNED\x10\x06*\xd1\x01\n" + "\fBattleStatus\x12\x1d\n" + "\x19BATTLE_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15BATTLE_STATUS_WAITING\x10\x01\x12\x1a\n" + "\x16BATTLE_STATUS_STARTING\x10\x02\x12\x18\n" + "\x14BATTLE_STATUS_ACTIVE\x10\x03\x12\x18\n" + "\x14BATTLE_STATUS_ENDING\x10\x04\x12\x1a\n" + "\x16BATTLE_STATUS_FINISHED\x10\x05\x12\x1b\n" + "\x17BATTLE_STATUS_CANCELLED\x10\x06*\xca\x01\n" + "\tPetStatus\x12\x1a\n" + "\x16PET_STATUS_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fPET_STATUS_IDLE\x10\x01\x12\x15\n" + "\x11PET_STATUS_ACTIVE\x10\x02\x12\x15\n" + "\x11PET_STATUS_BATTLE\x10\x03\x12\x17\n" + "\x13PET_STATUS_TRAINING\x10\x04\x12\x17\n" + "\x13PET_STATUS_SLEEPING\x10\x05\x12\x13\n" + "\x0fPET_STATUS_SICK\x10\x06\x12\x17\n" + "\x13PET_STATUS_EVOLVING\x10\a*\xba\x01\n" + "\n" + "ItemRarity\x12\x1b\n" + "\x17ITEM_RARITY_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12ITEM_RARITY_COMMON\x10\x01\x12\x18\n" + "\x14ITEM_RARITY_UNCOMMON\x10\x02\x12\x14\n" + "\x10ITEM_RARITY_RARE\x10\x03\x12\x14\n" + "\x10ITEM_RARITY_EPIC\x10\x04\x12\x19\n" + "\x15ITEM_RARITY_LEGENDARY\x10\x05\x12\x16\n" + "\x12ITEM_RARITY_MYTHIC\x10\x06*\xb2\x01\n" + "\tPetRarity\x12\x1a\n" + "\x16PET_RARITY_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11PET_RARITY_COMMON\x10\x01\x12\x17\n" + "\x13PET_RARITY_UNCOMMON\x10\x02\x12\x13\n" + "\x0fPET_RARITY_RARE\x10\x03\x12\x13\n" + "\x0fPET_RARITY_EPIC\x10\x04\x12\x18\n" + "\x14PET_RARITY_LEGENDARY\x10\x05\x12\x15\n" + "\x11PET_RARITY_MYTHIC\x10\x06*\xe5\x01\n" + "\vMessageFlag\x12\x1c\n" + "\x18MESSAGE_FLAG_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14MESSAGE_FLAG_REQUEST\x10\x01\x12\x19\n" + "\x15MESSAGE_FLAG_RESPONSE\x10\x02\x12\x16\n" + "\x12MESSAGE_FLAG_ERROR\x10\x04\x12\x16\n" + "\x12MESSAGE_FLAG_ASYNC\x10\b\x12\x1a\n" + "\x16MESSAGE_FLAG_BROADCAST\x10\x10\x12\x1a\n" + "\x16MESSAGE_FLAG_ENCRYPTED\x10 \x12\x1b\n" + "\x17MESSAGE_FLAG_COMPRESSED\x10@B@Z%greatestworks/internal/proto/protocol\xaa\x02\x16GreatestWorks.Protocolb\x06proto3" var ( file_proto_protocol_proto_rawDescOnce sync.Once file_proto_protocol_proto_rawDescData []byte ) func file_proto_protocol_proto_rawDescGZIP() []byte { file_proto_protocol_proto_rawDescOnce.Do(func() { file_proto_protocol_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_protocol_proto_rawDesc), len(file_proto_protocol_proto_rawDesc))) }) return file_proto_protocol_proto_rawDescData } var file_proto_protocol_proto_enumTypes = make([]protoimpl.EnumInfo, 16) var file_proto_protocol_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proto_protocol_proto_goTypes = []any{ (SystemMessageType)(0), // 0: greatestworks.protocol.SystemMessageType (PlayerMessageType)(0), // 1: greatestworks.protocol.PlayerMessageType (BattleMessageType)(0), // 2: greatestworks.protocol.BattleMessageType (PetMessageType)(0), // 3: greatestworks.protocol.PetMessageType (BuildingMessageType)(0), // 4: greatestworks.protocol.BuildingMessageType (SocialMessageType)(0), // 5: greatestworks.protocol.SocialMessageType (ItemMessageType)(0), // 6: greatestworks.protocol.ItemMessageType (QuestMessageType)(0), // 7: greatestworks.protocol.QuestMessageType (QueryMessageType)(0), // 8: greatestworks.protocol.QueryMessageType (ErrorCode)(0), // 9: greatestworks.protocol.ErrorCode (PlayerStatus)(0), // 10: greatestworks.protocol.PlayerStatus (BattleStatus)(0), // 11: greatestworks.protocol.BattleStatus (PetStatus)(0), // 12: greatestworks.protocol.PetStatus (ItemRarity)(0), // 13: greatestworks.protocol.ItemRarity (PetRarity)(0), // 14: greatestworks.protocol.PetRarity (MessageFlag)(0), // 15: greatestworks.protocol.MessageFlag (*ProtocolConstants)(nil), // 16: greatestworks.protocol.ProtocolConstants (*MessageHeader)(nil), // 17: greatestworks.protocol.MessageHeader (*BaseResponse)(nil), // 18: greatestworks.protocol.BaseResponse (*ErrorResponse)(nil), // 19: greatestworks.protocol.ErrorResponse } var file_proto_protocol_proto_depIdxs = []int32{ 9, // 0: greatestworks.protocol.BaseResponse.error_code:type_name -> greatestworks.protocol.ErrorCode 18, // 1: greatestworks.protocol.ErrorResponse.base:type_name -> greatestworks.protocol.BaseResponse 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_proto_protocol_proto_init() } func file_proto_protocol_proto_init() { if File_proto_protocol_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_protocol_proto_rawDesc), len(file_proto_protocol_proto_rawDesc)), NumEnums: 16, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proto_protocol_proto_goTypes, DependencyIndexes: file_proto_protocol_proto_depIdxs, EnumInfos: file_proto_protocol_proto_enumTypes, MessageInfos: file_proto_protocol_proto_msgTypes, }.Build() File_proto_protocol_proto = out.File file_proto_protocol_proto_goTypes = nil file_proto_protocol_proto_depIdxs = nil } ================================================ FILE: internal/proto/room/room.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/room.proto package room import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 房间类型枚举 type RoomType int32 const ( RoomType_ROOM_TYPE_UNSPECIFIED RoomType = 0 RoomType_ROOM_TYPE_CASUAL RoomType = 1 // 休闲模式 RoomType_ROOM_TYPE_RANKED RoomType = 2 // 排位模式 RoomType_ROOM_TYPE_CUSTOM RoomType = 3 // 自定义模式 RoomType_ROOM_TYPE_TOURNAMENT RoomType = 4 // 锦标赛模式 RoomType_ROOM_TYPE_PRACTICE RoomType = 5 // 练习模式 RoomType_ROOM_TYPE_SPECTATE RoomType = 6 // 观战模式 RoomType_ROOM_TYPE_PRIVATE_MATCH RoomType = 7 // 私人对战 ) // Enum value maps for RoomType. var ( RoomType_name = map[int32]string{ 0: "ROOM_TYPE_UNSPECIFIED", 1: "ROOM_TYPE_CASUAL", 2: "ROOM_TYPE_RANKED", 3: "ROOM_TYPE_CUSTOM", 4: "ROOM_TYPE_TOURNAMENT", 5: "ROOM_TYPE_PRACTICE", 6: "ROOM_TYPE_SPECTATE", 7: "ROOM_TYPE_PRIVATE_MATCH", } RoomType_value = map[string]int32{ "ROOM_TYPE_UNSPECIFIED": 0, "ROOM_TYPE_CASUAL": 1, "ROOM_TYPE_RANKED": 2, "ROOM_TYPE_CUSTOM": 3, "ROOM_TYPE_TOURNAMENT": 4, "ROOM_TYPE_PRACTICE": 5, "ROOM_TYPE_SPECTATE": 6, "ROOM_TYPE_PRIVATE_MATCH": 7, } ) func (x RoomType) Enum() *RoomType { p := new(RoomType) *p = x return p } func (x RoomType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (RoomType) Descriptor() protoreflect.EnumDescriptor { return file_proto_room_proto_enumTypes[0].Descriptor() } func (RoomType) Type() protoreflect.EnumType { return &file_proto_room_proto_enumTypes[0] } func (x RoomType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use RoomType.Descriptor instead. func (RoomType) EnumDescriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{0} } // 房间状态枚举 type RoomStatus int32 const ( RoomStatus_ROOM_STATUS_UNSPECIFIED RoomStatus = 0 RoomStatus_ROOM_STATUS_WAITING RoomStatus = 1 // 等待玩家 RoomStatus_ROOM_STATUS_PREPARING RoomStatus = 2 // 准备中 RoomStatus_ROOM_STATUS_IN_GAME RoomStatus = 3 // 游戏中 RoomStatus_ROOM_STATUS_FINISHED RoomStatus = 4 // 已结束 RoomStatus_ROOM_STATUS_DISBANDED RoomStatus = 5 // 已解散 RoomStatus_ROOM_STATUS_PAUSED RoomStatus = 6 // 已暂停 ) // Enum value maps for RoomStatus. var ( RoomStatus_name = map[int32]string{ 0: "ROOM_STATUS_UNSPECIFIED", 1: "ROOM_STATUS_WAITING", 2: "ROOM_STATUS_PREPARING", 3: "ROOM_STATUS_IN_GAME", 4: "ROOM_STATUS_FINISHED", 5: "ROOM_STATUS_DISBANDED", 6: "ROOM_STATUS_PAUSED", } RoomStatus_value = map[string]int32{ "ROOM_STATUS_UNSPECIFIED": 0, "ROOM_STATUS_WAITING": 1, "ROOM_STATUS_PREPARING": 2, "ROOM_STATUS_IN_GAME": 3, "ROOM_STATUS_FINISHED": 4, "ROOM_STATUS_DISBANDED": 5, "ROOM_STATUS_PAUSED": 6, } ) func (x RoomStatus) Enum() *RoomStatus { p := new(RoomStatus) *p = x return p } func (x RoomStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (RoomStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_room_proto_enumTypes[1].Descriptor() } func (RoomStatus) Type() protoreflect.EnumType { return &file_proto_room_proto_enumTypes[1] } func (x RoomStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use RoomStatus.Descriptor instead. func (RoomStatus) EnumDescriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{1} } // 玩家角色枚举 type PlayerRole int32 const ( PlayerRole_PLAYER_ROLE_UNSPECIFIED PlayerRole = 0 PlayerRole_PLAYER_ROLE_OWNER PlayerRole = 1 // 房主 PlayerRole_PLAYER_ROLE_MODERATOR PlayerRole = 2 // 管理员 PlayerRole_PLAYER_ROLE_PLAYER PlayerRole = 3 // 玩家 PlayerRole_PLAYER_ROLE_SPECTATOR PlayerRole = 4 // 观战者 ) // Enum value maps for PlayerRole. var ( PlayerRole_name = map[int32]string{ 0: "PLAYER_ROLE_UNSPECIFIED", 1: "PLAYER_ROLE_OWNER", 2: "PLAYER_ROLE_MODERATOR", 3: "PLAYER_ROLE_PLAYER", 4: "PLAYER_ROLE_SPECTATOR", } PlayerRole_value = map[string]int32{ "PLAYER_ROLE_UNSPECIFIED": 0, "PLAYER_ROLE_OWNER": 1, "PLAYER_ROLE_MODERATOR": 2, "PLAYER_ROLE_PLAYER": 3, "PLAYER_ROLE_SPECTATOR": 4, } ) func (x PlayerRole) Enum() *PlayerRole { p := new(PlayerRole) *p = x return p } func (x PlayerRole) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerRole) Descriptor() protoreflect.EnumDescriptor { return file_proto_room_proto_enumTypes[2].Descriptor() } func (PlayerRole) Type() protoreflect.EnumType { return &file_proto_room_proto_enumTypes[2] } func (x PlayerRole) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerRole.Descriptor instead. func (PlayerRole) EnumDescriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{2} } // 房间消息类型枚举 type RoomMessageType int32 const ( RoomMessageType_ROOM_MESSAGE_TYPE_UNSPECIFIED RoomMessageType = 0 RoomMessageType_ROOM_MESSAGE_TYPE_CHAT RoomMessageType = 1 // 聊天消息 RoomMessageType_ROOM_MESSAGE_TYPE_SYSTEM RoomMessageType = 2 // 系统消息 RoomMessageType_ROOM_MESSAGE_TYPE_JOIN RoomMessageType = 3 // 加入消息 RoomMessageType_ROOM_MESSAGE_TYPE_LEAVE RoomMessageType = 4 // 离开消息 RoomMessageType_ROOM_MESSAGE_TYPE_READY RoomMessageType = 5 // 准备消息 RoomMessageType_ROOM_MESSAGE_TYPE_START RoomMessageType = 6 // 开始消息 RoomMessageType_ROOM_MESSAGE_TYPE_END RoomMessageType = 7 // 结束消息 ) // Enum value maps for RoomMessageType. var ( RoomMessageType_name = map[int32]string{ 0: "ROOM_MESSAGE_TYPE_UNSPECIFIED", 1: "ROOM_MESSAGE_TYPE_CHAT", 2: "ROOM_MESSAGE_TYPE_SYSTEM", 3: "ROOM_MESSAGE_TYPE_JOIN", 4: "ROOM_MESSAGE_TYPE_LEAVE", 5: "ROOM_MESSAGE_TYPE_READY", 6: "ROOM_MESSAGE_TYPE_START", 7: "ROOM_MESSAGE_TYPE_END", } RoomMessageType_value = map[string]int32{ "ROOM_MESSAGE_TYPE_UNSPECIFIED": 0, "ROOM_MESSAGE_TYPE_CHAT": 1, "ROOM_MESSAGE_TYPE_SYSTEM": 2, "ROOM_MESSAGE_TYPE_JOIN": 3, "ROOM_MESSAGE_TYPE_LEAVE": 4, "ROOM_MESSAGE_TYPE_READY": 5, "ROOM_MESSAGE_TYPE_START": 6, "ROOM_MESSAGE_TYPE_END": 7, } ) func (x RoomMessageType) Enum() *RoomMessageType { p := new(RoomMessageType) *p = x return p } func (x RoomMessageType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (RoomMessageType) Descriptor() protoreflect.EnumDescriptor { return file_proto_room_proto_enumTypes[3].Descriptor() } func (RoomMessageType) Type() protoreflect.EnumType { return &file_proto_room_proto_enumTypes[3] } func (x RoomMessageType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use RoomMessageType.Descriptor instead. func (RoomMessageType) EnumDescriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{3} } // 房间排序枚举 type RoomSortBy int32 const ( RoomSortBy_ROOM_SORT_BY_UNSPECIFIED RoomSortBy = 0 RoomSortBy_ROOM_SORT_BY_NAME RoomSortBy = 1 // 按名称排序 RoomSortBy_ROOM_SORT_BY_PLAYERS RoomSortBy = 2 // 按玩家数量排序 RoomSortBy_ROOM_SORT_BY_CREATED_TIME RoomSortBy = 3 // 按创建时间排序 RoomSortBy_ROOM_SORT_BY_PING RoomSortBy = 4 // 按延迟排序 RoomSortBy_ROOM_SORT_BY_POPULARITY RoomSortBy = 5 // 按热门程度排序 ) // Enum value maps for RoomSortBy. var ( RoomSortBy_name = map[int32]string{ 0: "ROOM_SORT_BY_UNSPECIFIED", 1: "ROOM_SORT_BY_NAME", 2: "ROOM_SORT_BY_PLAYERS", 3: "ROOM_SORT_BY_CREATED_TIME", 4: "ROOM_SORT_BY_PING", 5: "ROOM_SORT_BY_POPULARITY", } RoomSortBy_value = map[string]int32{ "ROOM_SORT_BY_UNSPECIFIED": 0, "ROOM_SORT_BY_NAME": 1, "ROOM_SORT_BY_PLAYERS": 2, "ROOM_SORT_BY_CREATED_TIME": 3, "ROOM_SORT_BY_PING": 4, "ROOM_SORT_BY_POPULARITY": 5, } ) func (x RoomSortBy) Enum() *RoomSortBy { p := new(RoomSortBy) *p = x return p } func (x RoomSortBy) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (RoomSortBy) Descriptor() protoreflect.EnumDescriptor { return file_proto_room_proto_enumTypes[4].Descriptor() } func (RoomSortBy) Type() protoreflect.EnumType { return &file_proto_room_proto_enumTypes[4] } func (x RoomSortBy) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use RoomSortBy.Descriptor instead. func (RoomSortBy) EnumDescriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{4} } // 难度等级枚举 type DifficultyLevel int32 const ( DifficultyLevel_DIFFICULTY_LEVEL_UNSPECIFIED DifficultyLevel = 0 DifficultyLevel_DIFFICULTY_LEVEL_EASY DifficultyLevel = 1 // 简单 DifficultyLevel_DIFFICULTY_LEVEL_NORMAL DifficultyLevel = 2 // 普通 DifficultyLevel_DIFFICULTY_LEVEL_HARD DifficultyLevel = 3 // 困难 DifficultyLevel_DIFFICULTY_LEVEL_EXPERT DifficultyLevel = 4 // 专家 DifficultyLevel_DIFFICULTY_LEVEL_NIGHTMARE DifficultyLevel = 5 // 噩梦 ) // Enum value maps for DifficultyLevel. var ( DifficultyLevel_name = map[int32]string{ 0: "DIFFICULTY_LEVEL_UNSPECIFIED", 1: "DIFFICULTY_LEVEL_EASY", 2: "DIFFICULTY_LEVEL_NORMAL", 3: "DIFFICULTY_LEVEL_HARD", 4: "DIFFICULTY_LEVEL_EXPERT", 5: "DIFFICULTY_LEVEL_NIGHTMARE", } DifficultyLevel_value = map[string]int32{ "DIFFICULTY_LEVEL_UNSPECIFIED": 0, "DIFFICULTY_LEVEL_EASY": 1, "DIFFICULTY_LEVEL_NORMAL": 2, "DIFFICULTY_LEVEL_HARD": 3, "DIFFICULTY_LEVEL_EXPERT": 4, "DIFFICULTY_LEVEL_NIGHTMARE": 5, } ) func (x DifficultyLevel) Enum() *DifficultyLevel { p := new(DifficultyLevel) *p = x return p } func (x DifficultyLevel) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (DifficultyLevel) Descriptor() protoreflect.EnumDescriptor { return file_proto_room_proto_enumTypes[5].Descriptor() } func (DifficultyLevel) Type() protoreflect.EnumType { return &file_proto_room_proto_enumTypes[5] } func (x DifficultyLevel) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use DifficultyLevel.Descriptor instead. func (DifficultyLevel) EnumDescriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{5} } // 创建房间请求 type CreateRoomRequest struct { state protoimpl.MessageState `protogen:"open.v1"` OwnerId string `protobuf:"bytes,1,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` RoomName string `protobuf:"bytes,2,opt,name=room_name,json=roomName,proto3" json:"room_name,omitempty"` RoomType RoomType `protobuf:"varint,3,opt,name=room_type,json=roomType,proto3,enum=greatestworks.room.RoomType" json:"room_type,omitempty"` GameMode string `protobuf:"bytes,4,opt,name=game_mode,json=gameMode,proto3" json:"game_mode,omitempty"` MapId string `protobuf:"bytes,5,opt,name=map_id,json=mapId,proto3" json:"map_id,omitempty"` MaxPlayers int32 `protobuf:"varint,6,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` IsPrivate bool `protobuf:"varint,7,opt,name=is_private,json=isPrivate,proto3" json:"is_private,omitempty"` Password string `protobuf:"bytes,8,opt,name=password,proto3" json:"password,omitempty"` Settings *RoomSettings `protobuf:"bytes,9,opt,name=settings,proto3" json:"settings,omitempty"` CustomRules map[string]string `protobuf:"bytes,10,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateRoomRequest) Reset() { *x = CreateRoomRequest{} mi := &file_proto_room_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateRoomRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateRoomRequest) ProtoMessage() {} func (x *CreateRoomRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateRoomRequest.ProtoReflect.Descriptor instead. func (*CreateRoomRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{0} } func (x *CreateRoomRequest) GetOwnerId() string { if x != nil { return x.OwnerId } return "" } func (x *CreateRoomRequest) GetRoomName() string { if x != nil { return x.RoomName } return "" } func (x *CreateRoomRequest) GetRoomType() RoomType { if x != nil { return x.RoomType } return RoomType_ROOM_TYPE_UNSPECIFIED } func (x *CreateRoomRequest) GetGameMode() string { if x != nil { return x.GameMode } return "" } func (x *CreateRoomRequest) GetMapId() string { if x != nil { return x.MapId } return "" } func (x *CreateRoomRequest) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *CreateRoomRequest) GetIsPrivate() bool { if x != nil { return x.IsPrivate } return false } func (x *CreateRoomRequest) GetPassword() string { if x != nil { return x.Password } return "" } func (x *CreateRoomRequest) GetSettings() *RoomSettings { if x != nil { return x.Settings } return nil } func (x *CreateRoomRequest) GetCustomRules() map[string]string { if x != nil { return x.CustomRules } return nil } // 创建房间响应 type CreateRoomResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` RoomInfo *RoomInfo `protobuf:"bytes,3,opt,name=room_info,json=roomInfo,proto3" json:"room_info,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateRoomResponse) Reset() { *x = CreateRoomResponse{} mi := &file_proto_room_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateRoomResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateRoomResponse) ProtoMessage() {} func (x *CreateRoomResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateRoomResponse.ProtoReflect.Descriptor instead. func (*CreateRoomResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{1} } func (x *CreateRoomResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *CreateRoomResponse) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *CreateRoomResponse) GetRoomInfo() *RoomInfo { if x != nil { return x.RoomInfo } return nil } // 加入房间请求 type JoinRoomRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` InvitationCode string `protobuf:"bytes,4,opt,name=invitation_code,json=invitationCode,proto3" json:"invitation_code,omitempty"` PreferredTeam int32 `protobuf:"varint,5,opt,name=preferred_team,json=preferredTeam,proto3" json:"preferred_team,omitempty"` // 希望加入的队伍 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinRoomRequest) Reset() { *x = JoinRoomRequest{} mi := &file_proto_room_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinRoomRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinRoomRequest) ProtoMessage() {} func (x *JoinRoomRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinRoomRequest.ProtoReflect.Descriptor instead. func (*JoinRoomRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{2} } func (x *JoinRoomRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *JoinRoomRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *JoinRoomRequest) GetPassword() string { if x != nil { return x.Password } return "" } func (x *JoinRoomRequest) GetInvitationCode() string { if x != nil { return x.InvitationCode } return "" } func (x *JoinRoomRequest) GetPreferredTeam() int32 { if x != nil { return x.PreferredTeam } return 0 } // 加入房间响应 type JoinRoomResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` RoomInfo *RoomInfo `protobuf:"bytes,2,opt,name=room_info,json=roomInfo,proto3" json:"room_info,omitempty"` PlayerInfo *RoomPlayer `protobuf:"bytes,3,opt,name=player_info,json=playerInfo,proto3" json:"player_info,omitempty"` OtherPlayers []*RoomPlayer `protobuf:"bytes,4,rep,name=other_players,json=otherPlayers,proto3" json:"other_players,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinRoomResponse) Reset() { *x = JoinRoomResponse{} mi := &file_proto_room_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinRoomResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinRoomResponse) ProtoMessage() {} func (x *JoinRoomResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinRoomResponse.ProtoReflect.Descriptor instead. func (*JoinRoomResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{3} } func (x *JoinRoomResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *JoinRoomResponse) GetRoomInfo() *RoomInfo { if x != nil { return x.RoomInfo } return nil } func (x *JoinRoomResponse) GetPlayerInfo() *RoomPlayer { if x != nil { return x.PlayerInfo } return nil } func (x *JoinRoomResponse) GetOtherPlayers() []*RoomPlayer { if x != nil { return x.OtherPlayers } return nil } // 离开房间请求 type LeaveRoomRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveRoomRequest) Reset() { *x = LeaveRoomRequest{} mi := &file_proto_room_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveRoomRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveRoomRequest) ProtoMessage() {} func (x *LeaveRoomRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveRoomRequest.ProtoReflect.Descriptor instead. func (*LeaveRoomRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{4} } func (x *LeaveRoomRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *LeaveRoomRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } // 离开房间响应 type LeaveRoomResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveRoomResponse) Reset() { *x = LeaveRoomResponse{} mi := &file_proto_room_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveRoomResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveRoomResponse) ProtoMessage() {} func (x *LeaveRoomResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveRoomResponse.ProtoReflect.Descriptor instead. func (*LeaveRoomResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{5} } func (x *LeaveRoomResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 获取房间列表请求 type GetRoomListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RoomType RoomType `protobuf:"varint,1,opt,name=room_type,json=roomType,proto3,enum=greatestworks.room.RoomType" json:"room_type,omitempty"` GameMode string `protobuf:"bytes,2,opt,name=game_mode,json=gameMode,proto3" json:"game_mode,omitempty"` OnlyPublic bool `protobuf:"varint,3,opt,name=only_public,json=onlyPublic,proto3" json:"only_public,omitempty"` OnlyAvailable bool `protobuf:"varint,4,opt,name=only_available,json=onlyAvailable,proto3" json:"only_available,omitempty"` // 只显示有空位的房间 Limit int32 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,6,opt,name=offset,proto3" json:"offset,omitempty"` SortBy RoomSortBy `protobuf:"varint,7,opt,name=sort_by,json=sortBy,proto3,enum=greatestworks.room.RoomSortBy" json:"sort_by,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetRoomListRequest) Reset() { *x = GetRoomListRequest{} mi := &file_proto_room_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetRoomListRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetRoomListRequest) ProtoMessage() {} func (x *GetRoomListRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetRoomListRequest.ProtoReflect.Descriptor instead. func (*GetRoomListRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{6} } func (x *GetRoomListRequest) GetRoomType() RoomType { if x != nil { return x.RoomType } return RoomType_ROOM_TYPE_UNSPECIFIED } func (x *GetRoomListRequest) GetGameMode() string { if x != nil { return x.GameMode } return "" } func (x *GetRoomListRequest) GetOnlyPublic() bool { if x != nil { return x.OnlyPublic } return false } func (x *GetRoomListRequest) GetOnlyAvailable() bool { if x != nil { return x.OnlyAvailable } return false } func (x *GetRoomListRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetRoomListRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } func (x *GetRoomListRequest) GetSortBy() RoomSortBy { if x != nil { return x.SortBy } return RoomSortBy_ROOM_SORT_BY_UNSPECIFIED } // 获取房间列表响应 type GetRoomListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Rooms []*RoomInfo `protobuf:"bytes,2,rep,name=rooms,proto3" json:"rooms,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetRoomListResponse) Reset() { *x = GetRoomListResponse{} mi := &file_proto_room_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetRoomListResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetRoomListResponse) ProtoMessage() {} func (x *GetRoomListResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetRoomListResponse.ProtoReflect.Descriptor instead. func (*GetRoomListResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{7} } func (x *GetRoomListResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetRoomListResponse) GetRooms() []*RoomInfo { if x != nil { return x.Rooms } return nil } func (x *GetRoomListResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 获取房间信息请求 type GetRoomInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` // 查询者ID,用于权限检查 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetRoomInfoRequest) Reset() { *x = GetRoomInfoRequest{} mi := &file_proto_room_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetRoomInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetRoomInfoRequest) ProtoMessage() {} func (x *GetRoomInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetRoomInfoRequest.ProtoReflect.Descriptor instead. func (*GetRoomInfoRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{8} } func (x *GetRoomInfoRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *GetRoomInfoRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } // 获取房间信息响应 type GetRoomInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` RoomDetail *RoomDetail `protobuf:"bytes,2,opt,name=room_detail,json=roomDetail,proto3" json:"room_detail,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetRoomInfoResponse) Reset() { *x = GetRoomInfoResponse{} mi := &file_proto_room_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetRoomInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetRoomInfoResponse) ProtoMessage() {} func (x *GetRoomInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetRoomInfoResponse.ProtoReflect.Descriptor instead. func (*GetRoomInfoResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{9} } func (x *GetRoomInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetRoomInfoResponse) GetRoomDetail() *RoomDetail { if x != nil { return x.RoomDetail } return nil } // 更新房间设置请求 type UpdateRoomSettingsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` Settings *RoomSettings `protobuf:"bytes,3,opt,name=settings,proto3" json:"settings,omitempty"` CustomRules map[string]string `protobuf:"bytes,4,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateRoomSettingsRequest) Reset() { *x = UpdateRoomSettingsRequest{} mi := &file_proto_room_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateRoomSettingsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateRoomSettingsRequest) ProtoMessage() {} func (x *UpdateRoomSettingsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateRoomSettingsRequest.ProtoReflect.Descriptor instead. func (*UpdateRoomSettingsRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{10} } func (x *UpdateRoomSettingsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *UpdateRoomSettingsRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *UpdateRoomSettingsRequest) GetSettings() *RoomSettings { if x != nil { return x.Settings } return nil } func (x *UpdateRoomSettingsRequest) GetCustomRules() map[string]string { if x != nil { return x.CustomRules } return nil } // 更新房间设置响应 type UpdateRoomSettingsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewSettings *RoomSettings `protobuf:"bytes,2,opt,name=new_settings,json=newSettings,proto3" json:"new_settings,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateRoomSettingsResponse) Reset() { *x = UpdateRoomSettingsResponse{} mi := &file_proto_room_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateRoomSettingsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateRoomSettingsResponse) ProtoMessage() {} func (x *UpdateRoomSettingsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateRoomSettingsResponse.ProtoReflect.Descriptor instead. func (*UpdateRoomSettingsResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{11} } func (x *UpdateRoomSettingsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *UpdateRoomSettingsResponse) GetNewSettings() *RoomSettings { if x != nil { return x.NewSettings } return nil } // 踢出玩家请求 type KickPlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` KickerId string `protobuf:"bytes,1,opt,name=kicker_id,json=kickerId,proto3" json:"kicker_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` TargetPlayerId string `protobuf:"bytes,3,opt,name=target_player_id,json=targetPlayerId,proto3" json:"target_player_id,omitempty"` Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *KickPlayerRequest) Reset() { *x = KickPlayerRequest{} mi := &file_proto_room_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *KickPlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*KickPlayerRequest) ProtoMessage() {} func (x *KickPlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use KickPlayerRequest.ProtoReflect.Descriptor instead. func (*KickPlayerRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{12} } func (x *KickPlayerRequest) GetKickerId() string { if x != nil { return x.KickerId } return "" } func (x *KickPlayerRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *KickPlayerRequest) GetTargetPlayerId() string { if x != nil { return x.TargetPlayerId } return "" } func (x *KickPlayerRequest) GetReason() string { if x != nil { return x.Reason } return "" } // 踢出玩家响应 type KickPlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *KickPlayerResponse) Reset() { *x = KickPlayerResponse{} mi := &file_proto_room_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *KickPlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*KickPlayerResponse) ProtoMessage() {} func (x *KickPlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use KickPlayerResponse.ProtoReflect.Descriptor instead. func (*KickPlayerResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{13} } func (x *KickPlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 转移房主请求 type TransferOwnershipRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CurrentOwnerId string `protobuf:"bytes,1,opt,name=current_owner_id,json=currentOwnerId,proto3" json:"current_owner_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` NewOwnerId string `protobuf:"bytes,3,opt,name=new_owner_id,json=newOwnerId,proto3" json:"new_owner_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TransferOwnershipRequest) Reset() { *x = TransferOwnershipRequest{} mi := &file_proto_room_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TransferOwnershipRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferOwnershipRequest) ProtoMessage() {} func (x *TransferOwnershipRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferOwnershipRequest.ProtoReflect.Descriptor instead. func (*TransferOwnershipRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{14} } func (x *TransferOwnershipRequest) GetCurrentOwnerId() string { if x != nil { return x.CurrentOwnerId } return "" } func (x *TransferOwnershipRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *TransferOwnershipRequest) GetNewOwnerId() string { if x != nil { return x.NewOwnerId } return "" } // 转移房主响应 type TransferOwnershipResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewOwner *RoomPlayer `protobuf:"bytes,2,opt,name=new_owner,json=newOwner,proto3" json:"new_owner,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TransferOwnershipResponse) Reset() { *x = TransferOwnershipResponse{} mi := &file_proto_room_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TransferOwnershipResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferOwnershipResponse) ProtoMessage() {} func (x *TransferOwnershipResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferOwnershipResponse.ProtoReflect.Descriptor instead. func (*TransferOwnershipResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{15} } func (x *TransferOwnershipResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *TransferOwnershipResponse) GetNewOwner() *RoomPlayer { if x != nil { return x.NewOwner } return nil } // 开始游戏请求 type StartGameRequest struct { state protoimpl.MessageState `protogen:"open.v1"` OwnerId string `protobuf:"bytes,1,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` ForceStart bool `protobuf:"varint,3,opt,name=force_start,json=forceStart,proto3" json:"force_start,omitempty"` // 强制开始(即使人数不足) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *StartGameRequest) Reset() { *x = StartGameRequest{} mi := &file_proto_room_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *StartGameRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*StartGameRequest) ProtoMessage() {} func (x *StartGameRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StartGameRequest.ProtoReflect.Descriptor instead. func (*StartGameRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{16} } func (x *StartGameRequest) GetOwnerId() string { if x != nil { return x.OwnerId } return "" } func (x *StartGameRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *StartGameRequest) GetForceStart() bool { if x != nil { return x.ForceStart } return false } // 开始游戏响应 type StartGameResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` GameSessionId string `protobuf:"bytes,2,opt,name=game_session_id,json=gameSessionId,proto3" json:"game_session_id,omitempty"` StartTime int64 `protobuf:"varint,3,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *StartGameResponse) Reset() { *x = StartGameResponse{} mi := &file_proto_room_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *StartGameResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*StartGameResponse) ProtoMessage() {} func (x *StartGameResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StartGameResponse.ProtoReflect.Descriptor instead. func (*StartGameResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{17} } func (x *StartGameResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *StartGameResponse) GetGameSessionId() string { if x != nil { return x.GameSessionId } return "" } func (x *StartGameResponse) GetStartTime() int64 { if x != nil { return x.StartTime } return 0 } // 设置准备状态请求 type SetReadyRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` IsReady bool `protobuf:"varint,3,opt,name=is_ready,json=isReady,proto3" json:"is_ready,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetReadyRequest) Reset() { *x = SetReadyRequest{} mi := &file_proto_room_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetReadyRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetReadyRequest) ProtoMessage() {} func (x *SetReadyRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetReadyRequest.ProtoReflect.Descriptor instead. func (*SetReadyRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{18} } func (x *SetReadyRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *SetReadyRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *SetReadyRequest) GetIsReady() bool { if x != nil { return x.IsReady } return false } // 设置准备状态响应 type SetReadyResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` IsReady bool `protobuf:"varint,2,opt,name=is_ready,json=isReady,proto3" json:"is_ready,omitempty"` AllPlayersReady bool `protobuf:"varint,3,opt,name=all_players_ready,json=allPlayersReady,proto3" json:"all_players_ready,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetReadyResponse) Reset() { *x = SetReadyResponse{} mi := &file_proto_room_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetReadyResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetReadyResponse) ProtoMessage() {} func (x *SetReadyResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetReadyResponse.ProtoReflect.Descriptor instead. func (*SetReadyResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{19} } func (x *SetReadyResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SetReadyResponse) GetIsReady() bool { if x != nil { return x.IsReady } return false } func (x *SetReadyResponse) GetAllPlayersReady() bool { if x != nil { return x.AllPlayersReady } return false } // 邀请玩家请求 type InvitePlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` InviterId string `protobuf:"bytes,1,opt,name=inviter_id,json=inviterId,proto3" json:"inviter_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` TargetPlayerId string `protobuf:"bytes,3,opt,name=target_player_id,json=targetPlayerId,proto3" json:"target_player_id,omitempty"` Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InvitePlayerRequest) Reset() { *x = InvitePlayerRequest{} mi := &file_proto_room_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InvitePlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*InvitePlayerRequest) ProtoMessage() {} func (x *InvitePlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InvitePlayerRequest.ProtoReflect.Descriptor instead. func (*InvitePlayerRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{20} } func (x *InvitePlayerRequest) GetInviterId() string { if x != nil { return x.InviterId } return "" } func (x *InvitePlayerRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *InvitePlayerRequest) GetTargetPlayerId() string { if x != nil { return x.TargetPlayerId } return "" } func (x *InvitePlayerRequest) GetMessage() string { if x != nil { return x.Message } return "" } // 邀请玩家响应 type InvitePlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` InvitationId string `protobuf:"bytes,2,opt,name=invitation_id,json=invitationId,proto3" json:"invitation_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InvitePlayerResponse) Reset() { *x = InvitePlayerResponse{} mi := &file_proto_room_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InvitePlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*InvitePlayerResponse) ProtoMessage() {} func (x *InvitePlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InvitePlayerResponse.ProtoReflect.Descriptor instead. func (*InvitePlayerResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{21} } func (x *InvitePlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *InvitePlayerResponse) GetInvitationId() string { if x != nil { return x.InvitationId } return "" } // 搜索房间请求 type SearchRoomsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Keyword string `protobuf:"bytes,1,opt,name=keyword,proto3" json:"keyword,omitempty"` OwnerName string `protobuf:"bytes,2,opt,name=owner_name,json=ownerName,proto3" json:"owner_name,omitempty"` RoomType RoomType `protobuf:"varint,3,opt,name=room_type,json=roomType,proto3,enum=greatestworks.room.RoomType" json:"room_type,omitempty"` GameMode string `protobuf:"bytes,4,opt,name=game_mode,json=gameMode,proto3" json:"game_mode,omitempty"` OnlyPublic bool `protobuf:"varint,5,opt,name=only_public,json=onlyPublic,proto3" json:"only_public,omitempty"` OnlyAvailable bool `protobuf:"varint,6,opt,name=only_available,json=onlyAvailable,proto3" json:"only_available,omitempty"` Limit int32 `protobuf:"varint,7,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,8,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SearchRoomsRequest) Reset() { *x = SearchRoomsRequest{} mi := &file_proto_room_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SearchRoomsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchRoomsRequest) ProtoMessage() {} func (x *SearchRoomsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchRoomsRequest.ProtoReflect.Descriptor instead. func (*SearchRoomsRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{22} } func (x *SearchRoomsRequest) GetKeyword() string { if x != nil { return x.Keyword } return "" } func (x *SearchRoomsRequest) GetOwnerName() string { if x != nil { return x.OwnerName } return "" } func (x *SearchRoomsRequest) GetRoomType() RoomType { if x != nil { return x.RoomType } return RoomType_ROOM_TYPE_UNSPECIFIED } func (x *SearchRoomsRequest) GetGameMode() string { if x != nil { return x.GameMode } return "" } func (x *SearchRoomsRequest) GetOnlyPublic() bool { if x != nil { return x.OnlyPublic } return false } func (x *SearchRoomsRequest) GetOnlyAvailable() bool { if x != nil { return x.OnlyAvailable } return false } func (x *SearchRoomsRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *SearchRoomsRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 搜索房间响应 type SearchRoomsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Rooms []*RoomInfo `protobuf:"bytes,2,rep,name=rooms,proto3" json:"rooms,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SearchRoomsResponse) Reset() { *x = SearchRoomsResponse{} mi := &file_proto_room_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SearchRoomsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchRoomsResponse) ProtoMessage() {} func (x *SearchRoomsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchRoomsResponse.ProtoReflect.Descriptor instead. func (*SearchRoomsResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{23} } func (x *SearchRoomsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SearchRoomsResponse) GetRooms() []*RoomInfo { if x != nil { return x.Rooms } return nil } func (x *SearchRoomsResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 发送房间消息请求 type SendRoomMessageRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SenderId string `protobuf:"bytes,1,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` MessageType RoomMessageType `protobuf:"varint,4,opt,name=message_type,json=messageType,proto3,enum=greatestworks.room.RoomMessageType" json:"message_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SendRoomMessageRequest) Reset() { *x = SendRoomMessageRequest{} mi := &file_proto_room_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SendRoomMessageRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendRoomMessageRequest) ProtoMessage() {} func (x *SendRoomMessageRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendRoomMessageRequest.ProtoReflect.Descriptor instead. func (*SendRoomMessageRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{24} } func (x *SendRoomMessageRequest) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *SendRoomMessageRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *SendRoomMessageRequest) GetContent() string { if x != nil { return x.Content } return "" } func (x *SendRoomMessageRequest) GetMessageType() RoomMessageType { if x != nil { return x.MessageType } return RoomMessageType_ROOM_MESSAGE_TYPE_UNSPECIFIED } // 发送房间消息响应 type SendRoomMessageResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` MessageId string `protobuf:"bytes,2,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SendRoomMessageResponse) Reset() { *x = SendRoomMessageResponse{} mi := &file_proto_room_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SendRoomMessageResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SendRoomMessageResponse) ProtoMessage() {} func (x *SendRoomMessageResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SendRoomMessageResponse.ProtoReflect.Descriptor instead. func (*SendRoomMessageResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{25} } func (x *SendRoomMessageResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SendRoomMessageResponse) GetMessageId() string { if x != nil { return x.MessageId } return "" } // 设置房间密码请求 type SetRoomPasswordRequest struct { state protoimpl.MessageState `protogen:"open.v1"` OwnerId string `protobuf:"bytes,1,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` NewPassword string `protobuf:"bytes,3,opt,name=new_password,json=newPassword,proto3" json:"new_password,omitempty"` // 空字符串表示移除密码 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetRoomPasswordRequest) Reset() { *x = SetRoomPasswordRequest{} mi := &file_proto_room_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetRoomPasswordRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetRoomPasswordRequest) ProtoMessage() {} func (x *SetRoomPasswordRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetRoomPasswordRequest.ProtoReflect.Descriptor instead. func (*SetRoomPasswordRequest) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{26} } func (x *SetRoomPasswordRequest) GetOwnerId() string { if x != nil { return x.OwnerId } return "" } func (x *SetRoomPasswordRequest) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *SetRoomPasswordRequest) GetNewPassword() string { if x != nil { return x.NewPassword } return "" } // 设置房间密码响应 type SetRoomPasswordResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` HasPassword bool `protobuf:"varint,2,opt,name=has_password,json=hasPassword,proto3" json:"has_password,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetRoomPasswordResponse) Reset() { *x = SetRoomPasswordResponse{} mi := &file_proto_room_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetRoomPasswordResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetRoomPasswordResponse) ProtoMessage() {} func (x *SetRoomPasswordResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetRoomPasswordResponse.ProtoReflect.Descriptor instead. func (*SetRoomPasswordResponse) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{27} } func (x *SetRoomPasswordResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SetRoomPasswordResponse) GetHasPassword() bool { if x != nil { return x.HasPassword } return false } // 房间信息 type RoomInfo struct { state protoimpl.MessageState `protogen:"open.v1"` RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` RoomName string `protobuf:"bytes,2,opt,name=room_name,json=roomName,proto3" json:"room_name,omitempty"` OwnerId string `protobuf:"bytes,3,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` OwnerName string `protobuf:"bytes,4,opt,name=owner_name,json=ownerName,proto3" json:"owner_name,omitempty"` RoomType RoomType `protobuf:"varint,5,opt,name=room_type,json=roomType,proto3,enum=greatestworks.room.RoomType" json:"room_type,omitempty"` GameMode string `protobuf:"bytes,6,opt,name=game_mode,json=gameMode,proto3" json:"game_mode,omitempty"` MapId string `protobuf:"bytes,7,opt,name=map_id,json=mapId,proto3" json:"map_id,omitempty"` MapName string `protobuf:"bytes,8,opt,name=map_name,json=mapName,proto3" json:"map_name,omitempty"` Status RoomStatus `protobuf:"varint,9,opt,name=status,proto3,enum=greatestworks.room.RoomStatus" json:"status,omitempty"` CurrentPlayers int32 `protobuf:"varint,10,opt,name=current_players,json=currentPlayers,proto3" json:"current_players,omitempty"` MaxPlayers int32 `protobuf:"varint,11,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` IsPrivate bool `protobuf:"varint,12,opt,name=is_private,json=isPrivate,proto3" json:"is_private,omitempty"` HasPassword bool `protobuf:"varint,13,opt,name=has_password,json=hasPassword,proto3" json:"has_password,omitempty"` Ping int32 `protobuf:"varint,14,opt,name=ping,proto3" json:"ping,omitempty"` Region string `protobuf:"bytes,15,opt,name=region,proto3" json:"region,omitempty"` CreatedAt int64 `protobuf:"varint,16,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` Settings *RoomSettings `protobuf:"bytes,17,opt,name=settings,proto3" json:"settings,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomInfo) Reset() { *x = RoomInfo{} mi := &file_proto_room_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomInfo) ProtoMessage() {} func (x *RoomInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomInfo.ProtoReflect.Descriptor instead. func (*RoomInfo) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{28} } func (x *RoomInfo) GetRoomId() string { if x != nil { return x.RoomId } return "" } func (x *RoomInfo) GetRoomName() string { if x != nil { return x.RoomName } return "" } func (x *RoomInfo) GetOwnerId() string { if x != nil { return x.OwnerId } return "" } func (x *RoomInfo) GetOwnerName() string { if x != nil { return x.OwnerName } return "" } func (x *RoomInfo) GetRoomType() RoomType { if x != nil { return x.RoomType } return RoomType_ROOM_TYPE_UNSPECIFIED } func (x *RoomInfo) GetGameMode() string { if x != nil { return x.GameMode } return "" } func (x *RoomInfo) GetMapId() string { if x != nil { return x.MapId } return "" } func (x *RoomInfo) GetMapName() string { if x != nil { return x.MapName } return "" } func (x *RoomInfo) GetStatus() RoomStatus { if x != nil { return x.Status } return RoomStatus_ROOM_STATUS_UNSPECIFIED } func (x *RoomInfo) GetCurrentPlayers() int32 { if x != nil { return x.CurrentPlayers } return 0 } func (x *RoomInfo) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *RoomInfo) GetIsPrivate() bool { if x != nil { return x.IsPrivate } return false } func (x *RoomInfo) GetHasPassword() bool { if x != nil { return x.HasPassword } return false } func (x *RoomInfo) GetPing() int32 { if x != nil { return x.Ping } return 0 } func (x *RoomInfo) GetRegion() string { if x != nil { return x.Region } return "" } func (x *RoomInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *RoomInfo) GetSettings() *RoomSettings { if x != nil { return x.Settings } return nil } // 房间详情 type RoomDetail struct { state protoimpl.MessageState `protogen:"open.v1"` BasicInfo *RoomInfo `protobuf:"bytes,1,opt,name=basic_info,json=basicInfo,proto3" json:"basic_info,omitempty"` Players []*RoomPlayer `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty"` Teams []*RoomTeam `protobuf:"bytes,3,rep,name=teams,proto3" json:"teams,omitempty"` RecentMessages []*RoomMessage `protobuf:"bytes,4,rep,name=recent_messages,json=recentMessages,proto3" json:"recent_messages,omitempty"` CustomRules map[string]string `protobuf:"bytes,5,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Stats *RoomStats `protobuf:"bytes,6,opt,name=stats,proto3" json:"stats,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomDetail) Reset() { *x = RoomDetail{} mi := &file_proto_room_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomDetail) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomDetail) ProtoMessage() {} func (x *RoomDetail) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomDetail.ProtoReflect.Descriptor instead. func (*RoomDetail) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{29} } func (x *RoomDetail) GetBasicInfo() *RoomInfo { if x != nil { return x.BasicInfo } return nil } func (x *RoomDetail) GetPlayers() []*RoomPlayer { if x != nil { return x.Players } return nil } func (x *RoomDetail) GetTeams() []*RoomTeam { if x != nil { return x.Teams } return nil } func (x *RoomDetail) GetRecentMessages() []*RoomMessage { if x != nil { return x.RecentMessages } return nil } func (x *RoomDetail) GetCustomRules() map[string]string { if x != nil { return x.CustomRules } return nil } func (x *RoomDetail) GetStats() *RoomStats { if x != nil { return x.Stats } return nil } // 房间玩家 type RoomPlayer struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` PlayerName string `protobuf:"bytes,2,opt,name=player_name,json=playerName,proto3" json:"player_name,omitempty"` Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` Rank int32 `protobuf:"varint,4,opt,name=rank,proto3" json:"rank,omitempty"` Role PlayerRole `protobuf:"varint,5,opt,name=role,proto3,enum=greatestworks.room.PlayerRole" json:"role,omitempty"` TeamId int32 `protobuf:"varint,6,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` IsReady bool `protobuf:"varint,7,opt,name=is_ready,json=isReady,proto3" json:"is_ready,omitempty"` IsOnline bool `protobuf:"varint,8,opt,name=is_online,json=isOnline,proto3" json:"is_online,omitempty"` JoinedAt int64 `protobuf:"varint,9,opt,name=joined_at,json=joinedAt,proto3" json:"joined_at,omitempty"` Stats *PlayerStats `protobuf:"bytes,10,opt,name=stats,proto3" json:"stats,omitempty"` Position *common.Position `protobuf:"bytes,11,opt,name=position,proto3" json:"position,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomPlayer) Reset() { *x = RoomPlayer{} mi := &file_proto_room_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomPlayer) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomPlayer) ProtoMessage() {} func (x *RoomPlayer) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomPlayer.ProtoReflect.Descriptor instead. func (*RoomPlayer) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{30} } func (x *RoomPlayer) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *RoomPlayer) GetPlayerName() string { if x != nil { return x.PlayerName } return "" } func (x *RoomPlayer) GetLevel() int32 { if x != nil { return x.Level } return 0 } func (x *RoomPlayer) GetRank() int32 { if x != nil { return x.Rank } return 0 } func (x *RoomPlayer) GetRole() PlayerRole { if x != nil { return x.Role } return PlayerRole_PLAYER_ROLE_UNSPECIFIED } func (x *RoomPlayer) GetTeamId() int32 { if x != nil { return x.TeamId } return 0 } func (x *RoomPlayer) GetIsReady() bool { if x != nil { return x.IsReady } return false } func (x *RoomPlayer) GetIsOnline() bool { if x != nil { return x.IsOnline } return false } func (x *RoomPlayer) GetJoinedAt() int64 { if x != nil { return x.JoinedAt } return 0 } func (x *RoomPlayer) GetStats() *PlayerStats { if x != nil { return x.Stats } return nil } func (x *RoomPlayer) GetPosition() *common.Position { if x != nil { return x.Position } return nil } // 房间队伍 type RoomTeam struct { state protoimpl.MessageState `protogen:"open.v1"` TeamId int32 `protobuf:"varint,1,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` TeamName string `protobuf:"bytes,2,opt,name=team_name,json=teamName,proto3" json:"team_name,omitempty"` TeamColor string `protobuf:"bytes,3,opt,name=team_color,json=teamColor,proto3" json:"team_color,omitempty"` CurrentMembers int32 `protobuf:"varint,4,opt,name=current_members,json=currentMembers,proto3" json:"current_members,omitempty"` MaxMembers int32 `protobuf:"varint,5,opt,name=max_members,json=maxMembers,proto3" json:"max_members,omitempty"` PlayerIds []string `protobuf:"bytes,6,rep,name=player_ids,json=playerIds,proto3" json:"player_ids,omitempty"` IsReady bool `protobuf:"varint,7,opt,name=is_ready,json=isReady,proto3" json:"is_ready,omitempty"` TeamStats *TeamStats `protobuf:"bytes,8,opt,name=team_stats,json=teamStats,proto3" json:"team_stats,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomTeam) Reset() { *x = RoomTeam{} mi := &file_proto_room_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomTeam) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomTeam) ProtoMessage() {} func (x *RoomTeam) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomTeam.ProtoReflect.Descriptor instead. func (*RoomTeam) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{31} } func (x *RoomTeam) GetTeamId() int32 { if x != nil { return x.TeamId } return 0 } func (x *RoomTeam) GetTeamName() string { if x != nil { return x.TeamName } return "" } func (x *RoomTeam) GetTeamColor() string { if x != nil { return x.TeamColor } return "" } func (x *RoomTeam) GetCurrentMembers() int32 { if x != nil { return x.CurrentMembers } return 0 } func (x *RoomTeam) GetMaxMembers() int32 { if x != nil { return x.MaxMembers } return 0 } func (x *RoomTeam) GetPlayerIds() []string { if x != nil { return x.PlayerIds } return nil } func (x *RoomTeam) GetIsReady() bool { if x != nil { return x.IsReady } return false } func (x *RoomTeam) GetTeamStats() *TeamStats { if x != nil { return x.TeamStats } return nil } // 房间消息 type RoomMessage struct { state protoimpl.MessageState `protogen:"open.v1"` MessageId string `protobuf:"bytes,1,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` SenderId string `protobuf:"bytes,2,opt,name=sender_id,json=senderId,proto3" json:"sender_id,omitempty"` SenderName string `protobuf:"bytes,3,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` MessageType RoomMessageType `protobuf:"varint,5,opt,name=message_type,json=messageType,proto3,enum=greatestworks.room.RoomMessageType" json:"message_type,omitempty"` Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomMessage) Reset() { *x = RoomMessage{} mi := &file_proto_room_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomMessage) ProtoMessage() {} func (x *RoomMessage) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomMessage.ProtoReflect.Descriptor instead. func (*RoomMessage) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{32} } func (x *RoomMessage) GetMessageId() string { if x != nil { return x.MessageId } return "" } func (x *RoomMessage) GetSenderId() string { if x != nil { return x.SenderId } return "" } func (x *RoomMessage) GetSenderName() string { if x != nil { return x.SenderName } return "" } func (x *RoomMessage) GetContent() string { if x != nil { return x.Content } return "" } func (x *RoomMessage) GetMessageType() RoomMessageType { if x != nil { return x.MessageType } return RoomMessageType_ROOM_MESSAGE_TYPE_UNSPECIFIED } func (x *RoomMessage) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } // 房间设置 type RoomSettings struct { state protoimpl.MessageState `protogen:"open.v1"` GameDuration int32 `protobuf:"varint,1,opt,name=game_duration,json=gameDuration,proto3" json:"game_duration,omitempty"` // 游戏时长(分钟) PreparationTime int32 `protobuf:"varint,2,opt,name=preparation_time,json=preparationTime,proto3" json:"preparation_time,omitempty"` // 准备时间(秒) FriendlyFire bool `protobuf:"varint,3,opt,name=friendly_fire,json=friendlyFire,proto3" json:"friendly_fire,omitempty"` // 友军伤害 SpectatorsAllowed bool `protobuf:"varint,4,opt,name=spectators_allowed,json=spectatorsAllowed,proto3" json:"spectators_allowed,omitempty"` // 允许观战 MaxSpectators int32 `protobuf:"varint,5,opt,name=max_spectators,json=maxSpectators,proto3" json:"max_spectators,omitempty"` // 最大观战人数 AutoStart bool `protobuf:"varint,6,opt,name=auto_start,json=autoStart,proto3" json:"auto_start,omitempty"` // 自动开始 AutoStartCountdown int32 `protobuf:"varint,7,opt,name=auto_start_countdown,json=autoStartCountdown,proto3" json:"auto_start_countdown,omitempty"` // 自动开始倒计时 TeamBalance bool `protobuf:"varint,8,opt,name=team_balance,json=teamBalance,proto3" json:"team_balance,omitempty"` // 队伍平衡 Difficulty DifficultyLevel `protobuf:"varint,9,opt,name=difficulty,proto3,enum=greatestworks.room.DifficultyLevel" json:"difficulty,omitempty"` // 难度等级 ScoreLimits map[string]int32 `protobuf:"bytes,10,rep,name=score_limits,json=scoreLimits,proto3" json:"score_limits,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // 分数限制 BannedItems []string `protobuf:"bytes,11,rep,name=banned_items,json=bannedItems,proto3" json:"banned_items,omitempty"` // 禁用物品 BannedSkills []string `protobuf:"bytes,12,rep,name=banned_skills,json=bannedSkills,proto3" json:"banned_skills,omitempty"` // 禁用技能 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomSettings) Reset() { *x = RoomSettings{} mi := &file_proto_room_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomSettings) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomSettings) ProtoMessage() {} func (x *RoomSettings) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomSettings.ProtoReflect.Descriptor instead. func (*RoomSettings) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{33} } func (x *RoomSettings) GetGameDuration() int32 { if x != nil { return x.GameDuration } return 0 } func (x *RoomSettings) GetPreparationTime() int32 { if x != nil { return x.PreparationTime } return 0 } func (x *RoomSettings) GetFriendlyFire() bool { if x != nil { return x.FriendlyFire } return false } func (x *RoomSettings) GetSpectatorsAllowed() bool { if x != nil { return x.SpectatorsAllowed } return false } func (x *RoomSettings) GetMaxSpectators() int32 { if x != nil { return x.MaxSpectators } return 0 } func (x *RoomSettings) GetAutoStart() bool { if x != nil { return x.AutoStart } return false } func (x *RoomSettings) GetAutoStartCountdown() int32 { if x != nil { return x.AutoStartCountdown } return 0 } func (x *RoomSettings) GetTeamBalance() bool { if x != nil { return x.TeamBalance } return false } func (x *RoomSettings) GetDifficulty() DifficultyLevel { if x != nil { return x.Difficulty } return DifficultyLevel_DIFFICULTY_LEVEL_UNSPECIFIED } func (x *RoomSettings) GetScoreLimits() map[string]int32 { if x != nil { return x.ScoreLimits } return nil } func (x *RoomSettings) GetBannedItems() []string { if x != nil { return x.BannedItems } return nil } func (x *RoomSettings) GetBannedSkills() []string { if x != nil { return x.BannedSkills } return nil } // 玩家统计 type PlayerStats struct { state protoimpl.MessageState `protogen:"open.v1"` Kills int32 `protobuf:"varint,1,opt,name=kills,proto3" json:"kills,omitempty"` Deaths int32 `protobuf:"varint,2,opt,name=deaths,proto3" json:"deaths,omitempty"` Assists int32 `protobuf:"varint,3,opt,name=assists,proto3" json:"assists,omitempty"` KdRatio float32 `protobuf:"fixed32,4,opt,name=kd_ratio,json=kdRatio,proto3" json:"kd_ratio,omitempty"` Score int32 `protobuf:"varint,5,opt,name=score,proto3" json:"score,omitempty"` GamesPlayed int32 `protobuf:"varint,6,opt,name=games_played,json=gamesPlayed,proto3" json:"games_played,omitempty"` Wins int32 `protobuf:"varint,7,opt,name=wins,proto3" json:"wins,omitempty"` WinRate float32 `protobuf:"fixed32,8,opt,name=win_rate,json=winRate,proto3" json:"win_rate,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PlayerStats) Reset() { *x = PlayerStats{} mi := &file_proto_room_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *PlayerStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*PlayerStats) ProtoMessage() {} func (x *PlayerStats) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PlayerStats.ProtoReflect.Descriptor instead. func (*PlayerStats) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{34} } func (x *PlayerStats) GetKills() int32 { if x != nil { return x.Kills } return 0 } func (x *PlayerStats) GetDeaths() int32 { if x != nil { return x.Deaths } return 0 } func (x *PlayerStats) GetAssists() int32 { if x != nil { return x.Assists } return 0 } func (x *PlayerStats) GetKdRatio() float32 { if x != nil { return x.KdRatio } return 0 } func (x *PlayerStats) GetScore() int32 { if x != nil { return x.Score } return 0 } func (x *PlayerStats) GetGamesPlayed() int32 { if x != nil { return x.GamesPlayed } return 0 } func (x *PlayerStats) GetWins() int32 { if x != nil { return x.Wins } return 0 } func (x *PlayerStats) GetWinRate() float32 { if x != nil { return x.WinRate } return 0 } // 队伍统计 type TeamStats struct { state protoimpl.MessageState `protogen:"open.v1"` TotalScore int32 `protobuf:"varint,1,opt,name=total_score,json=totalScore,proto3" json:"total_score,omitempty"` TotalKills int32 `protobuf:"varint,2,opt,name=total_kills,json=totalKills,proto3" json:"total_kills,omitempty"` TotalDeaths int32 `protobuf:"varint,3,opt,name=total_deaths,json=totalDeaths,proto3" json:"total_deaths,omitempty"` AverageKd float32 `protobuf:"fixed32,4,opt,name=average_kd,json=averageKd,proto3" json:"average_kd,omitempty"` ObjectivesCompleted int32 `protobuf:"varint,5,opt,name=objectives_completed,json=objectivesCompleted,proto3" json:"objectives_completed,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamStats) Reset() { *x = TeamStats{} mi := &file_proto_room_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamStats) ProtoMessage() {} func (x *TeamStats) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamStats.ProtoReflect.Descriptor instead. func (*TeamStats) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{35} } func (x *TeamStats) GetTotalScore() int32 { if x != nil { return x.TotalScore } return 0 } func (x *TeamStats) GetTotalKills() int32 { if x != nil { return x.TotalKills } return 0 } func (x *TeamStats) GetTotalDeaths() int32 { if x != nil { return x.TotalDeaths } return 0 } func (x *TeamStats) GetAverageKd() float32 { if x != nil { return x.AverageKd } return 0 } func (x *TeamStats) GetObjectivesCompleted() int32 { if x != nil { return x.ObjectivesCompleted } return 0 } // 房间统计 type RoomStats struct { state protoimpl.MessageState `protogen:"open.v1"` TotalGamesPlayed int32 `protobuf:"varint,1,opt,name=total_games_played,json=totalGamesPlayed,proto3" json:"total_games_played,omitempty"` TotalPlaytime int64 `protobuf:"varint,2,opt,name=total_playtime,json=totalPlaytime,proto3" json:"total_playtime,omitempty"` AveragePlayers int32 `protobuf:"varint,3,opt,name=average_players,json=averagePlayers,proto3" json:"average_players,omitempty"` AverageGameDuration float32 `protobuf:"fixed32,4,opt,name=average_game_duration,json=averageGameDuration,proto3" json:"average_game_duration,omitempty"` LastActive int64 `protobuf:"varint,5,opt,name=last_active,json=lastActive,proto3" json:"last_active,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomStats) Reset() { *x = RoomStats{} mi := &file_proto_room_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RoomStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoomStats) ProtoMessage() {} func (x *RoomStats) ProtoReflect() protoreflect.Message { mi := &file_proto_room_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoomStats.ProtoReflect.Descriptor instead. func (*RoomStats) Descriptor() ([]byte, []int) { return file_proto_room_proto_rawDescGZIP(), []int{36} } func (x *RoomStats) GetTotalGamesPlayed() int32 { if x != nil { return x.TotalGamesPlayed } return 0 } func (x *RoomStats) GetTotalPlaytime() int64 { if x != nil { return x.TotalPlaytime } return 0 } func (x *RoomStats) GetAveragePlayers() int32 { if x != nil { return x.AveragePlayers } return 0 } func (x *RoomStats) GetAverageGameDuration() float32 { if x != nil { return x.AverageGameDuration } return 0 } func (x *RoomStats) GetLastActive() int64 { if x != nil { return x.LastActive } return 0 } var File_proto_room_proto protoreflect.FileDescriptor const file_proto_room_proto_rawDesc = "" + "\n" + "\x10proto/room.proto\x12\x12greatestworks.room\x1a\x12proto/common.proto\"\xef\x03\n" + "\x11CreateRoomRequest\x12\x19\n" + "\bowner_id\x18\x01 \x01(\tR\aownerId\x12\x1b\n" + "\troom_name\x18\x02 \x01(\tR\broomName\x129\n" + "\troom_type\x18\x03 \x01(\x0e2\x1c.greatestworks.room.RoomTypeR\broomType\x12\x1b\n" + "\tgame_mode\x18\x04 \x01(\tR\bgameMode\x12\x15\n" + "\x06map_id\x18\x05 \x01(\tR\x05mapId\x12\x1f\n" + "\vmax_players\x18\x06 \x01(\x05R\n" + "maxPlayers\x12\x1d\n" + "\n" + "is_private\x18\a \x01(\bR\tisPrivate\x12\x1a\n" + "\bpassword\x18\b \x01(\tR\bpassword\x12<\n" + "\bsettings\x18\t \x01(\v2 .greatestworks.room.RoomSettingsR\bsettings\x12Y\n" + "\fcustom_rules\x18\n" + " \x03(\v26.greatestworks.room.CreateRoomRequest.CustomRulesEntryR\vcustomRules\x1a>\n" + "\x10CustomRulesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa6\x01\n" + "\x12CreateRoomResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x129\n" + "\troom_info\x18\x03 \x01(\v2\x1c.greatestworks.room.RoomInfoR\broomInfo\"\xb3\x01\n" + "\x0fJoinRoomRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12\x1a\n" + "\bpassword\x18\x03 \x01(\tR\bpassword\x12'\n" + "\x0finvitation_code\x18\x04 \x01(\tR\x0einvitationCode\x12%\n" + "\x0epreferred_team\x18\x05 \x01(\x05R\rpreferredTeam\"\x91\x02\n" + "\x10JoinRoomResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x129\n" + "\troom_info\x18\x02 \x01(\v2\x1c.greatestworks.room.RoomInfoR\broomInfo\x12?\n" + "\vplayer_info\x18\x03 \x01(\v2\x1e.greatestworks.room.RoomPlayerR\n" + "playerInfo\x12C\n" + "\rother_players\x18\x04 \x03(\v2\x1e.greatestworks.room.RoomPlayerR\fotherPlayers\"H\n" + "\x10LeaveRoomRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\"Q\n" + "\x11LeaveRoomResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x9b\x02\n" + "\x12GetRoomListRequest\x129\n" + "\troom_type\x18\x01 \x01(\x0e2\x1c.greatestworks.room.RoomTypeR\broomType\x12\x1b\n" + "\tgame_mode\x18\x02 \x01(\tR\bgameMode\x12\x1f\n" + "\vonly_public\x18\x03 \x01(\bR\n" + "onlyPublic\x12%\n" + "\x0eonly_available\x18\x04 \x01(\bR\ronlyAvailable\x12\x14\n" + "\x05limit\x18\x05 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x06 \x01(\x05R\x06offset\x127\n" + "\asort_by\x18\a \x01(\x0e2\x1e.greatestworks.room.RoomSortByR\x06sortBy\"\xcd\x01\n" + "\x13GetRoomListResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x05rooms\x18\x02 \x03(\v2\x1c.greatestworks.room.RoomInfoR\x05rooms\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"J\n" + "\x12GetRoomInfoRequest\x12\x17\n" + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\"\x94\x01\n" + "\x13GetRoomInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12?\n" + "\vroom_detail\x18\x02 \x01(\v2\x1e.greatestworks.room.RoomDetailR\n" + "roomDetail\"\xb2\x02\n" + "\x19UpdateRoomSettingsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12<\n" + "\bsettings\x18\x03 \x01(\v2 .greatestworks.room.RoomSettingsR\bsettings\x12a\n" + "\fcustom_rules\x18\x04 \x03(\v2>.greatestworks.room.UpdateRoomSettingsRequest.CustomRulesEntryR\vcustomRules\x1a>\n" + "\x10CustomRulesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9f\x01\n" + "\x1aUpdateRoomSettingsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12C\n" + "\fnew_settings\x18\x02 \x01(\v2 .greatestworks.room.RoomSettingsR\vnewSettings\"\x8b\x01\n" + "\x11KickPlayerRequest\x12\x1b\n" + "\tkicker_id\x18\x01 \x01(\tR\bkickerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12(\n" + "\x10target_player_id\x18\x03 \x01(\tR\x0etargetPlayerId\x12\x16\n" + "\x06reason\x18\x04 \x01(\tR\x06reason\"R\n" + "\x12KickPlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x7f\n" + "\x18TransferOwnershipRequest\x12(\n" + "\x10current_owner_id\x18\x01 \x01(\tR\x0ecurrentOwnerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12 \n" + "\fnew_owner_id\x18\x03 \x01(\tR\n" + "newOwnerId\"\x96\x01\n" + "\x19TransferOwnershipResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12;\n" + "\tnew_owner\x18\x02 \x01(\v2\x1e.greatestworks.room.RoomPlayerR\bnewOwner\"g\n" + "\x10StartGameRequest\x12\x19\n" + "\bowner_id\x18\x01 \x01(\tR\aownerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12\x1f\n" + "\vforce_start\x18\x03 \x01(\bR\n" + "forceStart\"\x98\x01\n" + "\x11StartGameResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12&\n" + "\x0fgame_session_id\x18\x02 \x01(\tR\rgameSessionId\x12\x1d\n" + "\n" + "start_time\x18\x03 \x01(\x03R\tstartTime\"b\n" + "\x0fSetReadyRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12\x19\n" + "\bis_ready\x18\x03 \x01(\bR\aisReady\"\x97\x01\n" + "\x10SetReadyResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x19\n" + "\bis_ready\x18\x02 \x01(\bR\aisReady\x12*\n" + "\x11all_players_ready\x18\x03 \x01(\bR\x0fallPlayersReady\"\x91\x01\n" + "\x13InvitePlayerRequest\x12\x1d\n" + "\n" + "inviter_id\x18\x01 \x01(\tR\tinviterId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12(\n" + "\x10target_player_id\x18\x03 \x01(\tR\x0etargetPlayerId\x12\x18\n" + "\amessage\x18\x04 \x01(\tR\amessage\"y\n" + "\x14InvitePlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12#\n" + "\rinvitation_id\x18\x02 \x01(\tR\finvitationId\"\x9b\x02\n" + "\x12SearchRoomsRequest\x12\x18\n" + "\akeyword\x18\x01 \x01(\tR\akeyword\x12\x1d\n" + "\n" + "owner_name\x18\x02 \x01(\tR\townerName\x129\n" + "\troom_type\x18\x03 \x01(\x0e2\x1c.greatestworks.room.RoomTypeR\broomType\x12\x1b\n" + "\tgame_mode\x18\x04 \x01(\tR\bgameMode\x12\x1f\n" + "\vonly_public\x18\x05 \x01(\bR\n" + "onlyPublic\x12%\n" + "\x0eonly_available\x18\x06 \x01(\bR\ronlyAvailable\x12\x14\n" + "\x05limit\x18\a \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\b \x01(\x05R\x06offset\"\xcd\x01\n" + "\x13SearchRoomsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x05rooms\x18\x02 \x03(\v2\x1c.greatestworks.room.RoomInfoR\x05rooms\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\xb0\x01\n" + "\x16SendRoomMessageRequest\x12\x1b\n" + "\tsender_id\x18\x01 \x01(\tR\bsenderId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12\x18\n" + "\acontent\x18\x03 \x01(\tR\acontent\x12F\n" + "\fmessage_type\x18\x04 \x01(\x0e2#.greatestworks.room.RoomMessageTypeR\vmessageType\"v\n" + "\x17SendRoomMessageResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x1d\n" + "\n" + "message_id\x18\x02 \x01(\tR\tmessageId\"o\n" + "\x16SetRoomPasswordRequest\x12\x19\n" + "\bowner_id\x18\x01 \x01(\tR\aownerId\x12\x17\n" + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12!\n" + "\fnew_password\x18\x03 \x01(\tR\vnewPassword\"z\n" + "\x17SetRoomPasswordResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12!\n" + "\fhas_password\x18\x02 \x01(\bR\vhasPassword\"\xd1\x04\n" + "\bRoomInfo\x12\x17\n" + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12\x1b\n" + "\troom_name\x18\x02 \x01(\tR\broomName\x12\x19\n" + "\bowner_id\x18\x03 \x01(\tR\aownerId\x12\x1d\n" + "\n" + "owner_name\x18\x04 \x01(\tR\townerName\x129\n" + "\troom_type\x18\x05 \x01(\x0e2\x1c.greatestworks.room.RoomTypeR\broomType\x12\x1b\n" + "\tgame_mode\x18\x06 \x01(\tR\bgameMode\x12\x15\n" + "\x06map_id\x18\a \x01(\tR\x05mapId\x12\x19\n" + "\bmap_name\x18\b \x01(\tR\amapName\x126\n" + "\x06status\x18\t \x01(\x0e2\x1e.greatestworks.room.RoomStatusR\x06status\x12'\n" + "\x0fcurrent_players\x18\n" + " \x01(\x05R\x0ecurrentPlayers\x12\x1f\n" + "\vmax_players\x18\v \x01(\x05R\n" + "maxPlayers\x12\x1d\n" + "\n" + "is_private\x18\f \x01(\bR\tisPrivate\x12!\n" + "\fhas_password\x18\r \x01(\bR\vhasPassword\x12\x12\n" + "\x04ping\x18\x0e \x01(\x05R\x04ping\x12\x16\n" + "\x06region\x18\x0f \x01(\tR\x06region\x12\x1d\n" + "\n" + "created_at\x18\x10 \x01(\x03R\tcreatedAt\x12<\n" + "\bsettings\x18\x11 \x01(\v2 .greatestworks.room.RoomSettingsR\bsettings\"\xca\x03\n" + "\n" + "RoomDetail\x12;\n" + "\n" + "basic_info\x18\x01 \x01(\v2\x1c.greatestworks.room.RoomInfoR\tbasicInfo\x128\n" + "\aplayers\x18\x02 \x03(\v2\x1e.greatestworks.room.RoomPlayerR\aplayers\x122\n" + "\x05teams\x18\x03 \x03(\v2\x1c.greatestworks.room.RoomTeamR\x05teams\x12H\n" + "\x0frecent_messages\x18\x04 \x03(\v2\x1f.greatestworks.room.RoomMessageR\x0erecentMessages\x12R\n" + "\fcustom_rules\x18\x05 \x03(\v2/.greatestworks.room.RoomDetail.CustomRulesEntryR\vcustomRules\x123\n" + "\x05stats\x18\x06 \x01(\v2\x1d.greatestworks.room.RoomStatsR\x05stats\x1a>\n" + "\x10CustomRulesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x89\x03\n" + "\n" + "RoomPlayer\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x1f\n" + "\vplayer_name\x18\x02 \x01(\tR\n" + "playerName\x12\x14\n" + "\x05level\x18\x03 \x01(\x05R\x05level\x12\x12\n" + "\x04rank\x18\x04 \x01(\x05R\x04rank\x122\n" + "\x04role\x18\x05 \x01(\x0e2\x1e.greatestworks.room.PlayerRoleR\x04role\x12\x17\n" + "\ateam_id\x18\x06 \x01(\x05R\x06teamId\x12\x19\n" + "\bis_ready\x18\a \x01(\bR\aisReady\x12\x1b\n" + "\tis_online\x18\b \x01(\bR\bisOnline\x12\x1b\n" + "\tjoined_at\x18\t \x01(\x03R\bjoinedAt\x125\n" + "\x05stats\x18\n" + " \x01(\v2\x1f.greatestworks.room.PlayerStatsR\x05stats\x12:\n" + "\bposition\x18\v \x01(\v2\x1e.greatestworks.common.PositionR\bposition\"\xa1\x02\n" + "\bRoomTeam\x12\x17\n" + "\ateam_id\x18\x01 \x01(\x05R\x06teamId\x12\x1b\n" + "\tteam_name\x18\x02 \x01(\tR\bteamName\x12\x1d\n" + "\n" + "team_color\x18\x03 \x01(\tR\tteamColor\x12'\n" + "\x0fcurrent_members\x18\x04 \x01(\x05R\x0ecurrentMembers\x12\x1f\n" + "\vmax_members\x18\x05 \x01(\x05R\n" + "maxMembers\x12\x1d\n" + "\n" + "player_ids\x18\x06 \x03(\tR\tplayerIds\x12\x19\n" + "\bis_ready\x18\a \x01(\bR\aisReady\x12<\n" + "\n" + "team_stats\x18\b \x01(\v2\x1d.greatestworks.room.TeamStatsR\tteamStats\"\xea\x01\n" + "\vRoomMessage\x12\x1d\n" + "\n" + "message_id\x18\x01 \x01(\tR\tmessageId\x12\x1b\n" + "\tsender_id\x18\x02 \x01(\tR\bsenderId\x12\x1f\n" + "\vsender_name\x18\x03 \x01(\tR\n" + "senderName\x12\x18\n" + "\acontent\x18\x04 \x01(\tR\acontent\x12F\n" + "\fmessage_type\x18\x05 \x01(\x0e2#.greatestworks.room.RoomMessageTypeR\vmessageType\x12\x1c\n" + "\ttimestamp\x18\x06 \x01(\x03R\ttimestamp\"\xf0\x04\n" + "\fRoomSettings\x12#\n" + "\rgame_duration\x18\x01 \x01(\x05R\fgameDuration\x12)\n" + "\x10preparation_time\x18\x02 \x01(\x05R\x0fpreparationTime\x12#\n" + "\rfriendly_fire\x18\x03 \x01(\bR\ffriendlyFire\x12-\n" + "\x12spectators_allowed\x18\x04 \x01(\bR\x11spectatorsAllowed\x12%\n" + "\x0emax_spectators\x18\x05 \x01(\x05R\rmaxSpectators\x12\x1d\n" + "\n" + "auto_start\x18\x06 \x01(\bR\tautoStart\x120\n" + "\x14auto_start_countdown\x18\a \x01(\x05R\x12autoStartCountdown\x12!\n" + "\fteam_balance\x18\b \x01(\bR\vteamBalance\x12C\n" + "\n" + "difficulty\x18\t \x01(\x0e2#.greatestworks.room.DifficultyLevelR\n" + "difficulty\x12T\n" + "\fscore_limits\x18\n" + " \x03(\v21.greatestworks.room.RoomSettings.ScoreLimitsEntryR\vscoreLimits\x12!\n" + "\fbanned_items\x18\v \x03(\tR\vbannedItems\x12#\n" + "\rbanned_skills\x18\f \x03(\tR\fbannedSkills\x1a>\n" + "\x10ScoreLimitsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\x05R\x05value:\x028\x01\"\xd8\x01\n" + "\vPlayerStats\x12\x14\n" + "\x05kills\x18\x01 \x01(\x05R\x05kills\x12\x16\n" + "\x06deaths\x18\x02 \x01(\x05R\x06deaths\x12\x18\n" + "\aassists\x18\x03 \x01(\x05R\aassists\x12\x19\n" + "\bkd_ratio\x18\x04 \x01(\x02R\akdRatio\x12\x14\n" + "\x05score\x18\x05 \x01(\x05R\x05score\x12!\n" + "\fgames_played\x18\x06 \x01(\x05R\vgamesPlayed\x12\x12\n" + "\x04wins\x18\a \x01(\x05R\x04wins\x12\x19\n" + "\bwin_rate\x18\b \x01(\x02R\awinRate\"\xc2\x01\n" + "\tTeamStats\x12\x1f\n" + "\vtotal_score\x18\x01 \x01(\x05R\n" + "totalScore\x12\x1f\n" + "\vtotal_kills\x18\x02 \x01(\x05R\n" + "totalKills\x12!\n" + "\ftotal_deaths\x18\x03 \x01(\x05R\vtotalDeaths\x12\x1d\n" + "\n" + "average_kd\x18\x04 \x01(\x02R\taverageKd\x121\n" + "\x14objectives_completed\x18\x05 \x01(\x05R\x13objectivesCompleted\"\xde\x01\n" + "\tRoomStats\x12,\n" + "\x12total_games_played\x18\x01 \x01(\x05R\x10totalGamesPlayed\x12%\n" + "\x0etotal_playtime\x18\x02 \x01(\x03R\rtotalPlaytime\x12'\n" + "\x0faverage_players\x18\x03 \x01(\x05R\x0eaveragePlayers\x122\n" + "\x15average_game_duration\x18\x04 \x01(\x02R\x13averageGameDuration\x12\x1f\n" + "\vlast_active\x18\x05 \x01(\x03R\n" + "lastActive*\xce\x01\n" + "\bRoomType\x12\x19\n" + "\x15ROOM_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10ROOM_TYPE_CASUAL\x10\x01\x12\x14\n" + "\x10ROOM_TYPE_RANKED\x10\x02\x12\x14\n" + "\x10ROOM_TYPE_CUSTOM\x10\x03\x12\x18\n" + "\x14ROOM_TYPE_TOURNAMENT\x10\x04\x12\x16\n" + "\x12ROOM_TYPE_PRACTICE\x10\x05\x12\x16\n" + "\x12ROOM_TYPE_SPECTATE\x10\x06\x12\x1b\n" + "\x17ROOM_TYPE_PRIVATE_MATCH\x10\a*\xc3\x01\n" + "\n" + "RoomStatus\x12\x1b\n" + "\x17ROOM_STATUS_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13ROOM_STATUS_WAITING\x10\x01\x12\x19\n" + "\x15ROOM_STATUS_PREPARING\x10\x02\x12\x17\n" + "\x13ROOM_STATUS_IN_GAME\x10\x03\x12\x18\n" + "\x14ROOM_STATUS_FINISHED\x10\x04\x12\x19\n" + "\x15ROOM_STATUS_DISBANDED\x10\x05\x12\x16\n" + "\x12ROOM_STATUS_PAUSED\x10\x06*\x8e\x01\n" + "\n" + "PlayerRole\x12\x1b\n" + "\x17PLAYER_ROLE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11PLAYER_ROLE_OWNER\x10\x01\x12\x19\n" + "\x15PLAYER_ROLE_MODERATOR\x10\x02\x12\x16\n" + "\x12PLAYER_ROLE_PLAYER\x10\x03\x12\x19\n" + "\x15PLAYER_ROLE_SPECTATOR\x10\x04*\xfc\x01\n" + "\x0fRoomMessageType\x12!\n" + "\x1dROOM_MESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\x1a\n" + "\x16ROOM_MESSAGE_TYPE_CHAT\x10\x01\x12\x1c\n" + "\x18ROOM_MESSAGE_TYPE_SYSTEM\x10\x02\x12\x1a\n" + "\x16ROOM_MESSAGE_TYPE_JOIN\x10\x03\x12\x1b\n" + "\x17ROOM_MESSAGE_TYPE_LEAVE\x10\x04\x12\x1b\n" + "\x17ROOM_MESSAGE_TYPE_READY\x10\x05\x12\x1b\n" + "\x17ROOM_MESSAGE_TYPE_START\x10\x06\x12\x19\n" + "\x15ROOM_MESSAGE_TYPE_END\x10\a*\xae\x01\n" + "\n" + "RoomSortBy\x12\x1c\n" + "\x18ROOM_SORT_BY_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11ROOM_SORT_BY_NAME\x10\x01\x12\x18\n" + "\x14ROOM_SORT_BY_PLAYERS\x10\x02\x12\x1d\n" + "\x19ROOM_SORT_BY_CREATED_TIME\x10\x03\x12\x15\n" + "\x11ROOM_SORT_BY_PING\x10\x04\x12\x1b\n" + "\x17ROOM_SORT_BY_POPULARITY\x10\x05*\xc3\x01\n" + "\x0fDifficultyLevel\x12 \n" + "\x1cDIFFICULTY_LEVEL_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15DIFFICULTY_LEVEL_EASY\x10\x01\x12\x1b\n" + "\x17DIFFICULTY_LEVEL_NORMAL\x10\x02\x12\x19\n" + "\x15DIFFICULTY_LEVEL_HARD\x10\x03\x12\x1b\n" + "\x17DIFFICULTY_LEVEL_EXPERT\x10\x04\x12\x1e\n" + "\x1aDIFFICULTY_LEVEL_NIGHTMARE\x10\x052\xeb\n" + "\n" + "\vRoomService\x12[\n" + "\n" + "CreateRoom\x12%.greatestworks.room.CreateRoomRequest\x1a&.greatestworks.room.CreateRoomResponse\x12U\n" + "\bJoinRoom\x12#.greatestworks.room.JoinRoomRequest\x1a$.greatestworks.room.JoinRoomResponse\x12X\n" + "\tLeaveRoom\x12$.greatestworks.room.LeaveRoomRequest\x1a%.greatestworks.room.LeaveRoomResponse\x12^\n" + "\vGetRoomList\x12&.greatestworks.room.GetRoomListRequest\x1a'.greatestworks.room.GetRoomListResponse\x12^\n" + "\vGetRoomInfo\x12&.greatestworks.room.GetRoomInfoRequest\x1a'.greatestworks.room.GetRoomInfoResponse\x12s\n" + "\x12UpdateRoomSettings\x12-.greatestworks.room.UpdateRoomSettingsRequest\x1a..greatestworks.room.UpdateRoomSettingsResponse\x12[\n" + "\n" + "KickPlayer\x12%.greatestworks.room.KickPlayerRequest\x1a&.greatestworks.room.KickPlayerResponse\x12p\n" + "\x11TransferOwnership\x12,.greatestworks.room.TransferOwnershipRequest\x1a-.greatestworks.room.TransferOwnershipResponse\x12X\n" + "\tStartGame\x12$.greatestworks.room.StartGameRequest\x1a%.greatestworks.room.StartGameResponse\x12U\n" + "\bSetReady\x12#.greatestworks.room.SetReadyRequest\x1a$.greatestworks.room.SetReadyResponse\x12a\n" + "\fInvitePlayer\x12'.greatestworks.room.InvitePlayerRequest\x1a(.greatestworks.room.InvitePlayerResponse\x12^\n" + "\vSearchRooms\x12&.greatestworks.room.SearchRoomsRequest\x1a'.greatestworks.room.SearchRoomsResponse\x12j\n" + "\x0fSendRoomMessage\x12*.greatestworks.room.SendRoomMessageRequest\x1a+.greatestworks.room.SendRoomMessageResponse\x12j\n" + "\x0fSetRoomPassword\x12*.greatestworks.room.SetRoomPasswordRequest\x1a+.greatestworks.room.SetRoomPasswordResponseB8Z!greatestworks/internal/proto/room\xaa\x02\x12GreatestWorks.Roomb\x06proto3" var ( file_proto_room_proto_rawDescOnce sync.Once file_proto_room_proto_rawDescData []byte ) func file_proto_room_proto_rawDescGZIP() []byte { file_proto_room_proto_rawDescOnce.Do(func() { file_proto_room_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_room_proto_rawDesc), len(file_proto_room_proto_rawDesc))) }) return file_proto_room_proto_rawDescData } var file_proto_room_proto_enumTypes = make([]protoimpl.EnumInfo, 6) var file_proto_room_proto_msgTypes = make([]protoimpl.MessageInfo, 41) var file_proto_room_proto_goTypes = []any{ (RoomType)(0), // 0: greatestworks.room.RoomType (RoomStatus)(0), // 1: greatestworks.room.RoomStatus (PlayerRole)(0), // 2: greatestworks.room.PlayerRole (RoomMessageType)(0), // 3: greatestworks.room.RoomMessageType (RoomSortBy)(0), // 4: greatestworks.room.RoomSortBy (DifficultyLevel)(0), // 5: greatestworks.room.DifficultyLevel (*CreateRoomRequest)(nil), // 6: greatestworks.room.CreateRoomRequest (*CreateRoomResponse)(nil), // 7: greatestworks.room.CreateRoomResponse (*JoinRoomRequest)(nil), // 8: greatestworks.room.JoinRoomRequest (*JoinRoomResponse)(nil), // 9: greatestworks.room.JoinRoomResponse (*LeaveRoomRequest)(nil), // 10: greatestworks.room.LeaveRoomRequest (*LeaveRoomResponse)(nil), // 11: greatestworks.room.LeaveRoomResponse (*GetRoomListRequest)(nil), // 12: greatestworks.room.GetRoomListRequest (*GetRoomListResponse)(nil), // 13: greatestworks.room.GetRoomListResponse (*GetRoomInfoRequest)(nil), // 14: greatestworks.room.GetRoomInfoRequest (*GetRoomInfoResponse)(nil), // 15: greatestworks.room.GetRoomInfoResponse (*UpdateRoomSettingsRequest)(nil), // 16: greatestworks.room.UpdateRoomSettingsRequest (*UpdateRoomSettingsResponse)(nil), // 17: greatestworks.room.UpdateRoomSettingsResponse (*KickPlayerRequest)(nil), // 18: greatestworks.room.KickPlayerRequest (*KickPlayerResponse)(nil), // 19: greatestworks.room.KickPlayerResponse (*TransferOwnershipRequest)(nil), // 20: greatestworks.room.TransferOwnershipRequest (*TransferOwnershipResponse)(nil), // 21: greatestworks.room.TransferOwnershipResponse (*StartGameRequest)(nil), // 22: greatestworks.room.StartGameRequest (*StartGameResponse)(nil), // 23: greatestworks.room.StartGameResponse (*SetReadyRequest)(nil), // 24: greatestworks.room.SetReadyRequest (*SetReadyResponse)(nil), // 25: greatestworks.room.SetReadyResponse (*InvitePlayerRequest)(nil), // 26: greatestworks.room.InvitePlayerRequest (*InvitePlayerResponse)(nil), // 27: greatestworks.room.InvitePlayerResponse (*SearchRoomsRequest)(nil), // 28: greatestworks.room.SearchRoomsRequest (*SearchRoomsResponse)(nil), // 29: greatestworks.room.SearchRoomsResponse (*SendRoomMessageRequest)(nil), // 30: greatestworks.room.SendRoomMessageRequest (*SendRoomMessageResponse)(nil), // 31: greatestworks.room.SendRoomMessageResponse (*SetRoomPasswordRequest)(nil), // 32: greatestworks.room.SetRoomPasswordRequest (*SetRoomPasswordResponse)(nil), // 33: greatestworks.room.SetRoomPasswordResponse (*RoomInfo)(nil), // 34: greatestworks.room.RoomInfo (*RoomDetail)(nil), // 35: greatestworks.room.RoomDetail (*RoomPlayer)(nil), // 36: greatestworks.room.RoomPlayer (*RoomTeam)(nil), // 37: greatestworks.room.RoomTeam (*RoomMessage)(nil), // 38: greatestworks.room.RoomMessage (*RoomSettings)(nil), // 39: greatestworks.room.RoomSettings (*PlayerStats)(nil), // 40: greatestworks.room.PlayerStats (*TeamStats)(nil), // 41: greatestworks.room.TeamStats (*RoomStats)(nil), // 42: greatestworks.room.RoomStats nil, // 43: greatestworks.room.CreateRoomRequest.CustomRulesEntry nil, // 44: greatestworks.room.UpdateRoomSettingsRequest.CustomRulesEntry nil, // 45: greatestworks.room.RoomDetail.CustomRulesEntry nil, // 46: greatestworks.room.RoomSettings.ScoreLimitsEntry (*common.CommonResponse)(nil), // 47: greatestworks.common.CommonResponse (*common.PaginationInfo)(nil), // 48: greatestworks.common.PaginationInfo (*common.Position)(nil), // 49: greatestworks.common.Position } var file_proto_room_proto_depIdxs = []int32{ 0, // 0: greatestworks.room.CreateRoomRequest.room_type:type_name -> greatestworks.room.RoomType 39, // 1: greatestworks.room.CreateRoomRequest.settings:type_name -> greatestworks.room.RoomSettings 43, // 2: greatestworks.room.CreateRoomRequest.custom_rules:type_name -> greatestworks.room.CreateRoomRequest.CustomRulesEntry 47, // 3: greatestworks.room.CreateRoomResponse.common:type_name -> greatestworks.common.CommonResponse 34, // 4: greatestworks.room.CreateRoomResponse.room_info:type_name -> greatestworks.room.RoomInfo 47, // 5: greatestworks.room.JoinRoomResponse.common:type_name -> greatestworks.common.CommonResponse 34, // 6: greatestworks.room.JoinRoomResponse.room_info:type_name -> greatestworks.room.RoomInfo 36, // 7: greatestworks.room.JoinRoomResponse.player_info:type_name -> greatestworks.room.RoomPlayer 36, // 8: greatestworks.room.JoinRoomResponse.other_players:type_name -> greatestworks.room.RoomPlayer 47, // 9: greatestworks.room.LeaveRoomResponse.common:type_name -> greatestworks.common.CommonResponse 0, // 10: greatestworks.room.GetRoomListRequest.room_type:type_name -> greatestworks.room.RoomType 4, // 11: greatestworks.room.GetRoomListRequest.sort_by:type_name -> greatestworks.room.RoomSortBy 47, // 12: greatestworks.room.GetRoomListResponse.common:type_name -> greatestworks.common.CommonResponse 34, // 13: greatestworks.room.GetRoomListResponse.rooms:type_name -> greatestworks.room.RoomInfo 48, // 14: greatestworks.room.GetRoomListResponse.pagination:type_name -> greatestworks.common.PaginationInfo 47, // 15: greatestworks.room.GetRoomInfoResponse.common:type_name -> greatestworks.common.CommonResponse 35, // 16: greatestworks.room.GetRoomInfoResponse.room_detail:type_name -> greatestworks.room.RoomDetail 39, // 17: greatestworks.room.UpdateRoomSettingsRequest.settings:type_name -> greatestworks.room.RoomSettings 44, // 18: greatestworks.room.UpdateRoomSettingsRequest.custom_rules:type_name -> greatestworks.room.UpdateRoomSettingsRequest.CustomRulesEntry 47, // 19: greatestworks.room.UpdateRoomSettingsResponse.common:type_name -> greatestworks.common.CommonResponse 39, // 20: greatestworks.room.UpdateRoomSettingsResponse.new_settings:type_name -> greatestworks.room.RoomSettings 47, // 21: greatestworks.room.KickPlayerResponse.common:type_name -> greatestworks.common.CommonResponse 47, // 22: greatestworks.room.TransferOwnershipResponse.common:type_name -> greatestworks.common.CommonResponse 36, // 23: greatestworks.room.TransferOwnershipResponse.new_owner:type_name -> greatestworks.room.RoomPlayer 47, // 24: greatestworks.room.StartGameResponse.common:type_name -> greatestworks.common.CommonResponse 47, // 25: greatestworks.room.SetReadyResponse.common:type_name -> greatestworks.common.CommonResponse 47, // 26: greatestworks.room.InvitePlayerResponse.common:type_name -> greatestworks.common.CommonResponse 0, // 27: greatestworks.room.SearchRoomsRequest.room_type:type_name -> greatestworks.room.RoomType 47, // 28: greatestworks.room.SearchRoomsResponse.common:type_name -> greatestworks.common.CommonResponse 34, // 29: greatestworks.room.SearchRoomsResponse.rooms:type_name -> greatestworks.room.RoomInfo 48, // 30: greatestworks.room.SearchRoomsResponse.pagination:type_name -> greatestworks.common.PaginationInfo 3, // 31: greatestworks.room.SendRoomMessageRequest.message_type:type_name -> greatestworks.room.RoomMessageType 47, // 32: greatestworks.room.SendRoomMessageResponse.common:type_name -> greatestworks.common.CommonResponse 47, // 33: greatestworks.room.SetRoomPasswordResponse.common:type_name -> greatestworks.common.CommonResponse 0, // 34: greatestworks.room.RoomInfo.room_type:type_name -> greatestworks.room.RoomType 1, // 35: greatestworks.room.RoomInfo.status:type_name -> greatestworks.room.RoomStatus 39, // 36: greatestworks.room.RoomInfo.settings:type_name -> greatestworks.room.RoomSettings 34, // 37: greatestworks.room.RoomDetail.basic_info:type_name -> greatestworks.room.RoomInfo 36, // 38: greatestworks.room.RoomDetail.players:type_name -> greatestworks.room.RoomPlayer 37, // 39: greatestworks.room.RoomDetail.teams:type_name -> greatestworks.room.RoomTeam 38, // 40: greatestworks.room.RoomDetail.recent_messages:type_name -> greatestworks.room.RoomMessage 45, // 41: greatestworks.room.RoomDetail.custom_rules:type_name -> greatestworks.room.RoomDetail.CustomRulesEntry 42, // 42: greatestworks.room.RoomDetail.stats:type_name -> greatestworks.room.RoomStats 2, // 43: greatestworks.room.RoomPlayer.role:type_name -> greatestworks.room.PlayerRole 40, // 44: greatestworks.room.RoomPlayer.stats:type_name -> greatestworks.room.PlayerStats 49, // 45: greatestworks.room.RoomPlayer.position:type_name -> greatestworks.common.Position 41, // 46: greatestworks.room.RoomTeam.team_stats:type_name -> greatestworks.room.TeamStats 3, // 47: greatestworks.room.RoomMessage.message_type:type_name -> greatestworks.room.RoomMessageType 5, // 48: greatestworks.room.RoomSettings.difficulty:type_name -> greatestworks.room.DifficultyLevel 46, // 49: greatestworks.room.RoomSettings.score_limits:type_name -> greatestworks.room.RoomSettings.ScoreLimitsEntry 6, // 50: greatestworks.room.RoomService.CreateRoom:input_type -> greatestworks.room.CreateRoomRequest 8, // 51: greatestworks.room.RoomService.JoinRoom:input_type -> greatestworks.room.JoinRoomRequest 10, // 52: greatestworks.room.RoomService.LeaveRoom:input_type -> greatestworks.room.LeaveRoomRequest 12, // 53: greatestworks.room.RoomService.GetRoomList:input_type -> greatestworks.room.GetRoomListRequest 14, // 54: greatestworks.room.RoomService.GetRoomInfo:input_type -> greatestworks.room.GetRoomInfoRequest 16, // 55: greatestworks.room.RoomService.UpdateRoomSettings:input_type -> greatestworks.room.UpdateRoomSettingsRequest 18, // 56: greatestworks.room.RoomService.KickPlayer:input_type -> greatestworks.room.KickPlayerRequest 20, // 57: greatestworks.room.RoomService.TransferOwnership:input_type -> greatestworks.room.TransferOwnershipRequest 22, // 58: greatestworks.room.RoomService.StartGame:input_type -> greatestworks.room.StartGameRequest 24, // 59: greatestworks.room.RoomService.SetReady:input_type -> greatestworks.room.SetReadyRequest 26, // 60: greatestworks.room.RoomService.InvitePlayer:input_type -> greatestworks.room.InvitePlayerRequest 28, // 61: greatestworks.room.RoomService.SearchRooms:input_type -> greatestworks.room.SearchRoomsRequest 30, // 62: greatestworks.room.RoomService.SendRoomMessage:input_type -> greatestworks.room.SendRoomMessageRequest 32, // 63: greatestworks.room.RoomService.SetRoomPassword:input_type -> greatestworks.room.SetRoomPasswordRequest 7, // 64: greatestworks.room.RoomService.CreateRoom:output_type -> greatestworks.room.CreateRoomResponse 9, // 65: greatestworks.room.RoomService.JoinRoom:output_type -> greatestworks.room.JoinRoomResponse 11, // 66: greatestworks.room.RoomService.LeaveRoom:output_type -> greatestworks.room.LeaveRoomResponse 13, // 67: greatestworks.room.RoomService.GetRoomList:output_type -> greatestworks.room.GetRoomListResponse 15, // 68: greatestworks.room.RoomService.GetRoomInfo:output_type -> greatestworks.room.GetRoomInfoResponse 17, // 69: greatestworks.room.RoomService.UpdateRoomSettings:output_type -> greatestworks.room.UpdateRoomSettingsResponse 19, // 70: greatestworks.room.RoomService.KickPlayer:output_type -> greatestworks.room.KickPlayerResponse 21, // 71: greatestworks.room.RoomService.TransferOwnership:output_type -> greatestworks.room.TransferOwnershipResponse 23, // 72: greatestworks.room.RoomService.StartGame:output_type -> greatestworks.room.StartGameResponse 25, // 73: greatestworks.room.RoomService.SetReady:output_type -> greatestworks.room.SetReadyResponse 27, // 74: greatestworks.room.RoomService.InvitePlayer:output_type -> greatestworks.room.InvitePlayerResponse 29, // 75: greatestworks.room.RoomService.SearchRooms:output_type -> greatestworks.room.SearchRoomsResponse 31, // 76: greatestworks.room.RoomService.SendRoomMessage:output_type -> greatestworks.room.SendRoomMessageResponse 33, // 77: greatestworks.room.RoomService.SetRoomPassword:output_type -> greatestworks.room.SetRoomPasswordResponse 64, // [64:78] is the sub-list for method output_type 50, // [50:64] is the sub-list for method input_type 50, // [50:50] is the sub-list for extension type_name 50, // [50:50] is the sub-list for extension extendee 0, // [0:50] is the sub-list for field type_name } func init() { file_proto_room_proto_init() } func file_proto_room_proto_init() { if File_proto_room_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_room_proto_rawDesc), len(file_proto_room_proto_rawDesc)), NumEnums: 6, NumMessages: 41, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_room_proto_goTypes, DependencyIndexes: file_proto_room_proto_depIdxs, EnumInfos: file_proto_room_proto_enumTypes, MessageInfos: file_proto_room_proto_msgTypes, }.Build() File_proto_room_proto = out.File file_proto_room_proto_goTypes = nil file_proto_room_proto_depIdxs = nil } ================================================ FILE: internal/proto/scene/scene.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/scene.proto package scene import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 场景类型枚举 type SceneType int32 const ( SceneType_SCENE_TYPE_UNSPECIFIED SceneType = 0 SceneType_SCENE_TYPE_TOWN SceneType = 1 // 城镇 SceneType_SCENE_TYPE_DUNGEON SceneType = 2 // 地下城 SceneType_SCENE_TYPE_WILDERNESS SceneType = 3 // 野外 SceneType_SCENE_TYPE_BATTLE_ARENA SceneType = 4 // 战斗竞技场 SceneType_SCENE_TYPE_INSTANCE SceneType = 5 // 副本 SceneType_SCENE_TYPE_GUILD_HALL SceneType = 6 // 公会大厅 SceneType_SCENE_TYPE_PRIVATE_ROOM SceneType = 7 // 私人房间 SceneType_SCENE_TYPE_EVENT_AREA SceneType = 8 // 活动区域 SceneType_SCENE_TYPE_TRAINING_GROUND SceneType = 9 // 训练场 ) // Enum value maps for SceneType. var ( SceneType_name = map[int32]string{ 0: "SCENE_TYPE_UNSPECIFIED", 1: "SCENE_TYPE_TOWN", 2: "SCENE_TYPE_DUNGEON", 3: "SCENE_TYPE_WILDERNESS", 4: "SCENE_TYPE_BATTLE_ARENA", 5: "SCENE_TYPE_INSTANCE", 6: "SCENE_TYPE_GUILD_HALL", 7: "SCENE_TYPE_PRIVATE_ROOM", 8: "SCENE_TYPE_EVENT_AREA", 9: "SCENE_TYPE_TRAINING_GROUND", } SceneType_value = map[string]int32{ "SCENE_TYPE_UNSPECIFIED": 0, "SCENE_TYPE_TOWN": 1, "SCENE_TYPE_DUNGEON": 2, "SCENE_TYPE_WILDERNESS": 3, "SCENE_TYPE_BATTLE_ARENA": 4, "SCENE_TYPE_INSTANCE": 5, "SCENE_TYPE_GUILD_HALL": 6, "SCENE_TYPE_PRIVATE_ROOM": 7, "SCENE_TYPE_EVENT_AREA": 8, "SCENE_TYPE_TRAINING_GROUND": 9, } ) func (x SceneType) Enum() *SceneType { p := new(SceneType) *p = x return p } func (x SceneType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SceneType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[0].Descriptor() } func (SceneType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[0] } func (x SceneType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SceneType.Descriptor instead. func (SceneType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{0} } // 场景状态枚举 type SceneStatus int32 const ( SceneStatus_SCENE_STATUS_UNSPECIFIED SceneStatus = 0 SceneStatus_SCENE_STATUS_ACTIVE SceneStatus = 1 // 活跃 SceneStatus_SCENE_STATUS_MAINTENANCE SceneStatus = 2 // 维护中 SceneStatus_SCENE_STATUS_CLOSED SceneStatus = 3 // 已关闭 SceneStatus_SCENE_STATUS_FULL SceneStatus = 4 // 已满员 SceneStatus_SCENE_STATUS_LOADING SceneStatus = 5 // 加载中 ) // Enum value maps for SceneStatus. var ( SceneStatus_name = map[int32]string{ 0: "SCENE_STATUS_UNSPECIFIED", 1: "SCENE_STATUS_ACTIVE", 2: "SCENE_STATUS_MAINTENANCE", 3: "SCENE_STATUS_CLOSED", 4: "SCENE_STATUS_FULL", 5: "SCENE_STATUS_LOADING", } SceneStatus_value = map[string]int32{ "SCENE_STATUS_UNSPECIFIED": 0, "SCENE_STATUS_ACTIVE": 1, "SCENE_STATUS_MAINTENANCE": 2, "SCENE_STATUS_CLOSED": 3, "SCENE_STATUS_FULL": 4, "SCENE_STATUS_LOADING": 5, } ) func (x SceneStatus) Enum() *SceneStatus { p := new(SceneStatus) *p = x return p } func (x SceneStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SceneStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[1].Descriptor() } func (SceneStatus) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[1] } func (x SceneStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SceneStatus.Descriptor instead. func (SceneStatus) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{1} } // 玩家状态枚举 type PlayerState int32 const ( PlayerState_PLAYER_STATE_UNSPECIFIED PlayerState = 0 PlayerState_PLAYER_STATE_IDLE PlayerState = 1 // 空闲 PlayerState_PLAYER_STATE_MOVING PlayerState = 2 // 移动中 PlayerState_PLAYER_STATE_INTERACTING PlayerState = 3 // 交互中 PlayerState_PLAYER_STATE_COMBAT PlayerState = 4 // 战斗中 PlayerState_PLAYER_STATE_TRADING PlayerState = 5 // 交易中 PlayerState_PLAYER_STATE_AFK PlayerState = 6 // 暂离 PlayerState_PLAYER_STATE_INVISIBLE PlayerState = 7 // 隐身 ) // Enum value maps for PlayerState. var ( PlayerState_name = map[int32]string{ 0: "PLAYER_STATE_UNSPECIFIED", 1: "PLAYER_STATE_IDLE", 2: "PLAYER_STATE_MOVING", 3: "PLAYER_STATE_INTERACTING", 4: "PLAYER_STATE_COMBAT", 5: "PLAYER_STATE_TRADING", 6: "PLAYER_STATE_AFK", 7: "PLAYER_STATE_INVISIBLE", } PlayerState_value = map[string]int32{ "PLAYER_STATE_UNSPECIFIED": 0, "PLAYER_STATE_IDLE": 1, "PLAYER_STATE_MOVING": 2, "PLAYER_STATE_INTERACTING": 3, "PLAYER_STATE_COMBAT": 4, "PLAYER_STATE_TRADING": 5, "PLAYER_STATE_AFK": 6, "PLAYER_STATE_INVISIBLE": 7, } ) func (x PlayerState) Enum() *PlayerState { p := new(PlayerState) *p = x return p } func (x PlayerState) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PlayerState) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[2].Descriptor() } func (PlayerState) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[2] } func (x PlayerState) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PlayerState.Descriptor instead. func (PlayerState) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{2} } // 对象类型枚举 type ObjectType int32 const ( ObjectType_OBJECT_TYPE_UNSPECIFIED ObjectType = 0 ObjectType_OBJECT_TYPE_NPC ObjectType = 1 // NPC ObjectType_OBJECT_TYPE_ITEM ObjectType = 2 // 物品 ObjectType_OBJECT_TYPE_CHEST ObjectType = 3 // 宝箱 ObjectType_OBJECT_TYPE_DOOR ObjectType = 4 // 门 ObjectType_OBJECT_TYPE_PORTAL ObjectType = 5 // 传送门 ObjectType_OBJECT_TYPE_SIGN ObjectType = 6 // 标志牌 ObjectType_OBJECT_TYPE_DECORATION ObjectType = 7 // 装饰物 ObjectType_OBJECT_TYPE_FURNITURE ObjectType = 8 // 家具 ObjectType_OBJECT_TYPE_VEHICLE ObjectType = 9 // 载具 ObjectType_OBJECT_TYPE_RESOURCE ObjectType = 10 // 资源点 ObjectType_OBJECT_TYPE_TRAP ObjectType = 11 // 陷阱 ObjectType_OBJECT_TYPE_SWITCH ObjectType = 12 // 开关 ) // Enum value maps for ObjectType. var ( ObjectType_name = map[int32]string{ 0: "OBJECT_TYPE_UNSPECIFIED", 1: "OBJECT_TYPE_NPC", 2: "OBJECT_TYPE_ITEM", 3: "OBJECT_TYPE_CHEST", 4: "OBJECT_TYPE_DOOR", 5: "OBJECT_TYPE_PORTAL", 6: "OBJECT_TYPE_SIGN", 7: "OBJECT_TYPE_DECORATION", 8: "OBJECT_TYPE_FURNITURE", 9: "OBJECT_TYPE_VEHICLE", 10: "OBJECT_TYPE_RESOURCE", 11: "OBJECT_TYPE_TRAP", 12: "OBJECT_TYPE_SWITCH", } ObjectType_value = map[string]int32{ "OBJECT_TYPE_UNSPECIFIED": 0, "OBJECT_TYPE_NPC": 1, "OBJECT_TYPE_ITEM": 2, "OBJECT_TYPE_CHEST": 3, "OBJECT_TYPE_DOOR": 4, "OBJECT_TYPE_PORTAL": 5, "OBJECT_TYPE_SIGN": 6, "OBJECT_TYPE_DECORATION": 7, "OBJECT_TYPE_FURNITURE": 8, "OBJECT_TYPE_VEHICLE": 9, "OBJECT_TYPE_RESOURCE": 10, "OBJECT_TYPE_TRAP": 11, "OBJECT_TYPE_SWITCH": 12, } ) func (x ObjectType) Enum() *ObjectType { p := new(ObjectType) *p = x return p } func (x ObjectType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ObjectType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[3].Descriptor() } func (ObjectType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[3] } func (x ObjectType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ObjectType.Descriptor instead. func (ObjectType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{3} } // 对象状态枚举 type ObjectState int32 const ( ObjectState_OBJECT_STATE_UNSPECIFIED ObjectState = 0 ObjectState_OBJECT_STATE_NORMAL ObjectState = 1 // 正常 ObjectState_OBJECT_STATE_ACTIVATED ObjectState = 2 // 已激活 ObjectState_OBJECT_STATE_DISABLED ObjectState = 3 // 已禁用 ObjectState_OBJECT_STATE_BROKEN ObjectState = 4 // 已损坏 ObjectState_OBJECT_STATE_LOCKED ObjectState = 5 // 已锁定 ObjectState_OBJECT_STATE_HIDDEN ObjectState = 6 // 隐藏 ) // Enum value maps for ObjectState. var ( ObjectState_name = map[int32]string{ 0: "OBJECT_STATE_UNSPECIFIED", 1: "OBJECT_STATE_NORMAL", 2: "OBJECT_STATE_ACTIVATED", 3: "OBJECT_STATE_DISABLED", 4: "OBJECT_STATE_BROKEN", 5: "OBJECT_STATE_LOCKED", 6: "OBJECT_STATE_HIDDEN", } ObjectState_value = map[string]int32{ "OBJECT_STATE_UNSPECIFIED": 0, "OBJECT_STATE_NORMAL": 1, "OBJECT_STATE_ACTIVATED": 2, "OBJECT_STATE_DISABLED": 3, "OBJECT_STATE_BROKEN": 4, "OBJECT_STATE_LOCKED": 5, "OBJECT_STATE_HIDDEN": 6, } ) func (x ObjectState) Enum() *ObjectState { p := new(ObjectState) *p = x return p } func (x ObjectState) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ObjectState) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[4].Descriptor() } func (ObjectState) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[4] } func (x ObjectState) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ObjectState.Descriptor instead. func (ObjectState) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{4} } // 交互类型枚举 type InteractionType int32 const ( InteractionType_INTERACTION_TYPE_UNSPECIFIED InteractionType = 0 InteractionType_INTERACTION_TYPE_TALK InteractionType = 1 // 对话 InteractionType_INTERACTION_TYPE_USE InteractionType = 2 // 使用 InteractionType_INTERACTION_TYPE_EXAMINE InteractionType = 3 // 检查 InteractionType_INTERACTION_TYPE_OPEN InteractionType = 4 // 打开 InteractionType_INTERACTION_TYPE_CLOSE InteractionType = 5 // 关闭 InteractionType_INTERACTION_TYPE_PICKUP InteractionType = 6 // 拾取 InteractionType_INTERACTION_TYPE_ACTIVATE InteractionType = 7 // 激活 InteractionType_INTERACTION_TYPE_REPAIR InteractionType = 8 // 修理 InteractionType_INTERACTION_TYPE_UPGRADE InteractionType = 9 // 升级 InteractionType_INTERACTION_TYPE_DESTROY InteractionType = 10 // 摧毁 ) // Enum value maps for InteractionType. var ( InteractionType_name = map[int32]string{ 0: "INTERACTION_TYPE_UNSPECIFIED", 1: "INTERACTION_TYPE_TALK", 2: "INTERACTION_TYPE_USE", 3: "INTERACTION_TYPE_EXAMINE", 4: "INTERACTION_TYPE_OPEN", 5: "INTERACTION_TYPE_CLOSE", 6: "INTERACTION_TYPE_PICKUP", 7: "INTERACTION_TYPE_ACTIVATE", 8: "INTERACTION_TYPE_REPAIR", 9: "INTERACTION_TYPE_UPGRADE", 10: "INTERACTION_TYPE_DESTROY", } InteractionType_value = map[string]int32{ "INTERACTION_TYPE_UNSPECIFIED": 0, "INTERACTION_TYPE_TALK": 1, "INTERACTION_TYPE_USE": 2, "INTERACTION_TYPE_EXAMINE": 3, "INTERACTION_TYPE_OPEN": 4, "INTERACTION_TYPE_CLOSE": 5, "INTERACTION_TYPE_PICKUP": 6, "INTERACTION_TYPE_ACTIVATE": 7, "INTERACTION_TYPE_REPAIR": 8, "INTERACTION_TYPE_UPGRADE": 9, "INTERACTION_TYPE_DESTROY": 10, } ) func (x InteractionType) Enum() *InteractionType { p := new(InteractionType) *p = x return p } func (x InteractionType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (InteractionType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[5].Descriptor() } func (InteractionType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[5] } func (x InteractionType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use InteractionType.Descriptor instead. func (InteractionType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{5} } // 移动类型枚举 type MovementType int32 const ( MovementType_MOVEMENT_TYPE_UNSPECIFIED MovementType = 0 MovementType_MOVEMENT_TYPE_WALK MovementType = 1 // 走路 MovementType_MOVEMENT_TYPE_RUN MovementType = 2 // 跑步 MovementType_MOVEMENT_TYPE_TELEPORT MovementType = 3 // 传送 MovementType_MOVEMENT_TYPE_FLY MovementType = 4 // 飞行 MovementType_MOVEMENT_TYPE_SWIM MovementType = 5 // 游泳 MovementType_MOVEMENT_TYPE_MOUNT MovementType = 6 // 坐骑 ) // Enum value maps for MovementType. var ( MovementType_name = map[int32]string{ 0: "MOVEMENT_TYPE_UNSPECIFIED", 1: "MOVEMENT_TYPE_WALK", 2: "MOVEMENT_TYPE_RUN", 3: "MOVEMENT_TYPE_TELEPORT", 4: "MOVEMENT_TYPE_FLY", 5: "MOVEMENT_TYPE_SWIM", 6: "MOVEMENT_TYPE_MOUNT", } MovementType_value = map[string]int32{ "MOVEMENT_TYPE_UNSPECIFIED": 0, "MOVEMENT_TYPE_WALK": 1, "MOVEMENT_TYPE_RUN": 2, "MOVEMENT_TYPE_TELEPORT": 3, "MOVEMENT_TYPE_FLY": 4, "MOVEMENT_TYPE_SWIM": 5, "MOVEMENT_TYPE_MOUNT": 6, } ) func (x MovementType) Enum() *MovementType { p := new(MovementType) *p = x return p } func (x MovementType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MovementType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[6].Descriptor() } func (MovementType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[6] } func (x MovementType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MovementType.Descriptor instead. func (MovementType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{6} } // 天气类型枚举 type WeatherType int32 const ( WeatherType_WEATHER_TYPE_UNSPECIFIED WeatherType = 0 WeatherType_WEATHER_TYPE_CLEAR WeatherType = 1 // 晴天 WeatherType_WEATHER_TYPE_CLOUDY WeatherType = 2 // 多云 WeatherType_WEATHER_TYPE_RAINY WeatherType = 3 // 雨天 WeatherType_WEATHER_TYPE_STORMY WeatherType = 4 // 暴风雨 WeatherType_WEATHER_TYPE_SNOWY WeatherType = 5 // 雪天 WeatherType_WEATHER_TYPE_FOGGY WeatherType = 6 // 雾天 WeatherType_WEATHER_TYPE_WINDY WeatherType = 7 // 大风 ) // Enum value maps for WeatherType. var ( WeatherType_name = map[int32]string{ 0: "WEATHER_TYPE_UNSPECIFIED", 1: "WEATHER_TYPE_CLEAR", 2: "WEATHER_TYPE_CLOUDY", 3: "WEATHER_TYPE_RAINY", 4: "WEATHER_TYPE_STORMY", 5: "WEATHER_TYPE_SNOWY", 6: "WEATHER_TYPE_FOGGY", 7: "WEATHER_TYPE_WINDY", } WeatherType_value = map[string]int32{ "WEATHER_TYPE_UNSPECIFIED": 0, "WEATHER_TYPE_CLEAR": 1, "WEATHER_TYPE_CLOUDY": 2, "WEATHER_TYPE_RAINY": 3, "WEATHER_TYPE_STORMY": 4, "WEATHER_TYPE_SNOWY": 5, "WEATHER_TYPE_FOGGY": 6, "WEATHER_TYPE_WINDY": 7, } ) func (x WeatherType) Enum() *WeatherType { p := new(WeatherType) *p = x return p } func (x WeatherType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (WeatherType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[7].Descriptor() } func (WeatherType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[7] } func (x WeatherType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use WeatherType.Descriptor instead. func (WeatherType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{7} } // 时间段枚举 type TimeOfDay int32 const ( TimeOfDay_TIME_OF_DAY_UNSPECIFIED TimeOfDay = 0 TimeOfDay_TIME_OF_DAY_DAWN TimeOfDay = 1 // 黎明 TimeOfDay_TIME_OF_DAY_MORNING TimeOfDay = 2 // 上午 TimeOfDay_TIME_OF_DAY_NOON TimeOfDay = 3 // 中午 TimeOfDay_TIME_OF_DAY_AFTERNOON TimeOfDay = 4 // 下午 TimeOfDay_TIME_OF_DAY_EVENING TimeOfDay = 5 // 傍晚 TimeOfDay_TIME_OF_DAY_NIGHT TimeOfDay = 6 // 夜晚 TimeOfDay_TIME_OF_DAY_MIDNIGHT TimeOfDay = 7 // 午夜 ) // Enum value maps for TimeOfDay. var ( TimeOfDay_name = map[int32]string{ 0: "TIME_OF_DAY_UNSPECIFIED", 1: "TIME_OF_DAY_DAWN", 2: "TIME_OF_DAY_MORNING", 3: "TIME_OF_DAY_NOON", 4: "TIME_OF_DAY_AFTERNOON", 5: "TIME_OF_DAY_EVENING", 6: "TIME_OF_DAY_NIGHT", 7: "TIME_OF_DAY_MIDNIGHT", } TimeOfDay_value = map[string]int32{ "TIME_OF_DAY_UNSPECIFIED": 0, "TIME_OF_DAY_DAWN": 1, "TIME_OF_DAY_MORNING": 2, "TIME_OF_DAY_NOON": 3, "TIME_OF_DAY_AFTERNOON": 4, "TIME_OF_DAY_EVENING": 5, "TIME_OF_DAY_NIGHT": 6, "TIME_OF_DAY_MIDNIGHT": 7, } ) func (x TimeOfDay) Enum() *TimeOfDay { p := new(TimeOfDay) *p = x return p } func (x TimeOfDay) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (TimeOfDay) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[8].Descriptor() } func (TimeOfDay) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[8] } func (x TimeOfDay) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use TimeOfDay.Descriptor instead. func (TimeOfDay) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{8} } // 事件类型枚举 type EventType int32 const ( EventType_EVENT_TYPE_UNSPECIFIED EventType = 0 EventType_EVENT_TYPE_PLAYER_ENTER EventType = 1 // 玩家进入 EventType_EVENT_TYPE_PLAYER_LEAVE EventType = 2 // 玩家离开 EventType_EVENT_TYPE_OBJECT_INTERACTION EventType = 3 // 对象交互 EventType_EVENT_TYPE_COMBAT_START EventType = 4 // 战斗开始 EventType_EVENT_TYPE_COMBAT_END EventType = 5 // 战斗结束 EventType_EVENT_TYPE_ITEM_PICKUP EventType = 6 // 物品拾取 EventType_EVENT_TYPE_QUEST_TRIGGER EventType = 7 // 任务触发 EventType_EVENT_TYPE_ACHIEVEMENT_UNLOCK EventType = 8 // 成就解锁 EventType_EVENT_TYPE_WEATHER_CHANGE EventType = 9 // 天气变化 EventType_EVENT_TYPE_TIME_CHANGE EventType = 10 // 时间变化 ) // Enum value maps for EventType. var ( EventType_name = map[int32]string{ 0: "EVENT_TYPE_UNSPECIFIED", 1: "EVENT_TYPE_PLAYER_ENTER", 2: "EVENT_TYPE_PLAYER_LEAVE", 3: "EVENT_TYPE_OBJECT_INTERACTION", 4: "EVENT_TYPE_COMBAT_START", 5: "EVENT_TYPE_COMBAT_END", 6: "EVENT_TYPE_ITEM_PICKUP", 7: "EVENT_TYPE_QUEST_TRIGGER", 8: "EVENT_TYPE_ACHIEVEMENT_UNLOCK", 9: "EVENT_TYPE_WEATHER_CHANGE", 10: "EVENT_TYPE_TIME_CHANGE", } EventType_value = map[string]int32{ "EVENT_TYPE_UNSPECIFIED": 0, "EVENT_TYPE_PLAYER_ENTER": 1, "EVENT_TYPE_PLAYER_LEAVE": 2, "EVENT_TYPE_OBJECT_INTERACTION": 3, "EVENT_TYPE_COMBAT_START": 4, "EVENT_TYPE_COMBAT_END": 5, "EVENT_TYPE_ITEM_PICKUP": 6, "EVENT_TYPE_QUEST_TRIGGER": 7, "EVENT_TYPE_ACHIEVEMENT_UNLOCK": 8, "EVENT_TYPE_WEATHER_CHANGE": 9, "EVENT_TYPE_TIME_CHANGE": 10, } ) func (x EventType) Enum() *EventType { p := new(EventType) *p = x return p } func (x EventType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (EventType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[9].Descriptor() } func (EventType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[9] } func (x EventType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use EventType.Descriptor instead. func (EventType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{9} } // 效果类型枚举 type EffectType int32 const ( EffectType_EFFECT_TYPE_UNSPECIFIED EffectType = 0 EffectType_EFFECT_TYPE_BUFF EffectType = 1 // 增益 EffectType_EFFECT_TYPE_DEBUFF EffectType = 2 // 减益 EffectType_EFFECT_TYPE_DAMAGE EffectType = 3 // 伤害 EffectType_EFFECT_TYPE_HEAL EffectType = 4 // 治疗 EffectType_EFFECT_TYPE_TELEPORT EffectType = 5 // 传送 EffectType_EFFECT_TYPE_TRANSFORM EffectType = 6 // 变形 EffectType_EFFECT_TYPE_INVISIBILITY EffectType = 7 // 隐身 EffectType_EFFECT_TYPE_SPEED_BOOST EffectType = 8 // 速度提升 EffectType_EFFECT_TYPE_SHIELD EffectType = 9 // 护盾 EffectType_EFFECT_TYPE_STUN EffectType = 10 // 眩晕 ) // Enum value maps for EffectType. var ( EffectType_name = map[int32]string{ 0: "EFFECT_TYPE_UNSPECIFIED", 1: "EFFECT_TYPE_BUFF", 2: "EFFECT_TYPE_DEBUFF", 3: "EFFECT_TYPE_DAMAGE", 4: "EFFECT_TYPE_HEAL", 5: "EFFECT_TYPE_TELEPORT", 6: "EFFECT_TYPE_TRANSFORM", 7: "EFFECT_TYPE_INVISIBILITY", 8: "EFFECT_TYPE_SPEED_BOOST", 9: "EFFECT_TYPE_SHIELD", 10: "EFFECT_TYPE_STUN", } EffectType_value = map[string]int32{ "EFFECT_TYPE_UNSPECIFIED": 0, "EFFECT_TYPE_BUFF": 1, "EFFECT_TYPE_DEBUFF": 2, "EFFECT_TYPE_DAMAGE": 3, "EFFECT_TYPE_HEAL": 4, "EFFECT_TYPE_TELEPORT": 5, "EFFECT_TYPE_TRANSFORM": 6, "EFFECT_TYPE_INVISIBILITY": 7, "EFFECT_TYPE_SPEED_BOOST": 8, "EFFECT_TYPE_SHIELD": 9, "EFFECT_TYPE_STUN": 10, } ) func (x EffectType) Enum() *EffectType { p := new(EffectType) *p = x return p } func (x EffectType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (EffectType) Descriptor() protoreflect.EnumDescriptor { return file_proto_scene_proto_enumTypes[10].Descriptor() } func (EffectType) Type() protoreflect.EnumType { return &file_proto_scene_proto_enumTypes[10] } func (x EffectType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use EffectType.Descriptor instead. func (EffectType) EnumDescriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{10} } // 进入场景请求 type EnterSceneRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SceneId string `protobuf:"bytes,2,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` SpawnPosition *common.Position `protobuf:"bytes,3,opt,name=spawn_position,json=spawnPosition,proto3" json:"spawn_position,omitempty"` PreviousSceneId string `protobuf:"bytes,4,opt,name=previous_scene_id,json=previousSceneId,proto3" json:"previous_scene_id,omitempty"` EntranceId string `protobuf:"bytes,5,opt,name=entrance_id,json=entranceId,proto3" json:"entrance_id,omitempty"` // 入口ID(如传送门、门等) EntryContext map[string]string `protobuf:"bytes,6,rep,name=entry_context,json=entryContext,proto3" json:"entry_context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *EnterSceneRequest) Reset() { *x = EnterSceneRequest{} mi := &file_proto_scene_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *EnterSceneRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EnterSceneRequest) ProtoMessage() {} func (x *EnterSceneRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EnterSceneRequest.ProtoReflect.Descriptor instead. func (*EnterSceneRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{0} } func (x *EnterSceneRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *EnterSceneRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *EnterSceneRequest) GetSpawnPosition() *common.Position { if x != nil { return x.SpawnPosition } return nil } func (x *EnterSceneRequest) GetPreviousSceneId() string { if x != nil { return x.PreviousSceneId } return "" } func (x *EnterSceneRequest) GetEntranceId() string { if x != nil { return x.EntranceId } return "" } func (x *EnterSceneRequest) GetEntryContext() map[string]string { if x != nil { return x.EntryContext } return nil } // 进入场景响应 type EnterSceneResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` SceneInfo *SceneInfo `protobuf:"bytes,2,opt,name=scene_info,json=sceneInfo,proto3" json:"scene_info,omitempty"` PlayerPosition *common.Position `protobuf:"bytes,3,opt,name=player_position,json=playerPosition,proto3" json:"player_position,omitempty"` OtherPlayers []*ScenePlayer `protobuf:"bytes,4,rep,name=other_players,json=otherPlayers,proto3" json:"other_players,omitempty"` SceneObjects []*SceneObject `protobuf:"bytes,5,rep,name=scene_objects,json=sceneObjects,proto3" json:"scene_objects,omitempty"` Environment *SceneEnvironment `protobuf:"bytes,6,opt,name=environment,proto3" json:"environment,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *EnterSceneResponse) Reset() { *x = EnterSceneResponse{} mi := &file_proto_scene_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *EnterSceneResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*EnterSceneResponse) ProtoMessage() {} func (x *EnterSceneResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EnterSceneResponse.ProtoReflect.Descriptor instead. func (*EnterSceneResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{1} } func (x *EnterSceneResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *EnterSceneResponse) GetSceneInfo() *SceneInfo { if x != nil { return x.SceneInfo } return nil } func (x *EnterSceneResponse) GetPlayerPosition() *common.Position { if x != nil { return x.PlayerPosition } return nil } func (x *EnterSceneResponse) GetOtherPlayers() []*ScenePlayer { if x != nil { return x.OtherPlayers } return nil } func (x *EnterSceneResponse) GetSceneObjects() []*SceneObject { if x != nil { return x.SceneObjects } return nil } func (x *EnterSceneResponse) GetEnvironment() *SceneEnvironment { if x != nil { return x.Environment } return nil } // 离开场景请求 type LeaveSceneRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SceneId string `protobuf:"bytes,2,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` ExitId string `protobuf:"bytes,3,opt,name=exit_id,json=exitId,proto3" json:"exit_id,omitempty"` // 出口ID unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveSceneRequest) Reset() { *x = LeaveSceneRequest{} mi := &file_proto_scene_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveSceneRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveSceneRequest) ProtoMessage() {} func (x *LeaveSceneRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveSceneRequest.ProtoReflect.Descriptor instead. func (*LeaveSceneRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{2} } func (x *LeaveSceneRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *LeaveSceneRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *LeaveSceneRequest) GetExitId() string { if x != nil { return x.ExitId } return "" } // 离开场景响应 type LeaveSceneResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveSceneResponse) Reset() { *x = LeaveSceneResponse{} mi := &file_proto_scene_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveSceneResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveSceneResponse) ProtoMessage() {} func (x *LeaveSceneResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveSceneResponse.ProtoReflect.Descriptor instead. func (*LeaveSceneResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{3} } func (x *LeaveSceneResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 获取场景信息请求 type GetSceneInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SceneId string `protobuf:"bytes,1,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` // 查询者ID,用于权限检查 IncludePlayers bool `protobuf:"varint,3,opt,name=include_players,json=includePlayers,proto3" json:"include_players,omitempty"` IncludeObjects bool `protobuf:"varint,4,opt,name=include_objects,json=includeObjects,proto3" json:"include_objects,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSceneInfoRequest) Reset() { *x = GetSceneInfoRequest{} mi := &file_proto_scene_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSceneInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSceneInfoRequest) ProtoMessage() {} func (x *GetSceneInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSceneInfoRequest.ProtoReflect.Descriptor instead. func (*GetSceneInfoRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{4} } func (x *GetSceneInfoRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *GetSceneInfoRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *GetSceneInfoRequest) GetIncludePlayers() bool { if x != nil { return x.IncludePlayers } return false } func (x *GetSceneInfoRequest) GetIncludeObjects() bool { if x != nil { return x.IncludeObjects } return false } // 获取场景信息响应 type GetSceneInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` SceneInfo *SceneInfo `protobuf:"bytes,2,opt,name=scene_info,json=sceneInfo,proto3" json:"scene_info,omitempty"` Players []*ScenePlayer `protobuf:"bytes,3,rep,name=players,proto3" json:"players,omitempty"` Objects []*SceneObject `protobuf:"bytes,4,rep,name=objects,proto3" json:"objects,omitempty"` Environment *SceneEnvironment `protobuf:"bytes,5,opt,name=environment,proto3" json:"environment,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSceneInfoResponse) Reset() { *x = GetSceneInfoResponse{} mi := &file_proto_scene_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSceneInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSceneInfoResponse) ProtoMessage() {} func (x *GetSceneInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSceneInfoResponse.ProtoReflect.Descriptor instead. func (*GetSceneInfoResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{5} } func (x *GetSceneInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetSceneInfoResponse) GetSceneInfo() *SceneInfo { if x != nil { return x.SceneInfo } return nil } func (x *GetSceneInfoResponse) GetPlayers() []*ScenePlayer { if x != nil { return x.Players } return nil } func (x *GetSceneInfoResponse) GetObjects() []*SceneObject { if x != nil { return x.Objects } return nil } func (x *GetSceneInfoResponse) GetEnvironment() *SceneEnvironment { if x != nil { return x.Environment } return nil } // 移动到位置请求 type MoveToPositionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SceneId string `protobuf:"bytes,2,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` TargetPosition *common.Position `protobuf:"bytes,3,opt,name=target_position,json=targetPosition,proto3" json:"target_position,omitempty"` MovementType MovementType `protobuf:"varint,4,opt,name=movement_type,json=movementType,proto3,enum=greatestworks.scene.MovementType" json:"movement_type,omitempty"` Speed float32 `protobuf:"fixed32,5,opt,name=speed,proto3" json:"speed,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MoveToPositionRequest) Reset() { *x = MoveToPositionRequest{} mi := &file_proto_scene_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MoveToPositionRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*MoveToPositionRequest) ProtoMessage() {} func (x *MoveToPositionRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MoveToPositionRequest.ProtoReflect.Descriptor instead. func (*MoveToPositionRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{6} } func (x *MoveToPositionRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *MoveToPositionRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *MoveToPositionRequest) GetTargetPosition() *common.Position { if x != nil { return x.TargetPosition } return nil } func (x *MoveToPositionRequest) GetMovementType() MovementType { if x != nil { return x.MovementType } return MovementType_MOVEMENT_TYPE_UNSPECIFIED } func (x *MoveToPositionRequest) GetSpeed() float32 { if x != nil { return x.Speed } return 0 } // 移动到位置响应 type MoveToPositionResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewPosition *common.Position `protobuf:"bytes,2,opt,name=new_position,json=newPosition,proto3" json:"new_position,omitempty"` ActualSpeed float32 `protobuf:"fixed32,3,opt,name=actual_speed,json=actualSpeed,proto3" json:"actual_speed,omitempty"` MovementTime int64 `protobuf:"varint,4,opt,name=movement_time,json=movementTime,proto3" json:"movement_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MoveToPositionResponse) Reset() { *x = MoveToPositionResponse{} mi := &file_proto_scene_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *MoveToPositionResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*MoveToPositionResponse) ProtoMessage() {} func (x *MoveToPositionResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MoveToPositionResponse.ProtoReflect.Descriptor instead. func (*MoveToPositionResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{7} } func (x *MoveToPositionResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *MoveToPositionResponse) GetNewPosition() *common.Position { if x != nil { return x.NewPosition } return nil } func (x *MoveToPositionResponse) GetActualSpeed() float32 { if x != nil { return x.ActualSpeed } return 0 } func (x *MoveToPositionResponse) GetMovementTime() int64 { if x != nil { return x.MovementTime } return 0 } // 与对象交互请求 type InteractWithObjectRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SceneId string `protobuf:"bytes,2,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` ObjectId string `protobuf:"bytes,3,opt,name=object_id,json=objectId,proto3" json:"object_id,omitempty"` InteractionType InteractionType `protobuf:"varint,4,opt,name=interaction_type,json=interactionType,proto3,enum=greatestworks.scene.InteractionType" json:"interaction_type,omitempty"` Parameters map[string]string `protobuf:"bytes,5,rep,name=parameters,proto3" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InteractWithObjectRequest) Reset() { *x = InteractWithObjectRequest{} mi := &file_proto_scene_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InteractWithObjectRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*InteractWithObjectRequest) ProtoMessage() {} func (x *InteractWithObjectRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InteractWithObjectRequest.ProtoReflect.Descriptor instead. func (*InteractWithObjectRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{8} } func (x *InteractWithObjectRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *InteractWithObjectRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *InteractWithObjectRequest) GetObjectId() string { if x != nil { return x.ObjectId } return "" } func (x *InteractWithObjectRequest) GetInteractionType() InteractionType { if x != nil { return x.InteractionType } return InteractionType_INTERACTION_TYPE_UNSPECIFIED } func (x *InteractWithObjectRequest) GetParameters() map[string]string { if x != nil { return x.Parameters } return nil } // 与对象交互响应 type InteractWithObjectResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Result *InteractionResult `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` TriggeredEvents []*SceneEvent `protobuf:"bytes,3,rep,name=triggered_events,json=triggeredEvents,proto3" json:"triggered_events,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InteractWithObjectResponse) Reset() { *x = InteractWithObjectResponse{} mi := &file_proto_scene_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InteractWithObjectResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*InteractWithObjectResponse) ProtoMessage() {} func (x *InteractWithObjectResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InteractWithObjectResponse.ProtoReflect.Descriptor instead. func (*InteractWithObjectResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{9} } func (x *InteractWithObjectResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *InteractWithObjectResponse) GetResult() *InteractionResult { if x != nil { return x.Result } return nil } func (x *InteractWithObjectResponse) GetTriggeredEvents() []*SceneEvent { if x != nil { return x.TriggeredEvents } return nil } // 获取场景中玩家列表请求 type GetPlayersInSceneRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SceneId string `protobuf:"bytes,1,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` RequesterId string `protobuf:"bytes,2,opt,name=requester_id,json=requesterId,proto3" json:"requester_id,omitempty"` Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,4,opt,name=offset,proto3" json:"offset,omitempty"` Radius float32 `protobuf:"fixed32,5,opt,name=radius,proto3" json:"radius,omitempty"` // 搜索半径(以请求者为中心) CenterPosition *common.Position `protobuf:"bytes,6,opt,name=center_position,json=centerPosition,proto3" json:"center_position,omitempty"` // 搜索中心点 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPlayersInSceneRequest) Reset() { *x = GetPlayersInSceneRequest{} mi := &file_proto_scene_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPlayersInSceneRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPlayersInSceneRequest) ProtoMessage() {} func (x *GetPlayersInSceneRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPlayersInSceneRequest.ProtoReflect.Descriptor instead. func (*GetPlayersInSceneRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{10} } func (x *GetPlayersInSceneRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *GetPlayersInSceneRequest) GetRequesterId() string { if x != nil { return x.RequesterId } return "" } func (x *GetPlayersInSceneRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetPlayersInSceneRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } func (x *GetPlayersInSceneRequest) GetRadius() float32 { if x != nil { return x.Radius } return 0 } func (x *GetPlayersInSceneRequest) GetCenterPosition() *common.Position { if x != nil { return x.CenterPosition } return nil } // 获取场景中玩家列表响应 type GetPlayersInSceneResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Players []*ScenePlayer `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetPlayersInSceneResponse) Reset() { *x = GetPlayersInSceneResponse{} mi := &file_proto_scene_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetPlayersInSceneResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetPlayersInSceneResponse) ProtoMessage() {} func (x *GetPlayersInSceneResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetPlayersInSceneResponse.ProtoReflect.Descriptor instead. func (*GetPlayersInSceneResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{11} } func (x *GetPlayersInSceneResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetPlayersInSceneResponse) GetPlayers() []*ScenePlayer { if x != nil { return x.Players } return nil } func (x *GetPlayersInSceneResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 获取场景对象请求 type GetSceneObjectsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SceneId string `protobuf:"bytes,1,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` ObjectType ObjectType `protobuf:"varint,3,opt,name=object_type,json=objectType,proto3,enum=greatestworks.scene.ObjectType" json:"object_type,omitempty"` Radius float32 `protobuf:"fixed32,4,opt,name=radius,proto3" json:"radius,omitempty"` // 搜索半径 CenterPosition *common.Position `protobuf:"bytes,5,opt,name=center_position,json=centerPosition,proto3" json:"center_position,omitempty"` InteractiveOnly bool `protobuf:"varint,6,opt,name=interactive_only,json=interactiveOnly,proto3" json:"interactive_only,omitempty"` // 只返回可交互对象 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSceneObjectsRequest) Reset() { *x = GetSceneObjectsRequest{} mi := &file_proto_scene_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSceneObjectsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSceneObjectsRequest) ProtoMessage() {} func (x *GetSceneObjectsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSceneObjectsRequest.ProtoReflect.Descriptor instead. func (*GetSceneObjectsRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{12} } func (x *GetSceneObjectsRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *GetSceneObjectsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *GetSceneObjectsRequest) GetObjectType() ObjectType { if x != nil { return x.ObjectType } return ObjectType_OBJECT_TYPE_UNSPECIFIED } func (x *GetSceneObjectsRequest) GetRadius() float32 { if x != nil { return x.Radius } return 0 } func (x *GetSceneObjectsRequest) GetCenterPosition() *common.Position { if x != nil { return x.CenterPosition } return nil } func (x *GetSceneObjectsRequest) GetInteractiveOnly() bool { if x != nil { return x.InteractiveOnly } return false } // 获取场景对象响应 type GetSceneObjectsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Objects []*SceneObject `protobuf:"bytes,2,rep,name=objects,proto3" json:"objects,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSceneObjectsResponse) Reset() { *x = GetSceneObjectsResponse{} mi := &file_proto_scene_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSceneObjectsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSceneObjectsResponse) ProtoMessage() {} func (x *GetSceneObjectsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSceneObjectsResponse.ProtoReflect.Descriptor instead. func (*GetSceneObjectsResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{13} } func (x *GetSceneObjectsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetSceneObjectsResponse) GetObjects() []*SceneObject { if x != nil { return x.Objects } return nil } // 触发场景事件请求 type TriggerSceneEventRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SceneId string `protobuf:"bytes,2,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` EventId string `protobuf:"bytes,3,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` TriggerId string `protobuf:"bytes,4,opt,name=trigger_id,json=triggerId,proto3" json:"trigger_id,omitempty"` // 触发器ID EventData map[string]string `protobuf:"bytes,5,rep,name=event_data,json=eventData,proto3" json:"event_data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TriggerSceneEventRequest) Reset() { *x = TriggerSceneEventRequest{} mi := &file_proto_scene_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TriggerSceneEventRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TriggerSceneEventRequest) ProtoMessage() {} func (x *TriggerSceneEventRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TriggerSceneEventRequest.ProtoReflect.Descriptor instead. func (*TriggerSceneEventRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{14} } func (x *TriggerSceneEventRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *TriggerSceneEventRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *TriggerSceneEventRequest) GetEventId() string { if x != nil { return x.EventId } return "" } func (x *TriggerSceneEventRequest) GetTriggerId() string { if x != nil { return x.TriggerId } return "" } func (x *TriggerSceneEventRequest) GetEventData() map[string]string { if x != nil { return x.EventData } return nil } // 触发场景事件响应 type TriggerSceneEventResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Event *SceneEvent `protobuf:"bytes,2,opt,name=event,proto3" json:"event,omitempty"` Effects []*SceneEventEffect `protobuf:"bytes,3,rep,name=effects,proto3" json:"effects,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TriggerSceneEventResponse) Reset() { *x = TriggerSceneEventResponse{} mi := &file_proto_scene_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TriggerSceneEventResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*TriggerSceneEventResponse) ProtoMessage() {} func (x *TriggerSceneEventResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TriggerSceneEventResponse.ProtoReflect.Descriptor instead. func (*TriggerSceneEventResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{15} } func (x *TriggerSceneEventResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *TriggerSceneEventResponse) GetEvent() *SceneEvent { if x != nil { return x.Event } return nil } func (x *TriggerSceneEventResponse) GetEffects() []*SceneEventEffect { if x != nil { return x.Effects } return nil } // 获取可用场景列表请求 type GetAvailableScenesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` SceneType SceneType `protobuf:"varint,2,opt,name=scene_type,json=sceneType,proto3,enum=greatestworks.scene.SceneType" json:"scene_type,omitempty"` MinLevel int32 `protobuf:"varint,3,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` MaxLevel int32 `protobuf:"varint,4,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` OnlyUnlocked bool `protobuf:"varint,5,opt,name=only_unlocked,json=onlyUnlocked,proto3" json:"only_unlocked,omitempty"` Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,7,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetAvailableScenesRequest) Reset() { *x = GetAvailableScenesRequest{} mi := &file_proto_scene_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetAvailableScenesRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetAvailableScenesRequest) ProtoMessage() {} func (x *GetAvailableScenesRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetAvailableScenesRequest.ProtoReflect.Descriptor instead. func (*GetAvailableScenesRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{16} } func (x *GetAvailableScenesRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *GetAvailableScenesRequest) GetSceneType() SceneType { if x != nil { return x.SceneType } return SceneType_SCENE_TYPE_UNSPECIFIED } func (x *GetAvailableScenesRequest) GetMinLevel() int32 { if x != nil { return x.MinLevel } return 0 } func (x *GetAvailableScenesRequest) GetMaxLevel() int32 { if x != nil { return x.MaxLevel } return 0 } func (x *GetAvailableScenesRequest) GetOnlyUnlocked() bool { if x != nil { return x.OnlyUnlocked } return false } func (x *GetAvailableScenesRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *GetAvailableScenesRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 获取可用场景列表响应 type GetAvailableScenesResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Scenes []*SceneInfo `protobuf:"bytes,2,rep,name=scenes,proto3" json:"scenes,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetAvailableScenesResponse) Reset() { *x = GetAvailableScenesResponse{} mi := &file_proto_scene_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetAvailableScenesResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetAvailableScenesResponse) ProtoMessage() {} func (x *GetAvailableScenesResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetAvailableScenesResponse.ProtoReflect.Descriptor instead. func (*GetAvailableScenesResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{17} } func (x *GetAvailableScenesResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetAvailableScenesResponse) GetScenes() []*SceneInfo { if x != nil { return x.Scenes } return nil } func (x *GetAvailableScenesResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 传送到场景请求 type TeleportToSceneRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` TargetSceneId string `protobuf:"bytes,2,opt,name=target_scene_id,json=targetSceneId,proto3" json:"target_scene_id,omitempty"` TeleportPointId string `protobuf:"bytes,3,opt,name=teleport_point_id,json=teleportPointId,proto3" json:"teleport_point_id,omitempty"` // 传送点ID UseItem bool `protobuf:"varint,4,opt,name=use_item,json=useItem,proto3" json:"use_item,omitempty"` // 是否使用传送道具 ItemId string `protobuf:"bytes,5,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` // 传送道具ID unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeleportToSceneRequest) Reset() { *x = TeleportToSceneRequest{} mi := &file_proto_scene_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeleportToSceneRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeleportToSceneRequest) ProtoMessage() {} func (x *TeleportToSceneRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeleportToSceneRequest.ProtoReflect.Descriptor instead. func (*TeleportToSceneRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{18} } func (x *TeleportToSceneRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *TeleportToSceneRequest) GetTargetSceneId() string { if x != nil { return x.TargetSceneId } return "" } func (x *TeleportToSceneRequest) GetTeleportPointId() string { if x != nil { return x.TeleportPointId } return "" } func (x *TeleportToSceneRequest) GetUseItem() bool { if x != nil { return x.UseItem } return false } func (x *TeleportToSceneRequest) GetItemId() string { if x != nil { return x.ItemId } return "" } // 传送到场景响应 type TeleportToSceneResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` TargetSceneId string `protobuf:"bytes,2,opt,name=target_scene_id,json=targetSceneId,proto3" json:"target_scene_id,omitempty"` SpawnPosition *common.Position `protobuf:"bytes,3,opt,name=spawn_position,json=spawnPosition,proto3" json:"spawn_position,omitempty"` Cost int32 `protobuf:"varint,4,opt,name=cost,proto3" json:"cost,omitempty"` // 传送费用 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeleportToSceneResponse) Reset() { *x = TeleportToSceneResponse{} mi := &file_proto_scene_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeleportToSceneResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeleportToSceneResponse) ProtoMessage() {} func (x *TeleportToSceneResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeleportToSceneResponse.ProtoReflect.Descriptor instead. func (*TeleportToSceneResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{19} } func (x *TeleportToSceneResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *TeleportToSceneResponse) GetTargetSceneId() string { if x != nil { return x.TargetSceneId } return "" } func (x *TeleportToSceneResponse) GetSpawnPosition() *common.Position { if x != nil { return x.SpawnPosition } return nil } func (x *TeleportToSceneResponse) GetCost() int32 { if x != nil { return x.Cost } return 0 } // 设置天气请求 type SetWeatherRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SceneId string `protobuf:"bytes,1,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` AdminId string `protobuf:"bytes,2,opt,name=admin_id,json=adminId,proto3" json:"admin_id,omitempty"` // 管理员ID WeatherType WeatherType `protobuf:"varint,3,opt,name=weather_type,json=weatherType,proto3,enum=greatestworks.scene.WeatherType" json:"weather_type,omitempty"` Intensity int32 `protobuf:"varint,4,opt,name=intensity,proto3" json:"intensity,omitempty"` // 强度 (0-100) Duration int32 `protobuf:"varint,5,opt,name=duration,proto3" json:"duration,omitempty"` // 持续时间(秒) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetWeatherRequest) Reset() { *x = SetWeatherRequest{} mi := &file_proto_scene_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetWeatherRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetWeatherRequest) ProtoMessage() {} func (x *SetWeatherRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetWeatherRequest.ProtoReflect.Descriptor instead. func (*SetWeatherRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{20} } func (x *SetWeatherRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *SetWeatherRequest) GetAdminId() string { if x != nil { return x.AdminId } return "" } func (x *SetWeatherRequest) GetWeatherType() WeatherType { if x != nil { return x.WeatherType } return WeatherType_WEATHER_TYPE_UNSPECIFIED } func (x *SetWeatherRequest) GetIntensity() int32 { if x != nil { return x.Intensity } return 0 } func (x *SetWeatherRequest) GetDuration() int32 { if x != nil { return x.Duration } return 0 } // 设置天气响应 type SetWeatherResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewEnvironment *SceneEnvironment `protobuf:"bytes,2,opt,name=new_environment,json=newEnvironment,proto3" json:"new_environment,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SetWeatherResponse) Reset() { *x = SetWeatherResponse{} mi := &file_proto_scene_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SetWeatherResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SetWeatherResponse) ProtoMessage() {} func (x *SetWeatherResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SetWeatherResponse.ProtoReflect.Descriptor instead. func (*SetWeatherResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{21} } func (x *SetWeatherResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SetWeatherResponse) GetNewEnvironment() *SceneEnvironment { if x != nil { return x.NewEnvironment } return nil } // 获取场景统计请求 type GetSceneStatsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SceneId string `protobuf:"bytes,1,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` AdminId string `protobuf:"bytes,2,opt,name=admin_id,json=adminId,proto3" json:"admin_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSceneStatsRequest) Reset() { *x = GetSceneStatsRequest{} mi := &file_proto_scene_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSceneStatsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSceneStatsRequest) ProtoMessage() {} func (x *GetSceneStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSceneStatsRequest.ProtoReflect.Descriptor instead. func (*GetSceneStatsRequest) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{22} } func (x *GetSceneStatsRequest) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *GetSceneStatsRequest) GetAdminId() string { if x != nil { return x.AdminId } return "" } // 获取场景统计响应 type GetSceneStatsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Stats *SceneStats `protobuf:"bytes,2,opt,name=stats,proto3" json:"stats,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetSceneStatsResponse) Reset() { *x = GetSceneStatsResponse{} mi := &file_proto_scene_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetSceneStatsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetSceneStatsResponse) ProtoMessage() {} func (x *GetSceneStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetSceneStatsResponse.ProtoReflect.Descriptor instead. func (*GetSceneStatsResponse) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{23} } func (x *GetSceneStatsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetSceneStatsResponse) GetStats() *SceneStats { if x != nil { return x.Stats } return nil } // 场景信息 type SceneInfo struct { state protoimpl.MessageState `protogen:"open.v1"` SceneId string `protobuf:"bytes,1,opt,name=scene_id,json=sceneId,proto3" json:"scene_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` SceneType SceneType `protobuf:"varint,4,opt,name=scene_type,json=sceneType,proto3,enum=greatestworks.scene.SceneType" json:"scene_type,omitempty"` Status SceneStatus `protobuf:"varint,5,opt,name=status,proto3,enum=greatestworks.scene.SceneStatus" json:"status,omitempty"` MinLevel int32 `protobuf:"varint,6,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` MaxLevel int32 `protobuf:"varint,7,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` MaxPlayers int32 `protobuf:"varint,8,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` CurrentPlayers int32 `protobuf:"varint,9,opt,name=current_players,json=currentPlayers,proto3" json:"current_players,omitempty"` SpawnPoint *common.Position `protobuf:"bytes,10,opt,name=spawn_point,json=spawnPoint,proto3" json:"spawn_point,omitempty"` TeleportPoints []*TeleportPoint `protobuf:"bytes,11,rep,name=teleport_points,json=teleportPoints,proto3" json:"teleport_points,omitempty"` Settings *SceneSettings `protobuf:"bytes,12,opt,name=settings,proto3" json:"settings,omitempty"` CreatedAt int64 `protobuf:"varint,13,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastUpdated int64 `protobuf:"varint,14,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` Version string `protobuf:"bytes,15,opt,name=version,proto3" json:"version,omitempty"` Metadata map[string]string `protobuf:"bytes,16,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneInfo) Reset() { *x = SceneInfo{} mi := &file_proto_scene_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneInfo) ProtoMessage() {} func (x *SceneInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneInfo.ProtoReflect.Descriptor instead. func (*SceneInfo) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{24} } func (x *SceneInfo) GetSceneId() string { if x != nil { return x.SceneId } return "" } func (x *SceneInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *SceneInfo) GetDescription() string { if x != nil { return x.Description } return "" } func (x *SceneInfo) GetSceneType() SceneType { if x != nil { return x.SceneType } return SceneType_SCENE_TYPE_UNSPECIFIED } func (x *SceneInfo) GetStatus() SceneStatus { if x != nil { return x.Status } return SceneStatus_SCENE_STATUS_UNSPECIFIED } func (x *SceneInfo) GetMinLevel() int32 { if x != nil { return x.MinLevel } return 0 } func (x *SceneInfo) GetMaxLevel() int32 { if x != nil { return x.MaxLevel } return 0 } func (x *SceneInfo) GetMaxPlayers() int32 { if x != nil { return x.MaxPlayers } return 0 } func (x *SceneInfo) GetCurrentPlayers() int32 { if x != nil { return x.CurrentPlayers } return 0 } func (x *SceneInfo) GetSpawnPoint() *common.Position { if x != nil { return x.SpawnPoint } return nil } func (x *SceneInfo) GetTeleportPoints() []*TeleportPoint { if x != nil { return x.TeleportPoints } return nil } func (x *SceneInfo) GetSettings() *SceneSettings { if x != nil { return x.Settings } return nil } func (x *SceneInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *SceneInfo) GetLastUpdated() int64 { if x != nil { return x.LastUpdated } return 0 } func (x *SceneInfo) GetVersion() string { if x != nil { return x.Version } return "" } func (x *SceneInfo) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } // 场景玩家 type ScenePlayer struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` PlayerName string `protobuf:"bytes,2,opt,name=player_name,json=playerName,proto3" json:"player_name,omitempty"` Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` Position *common.Position `protobuf:"bytes,4,opt,name=position,proto3" json:"position,omitempty"` State PlayerState `protobuf:"varint,5,opt,name=state,proto3,enum=greatestworks.scene.PlayerState" json:"state,omitempty"` IsVisible bool `protobuf:"varint,6,opt,name=is_visible,json=isVisible,proto3" json:"is_visible,omitempty"` CurrentActivity string `protobuf:"bytes,7,opt,name=current_activity,json=currentActivity,proto3" json:"current_activity,omitempty"` // 当前活动 EnteredAt int64 `protobuf:"varint,8,opt,name=entered_at,json=enteredAt,proto3" json:"entered_at,omitempty"` LastUpdate int64 `protobuf:"varint,9,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` PlayerData map[string]string `protobuf:"bytes,10,rep,name=player_data,json=playerData,proto3" json:"player_data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ScenePlayer) Reset() { *x = ScenePlayer{} mi := &file_proto_scene_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ScenePlayer) String() string { return protoimpl.X.MessageStringOf(x) } func (*ScenePlayer) ProtoMessage() {} func (x *ScenePlayer) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ScenePlayer.ProtoReflect.Descriptor instead. func (*ScenePlayer) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{25} } func (x *ScenePlayer) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *ScenePlayer) GetPlayerName() string { if x != nil { return x.PlayerName } return "" } func (x *ScenePlayer) GetLevel() int32 { if x != nil { return x.Level } return 0 } func (x *ScenePlayer) GetPosition() *common.Position { if x != nil { return x.Position } return nil } func (x *ScenePlayer) GetState() PlayerState { if x != nil { return x.State } return PlayerState_PLAYER_STATE_UNSPECIFIED } func (x *ScenePlayer) GetIsVisible() bool { if x != nil { return x.IsVisible } return false } func (x *ScenePlayer) GetCurrentActivity() string { if x != nil { return x.CurrentActivity } return "" } func (x *ScenePlayer) GetEnteredAt() int64 { if x != nil { return x.EnteredAt } return 0 } func (x *ScenePlayer) GetLastUpdate() int64 { if x != nil { return x.LastUpdate } return 0 } func (x *ScenePlayer) GetPlayerData() map[string]string { if x != nil { return x.PlayerData } return nil } // 场景对象 type SceneObject struct { state protoimpl.MessageState `protogen:"open.v1"` ObjectId string `protobuf:"bytes,1,opt,name=object_id,json=objectId,proto3" json:"object_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` ObjectType ObjectType `protobuf:"varint,3,opt,name=object_type,json=objectType,proto3,enum=greatestworks.scene.ObjectType" json:"object_type,omitempty"` State ObjectState `protobuf:"varint,4,opt,name=state,proto3,enum=greatestworks.scene.ObjectState" json:"state,omitempty"` Position *common.Position `protobuf:"bytes,5,opt,name=position,proto3" json:"position,omitempty"` RotationY float32 `protobuf:"fixed32,6,opt,name=rotation_y,json=rotationY,proto3" json:"rotation_y,omitempty"` // Y轴旋转角度 IsInteractive bool `protobuf:"varint,7,opt,name=is_interactive,json=isInteractive,proto3" json:"is_interactive,omitempty"` IsVisible bool `protobuf:"varint,8,opt,name=is_visible,json=isVisible,proto3" json:"is_visible,omitempty"` AvailableInteractions []InteractionType `protobuf:"varint,9,rep,packed,name=available_interactions,json=availableInteractions,proto3,enum=greatestworks.scene.InteractionType" json:"available_interactions,omitempty"` Properties map[string]string `protobuf:"bytes,10,rep,name=properties,proto3" json:"properties,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastUpdated int64 `protobuf:"varint,12,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneObject) Reset() { *x = SceneObject{} mi := &file_proto_scene_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneObject) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneObject) ProtoMessage() {} func (x *SceneObject) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneObject.ProtoReflect.Descriptor instead. func (*SceneObject) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{26} } func (x *SceneObject) GetObjectId() string { if x != nil { return x.ObjectId } return "" } func (x *SceneObject) GetName() string { if x != nil { return x.Name } return "" } func (x *SceneObject) GetObjectType() ObjectType { if x != nil { return x.ObjectType } return ObjectType_OBJECT_TYPE_UNSPECIFIED } func (x *SceneObject) GetState() ObjectState { if x != nil { return x.State } return ObjectState_OBJECT_STATE_UNSPECIFIED } func (x *SceneObject) GetPosition() *common.Position { if x != nil { return x.Position } return nil } func (x *SceneObject) GetRotationY() float32 { if x != nil { return x.RotationY } return 0 } func (x *SceneObject) GetIsInteractive() bool { if x != nil { return x.IsInteractive } return false } func (x *SceneObject) GetIsVisible() bool { if x != nil { return x.IsVisible } return false } func (x *SceneObject) GetAvailableInteractions() []InteractionType { if x != nil { return x.AvailableInteractions } return nil } func (x *SceneObject) GetProperties() map[string]string { if x != nil { return x.Properties } return nil } func (x *SceneObject) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *SceneObject) GetLastUpdated() int64 { if x != nil { return x.LastUpdated } return 0 } // 场景环境 type SceneEnvironment struct { state protoimpl.MessageState `protogen:"open.v1"` Weather WeatherType `protobuf:"varint,1,opt,name=weather,proto3,enum=greatestworks.scene.WeatherType" json:"weather,omitempty"` WeatherIntensity int32 `protobuf:"varint,2,opt,name=weather_intensity,json=weatherIntensity,proto3" json:"weather_intensity,omitempty"` TimeOfDay TimeOfDay `protobuf:"varint,3,opt,name=time_of_day,json=timeOfDay,proto3,enum=greatestworks.scene.TimeOfDay" json:"time_of_day,omitempty"` AmbientLight float32 `protobuf:"fixed32,4,opt,name=ambient_light,json=ambientLight,proto3" json:"ambient_light,omitempty"` // 环境光强度 (0.0-1.0) Temperature float32 `protobuf:"fixed32,5,opt,name=temperature,proto3" json:"temperature,omitempty"` // 温度 Humidity float32 `protobuf:"fixed32,6,opt,name=humidity,proto3" json:"humidity,omitempty"` // 湿度 BackgroundMusic string `protobuf:"bytes,7,opt,name=background_music,json=backgroundMusic,proto3" json:"background_music,omitempty"` Effects []*EnvironmentalEffect `protobuf:"bytes,8,rep,name=effects,proto3" json:"effects,omitempty"` CustomSettings map[string]string `protobuf:"bytes,9,rep,name=custom_settings,json=customSettings,proto3" json:"custom_settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneEnvironment) Reset() { *x = SceneEnvironment{} mi := &file_proto_scene_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneEnvironment) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneEnvironment) ProtoMessage() {} func (x *SceneEnvironment) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneEnvironment.ProtoReflect.Descriptor instead. func (*SceneEnvironment) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{27} } func (x *SceneEnvironment) GetWeather() WeatherType { if x != nil { return x.Weather } return WeatherType_WEATHER_TYPE_UNSPECIFIED } func (x *SceneEnvironment) GetWeatherIntensity() int32 { if x != nil { return x.WeatherIntensity } return 0 } func (x *SceneEnvironment) GetTimeOfDay() TimeOfDay { if x != nil { return x.TimeOfDay } return TimeOfDay_TIME_OF_DAY_UNSPECIFIED } func (x *SceneEnvironment) GetAmbientLight() float32 { if x != nil { return x.AmbientLight } return 0 } func (x *SceneEnvironment) GetTemperature() float32 { if x != nil { return x.Temperature } return 0 } func (x *SceneEnvironment) GetHumidity() float32 { if x != nil { return x.Humidity } return 0 } func (x *SceneEnvironment) GetBackgroundMusic() string { if x != nil { return x.BackgroundMusic } return "" } func (x *SceneEnvironment) GetEffects() []*EnvironmentalEffect { if x != nil { return x.Effects } return nil } func (x *SceneEnvironment) GetCustomSettings() map[string]string { if x != nil { return x.CustomSettings } return nil } // 传送点 type TeleportPoint struct { state protoimpl.MessageState `protogen:"open.v1"` PointId string `protobuf:"bytes,1,opt,name=point_id,json=pointId,proto3" json:"point_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Position *common.Position `protobuf:"bytes,3,opt,name=position,proto3" json:"position,omitempty"` IsActive bool `protobuf:"varint,4,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"` RequiresDiscovery bool `protobuf:"varint,5,opt,name=requires_discovery,json=requiresDiscovery,proto3" json:"requires_discovery,omitempty"` // 需要发现才能使用 Cost int32 `protobuf:"varint,6,opt,name=cost,proto3" json:"cost,omitempty"` // 传送费用 RequiredItems []string `protobuf:"bytes,7,rep,name=required_items,json=requiredItems,proto3" json:"required_items,omitempty"` // 需要的物品 MinLevel int32 `protobuf:"varint,8,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` // 最低等级要求 Description string `protobuf:"bytes,9,opt,name=description,proto3" json:"description,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeleportPoint) Reset() { *x = TeleportPoint{} mi := &file_proto_scene_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeleportPoint) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeleportPoint) ProtoMessage() {} func (x *TeleportPoint) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeleportPoint.ProtoReflect.Descriptor instead. func (*TeleportPoint) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{28} } func (x *TeleportPoint) GetPointId() string { if x != nil { return x.PointId } return "" } func (x *TeleportPoint) GetName() string { if x != nil { return x.Name } return "" } func (x *TeleportPoint) GetPosition() *common.Position { if x != nil { return x.Position } return nil } func (x *TeleportPoint) GetIsActive() bool { if x != nil { return x.IsActive } return false } func (x *TeleportPoint) GetRequiresDiscovery() bool { if x != nil { return x.RequiresDiscovery } return false } func (x *TeleportPoint) GetCost() int32 { if x != nil { return x.Cost } return 0 } func (x *TeleportPoint) GetRequiredItems() []string { if x != nil { return x.RequiredItems } return nil } func (x *TeleportPoint) GetMinLevel() int32 { if x != nil { return x.MinLevel } return 0 } func (x *TeleportPoint) GetDescription() string { if x != nil { return x.Description } return "" } // 场景设置 type SceneSettings struct { state protoimpl.MessageState `protogen:"open.v1"` PvpEnabled bool `protobuf:"varint,1,opt,name=pvp_enabled,json=pvpEnabled,proto3" json:"pvp_enabled,omitempty"` // 是否允许PVP RespawnEnabled bool `protobuf:"varint,2,opt,name=respawn_enabled,json=respawnEnabled,proto3" json:"respawn_enabled,omitempty"` // 是否允许复活 RespawnTime int32 `protobuf:"varint,3,opt,name=respawn_time,json=respawnTime,proto3" json:"respawn_time,omitempty"` // 复活时间(秒) DropItemsOnDeath bool `protobuf:"varint,4,opt,name=drop_items_on_death,json=dropItemsOnDeath,proto3" json:"drop_items_on_death,omitempty"` // 死亡是否掉落物品 ExperienceMultiplier float32 `protobuf:"fixed32,5,opt,name=experience_multiplier,json=experienceMultiplier,proto3" json:"experience_multiplier,omitempty"` // 经验倍率 DropRateMultiplier float32 `protobuf:"fixed32,6,opt,name=drop_rate_multiplier,json=dropRateMultiplier,proto3" json:"drop_rate_multiplier,omitempty"` // 掉落率倍率 SafeZone bool `protobuf:"varint,7,opt,name=safe_zone,json=safeZone,proto3" json:"safe_zone,omitempty"` // 是否为安全区 AllowFlying bool `protobuf:"varint,8,opt,name=allow_flying,json=allowFlying,proto3" json:"allow_flying,omitempty"` // 是否允许飞行 AllowMount bool `protobuf:"varint,9,opt,name=allow_mount,json=allowMount,proto3" json:"allow_mount,omitempty"` // 是否允许坐骑 IdleTimeout int32 `protobuf:"varint,10,opt,name=idle_timeout,json=idleTimeout,proto3" json:"idle_timeout,omitempty"` // 闲置超时时间(秒) MovementModifiers map[string]float32 `protobuf:"bytes,11,rep,name=movement_modifiers,json=movementModifiers,proto3" json:"movement_modifiers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` // 移动修正器 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneSettings) Reset() { *x = SceneSettings{} mi := &file_proto_scene_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneSettings) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneSettings) ProtoMessage() {} func (x *SceneSettings) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneSettings.ProtoReflect.Descriptor instead. func (*SceneSettings) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{29} } func (x *SceneSettings) GetPvpEnabled() bool { if x != nil { return x.PvpEnabled } return false } func (x *SceneSettings) GetRespawnEnabled() bool { if x != nil { return x.RespawnEnabled } return false } func (x *SceneSettings) GetRespawnTime() int32 { if x != nil { return x.RespawnTime } return 0 } func (x *SceneSettings) GetDropItemsOnDeath() bool { if x != nil { return x.DropItemsOnDeath } return false } func (x *SceneSettings) GetExperienceMultiplier() float32 { if x != nil { return x.ExperienceMultiplier } return 0 } func (x *SceneSettings) GetDropRateMultiplier() float32 { if x != nil { return x.DropRateMultiplier } return 0 } func (x *SceneSettings) GetSafeZone() bool { if x != nil { return x.SafeZone } return false } func (x *SceneSettings) GetAllowFlying() bool { if x != nil { return x.AllowFlying } return false } func (x *SceneSettings) GetAllowMount() bool { if x != nil { return x.AllowMount } return false } func (x *SceneSettings) GetIdleTimeout() int32 { if x != nil { return x.IdleTimeout } return 0 } func (x *SceneSettings) GetMovementModifiers() map[string]float32 { if x != nil { return x.MovementModifiers } return nil } // 场景事件 type SceneEvent struct { state protoimpl.MessageState `protogen:"open.v1"` EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` EventName string `protobuf:"bytes,2,opt,name=event_name,json=eventName,proto3" json:"event_name,omitempty"` EventType EventType `protobuf:"varint,3,opt,name=event_type,json=eventType,proto3,enum=greatestworks.scene.EventType" json:"event_type,omitempty"` TriggerPlayerId string `protobuf:"bytes,4,opt,name=trigger_player_id,json=triggerPlayerId,proto3" json:"trigger_player_id,omitempty"` TriggerObjectId string `protobuf:"bytes,5,opt,name=trigger_object_id,json=triggerObjectId,proto3" json:"trigger_object_id,omitempty"` Location *common.Position `protobuf:"bytes,6,opt,name=location,proto3" json:"location,omitempty"` Timestamp int64 `protobuf:"varint,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"` EventData map[string]string `protobuf:"bytes,8,rep,name=event_data,json=eventData,proto3" json:"event_data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` AffectedPlayerIds []string `protobuf:"bytes,9,rep,name=affected_player_ids,json=affectedPlayerIds,proto3" json:"affected_player_ids,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneEvent) Reset() { *x = SceneEvent{} mi := &file_proto_scene_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneEvent) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneEvent) ProtoMessage() {} func (x *SceneEvent) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneEvent.ProtoReflect.Descriptor instead. func (*SceneEvent) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{30} } func (x *SceneEvent) GetEventId() string { if x != nil { return x.EventId } return "" } func (x *SceneEvent) GetEventName() string { if x != nil { return x.EventName } return "" } func (x *SceneEvent) GetEventType() EventType { if x != nil { return x.EventType } return EventType_EVENT_TYPE_UNSPECIFIED } func (x *SceneEvent) GetTriggerPlayerId() string { if x != nil { return x.TriggerPlayerId } return "" } func (x *SceneEvent) GetTriggerObjectId() string { if x != nil { return x.TriggerObjectId } return "" } func (x *SceneEvent) GetLocation() *common.Position { if x != nil { return x.Location } return nil } func (x *SceneEvent) GetTimestamp() int64 { if x != nil { return x.Timestamp } return 0 } func (x *SceneEvent) GetEventData() map[string]string { if x != nil { return x.EventData } return nil } func (x *SceneEvent) GetAffectedPlayerIds() []string { if x != nil { return x.AffectedPlayerIds } return nil } // 场景事件效果 type SceneEventEffect struct { state protoimpl.MessageState `protogen:"open.v1"` EffectId string `protobuf:"bytes,1,opt,name=effect_id,json=effectId,proto3" json:"effect_id,omitempty"` EffectType EffectType `protobuf:"varint,2,opt,name=effect_type,json=effectType,proto3,enum=greatestworks.scene.EffectType" json:"effect_type,omitempty"` TargetId string `protobuf:"bytes,3,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"` // 目标ID(玩家或对象) Duration int32 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` // 持续时间(秒) Magnitude float32 `protobuf:"fixed32,5,opt,name=magnitude,proto3" json:"magnitude,omitempty"` // 效果强度 Parameters map[string]string `protobuf:"bytes,6,rep,name=parameters,proto3" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneEventEffect) Reset() { *x = SceneEventEffect{} mi := &file_proto_scene_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneEventEffect) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneEventEffect) ProtoMessage() {} func (x *SceneEventEffect) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneEventEffect.ProtoReflect.Descriptor instead. func (*SceneEventEffect) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{31} } func (x *SceneEventEffect) GetEffectId() string { if x != nil { return x.EffectId } return "" } func (x *SceneEventEffect) GetEffectType() EffectType { if x != nil { return x.EffectType } return EffectType_EFFECT_TYPE_UNSPECIFIED } func (x *SceneEventEffect) GetTargetId() string { if x != nil { return x.TargetId } return "" } func (x *SceneEventEffect) GetDuration() int32 { if x != nil { return x.Duration } return 0 } func (x *SceneEventEffect) GetMagnitude() float32 { if x != nil { return x.Magnitude } return 0 } func (x *SceneEventEffect) GetParameters() map[string]string { if x != nil { return x.Parameters } return nil } // 交互结果 type InteractionResult struct { state protoimpl.MessageState `protogen:"open.v1"` Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` Rewards []*ItemReward `protobuf:"bytes,3,rep,name=rewards,proto3" json:"rewards,omitempty"` ExperienceGained int32 `protobuf:"varint,4,opt,name=experience_gained,json=experienceGained,proto3" json:"experience_gained,omitempty"` NewObjectState ObjectState `protobuf:"varint,5,opt,name=new_object_state,json=newObjectState,proto3,enum=greatestworks.scene.ObjectState" json:"new_object_state,omitempty"` ResultData map[string]string `protobuf:"bytes,6,rep,name=result_data,json=resultData,proto3" json:"result_data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InteractionResult) Reset() { *x = InteractionResult{} mi := &file_proto_scene_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InteractionResult) String() string { return protoimpl.X.MessageStringOf(x) } func (*InteractionResult) ProtoMessage() {} func (x *InteractionResult) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InteractionResult.ProtoReflect.Descriptor instead. func (*InteractionResult) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{32} } func (x *InteractionResult) GetSuccess() bool { if x != nil { return x.Success } return false } func (x *InteractionResult) GetMessage() string { if x != nil { return x.Message } return "" } func (x *InteractionResult) GetRewards() []*ItemReward { if x != nil { return x.Rewards } return nil } func (x *InteractionResult) GetExperienceGained() int32 { if x != nil { return x.ExperienceGained } return 0 } func (x *InteractionResult) GetNewObjectState() ObjectState { if x != nil { return x.NewObjectState } return ObjectState_OBJECT_STATE_UNSPECIFIED } func (x *InteractionResult) GetResultData() map[string]string { if x != nil { return x.ResultData } return nil } // 物品奖励 type ItemReward struct { state protoimpl.MessageState `protogen:"open.v1"` ItemId string `protobuf:"bytes,1,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` ItemName string `protobuf:"bytes,2,opt,name=item_name,json=itemName,proto3" json:"item_name,omitempty"` Quantity int32 `protobuf:"varint,3,opt,name=quantity,proto3" json:"quantity,omitempty"` Rarity common.ItemRarity `protobuf:"varint,4,opt,name=rarity,proto3,enum=greatestworks.common.ItemRarity" json:"rarity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ItemReward) Reset() { *x = ItemReward{} mi := &file_proto_scene_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ItemReward) String() string { return protoimpl.X.MessageStringOf(x) } func (*ItemReward) ProtoMessage() {} func (x *ItemReward) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ItemReward.ProtoReflect.Descriptor instead. func (*ItemReward) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{33} } func (x *ItemReward) GetItemId() string { if x != nil { return x.ItemId } return "" } func (x *ItemReward) GetItemName() string { if x != nil { return x.ItemName } return "" } func (x *ItemReward) GetQuantity() int32 { if x != nil { return x.Quantity } return 0 } func (x *ItemReward) GetRarity() common.ItemRarity { if x != nil { return x.Rarity } return common.ItemRarity(0) } // 环境效果 type EnvironmentalEffect struct { state protoimpl.MessageState `protogen:"open.v1"` EffectId string `protobuf:"bytes,1,opt,name=effect_id,json=effectId,proto3" json:"effect_id,omitempty"` EffectType EffectType `protobuf:"varint,2,opt,name=effect_type,json=effectType,proto3,enum=greatestworks.scene.EffectType" json:"effect_type,omitempty"` Intensity float32 `protobuf:"fixed32,3,opt,name=intensity,proto3" json:"intensity,omitempty"` Duration int32 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` // 持续时间(秒),0表示永久 Parameters map[string]string `protobuf:"bytes,5,rep,name=parameters,proto3" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *EnvironmentalEffect) Reset() { *x = EnvironmentalEffect{} mi := &file_proto_scene_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *EnvironmentalEffect) String() string { return protoimpl.X.MessageStringOf(x) } func (*EnvironmentalEffect) ProtoMessage() {} func (x *EnvironmentalEffect) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EnvironmentalEffect.ProtoReflect.Descriptor instead. func (*EnvironmentalEffect) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{34} } func (x *EnvironmentalEffect) GetEffectId() string { if x != nil { return x.EffectId } return "" } func (x *EnvironmentalEffect) GetEffectType() EffectType { if x != nil { return x.EffectType } return EffectType_EFFECT_TYPE_UNSPECIFIED } func (x *EnvironmentalEffect) GetIntensity() float32 { if x != nil { return x.Intensity } return 0 } func (x *EnvironmentalEffect) GetDuration() int32 { if x != nil { return x.Duration } return 0 } func (x *EnvironmentalEffect) GetParameters() map[string]string { if x != nil { return x.Parameters } return nil } // 场景统计 type SceneStats struct { state protoimpl.MessageState `protogen:"open.v1"` TotalVisits int32 `protobuf:"varint,1,opt,name=total_visits,json=totalVisits,proto3" json:"total_visits,omitempty"` UniqueVisitors int32 `protobuf:"varint,2,opt,name=unique_visitors,json=uniqueVisitors,proto3" json:"unique_visitors,omitempty"` CurrentOnline int32 `protobuf:"varint,3,opt,name=current_online,json=currentOnline,proto3" json:"current_online,omitempty"` PeakOnline int32 `protobuf:"varint,4,opt,name=peak_online,json=peakOnline,proto3" json:"peak_online,omitempty"` AverageSessionTime int64 `protobuf:"varint,5,opt,name=average_session_time,json=averageSessionTime,proto3" json:"average_session_time,omitempty"` TotalInteractions int32 `protobuf:"varint,6,opt,name=total_interactions,json=totalInteractions,proto3" json:"total_interactions,omitempty"` TotalEventsTriggered int32 `protobuf:"varint,7,opt,name=total_events_triggered,json=totalEventsTriggered,proto3" json:"total_events_triggered,omitempty"` PopularAreas map[string]int32 `protobuf:"bytes,8,rep,name=popular_areas,json=popularAreas,proto3" json:"popular_areas,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // 热门区域访问量 LastReset int64 `protobuf:"varint,9,opt,name=last_reset,json=lastReset,proto3" json:"last_reset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SceneStats) Reset() { *x = SceneStats{} mi := &file_proto_scene_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SceneStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*SceneStats) ProtoMessage() {} func (x *SceneStats) ProtoReflect() protoreflect.Message { mi := &file_proto_scene_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SceneStats.ProtoReflect.Descriptor instead. func (*SceneStats) Descriptor() ([]byte, []int) { return file_proto_scene_proto_rawDescGZIP(), []int{35} } func (x *SceneStats) GetTotalVisits() int32 { if x != nil { return x.TotalVisits } return 0 } func (x *SceneStats) GetUniqueVisitors() int32 { if x != nil { return x.UniqueVisitors } return 0 } func (x *SceneStats) GetCurrentOnline() int32 { if x != nil { return x.CurrentOnline } return 0 } func (x *SceneStats) GetPeakOnline() int32 { if x != nil { return x.PeakOnline } return 0 } func (x *SceneStats) GetAverageSessionTime() int64 { if x != nil { return x.AverageSessionTime } return 0 } func (x *SceneStats) GetTotalInteractions() int32 { if x != nil { return x.TotalInteractions } return 0 } func (x *SceneStats) GetTotalEventsTriggered() int32 { if x != nil { return x.TotalEventsTriggered } return 0 } func (x *SceneStats) GetPopularAreas() map[string]int32 { if x != nil { return x.PopularAreas } return nil } func (x *SceneStats) GetLastReset() int64 { if x != nil { return x.LastReset } return 0 } var File_proto_scene_proto protoreflect.FileDescriptor const file_proto_scene_proto_rawDesc = "" + "\n" + "\x11proto/scene.proto\x12\x13greatestworks.scene\x1a\x12proto/common.proto\"\xff\x02\n" + "\x11EnterSceneRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bscene_id\x18\x02 \x01(\tR\asceneId\x12E\n" + "\x0espawn_position\x18\x03 \x01(\v2\x1e.greatestworks.common.PositionR\rspawnPosition\x12*\n" + "\x11previous_scene_id\x18\x04 \x01(\tR\x0fpreviousSceneId\x12\x1f\n" + "\ventrance_id\x18\x05 \x01(\tR\n" + "entranceId\x12]\n" + "\rentry_context\x18\x06 \x03(\v28.greatestworks.scene.EnterSceneRequest.EntryContextEntryR\fentryContext\x1a?\n" + "\x11EntryContextEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb1\x03\n" + "\x12EnterSceneResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\n" + "scene_info\x18\x02 \x01(\v2\x1e.greatestworks.scene.SceneInfoR\tsceneInfo\x12G\n" + "\x0fplayer_position\x18\x03 \x01(\v2\x1e.greatestworks.common.PositionR\x0eplayerPosition\x12E\n" + "\rother_players\x18\x04 \x03(\v2 .greatestworks.scene.ScenePlayerR\fotherPlayers\x12E\n" + "\rscene_objects\x18\x05 \x03(\v2 .greatestworks.scene.SceneObjectR\fsceneObjects\x12G\n" + "\venvironment\x18\x06 \x01(\v2%.greatestworks.scene.SceneEnvironmentR\venvironment\"d\n" + "\x11LeaveSceneRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bscene_id\x18\x02 \x01(\tR\asceneId\x12\x17\n" + "\aexit_id\x18\x03 \x01(\tR\x06exitId\"R\n" + "\x12LeaveSceneResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x9f\x01\n" + "\x13GetSceneInfoRequest\x12\x19\n" + "\bscene_id\x18\x01 \x01(\tR\asceneId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\x12'\n" + "\x0finclude_players\x18\x03 \x01(\bR\x0eincludePlayers\x12'\n" + "\x0finclude_objects\x18\x04 \x01(\bR\x0eincludeObjects\"\xd4\x02\n" + "\x14GetSceneInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\n" + "scene_info\x18\x02 \x01(\v2\x1e.greatestworks.scene.SceneInfoR\tsceneInfo\x12:\n" + "\aplayers\x18\x03 \x03(\v2 .greatestworks.scene.ScenePlayerR\aplayers\x12:\n" + "\aobjects\x18\x04 \x03(\v2 .greatestworks.scene.SceneObjectR\aobjects\x12G\n" + "\venvironment\x18\x05 \x01(\v2%.greatestworks.scene.SceneEnvironmentR\venvironment\"\xf6\x01\n" + "\x15MoveToPositionRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bscene_id\x18\x02 \x01(\tR\asceneId\x12G\n" + "\x0ftarget_position\x18\x03 \x01(\v2\x1e.greatestworks.common.PositionR\x0etargetPosition\x12F\n" + "\rmovement_type\x18\x04 \x01(\x0e2!.greatestworks.scene.MovementTypeR\fmovementType\x12\x14\n" + "\x05speed\x18\x05 \x01(\x02R\x05speed\"\xe1\x01\n" + "\x16MoveToPositionResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12A\n" + "\fnew_position\x18\x02 \x01(\v2\x1e.greatestworks.common.PositionR\vnewPosition\x12!\n" + "\factual_speed\x18\x03 \x01(\x02R\vactualSpeed\x12#\n" + "\rmovement_time\x18\x04 \x01(\x03R\fmovementTime\"\xe0\x02\n" + "\x19InteractWithObjectRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bscene_id\x18\x02 \x01(\tR\asceneId\x12\x1b\n" + "\tobject_id\x18\x03 \x01(\tR\bobjectId\x12O\n" + "\x10interaction_type\x18\x04 \x01(\x0e2$.greatestworks.scene.InteractionTypeR\x0finteractionType\x12^\n" + "\n" + "parameters\x18\x05 \x03(\v2>.greatestworks.scene.InteractWithObjectRequest.ParametersEntryR\n" + "parameters\x1a=\n" + "\x0fParametersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe6\x01\n" + "\x1aInteractWithObjectResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12>\n" + "\x06result\x18\x02 \x01(\v2&.greatestworks.scene.InteractionResultR\x06result\x12J\n" + "\x10triggered_events\x18\x03 \x03(\v2\x1f.greatestworks.scene.SceneEventR\x0ftriggeredEvents\"\xe7\x01\n" + "\x18GetPlayersInSceneRequest\x12\x19\n" + "\bscene_id\x18\x01 \x01(\tR\asceneId\x12!\n" + "\frequester_id\x18\x02 \x01(\tR\vrequesterId\x12\x14\n" + "\x05limit\x18\x03 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\x04 \x01(\x05R\x06offset\x12\x16\n" + "\x06radius\x18\x05 \x01(\x02R\x06radius\x12G\n" + "\x0fcenter_position\x18\x06 \x01(\v2\x1e.greatestworks.common.PositionR\x0ecenterPosition\"\xdb\x01\n" + "\x19GetPlayersInSceneResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12:\n" + "\aplayers\x18\x02 \x03(\v2 .greatestworks.scene.ScenePlayerR\aplayers\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\x9e\x02\n" + "\x16GetSceneObjectsRequest\x12\x19\n" + "\bscene_id\x18\x01 \x01(\tR\asceneId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\x12@\n" + "\vobject_type\x18\x03 \x01(\x0e2\x1f.greatestworks.scene.ObjectTypeR\n" + "objectType\x12\x16\n" + "\x06radius\x18\x04 \x01(\x02R\x06radius\x12G\n" + "\x0fcenter_position\x18\x05 \x01(\v2\x1e.greatestworks.common.PositionR\x0ecenterPosition\x12)\n" + "\x10interactive_only\x18\x06 \x01(\bR\x0finteractiveOnly\"\x93\x01\n" + "\x17GetSceneObjectsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12:\n" + "\aobjects\x18\x02 \x03(\v2 .greatestworks.scene.SceneObjectR\aobjects\"\xa7\x02\n" + "\x18TriggerSceneEventRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x19\n" + "\bscene_id\x18\x02 \x01(\tR\asceneId\x12\x19\n" + "\bevent_id\x18\x03 \x01(\tR\aeventId\x12\x1d\n" + "\n" + "trigger_id\x18\x04 \x01(\tR\ttriggerId\x12[\n" + "\n" + "event_data\x18\x05 \x03(\v2<.greatestworks.scene.TriggerSceneEventRequest.EventDataEntryR\teventData\x1a<\n" + "\x0eEventDataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd1\x01\n" + "\x19TriggerSceneEventResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x125\n" + "\x05event\x18\x02 \x01(\v2\x1f.greatestworks.scene.SceneEventR\x05event\x12?\n" + "\aeffects\x18\x03 \x03(\v2%.greatestworks.scene.SceneEventEffectR\aeffects\"\x84\x02\n" + "\x19GetAvailableScenesRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12=\n" + "\n" + "scene_type\x18\x02 \x01(\x0e2\x1e.greatestworks.scene.SceneTypeR\tsceneType\x12\x1b\n" + "\tmin_level\x18\x03 \x01(\x05R\bminLevel\x12\x1b\n" + "\tmax_level\x18\x04 \x01(\x05R\bmaxLevel\x12#\n" + "\ronly_unlocked\x18\x05 \x01(\bR\fonlyUnlocked\x12\x14\n" + "\x05limit\x18\x06 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\a \x01(\x05R\x06offset\"\xd8\x01\n" + "\x1aGetAvailableScenesResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x126\n" + "\x06scenes\x18\x02 \x03(\v2\x1e.greatestworks.scene.SceneInfoR\x06scenes\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\xbd\x01\n" + "\x16TeleportToSceneRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12&\n" + "\x0ftarget_scene_id\x18\x02 \x01(\tR\rtargetSceneId\x12*\n" + "\x11teleport_point_id\x18\x03 \x01(\tR\x0fteleportPointId\x12\x19\n" + "\buse_item\x18\x04 \x01(\bR\auseItem\x12\x17\n" + "\aitem_id\x18\x05 \x01(\tR\x06itemId\"\xda\x01\n" + "\x17TeleportToSceneResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12&\n" + "\x0ftarget_scene_id\x18\x02 \x01(\tR\rtargetSceneId\x12E\n" + "\x0espawn_position\x18\x03 \x01(\v2\x1e.greatestworks.common.PositionR\rspawnPosition\x12\x12\n" + "\x04cost\x18\x04 \x01(\x05R\x04cost\"\xc8\x01\n" + "\x11SetWeatherRequest\x12\x19\n" + "\bscene_id\x18\x01 \x01(\tR\asceneId\x12\x19\n" + "\badmin_id\x18\x02 \x01(\tR\aadminId\x12C\n" + "\fweather_type\x18\x03 \x01(\x0e2 .greatestworks.scene.WeatherTypeR\vweatherType\x12\x1c\n" + "\tintensity\x18\x04 \x01(\x05R\tintensity\x12\x1a\n" + "\bduration\x18\x05 \x01(\x05R\bduration\"\xa2\x01\n" + "\x12SetWeatherResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12N\n" + "\x0fnew_environment\x18\x02 \x01(\v2%.greatestworks.scene.SceneEnvironmentR\x0enewEnvironment\"L\n" + "\x14GetSceneStatsRequest\x12\x19\n" + "\bscene_id\x18\x01 \x01(\tR\asceneId\x12\x19\n" + "\badmin_id\x18\x02 \x01(\tR\aadminId\"\x8c\x01\n" + "\x15GetSceneStatsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x125\n" + "\x05stats\x18\x02 \x01(\v2\x1f.greatestworks.scene.SceneStatsR\x05stats\"\x8a\x06\n" + "\tSceneInfo\x12\x19\n" + "\bscene_id\x18\x01 \x01(\tR\asceneId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x12=\n" + "\n" + "scene_type\x18\x04 \x01(\x0e2\x1e.greatestworks.scene.SceneTypeR\tsceneType\x128\n" + "\x06status\x18\x05 \x01(\x0e2 .greatestworks.scene.SceneStatusR\x06status\x12\x1b\n" + "\tmin_level\x18\x06 \x01(\x05R\bminLevel\x12\x1b\n" + "\tmax_level\x18\a \x01(\x05R\bmaxLevel\x12\x1f\n" + "\vmax_players\x18\b \x01(\x05R\n" + "maxPlayers\x12'\n" + "\x0fcurrent_players\x18\t \x01(\x05R\x0ecurrentPlayers\x12?\n" + "\vspawn_point\x18\n" + " \x01(\v2\x1e.greatestworks.common.PositionR\n" + "spawnPoint\x12K\n" + "\x0fteleport_points\x18\v \x03(\v2\".greatestworks.scene.TeleportPointR\x0eteleportPoints\x12>\n" + "\bsettings\x18\f \x01(\v2\".greatestworks.scene.SceneSettingsR\bsettings\x12\x1d\n" + "\n" + "created_at\x18\r \x01(\x03R\tcreatedAt\x12!\n" + "\flast_updated\x18\x0e \x01(\x03R\vlastUpdated\x12\x18\n" + "\aversion\x18\x0f \x01(\tR\aversion\x12H\n" + "\bmetadata\x18\x10 \x03(\v2,.greatestworks.scene.SceneInfo.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf1\x03\n" + "\vScenePlayer\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x1f\n" + "\vplayer_name\x18\x02 \x01(\tR\n" + "playerName\x12\x14\n" + "\x05level\x18\x03 \x01(\x05R\x05level\x12:\n" + "\bposition\x18\x04 \x01(\v2\x1e.greatestworks.common.PositionR\bposition\x126\n" + "\x05state\x18\x05 \x01(\x0e2 .greatestworks.scene.PlayerStateR\x05state\x12\x1d\n" + "\n" + "is_visible\x18\x06 \x01(\bR\tisVisible\x12)\n" + "\x10current_activity\x18\a \x01(\tR\x0fcurrentActivity\x12\x1d\n" + "\n" + "entered_at\x18\b \x01(\x03R\tenteredAt\x12\x1f\n" + "\vlast_update\x18\t \x01(\x03R\n" + "lastUpdate\x12Q\n" + "\vplayer_data\x18\n" + " \x03(\v20.greatestworks.scene.ScenePlayer.PlayerDataEntryR\n" + "playerData\x1a=\n" + "\x0fPlayerDataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x89\x05\n" + "\vSceneObject\x12\x1b\n" + "\tobject_id\x18\x01 \x01(\tR\bobjectId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12@\n" + "\vobject_type\x18\x03 \x01(\x0e2\x1f.greatestworks.scene.ObjectTypeR\n" + "objectType\x126\n" + "\x05state\x18\x04 \x01(\x0e2 .greatestworks.scene.ObjectStateR\x05state\x12:\n" + "\bposition\x18\x05 \x01(\v2\x1e.greatestworks.common.PositionR\bposition\x12\x1d\n" + "\n" + "rotation_y\x18\x06 \x01(\x02R\trotationY\x12%\n" + "\x0eis_interactive\x18\a \x01(\bR\risInteractive\x12\x1d\n" + "\n" + "is_visible\x18\b \x01(\bR\tisVisible\x12[\n" + "\x16available_interactions\x18\t \x03(\x0e2$.greatestworks.scene.InteractionTypeR\x15availableInteractions\x12P\n" + "\n" + "properties\x18\n" + " \x03(\v20.greatestworks.scene.SceneObject.PropertiesEntryR\n" + "properties\x12\x1d\n" + "\n" + "created_at\x18\v \x01(\x03R\tcreatedAt\x12!\n" + "\flast_updated\x18\f \x01(\x03R\vlastUpdated\x1a=\n" + "\x0fPropertiesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb4\x04\n" + "\x10SceneEnvironment\x12:\n" + "\aweather\x18\x01 \x01(\x0e2 .greatestworks.scene.WeatherTypeR\aweather\x12+\n" + "\x11weather_intensity\x18\x02 \x01(\x05R\x10weatherIntensity\x12>\n" + "\vtime_of_day\x18\x03 \x01(\x0e2\x1e.greatestworks.scene.TimeOfDayR\ttimeOfDay\x12#\n" + "\rambient_light\x18\x04 \x01(\x02R\fambientLight\x12 \n" + "\vtemperature\x18\x05 \x01(\x02R\vtemperature\x12\x1a\n" + "\bhumidity\x18\x06 \x01(\x02R\bhumidity\x12)\n" + "\x10background_music\x18\a \x01(\tR\x0fbackgroundMusic\x12B\n" + "\aeffects\x18\b \x03(\v2(.greatestworks.scene.EnvironmentalEffectR\aeffects\x12b\n" + "\x0fcustom_settings\x18\t \x03(\v29.greatestworks.scene.SceneEnvironment.CustomSettingsEntryR\x0ecustomSettings\x1aA\n" + "\x13CustomSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc0\x02\n" + "\rTeleportPoint\x12\x19\n" + "\bpoint_id\x18\x01 \x01(\tR\apointId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12:\n" + "\bposition\x18\x03 \x01(\v2\x1e.greatestworks.common.PositionR\bposition\x12\x1b\n" + "\tis_active\x18\x04 \x01(\bR\bisActive\x12-\n" + "\x12requires_discovery\x18\x05 \x01(\bR\x11requiresDiscovery\x12\x12\n" + "\x04cost\x18\x06 \x01(\x05R\x04cost\x12%\n" + "\x0erequired_items\x18\a \x03(\tR\rrequiredItems\x12\x1b\n" + "\tmin_level\x18\b \x01(\x05R\bminLevel\x12 \n" + "\vdescription\x18\t \x01(\tR\vdescription\"\xc6\x04\n" + "\rSceneSettings\x12\x1f\n" + "\vpvp_enabled\x18\x01 \x01(\bR\n" + "pvpEnabled\x12'\n" + "\x0frespawn_enabled\x18\x02 \x01(\bR\x0erespawnEnabled\x12!\n" + "\frespawn_time\x18\x03 \x01(\x05R\vrespawnTime\x12-\n" + "\x13drop_items_on_death\x18\x04 \x01(\bR\x10dropItemsOnDeath\x123\n" + "\x15experience_multiplier\x18\x05 \x01(\x02R\x14experienceMultiplier\x120\n" + "\x14drop_rate_multiplier\x18\x06 \x01(\x02R\x12dropRateMultiplier\x12\x1b\n" + "\tsafe_zone\x18\a \x01(\bR\bsafeZone\x12!\n" + "\fallow_flying\x18\b \x01(\bR\vallowFlying\x12\x1f\n" + "\vallow_mount\x18\t \x01(\bR\n" + "allowMount\x12!\n" + "\fidle_timeout\x18\n" + " \x01(\x05R\vidleTimeout\x12h\n" + "\x12movement_modifiers\x18\v \x03(\v29.greatestworks.scene.SceneSettings.MovementModifiersEntryR\x11movementModifiers\x1aD\n" + "\x16MovementModifiersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\x02R\x05value:\x028\x01\"\xf4\x03\n" + "\n" + "SceneEvent\x12\x19\n" + "\bevent_id\x18\x01 \x01(\tR\aeventId\x12\x1d\n" + "\n" + "event_name\x18\x02 \x01(\tR\teventName\x12=\n" + "\n" + "event_type\x18\x03 \x01(\x0e2\x1e.greatestworks.scene.EventTypeR\teventType\x12*\n" + "\x11trigger_player_id\x18\x04 \x01(\tR\x0ftriggerPlayerId\x12*\n" + "\x11trigger_object_id\x18\x05 \x01(\tR\x0ftriggerObjectId\x12:\n" + "\blocation\x18\x06 \x01(\v2\x1e.greatestworks.common.PositionR\blocation\x12\x1c\n" + "\ttimestamp\x18\a \x01(\x03R\ttimestamp\x12M\n" + "\n" + "event_data\x18\b \x03(\v2..greatestworks.scene.SceneEvent.EventDataEntryR\teventData\x12.\n" + "\x13affected_player_ids\x18\t \x03(\tR\x11affectedPlayerIds\x1a<\n" + "\x0eEventDataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xde\x02\n" + "\x10SceneEventEffect\x12\x1b\n" + "\teffect_id\x18\x01 \x01(\tR\beffectId\x12@\n" + "\veffect_type\x18\x02 \x01(\x0e2\x1f.greatestworks.scene.EffectTypeR\n" + "effectType\x12\x1b\n" + "\ttarget_id\x18\x03 \x01(\tR\btargetId\x12\x1a\n" + "\bduration\x18\x04 \x01(\x05R\bduration\x12\x1c\n" + "\tmagnitude\x18\x05 \x01(\x02R\tmagnitude\x12U\n" + "\n" + "parameters\x18\x06 \x03(\v25.greatestworks.scene.SceneEventEffect.ParametersEntryR\n" + "parameters\x1a=\n" + "\x0fParametersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x93\x03\n" + "\x11InteractionResult\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x129\n" + "\arewards\x18\x03 \x03(\v2\x1f.greatestworks.scene.ItemRewardR\arewards\x12+\n" + "\x11experience_gained\x18\x04 \x01(\x05R\x10experienceGained\x12J\n" + "\x10new_object_state\x18\x05 \x01(\x0e2 .greatestworks.scene.ObjectStateR\x0enewObjectState\x12W\n" + "\vresult_data\x18\x06 \x03(\v26.greatestworks.scene.InteractionResult.ResultDataEntryR\n" + "resultData\x1a=\n" + "\x0fResultDataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x98\x01\n" + "\n" + "ItemReward\x12\x17\n" + "\aitem_id\x18\x01 \x01(\tR\x06itemId\x12\x1b\n" + "\titem_name\x18\x02 \x01(\tR\bitemName\x12\x1a\n" + "\bquantity\x18\x03 \x01(\x05R\bquantity\x128\n" + "\x06rarity\x18\x04 \x01(\x0e2 .greatestworks.common.ItemRarityR\x06rarity\"\xc7\x02\n" + "\x13EnvironmentalEffect\x12\x1b\n" + "\teffect_id\x18\x01 \x01(\tR\beffectId\x12@\n" + "\veffect_type\x18\x02 \x01(\x0e2\x1f.greatestworks.scene.EffectTypeR\n" + "effectType\x12\x1c\n" + "\tintensity\x18\x03 \x01(\x02R\tintensity\x12\x1a\n" + "\bduration\x18\x04 \x01(\x05R\bduration\x12X\n" + "\n" + "parameters\x18\x05 \x03(\v28.greatestworks.scene.EnvironmentalEffect.ParametersEntryR\n" + "parameters\x1a=\n" + "\x0fParametersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xef\x03\n" + "\n" + "SceneStats\x12!\n" + "\ftotal_visits\x18\x01 \x01(\x05R\vtotalVisits\x12'\n" + "\x0funique_visitors\x18\x02 \x01(\x05R\x0euniqueVisitors\x12%\n" + "\x0ecurrent_online\x18\x03 \x01(\x05R\rcurrentOnline\x12\x1f\n" + "\vpeak_online\x18\x04 \x01(\x05R\n" + "peakOnline\x120\n" + "\x14average_session_time\x18\x05 \x01(\x03R\x12averageSessionTime\x12-\n" + "\x12total_interactions\x18\x06 \x01(\x05R\x11totalInteractions\x124\n" + "\x16total_events_triggered\x18\a \x01(\x05R\x14totalEventsTriggered\x12V\n" + "\rpopular_areas\x18\b \x03(\v21.greatestworks.scene.SceneStats.PopularAreasEntryR\fpopularAreas\x12\x1d\n" + "\n" + "last_reset\x18\t \x01(\x03R\tlastReset\x1a?\n" + "\x11PopularAreasEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\x05R\x05value:\x028\x01*\x98\x02\n" + "\tSceneType\x12\x1a\n" + "\x16SCENE_TYPE_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fSCENE_TYPE_TOWN\x10\x01\x12\x16\n" + "\x12SCENE_TYPE_DUNGEON\x10\x02\x12\x19\n" + "\x15SCENE_TYPE_WILDERNESS\x10\x03\x12\x1b\n" + "\x17SCENE_TYPE_BATTLE_ARENA\x10\x04\x12\x17\n" + "\x13SCENE_TYPE_INSTANCE\x10\x05\x12\x19\n" + "\x15SCENE_TYPE_GUILD_HALL\x10\x06\x12\x1b\n" + "\x17SCENE_TYPE_PRIVATE_ROOM\x10\a\x12\x19\n" + "\x15SCENE_TYPE_EVENT_AREA\x10\b\x12\x1e\n" + "\x1aSCENE_TYPE_TRAINING_GROUND\x10\t*\xac\x01\n" + "\vSceneStatus\x12\x1c\n" + "\x18SCENE_STATUS_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13SCENE_STATUS_ACTIVE\x10\x01\x12\x1c\n" + "\x18SCENE_STATUS_MAINTENANCE\x10\x02\x12\x17\n" + "\x13SCENE_STATUS_CLOSED\x10\x03\x12\x15\n" + "\x11SCENE_STATUS_FULL\x10\x04\x12\x18\n" + "\x14SCENE_STATUS_LOADING\x10\x05*\xde\x01\n" + "\vPlayerState\x12\x1c\n" + "\x18PLAYER_STATE_UNSPECIFIED\x10\x00\x12\x15\n" + "\x11PLAYER_STATE_IDLE\x10\x01\x12\x17\n" + "\x13PLAYER_STATE_MOVING\x10\x02\x12\x1c\n" + "\x18PLAYER_STATE_INTERACTING\x10\x03\x12\x17\n" + "\x13PLAYER_STATE_COMBAT\x10\x04\x12\x18\n" + "\x14PLAYER_STATE_TRADING\x10\x05\x12\x14\n" + "\x10PLAYER_STATE_AFK\x10\x06\x12\x1a\n" + "\x16PLAYER_STATE_INVISIBLE\x10\a*\xc7\x02\n" + "\n" + "ObjectType\x12\x1b\n" + "\x17OBJECT_TYPE_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fOBJECT_TYPE_NPC\x10\x01\x12\x14\n" + "\x10OBJECT_TYPE_ITEM\x10\x02\x12\x15\n" + "\x11OBJECT_TYPE_CHEST\x10\x03\x12\x14\n" + "\x10OBJECT_TYPE_DOOR\x10\x04\x12\x16\n" + "\x12OBJECT_TYPE_PORTAL\x10\x05\x12\x14\n" + "\x10OBJECT_TYPE_SIGN\x10\x06\x12\x1a\n" + "\x16OBJECT_TYPE_DECORATION\x10\a\x12\x19\n" + "\x15OBJECT_TYPE_FURNITURE\x10\b\x12\x17\n" + "\x13OBJECT_TYPE_VEHICLE\x10\t\x12\x18\n" + "\x14OBJECT_TYPE_RESOURCE\x10\n" + "\x12\x14\n" + "\x10OBJECT_TYPE_TRAP\x10\v\x12\x16\n" + "\x12OBJECT_TYPE_SWITCH\x10\f*\xc6\x01\n" + "\vObjectState\x12\x1c\n" + "\x18OBJECT_STATE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13OBJECT_STATE_NORMAL\x10\x01\x12\x1a\n" + "\x16OBJECT_STATE_ACTIVATED\x10\x02\x12\x19\n" + "\x15OBJECT_STATE_DISABLED\x10\x03\x12\x17\n" + "\x13OBJECT_STATE_BROKEN\x10\x04\x12\x17\n" + "\x13OBJECT_STATE_LOCKED\x10\x05\x12\x17\n" + "\x13OBJECT_STATE_HIDDEN\x10\x06*\xd2\x02\n" + "\x0fInteractionType\x12 \n" + "\x1cINTERACTION_TYPE_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15INTERACTION_TYPE_TALK\x10\x01\x12\x18\n" + "\x14INTERACTION_TYPE_USE\x10\x02\x12\x1c\n" + "\x18INTERACTION_TYPE_EXAMINE\x10\x03\x12\x19\n" + "\x15INTERACTION_TYPE_OPEN\x10\x04\x12\x1a\n" + "\x16INTERACTION_TYPE_CLOSE\x10\x05\x12\x1b\n" + "\x17INTERACTION_TYPE_PICKUP\x10\x06\x12\x1d\n" + "\x19INTERACTION_TYPE_ACTIVATE\x10\a\x12\x1b\n" + "\x17INTERACTION_TYPE_REPAIR\x10\b\x12\x1c\n" + "\x18INTERACTION_TYPE_UPGRADE\x10\t\x12\x1c\n" + "\x18INTERACTION_TYPE_DESTROY\x10\n" + "*\xc0\x01\n" + "\fMovementType\x12\x1d\n" + "\x19MOVEMENT_TYPE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12MOVEMENT_TYPE_WALK\x10\x01\x12\x15\n" + "\x11MOVEMENT_TYPE_RUN\x10\x02\x12\x1a\n" + "\x16MOVEMENT_TYPE_TELEPORT\x10\x03\x12\x15\n" + "\x11MOVEMENT_TYPE_FLY\x10\x04\x12\x16\n" + "\x12MOVEMENT_TYPE_SWIM\x10\x05\x12\x17\n" + "\x13MOVEMENT_TYPE_MOUNT\x10\x06*\xd5\x01\n" + "\vWeatherType\x12\x1c\n" + "\x18WEATHER_TYPE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12WEATHER_TYPE_CLEAR\x10\x01\x12\x17\n" + "\x13WEATHER_TYPE_CLOUDY\x10\x02\x12\x16\n" + "\x12WEATHER_TYPE_RAINY\x10\x03\x12\x17\n" + "\x13WEATHER_TYPE_STORMY\x10\x04\x12\x16\n" + "\x12WEATHER_TYPE_SNOWY\x10\x05\x12\x16\n" + "\x12WEATHER_TYPE_FOGGY\x10\x06\x12\x16\n" + "\x12WEATHER_TYPE_WINDY\x10\a*\xd2\x01\n" + "\tTimeOfDay\x12\x1b\n" + "\x17TIME_OF_DAY_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10TIME_OF_DAY_DAWN\x10\x01\x12\x17\n" + "\x13TIME_OF_DAY_MORNING\x10\x02\x12\x14\n" + "\x10TIME_OF_DAY_NOON\x10\x03\x12\x19\n" + "\x15TIME_OF_DAY_AFTERNOON\x10\x04\x12\x17\n" + "\x13TIME_OF_DAY_EVENING\x10\x05\x12\x15\n" + "\x11TIME_OF_DAY_NIGHT\x10\x06\x12\x18\n" + "\x14TIME_OF_DAY_MIDNIGHT\x10\a*\xd4\x02\n" + "\tEventType\x12\x1a\n" + "\x16EVENT_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n" + "\x17EVENT_TYPE_PLAYER_ENTER\x10\x01\x12\x1b\n" + "\x17EVENT_TYPE_PLAYER_LEAVE\x10\x02\x12!\n" + "\x1dEVENT_TYPE_OBJECT_INTERACTION\x10\x03\x12\x1b\n" + "\x17EVENT_TYPE_COMBAT_START\x10\x04\x12\x19\n" + "\x15EVENT_TYPE_COMBAT_END\x10\x05\x12\x1a\n" + "\x16EVENT_TYPE_ITEM_PICKUP\x10\x06\x12\x1c\n" + "\x18EVENT_TYPE_QUEST_TRIGGER\x10\a\x12!\n" + "\x1dEVENT_TYPE_ACHIEVEMENT_UNLOCK\x10\b\x12\x1d\n" + "\x19EVENT_TYPE_WEATHER_CHANGE\x10\t\x12\x1a\n" + "\x16EVENT_TYPE_TIME_CHANGE\x10\n" + "*\xa3\x02\n" + "\n" + "EffectType\x12\x1b\n" + "\x17EFFECT_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10EFFECT_TYPE_BUFF\x10\x01\x12\x16\n" + "\x12EFFECT_TYPE_DEBUFF\x10\x02\x12\x16\n" + "\x12EFFECT_TYPE_DAMAGE\x10\x03\x12\x14\n" + "\x10EFFECT_TYPE_HEAL\x10\x04\x12\x18\n" + "\x14EFFECT_TYPE_TELEPORT\x10\x05\x12\x19\n" + "\x15EFFECT_TYPE_TRANSFORM\x10\x06\x12\x1c\n" + "\x18EFFECT_TYPE_INVISIBILITY\x10\a\x12\x1b\n" + "\x17EFFECT_TYPE_SPEED_BOOST\x10\b\x12\x16\n" + "\x12EFFECT_TYPE_SHIELD\x10\t\x12\x14\n" + "\x10EFFECT_TYPE_STUN\x10\n" + "2\x95\n" + "\n" + "\fSceneService\x12]\n" + "\n" + "EnterScene\x12&.greatestworks.scene.EnterSceneRequest\x1a'.greatestworks.scene.EnterSceneResponse\x12]\n" + "\n" + "LeaveScene\x12&.greatestworks.scene.LeaveSceneRequest\x1a'.greatestworks.scene.LeaveSceneResponse\x12c\n" + "\fGetSceneInfo\x12(.greatestworks.scene.GetSceneInfoRequest\x1a).greatestworks.scene.GetSceneInfoResponse\x12i\n" + "\x0eMoveToPosition\x12*.greatestworks.scene.MoveToPositionRequest\x1a+.greatestworks.scene.MoveToPositionResponse\x12u\n" + "\x12InteractWithObject\x12..greatestworks.scene.InteractWithObjectRequest\x1a/.greatestworks.scene.InteractWithObjectResponse\x12r\n" + "\x11GetPlayersInScene\x12-.greatestworks.scene.GetPlayersInSceneRequest\x1a..greatestworks.scene.GetPlayersInSceneResponse\x12l\n" + "\x0fGetSceneObjects\x12+.greatestworks.scene.GetSceneObjectsRequest\x1a,.greatestworks.scene.GetSceneObjectsResponse\x12r\n" + "\x11TriggerSceneEvent\x12-.greatestworks.scene.TriggerSceneEventRequest\x1a..greatestworks.scene.TriggerSceneEventResponse\x12u\n" + "\x12GetAvailableScenes\x12..greatestworks.scene.GetAvailableScenesRequest\x1a/.greatestworks.scene.GetAvailableScenesResponse\x12l\n" + "\x0fTeleportToScene\x12+.greatestworks.scene.TeleportToSceneRequest\x1a,.greatestworks.scene.TeleportToSceneResponse\x12]\n" + "\n" + "SetWeather\x12&.greatestworks.scene.SetWeatherRequest\x1a'.greatestworks.scene.SetWeatherResponse\x12f\n" + "\rGetSceneStats\x12).greatestworks.scene.GetSceneStatsRequest\x1a*.greatestworks.scene.GetSceneStatsResponseB:Z\"greatestworks/internal/proto/scene\xaa\x02\x13GreatestWorks.Sceneb\x06proto3" var ( file_proto_scene_proto_rawDescOnce sync.Once file_proto_scene_proto_rawDescData []byte ) func file_proto_scene_proto_rawDescGZIP() []byte { file_proto_scene_proto_rawDescOnce.Do(func() { file_proto_scene_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_scene_proto_rawDesc), len(file_proto_scene_proto_rawDesc))) }) return file_proto_scene_proto_rawDescData } var file_proto_scene_proto_enumTypes = make([]protoimpl.EnumInfo, 11) var file_proto_scene_proto_msgTypes = make([]protoimpl.MessageInfo, 49) var file_proto_scene_proto_goTypes = []any{ (SceneType)(0), // 0: greatestworks.scene.SceneType (SceneStatus)(0), // 1: greatestworks.scene.SceneStatus (PlayerState)(0), // 2: greatestworks.scene.PlayerState (ObjectType)(0), // 3: greatestworks.scene.ObjectType (ObjectState)(0), // 4: greatestworks.scene.ObjectState (InteractionType)(0), // 5: greatestworks.scene.InteractionType (MovementType)(0), // 6: greatestworks.scene.MovementType (WeatherType)(0), // 7: greatestworks.scene.WeatherType (TimeOfDay)(0), // 8: greatestworks.scene.TimeOfDay (EventType)(0), // 9: greatestworks.scene.EventType (EffectType)(0), // 10: greatestworks.scene.EffectType (*EnterSceneRequest)(nil), // 11: greatestworks.scene.EnterSceneRequest (*EnterSceneResponse)(nil), // 12: greatestworks.scene.EnterSceneResponse (*LeaveSceneRequest)(nil), // 13: greatestworks.scene.LeaveSceneRequest (*LeaveSceneResponse)(nil), // 14: greatestworks.scene.LeaveSceneResponse (*GetSceneInfoRequest)(nil), // 15: greatestworks.scene.GetSceneInfoRequest (*GetSceneInfoResponse)(nil), // 16: greatestworks.scene.GetSceneInfoResponse (*MoveToPositionRequest)(nil), // 17: greatestworks.scene.MoveToPositionRequest (*MoveToPositionResponse)(nil), // 18: greatestworks.scene.MoveToPositionResponse (*InteractWithObjectRequest)(nil), // 19: greatestworks.scene.InteractWithObjectRequest (*InteractWithObjectResponse)(nil), // 20: greatestworks.scene.InteractWithObjectResponse (*GetPlayersInSceneRequest)(nil), // 21: greatestworks.scene.GetPlayersInSceneRequest (*GetPlayersInSceneResponse)(nil), // 22: greatestworks.scene.GetPlayersInSceneResponse (*GetSceneObjectsRequest)(nil), // 23: greatestworks.scene.GetSceneObjectsRequest (*GetSceneObjectsResponse)(nil), // 24: greatestworks.scene.GetSceneObjectsResponse (*TriggerSceneEventRequest)(nil), // 25: greatestworks.scene.TriggerSceneEventRequest (*TriggerSceneEventResponse)(nil), // 26: greatestworks.scene.TriggerSceneEventResponse (*GetAvailableScenesRequest)(nil), // 27: greatestworks.scene.GetAvailableScenesRequest (*GetAvailableScenesResponse)(nil), // 28: greatestworks.scene.GetAvailableScenesResponse (*TeleportToSceneRequest)(nil), // 29: greatestworks.scene.TeleportToSceneRequest (*TeleportToSceneResponse)(nil), // 30: greatestworks.scene.TeleportToSceneResponse (*SetWeatherRequest)(nil), // 31: greatestworks.scene.SetWeatherRequest (*SetWeatherResponse)(nil), // 32: greatestworks.scene.SetWeatherResponse (*GetSceneStatsRequest)(nil), // 33: greatestworks.scene.GetSceneStatsRequest (*GetSceneStatsResponse)(nil), // 34: greatestworks.scene.GetSceneStatsResponse (*SceneInfo)(nil), // 35: greatestworks.scene.SceneInfo (*ScenePlayer)(nil), // 36: greatestworks.scene.ScenePlayer (*SceneObject)(nil), // 37: greatestworks.scene.SceneObject (*SceneEnvironment)(nil), // 38: greatestworks.scene.SceneEnvironment (*TeleportPoint)(nil), // 39: greatestworks.scene.TeleportPoint (*SceneSettings)(nil), // 40: greatestworks.scene.SceneSettings (*SceneEvent)(nil), // 41: greatestworks.scene.SceneEvent (*SceneEventEffect)(nil), // 42: greatestworks.scene.SceneEventEffect (*InteractionResult)(nil), // 43: greatestworks.scene.InteractionResult (*ItemReward)(nil), // 44: greatestworks.scene.ItemReward (*EnvironmentalEffect)(nil), // 45: greatestworks.scene.EnvironmentalEffect (*SceneStats)(nil), // 46: greatestworks.scene.SceneStats nil, // 47: greatestworks.scene.EnterSceneRequest.EntryContextEntry nil, // 48: greatestworks.scene.InteractWithObjectRequest.ParametersEntry nil, // 49: greatestworks.scene.TriggerSceneEventRequest.EventDataEntry nil, // 50: greatestworks.scene.SceneInfo.MetadataEntry nil, // 51: greatestworks.scene.ScenePlayer.PlayerDataEntry nil, // 52: greatestworks.scene.SceneObject.PropertiesEntry nil, // 53: greatestworks.scene.SceneEnvironment.CustomSettingsEntry nil, // 54: greatestworks.scene.SceneSettings.MovementModifiersEntry nil, // 55: greatestworks.scene.SceneEvent.EventDataEntry nil, // 56: greatestworks.scene.SceneEventEffect.ParametersEntry nil, // 57: greatestworks.scene.InteractionResult.ResultDataEntry nil, // 58: greatestworks.scene.EnvironmentalEffect.ParametersEntry nil, // 59: greatestworks.scene.SceneStats.PopularAreasEntry (*common.Position)(nil), // 60: greatestworks.common.Position (*common.CommonResponse)(nil), // 61: greatestworks.common.CommonResponse (*common.PaginationInfo)(nil), // 62: greatestworks.common.PaginationInfo (common.ItemRarity)(0), // 63: greatestworks.common.ItemRarity } var file_proto_scene_proto_depIdxs = []int32{ 60, // 0: greatestworks.scene.EnterSceneRequest.spawn_position:type_name -> greatestworks.common.Position 47, // 1: greatestworks.scene.EnterSceneRequest.entry_context:type_name -> greatestworks.scene.EnterSceneRequest.EntryContextEntry 61, // 2: greatestworks.scene.EnterSceneResponse.common:type_name -> greatestworks.common.CommonResponse 35, // 3: greatestworks.scene.EnterSceneResponse.scene_info:type_name -> greatestworks.scene.SceneInfo 60, // 4: greatestworks.scene.EnterSceneResponse.player_position:type_name -> greatestworks.common.Position 36, // 5: greatestworks.scene.EnterSceneResponse.other_players:type_name -> greatestworks.scene.ScenePlayer 37, // 6: greatestworks.scene.EnterSceneResponse.scene_objects:type_name -> greatestworks.scene.SceneObject 38, // 7: greatestworks.scene.EnterSceneResponse.environment:type_name -> greatestworks.scene.SceneEnvironment 61, // 8: greatestworks.scene.LeaveSceneResponse.common:type_name -> greatestworks.common.CommonResponse 61, // 9: greatestworks.scene.GetSceneInfoResponse.common:type_name -> greatestworks.common.CommonResponse 35, // 10: greatestworks.scene.GetSceneInfoResponse.scene_info:type_name -> greatestworks.scene.SceneInfo 36, // 11: greatestworks.scene.GetSceneInfoResponse.players:type_name -> greatestworks.scene.ScenePlayer 37, // 12: greatestworks.scene.GetSceneInfoResponse.objects:type_name -> greatestworks.scene.SceneObject 38, // 13: greatestworks.scene.GetSceneInfoResponse.environment:type_name -> greatestworks.scene.SceneEnvironment 60, // 14: greatestworks.scene.MoveToPositionRequest.target_position:type_name -> greatestworks.common.Position 6, // 15: greatestworks.scene.MoveToPositionRequest.movement_type:type_name -> greatestworks.scene.MovementType 61, // 16: greatestworks.scene.MoveToPositionResponse.common:type_name -> greatestworks.common.CommonResponse 60, // 17: greatestworks.scene.MoveToPositionResponse.new_position:type_name -> greatestworks.common.Position 5, // 18: greatestworks.scene.InteractWithObjectRequest.interaction_type:type_name -> greatestworks.scene.InteractionType 48, // 19: greatestworks.scene.InteractWithObjectRequest.parameters:type_name -> greatestworks.scene.InteractWithObjectRequest.ParametersEntry 61, // 20: greatestworks.scene.InteractWithObjectResponse.common:type_name -> greatestworks.common.CommonResponse 43, // 21: greatestworks.scene.InteractWithObjectResponse.result:type_name -> greatestworks.scene.InteractionResult 41, // 22: greatestworks.scene.InteractWithObjectResponse.triggered_events:type_name -> greatestworks.scene.SceneEvent 60, // 23: greatestworks.scene.GetPlayersInSceneRequest.center_position:type_name -> greatestworks.common.Position 61, // 24: greatestworks.scene.GetPlayersInSceneResponse.common:type_name -> greatestworks.common.CommonResponse 36, // 25: greatestworks.scene.GetPlayersInSceneResponse.players:type_name -> greatestworks.scene.ScenePlayer 62, // 26: greatestworks.scene.GetPlayersInSceneResponse.pagination:type_name -> greatestworks.common.PaginationInfo 3, // 27: greatestworks.scene.GetSceneObjectsRequest.object_type:type_name -> greatestworks.scene.ObjectType 60, // 28: greatestworks.scene.GetSceneObjectsRequest.center_position:type_name -> greatestworks.common.Position 61, // 29: greatestworks.scene.GetSceneObjectsResponse.common:type_name -> greatestworks.common.CommonResponse 37, // 30: greatestworks.scene.GetSceneObjectsResponse.objects:type_name -> greatestworks.scene.SceneObject 49, // 31: greatestworks.scene.TriggerSceneEventRequest.event_data:type_name -> greatestworks.scene.TriggerSceneEventRequest.EventDataEntry 61, // 32: greatestworks.scene.TriggerSceneEventResponse.common:type_name -> greatestworks.common.CommonResponse 41, // 33: greatestworks.scene.TriggerSceneEventResponse.event:type_name -> greatestworks.scene.SceneEvent 42, // 34: greatestworks.scene.TriggerSceneEventResponse.effects:type_name -> greatestworks.scene.SceneEventEffect 0, // 35: greatestworks.scene.GetAvailableScenesRequest.scene_type:type_name -> greatestworks.scene.SceneType 61, // 36: greatestworks.scene.GetAvailableScenesResponse.common:type_name -> greatestworks.common.CommonResponse 35, // 37: greatestworks.scene.GetAvailableScenesResponse.scenes:type_name -> greatestworks.scene.SceneInfo 62, // 38: greatestworks.scene.GetAvailableScenesResponse.pagination:type_name -> greatestworks.common.PaginationInfo 61, // 39: greatestworks.scene.TeleportToSceneResponse.common:type_name -> greatestworks.common.CommonResponse 60, // 40: greatestworks.scene.TeleportToSceneResponse.spawn_position:type_name -> greatestworks.common.Position 7, // 41: greatestworks.scene.SetWeatherRequest.weather_type:type_name -> greatestworks.scene.WeatherType 61, // 42: greatestworks.scene.SetWeatherResponse.common:type_name -> greatestworks.common.CommonResponse 38, // 43: greatestworks.scene.SetWeatherResponse.new_environment:type_name -> greatestworks.scene.SceneEnvironment 61, // 44: greatestworks.scene.GetSceneStatsResponse.common:type_name -> greatestworks.common.CommonResponse 46, // 45: greatestworks.scene.GetSceneStatsResponse.stats:type_name -> greatestworks.scene.SceneStats 0, // 46: greatestworks.scene.SceneInfo.scene_type:type_name -> greatestworks.scene.SceneType 1, // 47: greatestworks.scene.SceneInfo.status:type_name -> greatestworks.scene.SceneStatus 60, // 48: greatestworks.scene.SceneInfo.spawn_point:type_name -> greatestworks.common.Position 39, // 49: greatestworks.scene.SceneInfo.teleport_points:type_name -> greatestworks.scene.TeleportPoint 40, // 50: greatestworks.scene.SceneInfo.settings:type_name -> greatestworks.scene.SceneSettings 50, // 51: greatestworks.scene.SceneInfo.metadata:type_name -> greatestworks.scene.SceneInfo.MetadataEntry 60, // 52: greatestworks.scene.ScenePlayer.position:type_name -> greatestworks.common.Position 2, // 53: greatestworks.scene.ScenePlayer.state:type_name -> greatestworks.scene.PlayerState 51, // 54: greatestworks.scene.ScenePlayer.player_data:type_name -> greatestworks.scene.ScenePlayer.PlayerDataEntry 3, // 55: greatestworks.scene.SceneObject.object_type:type_name -> greatestworks.scene.ObjectType 4, // 56: greatestworks.scene.SceneObject.state:type_name -> greatestworks.scene.ObjectState 60, // 57: greatestworks.scene.SceneObject.position:type_name -> greatestworks.common.Position 5, // 58: greatestworks.scene.SceneObject.available_interactions:type_name -> greatestworks.scene.InteractionType 52, // 59: greatestworks.scene.SceneObject.properties:type_name -> greatestworks.scene.SceneObject.PropertiesEntry 7, // 60: greatestworks.scene.SceneEnvironment.weather:type_name -> greatestworks.scene.WeatherType 8, // 61: greatestworks.scene.SceneEnvironment.time_of_day:type_name -> greatestworks.scene.TimeOfDay 45, // 62: greatestworks.scene.SceneEnvironment.effects:type_name -> greatestworks.scene.EnvironmentalEffect 53, // 63: greatestworks.scene.SceneEnvironment.custom_settings:type_name -> greatestworks.scene.SceneEnvironment.CustomSettingsEntry 60, // 64: greatestworks.scene.TeleportPoint.position:type_name -> greatestworks.common.Position 54, // 65: greatestworks.scene.SceneSettings.movement_modifiers:type_name -> greatestworks.scene.SceneSettings.MovementModifiersEntry 9, // 66: greatestworks.scene.SceneEvent.event_type:type_name -> greatestworks.scene.EventType 60, // 67: greatestworks.scene.SceneEvent.location:type_name -> greatestworks.common.Position 55, // 68: greatestworks.scene.SceneEvent.event_data:type_name -> greatestworks.scene.SceneEvent.EventDataEntry 10, // 69: greatestworks.scene.SceneEventEffect.effect_type:type_name -> greatestworks.scene.EffectType 56, // 70: greatestworks.scene.SceneEventEffect.parameters:type_name -> greatestworks.scene.SceneEventEffect.ParametersEntry 44, // 71: greatestworks.scene.InteractionResult.rewards:type_name -> greatestworks.scene.ItemReward 4, // 72: greatestworks.scene.InteractionResult.new_object_state:type_name -> greatestworks.scene.ObjectState 57, // 73: greatestworks.scene.InteractionResult.result_data:type_name -> greatestworks.scene.InteractionResult.ResultDataEntry 63, // 74: greatestworks.scene.ItemReward.rarity:type_name -> greatestworks.common.ItemRarity 10, // 75: greatestworks.scene.EnvironmentalEffect.effect_type:type_name -> greatestworks.scene.EffectType 58, // 76: greatestworks.scene.EnvironmentalEffect.parameters:type_name -> greatestworks.scene.EnvironmentalEffect.ParametersEntry 59, // 77: greatestworks.scene.SceneStats.popular_areas:type_name -> greatestworks.scene.SceneStats.PopularAreasEntry 11, // 78: greatestworks.scene.SceneService.EnterScene:input_type -> greatestworks.scene.EnterSceneRequest 13, // 79: greatestworks.scene.SceneService.LeaveScene:input_type -> greatestworks.scene.LeaveSceneRequest 15, // 80: greatestworks.scene.SceneService.GetSceneInfo:input_type -> greatestworks.scene.GetSceneInfoRequest 17, // 81: greatestworks.scene.SceneService.MoveToPosition:input_type -> greatestworks.scene.MoveToPositionRequest 19, // 82: greatestworks.scene.SceneService.InteractWithObject:input_type -> greatestworks.scene.InteractWithObjectRequest 21, // 83: greatestworks.scene.SceneService.GetPlayersInScene:input_type -> greatestworks.scene.GetPlayersInSceneRequest 23, // 84: greatestworks.scene.SceneService.GetSceneObjects:input_type -> greatestworks.scene.GetSceneObjectsRequest 25, // 85: greatestworks.scene.SceneService.TriggerSceneEvent:input_type -> greatestworks.scene.TriggerSceneEventRequest 27, // 86: greatestworks.scene.SceneService.GetAvailableScenes:input_type -> greatestworks.scene.GetAvailableScenesRequest 29, // 87: greatestworks.scene.SceneService.TeleportToScene:input_type -> greatestworks.scene.TeleportToSceneRequest 31, // 88: greatestworks.scene.SceneService.SetWeather:input_type -> greatestworks.scene.SetWeatherRequest 33, // 89: greatestworks.scene.SceneService.GetSceneStats:input_type -> greatestworks.scene.GetSceneStatsRequest 12, // 90: greatestworks.scene.SceneService.EnterScene:output_type -> greatestworks.scene.EnterSceneResponse 14, // 91: greatestworks.scene.SceneService.LeaveScene:output_type -> greatestworks.scene.LeaveSceneResponse 16, // 92: greatestworks.scene.SceneService.GetSceneInfo:output_type -> greatestworks.scene.GetSceneInfoResponse 18, // 93: greatestworks.scene.SceneService.MoveToPosition:output_type -> greatestworks.scene.MoveToPositionResponse 20, // 94: greatestworks.scene.SceneService.InteractWithObject:output_type -> greatestworks.scene.InteractWithObjectResponse 22, // 95: greatestworks.scene.SceneService.GetPlayersInScene:output_type -> greatestworks.scene.GetPlayersInSceneResponse 24, // 96: greatestworks.scene.SceneService.GetSceneObjects:output_type -> greatestworks.scene.GetSceneObjectsResponse 26, // 97: greatestworks.scene.SceneService.TriggerSceneEvent:output_type -> greatestworks.scene.TriggerSceneEventResponse 28, // 98: greatestworks.scene.SceneService.GetAvailableScenes:output_type -> greatestworks.scene.GetAvailableScenesResponse 30, // 99: greatestworks.scene.SceneService.TeleportToScene:output_type -> greatestworks.scene.TeleportToSceneResponse 32, // 100: greatestworks.scene.SceneService.SetWeather:output_type -> greatestworks.scene.SetWeatherResponse 34, // 101: greatestworks.scene.SceneService.GetSceneStats:output_type -> greatestworks.scene.GetSceneStatsResponse 90, // [90:102] is the sub-list for method output_type 78, // [78:90] is the sub-list for method input_type 78, // [78:78] is the sub-list for extension type_name 78, // [78:78] is the sub-list for extension extendee 0, // [0:78] is the sub-list for field type_name } func init() { file_proto_scene_proto_init() } func file_proto_scene_proto_init() { if File_proto_scene_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_scene_proto_rawDesc), len(file_proto_scene_proto_rawDesc)), NumEnums: 11, NumMessages: 49, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_scene_proto_goTypes, DependencyIndexes: file_proto_scene_proto_depIdxs, EnumInfos: file_proto_scene_proto_enumTypes, MessageInfos: file_proto_scene_proto_msgTypes, }.Build() File_proto_scene_proto = out.File file_proto_scene_proto_goTypes = nil file_proto_scene_proto_depIdxs = nil } ================================================ FILE: internal/proto/team/team.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 // protoc v4.25.1 // source: proto/team.proto package team import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" common "greatestworks/internal/proto/common" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 团队类型枚举 type TeamType int32 const ( TeamType_TEAM_TYPE_UNSPECIFIED TeamType = 0 TeamType_TEAM_TYPE_CASUAL TeamType = 1 // 休闲团队 TeamType_TEAM_TYPE_COMPETITIVE TeamType = 2 // 竞技团队 TeamType_TEAM_TYPE_PVE TeamType = 3 // PVE团队 TeamType_TEAM_TYPE_PVP TeamType = 4 // PVP团队 TeamType_TEAM_TYPE_GUILD TeamType = 5 // 公会 TeamType_TEAM_TYPE_PARTY TeamType = 6 // 小队 TeamType_TEAM_TYPE_RAID TeamType = 7 // 团本队伍 TeamType_TEAM_TYPE_TOURNAMENT TeamType = 8 // 锦标赛队伍 ) // Enum value maps for TeamType. var ( TeamType_name = map[int32]string{ 0: "TEAM_TYPE_UNSPECIFIED", 1: "TEAM_TYPE_CASUAL", 2: "TEAM_TYPE_COMPETITIVE", 3: "TEAM_TYPE_PVE", 4: "TEAM_TYPE_PVP", 5: "TEAM_TYPE_GUILD", 6: "TEAM_TYPE_PARTY", 7: "TEAM_TYPE_RAID", 8: "TEAM_TYPE_TOURNAMENT", } TeamType_value = map[string]int32{ "TEAM_TYPE_UNSPECIFIED": 0, "TEAM_TYPE_CASUAL": 1, "TEAM_TYPE_COMPETITIVE": 2, "TEAM_TYPE_PVE": 3, "TEAM_TYPE_PVP": 4, "TEAM_TYPE_GUILD": 5, "TEAM_TYPE_PARTY": 6, "TEAM_TYPE_RAID": 7, "TEAM_TYPE_TOURNAMENT": 8, } ) func (x TeamType) Enum() *TeamType { p := new(TeamType) *p = x return p } func (x TeamType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (TeamType) Descriptor() protoreflect.EnumDescriptor { return file_proto_team_proto_enumTypes[0].Descriptor() } func (TeamType) Type() protoreflect.EnumType { return &file_proto_team_proto_enumTypes[0] } func (x TeamType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use TeamType.Descriptor instead. func (TeamType) EnumDescriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{0} } // 团队状态枚举 type TeamStatus int32 const ( TeamStatus_TEAM_STATUS_UNSPECIFIED TeamStatus = 0 TeamStatus_TEAM_STATUS_ACTIVE TeamStatus = 1 // 活跃 TeamStatus_TEAM_STATUS_INACTIVE TeamStatus = 2 // 不活跃 TeamStatus_TEAM_STATUS_DISBANDED TeamStatus = 3 // 已解散 TeamStatus_TEAM_STATUS_SUSPENDED TeamStatus = 4 // 已暂停 TeamStatus_TEAM_STATUS_RECRUITING TeamStatus = 5 // 招募中 TeamStatus_TEAM_STATUS_FULL TeamStatus = 6 // 已满员 ) // Enum value maps for TeamStatus. var ( TeamStatus_name = map[int32]string{ 0: "TEAM_STATUS_UNSPECIFIED", 1: "TEAM_STATUS_ACTIVE", 2: "TEAM_STATUS_INACTIVE", 3: "TEAM_STATUS_DISBANDED", 4: "TEAM_STATUS_SUSPENDED", 5: "TEAM_STATUS_RECRUITING", 6: "TEAM_STATUS_FULL", } TeamStatus_value = map[string]int32{ "TEAM_STATUS_UNSPECIFIED": 0, "TEAM_STATUS_ACTIVE": 1, "TEAM_STATUS_INACTIVE": 2, "TEAM_STATUS_DISBANDED": 3, "TEAM_STATUS_SUSPENDED": 4, "TEAM_STATUS_RECRUITING": 5, "TEAM_STATUS_FULL": 6, } ) func (x TeamStatus) Enum() *TeamStatus { p := new(TeamStatus) *p = x return p } func (x TeamStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (TeamStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_team_proto_enumTypes[1].Descriptor() } func (TeamStatus) Type() protoreflect.EnumType { return &file_proto_team_proto_enumTypes[1] } func (x TeamStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use TeamStatus.Descriptor instead. func (TeamStatus) EnumDescriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{1} } // 成员角色枚举 type MemberRole int32 const ( MemberRole_MEMBER_ROLE_UNSPECIFIED MemberRole = 0 MemberRole_MEMBER_ROLE_LEADER MemberRole = 1 // 队长 MemberRole_MEMBER_ROLE_OFFICER MemberRole = 2 // 副队长 MemberRole_MEMBER_ROLE_VETERAN MemberRole = 3 // 老队员 MemberRole_MEMBER_ROLE_MEMBER MemberRole = 4 // 普通成员 MemberRole_MEMBER_ROLE_RECRUIT MemberRole = 5 // 新成员 ) // Enum value maps for MemberRole. var ( MemberRole_name = map[int32]string{ 0: "MEMBER_ROLE_UNSPECIFIED", 1: "MEMBER_ROLE_LEADER", 2: "MEMBER_ROLE_OFFICER", 3: "MEMBER_ROLE_VETERAN", 4: "MEMBER_ROLE_MEMBER", 5: "MEMBER_ROLE_RECRUIT", } MemberRole_value = map[string]int32{ "MEMBER_ROLE_UNSPECIFIED": 0, "MEMBER_ROLE_LEADER": 1, "MEMBER_ROLE_OFFICER": 2, "MEMBER_ROLE_VETERAN": 3, "MEMBER_ROLE_MEMBER": 4, "MEMBER_ROLE_RECRUIT": 5, } ) func (x MemberRole) Enum() *MemberRole { p := new(MemberRole) *p = x return p } func (x MemberRole) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MemberRole) Descriptor() protoreflect.EnumDescriptor { return file_proto_team_proto_enumTypes[2].Descriptor() } func (MemberRole) Type() protoreflect.EnumType { return &file_proto_team_proto_enumTypes[2] } func (x MemberRole) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MemberRole.Descriptor instead. func (MemberRole) EnumDescriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{2} } // 成员状态枚举 type MemberStatus int32 const ( MemberStatus_MEMBER_STATUS_UNSPECIFIED MemberStatus = 0 MemberStatus_MEMBER_STATUS_ONLINE MemberStatus = 1 // 在线 MemberStatus_MEMBER_STATUS_OFFLINE MemberStatus = 2 // 离线 MemberStatus_MEMBER_STATUS_IN_BATTLE MemberStatus = 3 // 战斗中 MemberStatus_MEMBER_STATUS_AFK MemberStatus = 4 // 暂离 MemberStatus_MEMBER_STATUS_BUSY MemberStatus = 5 // 忙碌 ) // Enum value maps for MemberStatus. var ( MemberStatus_name = map[int32]string{ 0: "MEMBER_STATUS_UNSPECIFIED", 1: "MEMBER_STATUS_ONLINE", 2: "MEMBER_STATUS_OFFLINE", 3: "MEMBER_STATUS_IN_BATTLE", 4: "MEMBER_STATUS_AFK", 5: "MEMBER_STATUS_BUSY", } MemberStatus_value = map[string]int32{ "MEMBER_STATUS_UNSPECIFIED": 0, "MEMBER_STATUS_ONLINE": 1, "MEMBER_STATUS_OFFLINE": 2, "MEMBER_STATUS_IN_BATTLE": 3, "MEMBER_STATUS_AFK": 4, "MEMBER_STATUS_BUSY": 5, } ) func (x MemberStatus) Enum() *MemberStatus { p := new(MemberStatus) *p = x return p } func (x MemberStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (MemberStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_team_proto_enumTypes[3].Descriptor() } func (MemberStatus) Type() protoreflect.EnumType { return &file_proto_team_proto_enumTypes[3] } func (x MemberStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use MemberStatus.Descriptor instead. func (MemberStatus) EnumDescriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{3} } // 邀请状态枚举 type InvitationStatus int32 const ( InvitationStatus_INVITATION_STATUS_UNSPECIFIED InvitationStatus = 0 InvitationStatus_INVITATION_STATUS_PENDING InvitationStatus = 1 // 待处理 InvitationStatus_INVITATION_STATUS_ACCEPTED InvitationStatus = 2 // 已接受 InvitationStatus_INVITATION_STATUS_REJECTED InvitationStatus = 3 // 已拒绝 InvitationStatus_INVITATION_STATUS_EXPIRED InvitationStatus = 4 // 已过期 InvitationStatus_INVITATION_STATUS_CANCELLED InvitationStatus = 5 // 已取消 ) // Enum value maps for InvitationStatus. var ( InvitationStatus_name = map[int32]string{ 0: "INVITATION_STATUS_UNSPECIFIED", 1: "INVITATION_STATUS_PENDING", 2: "INVITATION_STATUS_ACCEPTED", 3: "INVITATION_STATUS_REJECTED", 4: "INVITATION_STATUS_EXPIRED", 5: "INVITATION_STATUS_CANCELLED", } InvitationStatus_value = map[string]int32{ "INVITATION_STATUS_UNSPECIFIED": 0, "INVITATION_STATUS_PENDING": 1, "INVITATION_STATUS_ACCEPTED": 2, "INVITATION_STATUS_REJECTED": 3, "INVITATION_STATUS_EXPIRED": 4, "INVITATION_STATUS_CANCELLED": 5, } ) func (x InvitationStatus) Enum() *InvitationStatus { p := new(InvitationStatus) *p = x return p } func (x InvitationStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (InvitationStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_team_proto_enumTypes[4].Descriptor() } func (InvitationStatus) Type() protoreflect.EnumType { return &file_proto_team_proto_enumTypes[4] } func (x InvitationStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use InvitationStatus.Descriptor instead. func (InvitationStatus) EnumDescriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{4} } // 申请状态枚举 type ApplicationStatus int32 const ( ApplicationStatus_APPLICATION_STATUS_UNSPECIFIED ApplicationStatus = 0 ApplicationStatus_APPLICATION_STATUS_PENDING ApplicationStatus = 1 // 待审核 ApplicationStatus_APPLICATION_STATUS_APPROVED ApplicationStatus = 2 // 已批准 ApplicationStatus_APPLICATION_STATUS_REJECTED ApplicationStatus = 3 // 已拒绝 ApplicationStatus_APPLICATION_STATUS_WITHDRAWN ApplicationStatus = 4 // 已撤回 ) // Enum value maps for ApplicationStatus. var ( ApplicationStatus_name = map[int32]string{ 0: "APPLICATION_STATUS_UNSPECIFIED", 1: "APPLICATION_STATUS_PENDING", 2: "APPLICATION_STATUS_APPROVED", 3: "APPLICATION_STATUS_REJECTED", 4: "APPLICATION_STATUS_WITHDRAWN", } ApplicationStatus_value = map[string]int32{ "APPLICATION_STATUS_UNSPECIFIED": 0, "APPLICATION_STATUS_PENDING": 1, "APPLICATION_STATUS_APPROVED": 2, "APPLICATION_STATUS_REJECTED": 3, "APPLICATION_STATUS_WITHDRAWN": 4, } ) func (x ApplicationStatus) Enum() *ApplicationStatus { p := new(ApplicationStatus) *p = x return p } func (x ApplicationStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (ApplicationStatus) Descriptor() protoreflect.EnumDescriptor { return file_proto_team_proto_enumTypes[5].Descriptor() } func (ApplicationStatus) Type() protoreflect.EnumType { return &file_proto_team_proto_enumTypes[5] } func (x ApplicationStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use ApplicationStatus.Descriptor instead. func (ApplicationStatus) EnumDescriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{5} } // 创建团队请求 type CreateTeamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CreatorId string `protobuf:"bytes,1,opt,name=creator_id,json=creatorId,proto3" json:"creator_id,omitempty"` TeamName string `protobuf:"bytes,2,opt,name=team_name,json=teamName,proto3" json:"team_name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` TeamType TeamType `protobuf:"varint,4,opt,name=team_type,json=teamType,proto3,enum=greatestworks.team.TeamType" json:"team_type,omitempty"` MaxMembers int32 `protobuf:"varint,5,opt,name=max_members,json=maxMembers,proto3" json:"max_members,omitempty"` IsPublic bool `protobuf:"varint,6,opt,name=is_public,json=isPublic,proto3" json:"is_public,omitempty"` Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"` Settings map[string]string `protobuf:"bytes,8,rep,name=settings,proto3" json:"settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateTeamRequest) Reset() { *x = CreateTeamRequest{} mi := &file_proto_team_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateTeamRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateTeamRequest) ProtoMessage() {} func (x *CreateTeamRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateTeamRequest.ProtoReflect.Descriptor instead. func (*CreateTeamRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{0} } func (x *CreateTeamRequest) GetCreatorId() string { if x != nil { return x.CreatorId } return "" } func (x *CreateTeamRequest) GetTeamName() string { if x != nil { return x.TeamName } return "" } func (x *CreateTeamRequest) GetDescription() string { if x != nil { return x.Description } return "" } func (x *CreateTeamRequest) GetTeamType() TeamType { if x != nil { return x.TeamType } return TeamType_TEAM_TYPE_UNSPECIFIED } func (x *CreateTeamRequest) GetMaxMembers() int32 { if x != nil { return x.MaxMembers } return 0 } func (x *CreateTeamRequest) GetIsPublic() bool { if x != nil { return x.IsPublic } return false } func (x *CreateTeamRequest) GetPassword() string { if x != nil { return x.Password } return "" } func (x *CreateTeamRequest) GetSettings() map[string]string { if x != nil { return x.Settings } return nil } // 创建团队响应 type CreateTeamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` TeamInfo *TeamInfo `protobuf:"bytes,3,opt,name=team_info,json=teamInfo,proto3" json:"team_info,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateTeamResponse) Reset() { *x = CreateTeamResponse{} mi := &file_proto_team_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateTeamResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateTeamResponse) ProtoMessage() {} func (x *CreateTeamResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateTeamResponse.ProtoReflect.Descriptor instead. func (*CreateTeamResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{1} } func (x *CreateTeamResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *CreateTeamResponse) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *CreateTeamResponse) GetTeamInfo() *TeamInfo { if x != nil { return x.TeamInfo } return nil } // 加入团队请求 type JoinTeamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` // 如果是私有团队 InvitationCode string `protobuf:"bytes,4,opt,name=invitation_code,json=invitationCode,proto3" json:"invitation_code,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinTeamRequest) Reset() { *x = JoinTeamRequest{} mi := &file_proto_team_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinTeamRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinTeamRequest) ProtoMessage() {} func (x *JoinTeamRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinTeamRequest.ProtoReflect.Descriptor instead. func (*JoinTeamRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{2} } func (x *JoinTeamRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *JoinTeamRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *JoinTeamRequest) GetPassword() string { if x != nil { return x.Password } return "" } func (x *JoinTeamRequest) GetInvitationCode() string { if x != nil { return x.InvitationCode } return "" } // 加入团队响应 type JoinTeamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` TeamInfo *TeamInfo `protobuf:"bytes,2,opt,name=team_info,json=teamInfo,proto3" json:"team_info,omitempty"` MemberInfo *TeamMember `protobuf:"bytes,3,opt,name=member_info,json=memberInfo,proto3" json:"member_info,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *JoinTeamResponse) Reset() { *x = JoinTeamResponse{} mi := &file_proto_team_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *JoinTeamResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*JoinTeamResponse) ProtoMessage() {} func (x *JoinTeamResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use JoinTeamResponse.ProtoReflect.Descriptor instead. func (*JoinTeamResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{3} } func (x *JoinTeamResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *JoinTeamResponse) GetTeamInfo() *TeamInfo { if x != nil { return x.TeamInfo } return nil } func (x *JoinTeamResponse) GetMemberInfo() *TeamMember { if x != nil { return x.MemberInfo } return nil } // 离开团队请求 type LeaveTeamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveTeamRequest) Reset() { *x = LeaveTeamRequest{} mi := &file_proto_team_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveTeamRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveTeamRequest) ProtoMessage() {} func (x *LeaveTeamRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveTeamRequest.ProtoReflect.Descriptor instead. func (*LeaveTeamRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{4} } func (x *LeaveTeamRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *LeaveTeamRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } // 离开团队响应 type LeaveTeamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *LeaveTeamResponse) Reset() { *x = LeaveTeamResponse{} mi := &file_proto_team_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *LeaveTeamResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*LeaveTeamResponse) ProtoMessage() {} func (x *LeaveTeamResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LeaveTeamResponse.ProtoReflect.Descriptor instead. func (*LeaveTeamResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{5} } func (x *LeaveTeamResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 邀请玩家请求 type InvitePlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` InviterId string `protobuf:"bytes,1,opt,name=inviter_id,json=inviterId,proto3" json:"inviter_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` TargetPlayerId string `protobuf:"bytes,3,opt,name=target_player_id,json=targetPlayerId,proto3" json:"target_player_id,omitempty"` Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InvitePlayerRequest) Reset() { *x = InvitePlayerRequest{} mi := &file_proto_team_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InvitePlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*InvitePlayerRequest) ProtoMessage() {} func (x *InvitePlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InvitePlayerRequest.ProtoReflect.Descriptor instead. func (*InvitePlayerRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{6} } func (x *InvitePlayerRequest) GetInviterId() string { if x != nil { return x.InviterId } return "" } func (x *InvitePlayerRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *InvitePlayerRequest) GetTargetPlayerId() string { if x != nil { return x.TargetPlayerId } return "" } func (x *InvitePlayerRequest) GetMessage() string { if x != nil { return x.Message } return "" } // 邀请玩家响应 type InvitePlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` InvitationId string `protobuf:"bytes,2,opt,name=invitation_id,json=invitationId,proto3" json:"invitation_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *InvitePlayerResponse) Reset() { *x = InvitePlayerResponse{} mi := &file_proto_team_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *InvitePlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*InvitePlayerResponse) ProtoMessage() {} func (x *InvitePlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InvitePlayerResponse.ProtoReflect.Descriptor instead. func (*InvitePlayerResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{7} } func (x *InvitePlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *InvitePlayerResponse) GetInvitationId() string { if x != nil { return x.InvitationId } return "" } // 踢出玩家请求 type KickPlayerRequest struct { state protoimpl.MessageState `protogen:"open.v1"` KickerId string `protobuf:"bytes,1,opt,name=kicker_id,json=kickerId,proto3" json:"kicker_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` TargetPlayerId string `protobuf:"bytes,3,opt,name=target_player_id,json=targetPlayerId,proto3" json:"target_player_id,omitempty"` Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *KickPlayerRequest) Reset() { *x = KickPlayerRequest{} mi := &file_proto_team_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *KickPlayerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*KickPlayerRequest) ProtoMessage() {} func (x *KickPlayerRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use KickPlayerRequest.ProtoReflect.Descriptor instead. func (*KickPlayerRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{8} } func (x *KickPlayerRequest) GetKickerId() string { if x != nil { return x.KickerId } return "" } func (x *KickPlayerRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *KickPlayerRequest) GetTargetPlayerId() string { if x != nil { return x.TargetPlayerId } return "" } func (x *KickPlayerRequest) GetReason() string { if x != nil { return x.Reason } return "" } // 踢出玩家响应 type KickPlayerResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *KickPlayerResponse) Reset() { *x = KickPlayerResponse{} mi := &file_proto_team_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *KickPlayerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*KickPlayerResponse) ProtoMessage() {} func (x *KickPlayerResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use KickPlayerResponse.ProtoReflect.Descriptor instead. func (*KickPlayerResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{9} } func (x *KickPlayerResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } // 转让队长请求 type TransferLeadershipRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CurrentLeaderId string `protobuf:"bytes,1,opt,name=current_leader_id,json=currentLeaderId,proto3" json:"current_leader_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` NewLeaderId string `protobuf:"bytes,3,opt,name=new_leader_id,json=newLeaderId,proto3" json:"new_leader_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TransferLeadershipRequest) Reset() { *x = TransferLeadershipRequest{} mi := &file_proto_team_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TransferLeadershipRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferLeadershipRequest) ProtoMessage() {} func (x *TransferLeadershipRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferLeadershipRequest.ProtoReflect.Descriptor instead. func (*TransferLeadershipRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{10} } func (x *TransferLeadershipRequest) GetCurrentLeaderId() string { if x != nil { return x.CurrentLeaderId } return "" } func (x *TransferLeadershipRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *TransferLeadershipRequest) GetNewLeaderId() string { if x != nil { return x.NewLeaderId } return "" } // 转让队长响应 type TransferLeadershipResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewLeader *TeamMember `protobuf:"bytes,2,opt,name=new_leader,json=newLeader,proto3" json:"new_leader,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TransferLeadershipResponse) Reset() { *x = TransferLeadershipResponse{} mi := &file_proto_team_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TransferLeadershipResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferLeadershipResponse) ProtoMessage() {} func (x *TransferLeadershipResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferLeadershipResponse.ProtoReflect.Descriptor instead. func (*TransferLeadershipResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{11} } func (x *TransferLeadershipResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *TransferLeadershipResponse) GetNewLeader() *TeamMember { if x != nil { return x.NewLeader } return nil } // 获取团队信息请求 type GetTeamInfoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` TeamId string `protobuf:"bytes,1,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` PlayerId string `protobuf:"bytes,2,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` // 查询者ID,用于权限检查 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetTeamInfoRequest) Reset() { *x = GetTeamInfoRequest{} mi := &file_proto_team_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetTeamInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetTeamInfoRequest) ProtoMessage() {} func (x *GetTeamInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetTeamInfoRequest.ProtoReflect.Descriptor instead. func (*GetTeamInfoRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{12} } func (x *GetTeamInfoRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *GetTeamInfoRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } // 获取团队信息响应 type GetTeamInfoResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` TeamInfo *TeamInfo `protobuf:"bytes,2,opt,name=team_info,json=teamInfo,proto3" json:"team_info,omitempty"` Members []*TeamMember `protobuf:"bytes,3,rep,name=members,proto3" json:"members,omitempty"` PendingInvitations []*TeamInvitation `protobuf:"bytes,4,rep,name=pending_invitations,json=pendingInvitations,proto3" json:"pending_invitations,omitempty"` PendingApplications []*TeamApplication `protobuf:"bytes,5,rep,name=pending_applications,json=pendingApplications,proto3" json:"pending_applications,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetTeamInfoResponse) Reset() { *x = GetTeamInfoResponse{} mi := &file_proto_team_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetTeamInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetTeamInfoResponse) ProtoMessage() {} func (x *GetTeamInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetTeamInfoResponse.ProtoReflect.Descriptor instead. func (*GetTeamInfoResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{13} } func (x *GetTeamInfoResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *GetTeamInfoResponse) GetTeamInfo() *TeamInfo { if x != nil { return x.TeamInfo } return nil } func (x *GetTeamInfoResponse) GetMembers() []*TeamMember { if x != nil { return x.Members } return nil } func (x *GetTeamInfoResponse) GetPendingInvitations() []*TeamInvitation { if x != nil { return x.PendingInvitations } return nil } func (x *GetTeamInfoResponse) GetPendingApplications() []*TeamApplication { if x != nil { return x.PendingApplications } return nil } // 搜索团队请求 type SearchTeamsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` TeamType TeamType `protobuf:"varint,2,opt,name=team_type,json=teamType,proto3,enum=greatestworks.team.TeamType" json:"team_type,omitempty"` OnlyPublic bool `protobuf:"varint,3,opt,name=only_public,json=onlyPublic,proto3" json:"only_public,omitempty"` MinLevel int32 `protobuf:"varint,4,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` MaxLevel int32 `protobuf:"varint,5,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` Offset int32 `protobuf:"varint,7,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SearchTeamsRequest) Reset() { *x = SearchTeamsRequest{} mi := &file_proto_team_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SearchTeamsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchTeamsRequest) ProtoMessage() {} func (x *SearchTeamsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchTeamsRequest.ProtoReflect.Descriptor instead. func (*SearchTeamsRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{14} } func (x *SearchTeamsRequest) GetQuery() string { if x != nil { return x.Query } return "" } func (x *SearchTeamsRequest) GetTeamType() TeamType { if x != nil { return x.TeamType } return TeamType_TEAM_TYPE_UNSPECIFIED } func (x *SearchTeamsRequest) GetOnlyPublic() bool { if x != nil { return x.OnlyPublic } return false } func (x *SearchTeamsRequest) GetMinLevel() int32 { if x != nil { return x.MinLevel } return 0 } func (x *SearchTeamsRequest) GetMaxLevel() int32 { if x != nil { return x.MaxLevel } return 0 } func (x *SearchTeamsRequest) GetLimit() int32 { if x != nil { return x.Limit } return 0 } func (x *SearchTeamsRequest) GetOffset() int32 { if x != nil { return x.Offset } return 0 } // 搜索团队响应 type SearchTeamsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` Teams []*TeamInfo `protobuf:"bytes,2,rep,name=teams,proto3" json:"teams,omitempty"` Pagination *common.PaginationInfo `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SearchTeamsResponse) Reset() { *x = SearchTeamsResponse{} mi := &file_proto_team_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *SearchTeamsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SearchTeamsResponse) ProtoMessage() {} func (x *SearchTeamsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SearchTeamsResponse.ProtoReflect.Descriptor instead. func (*SearchTeamsResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{15} } func (x *SearchTeamsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *SearchTeamsResponse) GetTeams() []*TeamInfo { if x != nil { return x.Teams } return nil } func (x *SearchTeamsResponse) GetPagination() *common.PaginationInfo { if x != nil { return x.Pagination } return nil } // 更新团队设置请求 type UpdateTeamSettingsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` Settings *TeamSettings `protobuf:"bytes,3,opt,name=settings,proto3" json:"settings,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateTeamSettingsRequest) Reset() { *x = UpdateTeamSettingsRequest{} mi := &file_proto_team_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateTeamSettingsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateTeamSettingsRequest) ProtoMessage() {} func (x *UpdateTeamSettingsRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateTeamSettingsRequest.ProtoReflect.Descriptor instead. func (*UpdateTeamSettingsRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{16} } func (x *UpdateTeamSettingsRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *UpdateTeamSettingsRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *UpdateTeamSettingsRequest) GetSettings() *TeamSettings { if x != nil { return x.Settings } return nil } // 更新团队设置响应 type UpdateTeamSettingsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewSettings *TeamSettings `protobuf:"bytes,2,opt,name=new_settings,json=newSettings,proto3" json:"new_settings,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateTeamSettingsResponse) Reset() { *x = UpdateTeamSettingsResponse{} mi := &file_proto_team_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateTeamSettingsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateTeamSettingsResponse) ProtoMessage() {} func (x *UpdateTeamSettingsResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateTeamSettingsResponse.ProtoReflect.Descriptor instead. func (*UpdateTeamSettingsResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{17} } func (x *UpdateTeamSettingsResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *UpdateTeamSettingsResponse) GetNewSettings() *TeamSettings { if x != nil { return x.NewSettings } return nil } // 处理邀请请求 type HandleInvitationRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` InvitationId string `protobuf:"bytes,2,opt,name=invitation_id,json=invitationId,proto3" json:"invitation_id,omitempty"` Accept bool `protobuf:"varint,3,opt,name=accept,proto3" json:"accept,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HandleInvitationRequest) Reset() { *x = HandleInvitationRequest{} mi := &file_proto_team_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HandleInvitationRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HandleInvitationRequest) ProtoMessage() {} func (x *HandleInvitationRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HandleInvitationRequest.ProtoReflect.Descriptor instead. func (*HandleInvitationRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{18} } func (x *HandleInvitationRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *HandleInvitationRequest) GetInvitationId() string { if x != nil { return x.InvitationId } return "" } func (x *HandleInvitationRequest) GetAccept() bool { if x != nil { return x.Accept } return false } // 处理邀请响应 type HandleInvitationResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` TeamInfo *TeamInfo `protobuf:"bytes,2,opt,name=team_info,json=teamInfo,proto3" json:"team_info,omitempty"` // 如果接受邀请 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HandleInvitationResponse) Reset() { *x = HandleInvitationResponse{} mi := &file_proto_team_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HandleInvitationResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*HandleInvitationResponse) ProtoMessage() {} func (x *HandleInvitationResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HandleInvitationResponse.ProtoReflect.Descriptor instead. func (*HandleInvitationResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{19} } func (x *HandleInvitationResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *HandleInvitationResponse) GetTeamInfo() *TeamInfo { if x != nil { return x.TeamInfo } return nil } // 申请加入团队请求 type ApplyToJoinRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ApplyToJoinRequest) Reset() { *x = ApplyToJoinRequest{} mi := &file_proto_team_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ApplyToJoinRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ApplyToJoinRequest) ProtoMessage() {} func (x *ApplyToJoinRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ApplyToJoinRequest.ProtoReflect.Descriptor instead. func (*ApplyToJoinRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{20} } func (x *ApplyToJoinRequest) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *ApplyToJoinRequest) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *ApplyToJoinRequest) GetMessage() string { if x != nil { return x.Message } return "" } // 申请加入团队响应 type ApplyToJoinResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` ApplicationId string `protobuf:"bytes,2,opt,name=application_id,json=applicationId,proto3" json:"application_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ApplyToJoinResponse) Reset() { *x = ApplyToJoinResponse{} mi := &file_proto_team_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ApplyToJoinResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ApplyToJoinResponse) ProtoMessage() {} func (x *ApplyToJoinResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ApplyToJoinResponse.ProtoReflect.Descriptor instead. func (*ApplyToJoinResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{21} } func (x *ApplyToJoinResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *ApplyToJoinResponse) GetApplicationId() string { if x != nil { return x.ApplicationId } return "" } // 处理申请请求 type HandleApplicationRequest struct { state protoimpl.MessageState `protogen:"open.v1"` HandlerId string `protobuf:"bytes,1,opt,name=handler_id,json=handlerId,proto3" json:"handler_id,omitempty"` ApplicationId string `protobuf:"bytes,2,opt,name=application_id,json=applicationId,proto3" json:"application_id,omitempty"` Approve bool `protobuf:"varint,3,opt,name=approve,proto3" json:"approve,omitempty"` Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HandleApplicationRequest) Reset() { *x = HandleApplicationRequest{} mi := &file_proto_team_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HandleApplicationRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HandleApplicationRequest) ProtoMessage() {} func (x *HandleApplicationRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HandleApplicationRequest.ProtoReflect.Descriptor instead. func (*HandleApplicationRequest) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{22} } func (x *HandleApplicationRequest) GetHandlerId() string { if x != nil { return x.HandlerId } return "" } func (x *HandleApplicationRequest) GetApplicationId() string { if x != nil { return x.ApplicationId } return "" } func (x *HandleApplicationRequest) GetApprove() bool { if x != nil { return x.Approve } return false } func (x *HandleApplicationRequest) GetReason() string { if x != nil { return x.Reason } return "" } // 处理申请响应 type HandleApplicationResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Common *common.CommonResponse `protobuf:"bytes,1,opt,name=common,proto3" json:"common,omitempty"` NewMember *TeamMember `protobuf:"bytes,2,opt,name=new_member,json=newMember,proto3" json:"new_member,omitempty"` // 如果批准申请 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HandleApplicationResponse) Reset() { *x = HandleApplicationResponse{} mi := &file_proto_team_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *HandleApplicationResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*HandleApplicationResponse) ProtoMessage() {} func (x *HandleApplicationResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HandleApplicationResponse.ProtoReflect.Descriptor instead. func (*HandleApplicationResponse) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{23} } func (x *HandleApplicationResponse) GetCommon() *common.CommonResponse { if x != nil { return x.Common } return nil } func (x *HandleApplicationResponse) GetNewMember() *TeamMember { if x != nil { return x.NewMember } return nil } // 团队信息 type TeamInfo struct { state protoimpl.MessageState `protogen:"open.v1"` TeamId string `protobuf:"bytes,1,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` TeamType TeamType `protobuf:"varint,4,opt,name=team_type,json=teamType,proto3,enum=greatestworks.team.TeamType" json:"team_type,omitempty"` Status TeamStatus `protobuf:"varint,5,opt,name=status,proto3,enum=greatestworks.team.TeamStatus" json:"status,omitempty"` LeaderId string `protobuf:"bytes,6,opt,name=leader_id,json=leaderId,proto3" json:"leader_id,omitempty"` LeaderName string `protobuf:"bytes,7,opt,name=leader_name,json=leaderName,proto3" json:"leader_name,omitempty"` MemberCount int32 `protobuf:"varint,8,opt,name=member_count,json=memberCount,proto3" json:"member_count,omitempty"` MaxMembers int32 `protobuf:"varint,9,opt,name=max_members,json=maxMembers,proto3" json:"max_members,omitempty"` AverageLevel int32 `protobuf:"varint,10,opt,name=average_level,json=averageLevel,proto3" json:"average_level,omitempty"` IsPublic bool `protobuf:"varint,11,opt,name=is_public,json=isPublic,proto3" json:"is_public,omitempty"` IsRecruiting bool `protobuf:"varint,12,opt,name=is_recruiting,json=isRecruiting,proto3" json:"is_recruiting,omitempty"` CreatedAt int64 `protobuf:"varint,13,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastActivity int64 `protobuf:"varint,14,opt,name=last_activity,json=lastActivity,proto3" json:"last_activity,omitempty"` Settings *TeamSettings `protobuf:"bytes,15,opt,name=settings,proto3" json:"settings,omitempty"` Stats *TeamStats `protobuf:"bytes,16,opt,name=stats,proto3" json:"stats,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamInfo) Reset() { *x = TeamInfo{} mi := &file_proto_team_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamInfo) ProtoMessage() {} func (x *TeamInfo) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamInfo.ProtoReflect.Descriptor instead. func (*TeamInfo) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{24} } func (x *TeamInfo) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *TeamInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *TeamInfo) GetDescription() string { if x != nil { return x.Description } return "" } func (x *TeamInfo) GetTeamType() TeamType { if x != nil { return x.TeamType } return TeamType_TEAM_TYPE_UNSPECIFIED } func (x *TeamInfo) GetStatus() TeamStatus { if x != nil { return x.Status } return TeamStatus_TEAM_STATUS_UNSPECIFIED } func (x *TeamInfo) GetLeaderId() string { if x != nil { return x.LeaderId } return "" } func (x *TeamInfo) GetLeaderName() string { if x != nil { return x.LeaderName } return "" } func (x *TeamInfo) GetMemberCount() int32 { if x != nil { return x.MemberCount } return 0 } func (x *TeamInfo) GetMaxMembers() int32 { if x != nil { return x.MaxMembers } return 0 } func (x *TeamInfo) GetAverageLevel() int32 { if x != nil { return x.AverageLevel } return 0 } func (x *TeamInfo) GetIsPublic() bool { if x != nil { return x.IsPublic } return false } func (x *TeamInfo) GetIsRecruiting() bool { if x != nil { return x.IsRecruiting } return false } func (x *TeamInfo) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *TeamInfo) GetLastActivity() int64 { if x != nil { return x.LastActivity } return 0 } func (x *TeamInfo) GetSettings() *TeamSettings { if x != nil { return x.Settings } return nil } func (x *TeamInfo) GetStats() *TeamStats { if x != nil { return x.Stats } return nil } // 团队成员 type TeamMember struct { state protoimpl.MessageState `protogen:"open.v1"` PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` PlayerName string `protobuf:"bytes,2,opt,name=player_name,json=playerName,proto3" json:"player_name,omitempty"` Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` Role MemberRole `protobuf:"varint,4,opt,name=role,proto3,enum=greatestworks.team.MemberRole" json:"role,omitempty"` Status MemberStatus `protobuf:"varint,5,opt,name=status,proto3,enum=greatestworks.team.MemberStatus" json:"status,omitempty"` JoinedAt int64 `protobuf:"varint,6,opt,name=joined_at,json=joinedAt,proto3" json:"joined_at,omitempty"` LastOnline int64 `protobuf:"varint,7,opt,name=last_online,json=lastOnline,proto3" json:"last_online,omitempty"` ContributionPoints int32 `protobuf:"varint,8,opt,name=contribution_points,json=contributionPoints,proto3" json:"contribution_points,omitempty"` Position *common.Position `protobuf:"bytes,9,opt,name=position,proto3" json:"position,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamMember) Reset() { *x = TeamMember{} mi := &file_proto_team_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamMember) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamMember) ProtoMessage() {} func (x *TeamMember) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamMember.ProtoReflect.Descriptor instead. func (*TeamMember) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{25} } func (x *TeamMember) GetPlayerId() string { if x != nil { return x.PlayerId } return "" } func (x *TeamMember) GetPlayerName() string { if x != nil { return x.PlayerName } return "" } func (x *TeamMember) GetLevel() int32 { if x != nil { return x.Level } return 0 } func (x *TeamMember) GetRole() MemberRole { if x != nil { return x.Role } return MemberRole_MEMBER_ROLE_UNSPECIFIED } func (x *TeamMember) GetStatus() MemberStatus { if x != nil { return x.Status } return MemberStatus_MEMBER_STATUS_UNSPECIFIED } func (x *TeamMember) GetJoinedAt() int64 { if x != nil { return x.JoinedAt } return 0 } func (x *TeamMember) GetLastOnline() int64 { if x != nil { return x.LastOnline } return 0 } func (x *TeamMember) GetContributionPoints() int32 { if x != nil { return x.ContributionPoints } return 0 } func (x *TeamMember) GetPosition() *common.Position { if x != nil { return x.Position } return nil } // 团队邀请 type TeamInvitation struct { state protoimpl.MessageState `protogen:"open.v1"` InvitationId string `protobuf:"bytes,1,opt,name=invitation_id,json=invitationId,proto3" json:"invitation_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` TeamName string `protobuf:"bytes,3,opt,name=team_name,json=teamName,proto3" json:"team_name,omitempty"` InviterId string `protobuf:"bytes,4,opt,name=inviter_id,json=inviterId,proto3" json:"inviter_id,omitempty"` InviterName string `protobuf:"bytes,5,opt,name=inviter_name,json=inviterName,proto3" json:"inviter_name,omitempty"` InviteeId string `protobuf:"bytes,6,opt,name=invitee_id,json=inviteeId,proto3" json:"invitee_id,omitempty"` InviteeName string `protobuf:"bytes,7,opt,name=invitee_name,json=inviteeName,proto3" json:"invitee_name,omitempty"` Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` CreatedAt int64 `protobuf:"varint,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` ExpiresAt int64 `protobuf:"varint,10,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` Status InvitationStatus `protobuf:"varint,11,opt,name=status,proto3,enum=greatestworks.team.InvitationStatus" json:"status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamInvitation) Reset() { *x = TeamInvitation{} mi := &file_proto_team_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamInvitation) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamInvitation) ProtoMessage() {} func (x *TeamInvitation) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamInvitation.ProtoReflect.Descriptor instead. func (*TeamInvitation) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{26} } func (x *TeamInvitation) GetInvitationId() string { if x != nil { return x.InvitationId } return "" } func (x *TeamInvitation) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *TeamInvitation) GetTeamName() string { if x != nil { return x.TeamName } return "" } func (x *TeamInvitation) GetInviterId() string { if x != nil { return x.InviterId } return "" } func (x *TeamInvitation) GetInviterName() string { if x != nil { return x.InviterName } return "" } func (x *TeamInvitation) GetInviteeId() string { if x != nil { return x.InviteeId } return "" } func (x *TeamInvitation) GetInviteeName() string { if x != nil { return x.InviteeName } return "" } func (x *TeamInvitation) GetMessage() string { if x != nil { return x.Message } return "" } func (x *TeamInvitation) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *TeamInvitation) GetExpiresAt() int64 { if x != nil { return x.ExpiresAt } return 0 } func (x *TeamInvitation) GetStatus() InvitationStatus { if x != nil { return x.Status } return InvitationStatus_INVITATION_STATUS_UNSPECIFIED } // 团队申请 type TeamApplication struct { state protoimpl.MessageState `protogen:"open.v1"` ApplicationId string `protobuf:"bytes,1,opt,name=application_id,json=applicationId,proto3" json:"application_id,omitempty"` TeamId string `protobuf:"bytes,2,opt,name=team_id,json=teamId,proto3" json:"team_id,omitempty"` ApplicantId string `protobuf:"bytes,3,opt,name=applicant_id,json=applicantId,proto3" json:"applicant_id,omitempty"` ApplicantName string `protobuf:"bytes,4,opt,name=applicant_name,json=applicantName,proto3" json:"applicant_name,omitempty"` ApplicantLevel int32 `protobuf:"varint,5,opt,name=applicant_level,json=applicantLevel,proto3" json:"applicant_level,omitempty"` Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` CreatedAt int64 `protobuf:"varint,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` Status ApplicationStatus `protobuf:"varint,8,opt,name=status,proto3,enum=greatestworks.team.ApplicationStatus" json:"status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamApplication) Reset() { *x = TeamApplication{} mi := &file_proto_team_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamApplication) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamApplication) ProtoMessage() {} func (x *TeamApplication) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamApplication.ProtoReflect.Descriptor instead. func (*TeamApplication) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{27} } func (x *TeamApplication) GetApplicationId() string { if x != nil { return x.ApplicationId } return "" } func (x *TeamApplication) GetTeamId() string { if x != nil { return x.TeamId } return "" } func (x *TeamApplication) GetApplicantId() string { if x != nil { return x.ApplicantId } return "" } func (x *TeamApplication) GetApplicantName() string { if x != nil { return x.ApplicantName } return "" } func (x *TeamApplication) GetApplicantLevel() int32 { if x != nil { return x.ApplicantLevel } return 0 } func (x *TeamApplication) GetMessage() string { if x != nil { return x.Message } return "" } func (x *TeamApplication) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *TeamApplication) GetStatus() ApplicationStatus { if x != nil { return x.Status } return ApplicationStatus_APPLICATION_STATUS_UNSPECIFIED } // 团队设置 type TeamSettings struct { state protoimpl.MessageState `protogen:"open.v1"` AutoAcceptApplications bool `protobuf:"varint,1,opt,name=auto_accept_applications,json=autoAcceptApplications,proto3" json:"auto_accept_applications,omitempty"` MinLevelRequirement int32 `protobuf:"varint,2,opt,name=min_level_requirement,json=minLevelRequirement,proto3" json:"min_level_requirement,omitempty"` MaxLevelRequirement int32 `protobuf:"varint,3,opt,name=max_level_requirement,json=maxLevelRequirement,proto3" json:"max_level_requirement,omitempty"` AllowMemberInvite bool `protobuf:"varint,4,opt,name=allow_member_invite,json=allowMemberInvite,proto3" json:"allow_member_invite,omitempty"` RequireApproval bool `protobuf:"varint,5,opt,name=require_approval,json=requireApproval,proto3" json:"require_approval,omitempty"` WelcomeMessage string `protobuf:"bytes,6,opt,name=welcome_message,json=welcomeMessage,proto3" json:"welcome_message,omitempty"` Tags []string `protobuf:"bytes,7,rep,name=tags,proto3" json:"tags,omitempty"` CustomSettings map[string]string `protobuf:"bytes,8,rep,name=custom_settings,json=customSettings,proto3" json:"custom_settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamSettings) Reset() { *x = TeamSettings{} mi := &file_proto_team_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamSettings) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamSettings) ProtoMessage() {} func (x *TeamSettings) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamSettings.ProtoReflect.Descriptor instead. func (*TeamSettings) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{28} } func (x *TeamSettings) GetAutoAcceptApplications() bool { if x != nil { return x.AutoAcceptApplications } return false } func (x *TeamSettings) GetMinLevelRequirement() int32 { if x != nil { return x.MinLevelRequirement } return 0 } func (x *TeamSettings) GetMaxLevelRequirement() int32 { if x != nil { return x.MaxLevelRequirement } return 0 } func (x *TeamSettings) GetAllowMemberInvite() bool { if x != nil { return x.AllowMemberInvite } return false } func (x *TeamSettings) GetRequireApproval() bool { if x != nil { return x.RequireApproval } return false } func (x *TeamSettings) GetWelcomeMessage() string { if x != nil { return x.WelcomeMessage } return "" } func (x *TeamSettings) GetTags() []string { if x != nil { return x.Tags } return nil } func (x *TeamSettings) GetCustomSettings() map[string]string { if x != nil { return x.CustomSettings } return nil } // 团队统计 type TeamStats struct { state protoimpl.MessageState `protogen:"open.v1"` TotalBattles int32 `protobuf:"varint,1,opt,name=total_battles,json=totalBattles,proto3" json:"total_battles,omitempty"` Wins int32 `protobuf:"varint,2,opt,name=wins,proto3" json:"wins,omitempty"` Losses int32 `protobuf:"varint,3,opt,name=losses,proto3" json:"losses,omitempty"` WinRate float32 `protobuf:"fixed32,4,opt,name=win_rate,json=winRate,proto3" json:"win_rate,omitempty"` TotalExperience int32 `protobuf:"varint,5,opt,name=total_experience,json=totalExperience,proto3" json:"total_experience,omitempty"` AchievementsUnlocked int32 `protobuf:"varint,6,opt,name=achievements_unlocked,json=achievementsUnlocked,proto3" json:"achievements_unlocked,omitempty"` TotalPlaytime int64 `protobuf:"varint,7,opt,name=total_playtime,json=totalPlaytime,proto3" json:"total_playtime,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TeamStats) Reset() { *x = TeamStats{} mi := &file_proto_team_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *TeamStats) String() string { return protoimpl.X.MessageStringOf(x) } func (*TeamStats) ProtoMessage() {} func (x *TeamStats) ProtoReflect() protoreflect.Message { mi := &file_proto_team_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TeamStats.ProtoReflect.Descriptor instead. func (*TeamStats) Descriptor() ([]byte, []int) { return file_proto_team_proto_rawDescGZIP(), []int{29} } func (x *TeamStats) GetTotalBattles() int32 { if x != nil { return x.TotalBattles } return 0 } func (x *TeamStats) GetWins() int32 { if x != nil { return x.Wins } return 0 } func (x *TeamStats) GetLosses() int32 { if x != nil { return x.Losses } return 0 } func (x *TeamStats) GetWinRate() float32 { if x != nil { return x.WinRate } return 0 } func (x *TeamStats) GetTotalExperience() int32 { if x != nil { return x.TotalExperience } return 0 } func (x *TeamStats) GetAchievementsUnlocked() int32 { if x != nil { return x.AchievementsUnlocked } return 0 } func (x *TeamStats) GetTotalPlaytime() int64 { if x != nil { return x.TotalPlaytime } return 0 } var File_proto_team_proto protoreflect.FileDescriptor const file_proto_team_proto_rawDesc = "" + "\n" + "\x10proto/team.proto\x12\x12greatestworks.team\x1a\x12proto/common.proto\"\x94\x03\n" + "\x11CreateTeamRequest\x12\x1d\n" + "\n" + "creator_id\x18\x01 \x01(\tR\tcreatorId\x12\x1b\n" + "\tteam_name\x18\x02 \x01(\tR\bteamName\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x129\n" + "\tteam_type\x18\x04 \x01(\x0e2\x1c.greatestworks.team.TeamTypeR\bteamType\x12\x1f\n" + "\vmax_members\x18\x05 \x01(\x05R\n" + "maxMembers\x12\x1b\n" + "\tis_public\x18\x06 \x01(\bR\bisPublic\x12\x1a\n" + "\bpassword\x18\a \x01(\tR\bpassword\x12O\n" + "\bsettings\x18\b \x03(\v23.greatestworks.team.CreateTeamRequest.SettingsEntryR\bsettings\x1a;\n" + "\rSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa6\x01\n" + "\x12CreateTeamResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x129\n" + "\tteam_info\x18\x03 \x01(\v2\x1c.greatestworks.team.TeamInfoR\bteamInfo\"\x8c\x01\n" + "\x0fJoinTeamRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12\x1a\n" + "\bpassword\x18\x03 \x01(\tR\bpassword\x12'\n" + "\x0finvitation_code\x18\x04 \x01(\tR\x0einvitationCode\"\xcc\x01\n" + "\x10JoinTeamResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x129\n" + "\tteam_info\x18\x02 \x01(\v2\x1c.greatestworks.team.TeamInfoR\bteamInfo\x12?\n" + "\vmember_info\x18\x03 \x01(\v2\x1e.greatestworks.team.TeamMemberR\n" + "memberInfo\"H\n" + "\x10LeaveTeamRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\"Q\n" + "\x11LeaveTeamResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x91\x01\n" + "\x13InvitePlayerRequest\x12\x1d\n" + "\n" + "inviter_id\x18\x01 \x01(\tR\tinviterId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12(\n" + "\x10target_player_id\x18\x03 \x01(\tR\x0etargetPlayerId\x12\x18\n" + "\amessage\x18\x04 \x01(\tR\amessage\"y\n" + "\x14InvitePlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12#\n" + "\rinvitation_id\x18\x02 \x01(\tR\finvitationId\"\x8b\x01\n" + "\x11KickPlayerRequest\x12\x1b\n" + "\tkicker_id\x18\x01 \x01(\tR\bkickerId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12(\n" + "\x10target_player_id\x18\x03 \x01(\tR\x0etargetPlayerId\x12\x16\n" + "\x06reason\x18\x04 \x01(\tR\x06reason\"R\n" + "\x12KickPlayerResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\"\x84\x01\n" + "\x19TransferLeadershipRequest\x12*\n" + "\x11current_leader_id\x18\x01 \x01(\tR\x0fcurrentLeaderId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12\"\n" + "\rnew_leader_id\x18\x03 \x01(\tR\vnewLeaderId\"\x99\x01\n" + "\x1aTransferLeadershipResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\n" + "new_leader\x18\x02 \x01(\v2\x1e.greatestworks.team.TeamMemberR\tnewLeader\"J\n" + "\x12GetTeamInfoRequest\x12\x17\n" + "\ateam_id\x18\x01 \x01(\tR\x06teamId\x12\x1b\n" + "\tplayer_id\x18\x02 \x01(\tR\bplayerId\"\xf5\x02\n" + "\x13GetTeamInfoResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x129\n" + "\tteam_info\x18\x02 \x01(\v2\x1c.greatestworks.team.TeamInfoR\bteamInfo\x128\n" + "\amembers\x18\x03 \x03(\v2\x1e.greatestworks.team.TeamMemberR\amembers\x12S\n" + "\x13pending_invitations\x18\x04 \x03(\v2\".greatestworks.team.TeamInvitationR\x12pendingInvitations\x12V\n" + "\x14pending_applications\x18\x05 \x03(\v2#.greatestworks.team.TeamApplicationR\x13pendingApplications\"\xee\x01\n" + "\x12SearchTeamsRequest\x12\x14\n" + "\x05query\x18\x01 \x01(\tR\x05query\x129\n" + "\tteam_type\x18\x02 \x01(\x0e2\x1c.greatestworks.team.TeamTypeR\bteamType\x12\x1f\n" + "\vonly_public\x18\x03 \x01(\bR\n" + "onlyPublic\x12\x1b\n" + "\tmin_level\x18\x04 \x01(\x05R\bminLevel\x12\x1b\n" + "\tmax_level\x18\x05 \x01(\x05R\bmaxLevel\x12\x14\n" + "\x05limit\x18\x06 \x01(\x05R\x05limit\x12\x16\n" + "\x06offset\x18\a \x01(\x05R\x06offset\"\xcd\x01\n" + "\x13SearchTeamsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x122\n" + "\x05teams\x18\x02 \x03(\v2\x1c.greatestworks.team.TeamInfoR\x05teams\x12D\n" + "\n" + "pagination\x18\x03 \x01(\v2$.greatestworks.common.PaginationInfoR\n" + "pagination\"\x8f\x01\n" + "\x19UpdateTeamSettingsRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12<\n" + "\bsettings\x18\x03 \x01(\v2 .greatestworks.team.TeamSettingsR\bsettings\"\x9f\x01\n" + "\x1aUpdateTeamSettingsResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12C\n" + "\fnew_settings\x18\x02 \x01(\v2 .greatestworks.team.TeamSettingsR\vnewSettings\"s\n" + "\x17HandleInvitationRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12#\n" + "\rinvitation_id\x18\x02 \x01(\tR\finvitationId\x12\x16\n" + "\x06accept\x18\x03 \x01(\bR\x06accept\"\x93\x01\n" + "\x18HandleInvitationResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x129\n" + "\tteam_info\x18\x02 \x01(\v2\x1c.greatestworks.team.TeamInfoR\bteamInfo\"d\n" + "\x12ApplyToJoinRequest\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12\x18\n" + "\amessage\x18\x03 \x01(\tR\amessage\"z\n" + "\x13ApplyToJoinResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12%\n" + "\x0eapplication_id\x18\x02 \x01(\tR\rapplicationId\"\x92\x01\n" + "\x18HandleApplicationRequest\x12\x1d\n" + "\n" + "handler_id\x18\x01 \x01(\tR\thandlerId\x12%\n" + "\x0eapplication_id\x18\x02 \x01(\tR\rapplicationId\x12\x18\n" + "\aapprove\x18\x03 \x01(\bR\aapprove\x12\x16\n" + "\x06reason\x18\x04 \x01(\tR\x06reason\"\x98\x01\n" + "\x19HandleApplicationResponse\x12<\n" + "\x06common\x18\x01 \x01(\v2$.greatestworks.common.CommonResponseR\x06common\x12=\n" + "\n" + "new_member\x18\x02 \x01(\v2\x1e.greatestworks.team.TeamMemberR\tnewMember\"\xec\x04\n" + "\bTeamInfo\x12\x17\n" + "\ateam_id\x18\x01 \x01(\tR\x06teamId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x129\n" + "\tteam_type\x18\x04 \x01(\x0e2\x1c.greatestworks.team.TeamTypeR\bteamType\x126\n" + "\x06status\x18\x05 \x01(\x0e2\x1e.greatestworks.team.TeamStatusR\x06status\x12\x1b\n" + "\tleader_id\x18\x06 \x01(\tR\bleaderId\x12\x1f\n" + "\vleader_name\x18\a \x01(\tR\n" + "leaderName\x12!\n" + "\fmember_count\x18\b \x01(\x05R\vmemberCount\x12\x1f\n" + "\vmax_members\x18\t \x01(\x05R\n" + "maxMembers\x12#\n" + "\raverage_level\x18\n" + " \x01(\x05R\faverageLevel\x12\x1b\n" + "\tis_public\x18\v \x01(\bR\bisPublic\x12#\n" + "\ris_recruiting\x18\f \x01(\bR\fisRecruiting\x12\x1d\n" + "\n" + "created_at\x18\r \x01(\x03R\tcreatedAt\x12#\n" + "\rlast_activity\x18\x0e \x01(\x03R\flastActivity\x12<\n" + "\bsettings\x18\x0f \x01(\v2 .greatestworks.team.TeamSettingsR\bsettings\x123\n" + "\x05stats\x18\x10 \x01(\v2\x1d.greatestworks.team.TeamStatsR\x05stats\"\xf9\x02\n" + "\n" + "TeamMember\x12\x1b\n" + "\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x1f\n" + "\vplayer_name\x18\x02 \x01(\tR\n" + "playerName\x12\x14\n" + "\x05level\x18\x03 \x01(\x05R\x05level\x122\n" + "\x04role\x18\x04 \x01(\x0e2\x1e.greatestworks.team.MemberRoleR\x04role\x128\n" + "\x06status\x18\x05 \x01(\x0e2 .greatestworks.team.MemberStatusR\x06status\x12\x1b\n" + "\tjoined_at\x18\x06 \x01(\x03R\bjoinedAt\x12\x1f\n" + "\vlast_online\x18\a \x01(\x03R\n" + "lastOnline\x12/\n" + "\x13contribution_points\x18\b \x01(\x05R\x12contributionPoints\x12:\n" + "\bposition\x18\t \x01(\v2\x1e.greatestworks.common.PositionR\bposition\"\x85\x03\n" + "\x0eTeamInvitation\x12#\n" + "\rinvitation_id\x18\x01 \x01(\tR\finvitationId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12\x1b\n" + "\tteam_name\x18\x03 \x01(\tR\bteamName\x12\x1d\n" + "\n" + "inviter_id\x18\x04 \x01(\tR\tinviterId\x12!\n" + "\finviter_name\x18\x05 \x01(\tR\vinviterName\x12\x1d\n" + "\n" + "invitee_id\x18\x06 \x01(\tR\tinviteeId\x12!\n" + "\finvitee_name\x18\a \x01(\tR\vinviteeName\x12\x18\n" + "\amessage\x18\b \x01(\tR\amessage\x12\x1d\n" + "\n" + "created_at\x18\t \x01(\x03R\tcreatedAt\x12\x1d\n" + "\n" + "expires_at\x18\n" + " \x01(\x03R\texpiresAt\x12<\n" + "\x06status\x18\v \x01(\x0e2$.greatestworks.team.InvitationStatusR\x06status\"\xbc\x02\n" + "\x0fTeamApplication\x12%\n" + "\x0eapplication_id\x18\x01 \x01(\tR\rapplicationId\x12\x17\n" + "\ateam_id\x18\x02 \x01(\tR\x06teamId\x12!\n" + "\fapplicant_id\x18\x03 \x01(\tR\vapplicantId\x12%\n" + "\x0eapplicant_name\x18\x04 \x01(\tR\rapplicantName\x12'\n" + "\x0fapplicant_level\x18\x05 \x01(\x05R\x0eapplicantLevel\x12\x18\n" + "\amessage\x18\x06 \x01(\tR\amessage\x12\x1d\n" + "\n" + "created_at\x18\a \x01(\x03R\tcreatedAt\x12=\n" + "\x06status\x18\b \x01(\x0e2%.greatestworks.team.ApplicationStatusR\x06status\"\xea\x03\n" + "\fTeamSettings\x128\n" + "\x18auto_accept_applications\x18\x01 \x01(\bR\x16autoAcceptApplications\x122\n" + "\x15min_level_requirement\x18\x02 \x01(\x05R\x13minLevelRequirement\x122\n" + "\x15max_level_requirement\x18\x03 \x01(\x05R\x13maxLevelRequirement\x12.\n" + "\x13allow_member_invite\x18\x04 \x01(\bR\x11allowMemberInvite\x12)\n" + "\x10require_approval\x18\x05 \x01(\bR\x0frequireApproval\x12'\n" + "\x0fwelcome_message\x18\x06 \x01(\tR\x0ewelcomeMessage\x12\x12\n" + "\x04tags\x18\a \x03(\tR\x04tags\x12]\n" + "\x0fcustom_settings\x18\b \x03(\v24.greatestworks.team.TeamSettings.CustomSettingsEntryR\x0ecustomSettings\x1aA\n" + "\x13CustomSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xfe\x01\n" + "\tTeamStats\x12#\n" + "\rtotal_battles\x18\x01 \x01(\x05R\ftotalBattles\x12\x12\n" + "\x04wins\x18\x02 \x01(\x05R\x04wins\x12\x16\n" + "\x06losses\x18\x03 \x01(\x05R\x06losses\x12\x19\n" + "\bwin_rate\x18\x04 \x01(\x02R\awinRate\x12)\n" + "\x10total_experience\x18\x05 \x01(\x05R\x0ftotalExperience\x123\n" + "\x15achievements_unlocked\x18\x06 \x01(\x05R\x14achievementsUnlocked\x12%\n" + "\x0etotal_playtime\x18\a \x01(\x03R\rtotalPlaytime*\xd4\x01\n" + "\bTeamType\x12\x19\n" + "\x15TEAM_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10TEAM_TYPE_CASUAL\x10\x01\x12\x19\n" + "\x15TEAM_TYPE_COMPETITIVE\x10\x02\x12\x11\n" + "\rTEAM_TYPE_PVE\x10\x03\x12\x11\n" + "\rTEAM_TYPE_PVP\x10\x04\x12\x13\n" + "\x0fTEAM_TYPE_GUILD\x10\x05\x12\x13\n" + "\x0fTEAM_TYPE_PARTY\x10\x06\x12\x12\n" + "\x0eTEAM_TYPE_RAID\x10\a\x12\x18\n" + "\x14TEAM_TYPE_TOURNAMENT\x10\b*\xc3\x01\n" + "\n" + "TeamStatus\x12\x1b\n" + "\x17TEAM_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12TEAM_STATUS_ACTIVE\x10\x01\x12\x18\n" + "\x14TEAM_STATUS_INACTIVE\x10\x02\x12\x19\n" + "\x15TEAM_STATUS_DISBANDED\x10\x03\x12\x19\n" + "\x15TEAM_STATUS_SUSPENDED\x10\x04\x12\x1a\n" + "\x16TEAM_STATUS_RECRUITING\x10\x05\x12\x14\n" + "\x10TEAM_STATUS_FULL\x10\x06*\xa4\x01\n" + "\n" + "MemberRole\x12\x1b\n" + "\x17MEMBER_ROLE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12MEMBER_ROLE_LEADER\x10\x01\x12\x17\n" + "\x13MEMBER_ROLE_OFFICER\x10\x02\x12\x17\n" + "\x13MEMBER_ROLE_VETERAN\x10\x03\x12\x16\n" + "\x12MEMBER_ROLE_MEMBER\x10\x04\x12\x17\n" + "\x13MEMBER_ROLE_RECRUIT\x10\x05*\xae\x01\n" + "\fMemberStatus\x12\x1d\n" + "\x19MEMBER_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14MEMBER_STATUS_ONLINE\x10\x01\x12\x19\n" + "\x15MEMBER_STATUS_OFFLINE\x10\x02\x12\x1b\n" + "\x17MEMBER_STATUS_IN_BATTLE\x10\x03\x12\x15\n" + "\x11MEMBER_STATUS_AFK\x10\x04\x12\x16\n" + "\x12MEMBER_STATUS_BUSY\x10\x05*\xd4\x01\n" + "\x10InvitationStatus\x12!\n" + "\x1dINVITATION_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19INVITATION_STATUS_PENDING\x10\x01\x12\x1e\n" + "\x1aINVITATION_STATUS_ACCEPTED\x10\x02\x12\x1e\n" + "\x1aINVITATION_STATUS_REJECTED\x10\x03\x12\x1d\n" + "\x19INVITATION_STATUS_EXPIRED\x10\x04\x12\x1f\n" + "\x1bINVITATION_STATUS_CANCELLED\x10\x05*\xbb\x01\n" + "\x11ApplicationStatus\x12\"\n" + "\x1eAPPLICATION_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aAPPLICATION_STATUS_PENDING\x10\x01\x12\x1f\n" + "\x1bAPPLICATION_STATUS_APPROVED\x10\x02\x12\x1f\n" + "\x1bAPPLICATION_STATUS_REJECTED\x10\x03\x12 \n" + "\x1cAPPLICATION_STATUS_WITHDRAWN\x10\x042\xc6\t\n" + "\vTeamService\x12[\n" + "\n" + "CreateTeam\x12%.greatestworks.team.CreateTeamRequest\x1a&.greatestworks.team.CreateTeamResponse\x12U\n" + "\bJoinTeam\x12#.greatestworks.team.JoinTeamRequest\x1a$.greatestworks.team.JoinTeamResponse\x12X\n" + "\tLeaveTeam\x12$.greatestworks.team.LeaveTeamRequest\x1a%.greatestworks.team.LeaveTeamResponse\x12a\n" + "\fInvitePlayer\x12'.greatestworks.team.InvitePlayerRequest\x1a(.greatestworks.team.InvitePlayerResponse\x12[\n" + "\n" + "KickPlayer\x12%.greatestworks.team.KickPlayerRequest\x1a&.greatestworks.team.KickPlayerResponse\x12s\n" + "\x12TransferLeadership\x12-.greatestworks.team.TransferLeadershipRequest\x1a..greatestworks.team.TransferLeadershipResponse\x12^\n" + "\vGetTeamInfo\x12&.greatestworks.team.GetTeamInfoRequest\x1a'.greatestworks.team.GetTeamInfoResponse\x12^\n" + "\vSearchTeams\x12&.greatestworks.team.SearchTeamsRequest\x1a'.greatestworks.team.SearchTeamsResponse\x12s\n" + "\x12UpdateTeamSettings\x12-.greatestworks.team.UpdateTeamSettingsRequest\x1a..greatestworks.team.UpdateTeamSettingsResponse\x12m\n" + "\x10HandleInvitation\x12+.greatestworks.team.HandleInvitationRequest\x1a,.greatestworks.team.HandleInvitationResponse\x12^\n" + "\vApplyToJoin\x12&.greatestworks.team.ApplyToJoinRequest\x1a'.greatestworks.team.ApplyToJoinResponse\x12p\n" + "\x11HandleApplication\x12,.greatestworks.team.HandleApplicationRequest\x1a-.greatestworks.team.HandleApplicationResponseB8Z!greatestworks/internal/proto/team\xaa\x02\x12GreatestWorks.Teamb\x06proto3" var ( file_proto_team_proto_rawDescOnce sync.Once file_proto_team_proto_rawDescData []byte ) func file_proto_team_proto_rawDescGZIP() []byte { file_proto_team_proto_rawDescOnce.Do(func() { file_proto_team_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_team_proto_rawDesc), len(file_proto_team_proto_rawDesc))) }) return file_proto_team_proto_rawDescData } var file_proto_team_proto_enumTypes = make([]protoimpl.EnumInfo, 6) var file_proto_team_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_proto_team_proto_goTypes = []any{ (TeamType)(0), // 0: greatestworks.team.TeamType (TeamStatus)(0), // 1: greatestworks.team.TeamStatus (MemberRole)(0), // 2: greatestworks.team.MemberRole (MemberStatus)(0), // 3: greatestworks.team.MemberStatus (InvitationStatus)(0), // 4: greatestworks.team.InvitationStatus (ApplicationStatus)(0), // 5: greatestworks.team.ApplicationStatus (*CreateTeamRequest)(nil), // 6: greatestworks.team.CreateTeamRequest (*CreateTeamResponse)(nil), // 7: greatestworks.team.CreateTeamResponse (*JoinTeamRequest)(nil), // 8: greatestworks.team.JoinTeamRequest (*JoinTeamResponse)(nil), // 9: greatestworks.team.JoinTeamResponse (*LeaveTeamRequest)(nil), // 10: greatestworks.team.LeaveTeamRequest (*LeaveTeamResponse)(nil), // 11: greatestworks.team.LeaveTeamResponse (*InvitePlayerRequest)(nil), // 12: greatestworks.team.InvitePlayerRequest (*InvitePlayerResponse)(nil), // 13: greatestworks.team.InvitePlayerResponse (*KickPlayerRequest)(nil), // 14: greatestworks.team.KickPlayerRequest (*KickPlayerResponse)(nil), // 15: greatestworks.team.KickPlayerResponse (*TransferLeadershipRequest)(nil), // 16: greatestworks.team.TransferLeadershipRequest (*TransferLeadershipResponse)(nil), // 17: greatestworks.team.TransferLeadershipResponse (*GetTeamInfoRequest)(nil), // 18: greatestworks.team.GetTeamInfoRequest (*GetTeamInfoResponse)(nil), // 19: greatestworks.team.GetTeamInfoResponse (*SearchTeamsRequest)(nil), // 20: greatestworks.team.SearchTeamsRequest (*SearchTeamsResponse)(nil), // 21: greatestworks.team.SearchTeamsResponse (*UpdateTeamSettingsRequest)(nil), // 22: greatestworks.team.UpdateTeamSettingsRequest (*UpdateTeamSettingsResponse)(nil), // 23: greatestworks.team.UpdateTeamSettingsResponse (*HandleInvitationRequest)(nil), // 24: greatestworks.team.HandleInvitationRequest (*HandleInvitationResponse)(nil), // 25: greatestworks.team.HandleInvitationResponse (*ApplyToJoinRequest)(nil), // 26: greatestworks.team.ApplyToJoinRequest (*ApplyToJoinResponse)(nil), // 27: greatestworks.team.ApplyToJoinResponse (*HandleApplicationRequest)(nil), // 28: greatestworks.team.HandleApplicationRequest (*HandleApplicationResponse)(nil), // 29: greatestworks.team.HandleApplicationResponse (*TeamInfo)(nil), // 30: greatestworks.team.TeamInfo (*TeamMember)(nil), // 31: greatestworks.team.TeamMember (*TeamInvitation)(nil), // 32: greatestworks.team.TeamInvitation (*TeamApplication)(nil), // 33: greatestworks.team.TeamApplication (*TeamSettings)(nil), // 34: greatestworks.team.TeamSettings (*TeamStats)(nil), // 35: greatestworks.team.TeamStats nil, // 36: greatestworks.team.CreateTeamRequest.SettingsEntry nil, // 37: greatestworks.team.TeamSettings.CustomSettingsEntry (*common.CommonResponse)(nil), // 38: greatestworks.common.CommonResponse (*common.PaginationInfo)(nil), // 39: greatestworks.common.PaginationInfo (*common.Position)(nil), // 40: greatestworks.common.Position } var file_proto_team_proto_depIdxs = []int32{ 0, // 0: greatestworks.team.CreateTeamRequest.team_type:type_name -> greatestworks.team.TeamType 36, // 1: greatestworks.team.CreateTeamRequest.settings:type_name -> greatestworks.team.CreateTeamRequest.SettingsEntry 38, // 2: greatestworks.team.CreateTeamResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 3: greatestworks.team.CreateTeamResponse.team_info:type_name -> greatestworks.team.TeamInfo 38, // 4: greatestworks.team.JoinTeamResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 5: greatestworks.team.JoinTeamResponse.team_info:type_name -> greatestworks.team.TeamInfo 31, // 6: greatestworks.team.JoinTeamResponse.member_info:type_name -> greatestworks.team.TeamMember 38, // 7: greatestworks.team.LeaveTeamResponse.common:type_name -> greatestworks.common.CommonResponse 38, // 8: greatestworks.team.InvitePlayerResponse.common:type_name -> greatestworks.common.CommonResponse 38, // 9: greatestworks.team.KickPlayerResponse.common:type_name -> greatestworks.common.CommonResponse 38, // 10: greatestworks.team.TransferLeadershipResponse.common:type_name -> greatestworks.common.CommonResponse 31, // 11: greatestworks.team.TransferLeadershipResponse.new_leader:type_name -> greatestworks.team.TeamMember 38, // 12: greatestworks.team.GetTeamInfoResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 13: greatestworks.team.GetTeamInfoResponse.team_info:type_name -> greatestworks.team.TeamInfo 31, // 14: greatestworks.team.GetTeamInfoResponse.members:type_name -> greatestworks.team.TeamMember 32, // 15: greatestworks.team.GetTeamInfoResponse.pending_invitations:type_name -> greatestworks.team.TeamInvitation 33, // 16: greatestworks.team.GetTeamInfoResponse.pending_applications:type_name -> greatestworks.team.TeamApplication 0, // 17: greatestworks.team.SearchTeamsRequest.team_type:type_name -> greatestworks.team.TeamType 38, // 18: greatestworks.team.SearchTeamsResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 19: greatestworks.team.SearchTeamsResponse.teams:type_name -> greatestworks.team.TeamInfo 39, // 20: greatestworks.team.SearchTeamsResponse.pagination:type_name -> greatestworks.common.PaginationInfo 34, // 21: greatestworks.team.UpdateTeamSettingsRequest.settings:type_name -> greatestworks.team.TeamSettings 38, // 22: greatestworks.team.UpdateTeamSettingsResponse.common:type_name -> greatestworks.common.CommonResponse 34, // 23: greatestworks.team.UpdateTeamSettingsResponse.new_settings:type_name -> greatestworks.team.TeamSettings 38, // 24: greatestworks.team.HandleInvitationResponse.common:type_name -> greatestworks.common.CommonResponse 30, // 25: greatestworks.team.HandleInvitationResponse.team_info:type_name -> greatestworks.team.TeamInfo 38, // 26: greatestworks.team.ApplyToJoinResponse.common:type_name -> greatestworks.common.CommonResponse 38, // 27: greatestworks.team.HandleApplicationResponse.common:type_name -> greatestworks.common.CommonResponse 31, // 28: greatestworks.team.HandleApplicationResponse.new_member:type_name -> greatestworks.team.TeamMember 0, // 29: greatestworks.team.TeamInfo.team_type:type_name -> greatestworks.team.TeamType 1, // 30: greatestworks.team.TeamInfo.status:type_name -> greatestworks.team.TeamStatus 34, // 31: greatestworks.team.TeamInfo.settings:type_name -> greatestworks.team.TeamSettings 35, // 32: greatestworks.team.TeamInfo.stats:type_name -> greatestworks.team.TeamStats 2, // 33: greatestworks.team.TeamMember.role:type_name -> greatestworks.team.MemberRole 3, // 34: greatestworks.team.TeamMember.status:type_name -> greatestworks.team.MemberStatus 40, // 35: greatestworks.team.TeamMember.position:type_name -> greatestworks.common.Position 4, // 36: greatestworks.team.TeamInvitation.status:type_name -> greatestworks.team.InvitationStatus 5, // 37: greatestworks.team.TeamApplication.status:type_name -> greatestworks.team.ApplicationStatus 37, // 38: greatestworks.team.TeamSettings.custom_settings:type_name -> greatestworks.team.TeamSettings.CustomSettingsEntry 6, // 39: greatestworks.team.TeamService.CreateTeam:input_type -> greatestworks.team.CreateTeamRequest 8, // 40: greatestworks.team.TeamService.JoinTeam:input_type -> greatestworks.team.JoinTeamRequest 10, // 41: greatestworks.team.TeamService.LeaveTeam:input_type -> greatestworks.team.LeaveTeamRequest 12, // 42: greatestworks.team.TeamService.InvitePlayer:input_type -> greatestworks.team.InvitePlayerRequest 14, // 43: greatestworks.team.TeamService.KickPlayer:input_type -> greatestworks.team.KickPlayerRequest 16, // 44: greatestworks.team.TeamService.TransferLeadership:input_type -> greatestworks.team.TransferLeadershipRequest 18, // 45: greatestworks.team.TeamService.GetTeamInfo:input_type -> greatestworks.team.GetTeamInfoRequest 20, // 46: greatestworks.team.TeamService.SearchTeams:input_type -> greatestworks.team.SearchTeamsRequest 22, // 47: greatestworks.team.TeamService.UpdateTeamSettings:input_type -> greatestworks.team.UpdateTeamSettingsRequest 24, // 48: greatestworks.team.TeamService.HandleInvitation:input_type -> greatestworks.team.HandleInvitationRequest 26, // 49: greatestworks.team.TeamService.ApplyToJoin:input_type -> greatestworks.team.ApplyToJoinRequest 28, // 50: greatestworks.team.TeamService.HandleApplication:input_type -> greatestworks.team.HandleApplicationRequest 7, // 51: greatestworks.team.TeamService.CreateTeam:output_type -> greatestworks.team.CreateTeamResponse 9, // 52: greatestworks.team.TeamService.JoinTeam:output_type -> greatestworks.team.JoinTeamResponse 11, // 53: greatestworks.team.TeamService.LeaveTeam:output_type -> greatestworks.team.LeaveTeamResponse 13, // 54: greatestworks.team.TeamService.InvitePlayer:output_type -> greatestworks.team.InvitePlayerResponse 15, // 55: greatestworks.team.TeamService.KickPlayer:output_type -> greatestworks.team.KickPlayerResponse 17, // 56: greatestworks.team.TeamService.TransferLeadership:output_type -> greatestworks.team.TransferLeadershipResponse 19, // 57: greatestworks.team.TeamService.GetTeamInfo:output_type -> greatestworks.team.GetTeamInfoResponse 21, // 58: greatestworks.team.TeamService.SearchTeams:output_type -> greatestworks.team.SearchTeamsResponse 23, // 59: greatestworks.team.TeamService.UpdateTeamSettings:output_type -> greatestworks.team.UpdateTeamSettingsResponse 25, // 60: greatestworks.team.TeamService.HandleInvitation:output_type -> greatestworks.team.HandleInvitationResponse 27, // 61: greatestworks.team.TeamService.ApplyToJoin:output_type -> greatestworks.team.ApplyToJoinResponse 29, // 62: greatestworks.team.TeamService.HandleApplication:output_type -> greatestworks.team.HandleApplicationResponse 51, // [51:63] is the sub-list for method output_type 39, // [39:51] is the sub-list for method input_type 39, // [39:39] is the sub-list for extension type_name 39, // [39:39] is the sub-list for extension extendee 0, // [0:39] is the sub-list for field type_name } func init() { file_proto_team_proto_init() } func file_proto_team_proto_init() { if File_proto_team_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_team_proto_rawDesc), len(file_proto_team_proto_rawDesc)), NumEnums: 6, NumMessages: 32, NumExtensions: 0, NumServices: 1, }, GoTypes: file_proto_team_proto_goTypes, DependencyIndexes: file_proto_team_proto_depIdxs, EnumInfos: file_proto_team_proto_enumTypes, MessageInfos: file_proto_team_proto_msgTypes, }.Build() File_proto_team_proto = out.File file_proto_team_proto_goTypes = nil file_proto_team_proto_depIdxs = nil } ================================================ FILE: internal/readme.md ================================================ # Internal Module Documentation ## 📋 模块说明 ### 🏗️ 模块架构设计 每个模块遵循DDD(领域驱动设计)架构,包含以下核心组件: #### 📦 核心组件 - **Model**: 对应的数据存储和领域模型 - **System**: 该模块的管理系统,负责数据的CRUD操作 - **Owner**: 定义从属模块需要实现的方法接口 - **Handler**: 处理从属模块需要的业务逻辑 - **Abstract**: 模块成员的抽象,接口定义 ### 🎯 领域驱动设计 (DDD) 本项目采用完整的DDD架构模式,将业务逻辑分为以下层次: #### 🏛️ 领域层 (Domain Layer) ``` domain/ ├── player/ # 玩家领域 │ ├── beginner/ # 新手引导系统 │ ├── hangup/ # 挂机系统 │ ├── honor/ # 荣誉系统 │ ├── player.go # 玩家聚合根 │ ├── service.go # 领域服务 │ └── repository.go # 仓储接口 ├── battle/ # 战斗领域 ├── social/ # 社交领域 (31个文件) ├── building/ # 建筑领域 ├── pet/ # 宠物领域 ├── ranking/ # 排行榜领域 ├── minigame/ # 小游戏领域 ├── npc/ # NPC领域 ├── quest/ # 任务领域 ├── scene/ # 场景领域 (24个文件) ├── skill/ # 技能领域 ├── inventory/ # 背包领域 │ ├── dressup/ # 装扮系统 │ └── synthesis/ # 合成系统 └── events/ # 领域事件 ``` #### 🏗️ 基础设施层 (Infrastructure Layer) ``` infrastructure/ ├── persistence/ # 数据持久化 (10个文件) │ ├── base_repository.go # 基础仓储 │ ├── player_repository.go # 玩家仓储 │ ├── battle_repository.go # 战斗仓储 │ ├── hangup_repository.go # 挂机仓储 │ ├── weather_repository.go # 天气仓储 │ ├── plant_repository.go # 植物仓储 │ └── npc_repository.go # NPC仓储 ├── cache/ # 缓存服务 ├── messaging/ # 消息服务 (5个文件) │ ├── nats_publisher.go # NATS发布者 │ ├── nats_subscriber.go # NATS订阅者 │ ├── event_dispatcher.go # 事件分发器 │ └── worker_pool.go # 工作池 ├── network/ # 网络服务 ├── config/ # 配置管理 (7个文件) ├── logging/ # 日志服务 ├── auth/ # 认证服务 ├── container/ # 依赖注入容器 └── monitoring/ # 监控服务 ``` #### 🌐 接口层 (Interface Layer) ``` interfaces/ ├── http/ # HTTP接口 (13个文件) │ ├── auth/ # 认证接口 │ ├── gm/ # GM管理接口 │ └── server.go # HTTP服务器 ├── tcp/ # TCP接口 (14个文件) │ ├── handlers/ # TCP处理器 │ ├── connection/ # 连接管理 │ └── protocol/ # 协议定义 └── rpc/ # RPC接口 (4个文件) ``` ### 🔧 核心模块说明 #### 📊 模块管理器 - **module_manager.go**: 模块生命周期管理 - **imodule.go**: 模块接口定义 - **base_module.go**: 基础模块实现 #### 🎮 游戏核心 - **game/**: 游戏核心逻辑 - **events/**: 事件系统 - **errors/**: 错误处理 #### 🌐 网络通信 - **network/**: 网络协议处理 - **proto/**: 协议定义 #### 🗄️ 数据存储 - **database/**: 数据库连接 - **config/**: 配置管理 - **auth/**: 认证系统 ### 🚀 开发指南 #### 添加新领域模块 1. 在 `domain/` 下创建新领域目录 2. 定义领域实体、值对象和聚合根 3. 实现领域服务和仓储接口 4. 在 `infrastructure/` 下实现具体实现 5. 在 `interfaces/` 下添加接口层 #### 模块开发规范 - 遵循DDD架构原则 - 使用依赖注入容器 - 实现统一的错误处理 - 采用结构化日志 - 编写完整的单元测试 ### 📈 性能优化 #### 数据库优化 - 使用连接池管理数据库连接 - 实现读写分离策略 - 合理使用缓存机制 #### 网络优化 - TCP连接复用 - 消息批处理 - 协议压缩 #### 内存优化 - 对象池复用 - 合理的内存分配 - 垃圾回收优化 ================================================ FILE: k8s/local/auth-service.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: auth-service namespace: gaming labels: app: auth-service spec: replicas: 1 selector: matchLabels: app: auth-service template: metadata: labels: app: auth-service spec: containers: - name: auth-service image: greatestworks-auth:dev imagePullPolicy: IfNotPresent ports: - containerPort: 8080 env: - name: APP_ENV value: development - name: SERVER_HTTP_HOST value: "0.0.0.0" - name: SERVER_HTTP_PORT value: "8080" - name: MONGODB_URI value: "mongodb://admin:admin123@mongodb:27017/admin" - name: MONGODB_DATABASE value: "auth_service" - name: REDIS_ADDR value: "redis:6379" - name: REDIS_PASSWORD value: "redis123" - name: JWT_SECRET value: "dev-secret-change-me" resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "512Mi" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: auth-service namespace: gaming labels: app: auth-service spec: type: NodePort selector: app: auth-service ports: - name: http port: 8080 targetPort: 8080 nodePort: 30080 ================================================ FILE: k8s/local/configmap-gateway.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: gateway-config namespace: gaming data: gateway-service.yaml: | app: name: "GreatestWorks Gateway" version: "1.0.0" environment: "development" debug: false service: name: "gateway-service" version: "1.0.0" environment: "development" node_id: "gateway-node-1" server: tcp: host: "0.0.0.0" port: 9090 max_connections: 10000 read_timeout: "30s" write_timeout: "30s" buffer_size: 4096 compression_enabled: false encryption_enabled: false heartbeat_enabled: true heartbeat_interval: "30s" heartbeat_timeout: "10s" heartbeat_max_missed: 3 keep_alive: true keep_alive_interval: "30s" no_delay: true database: redis: addr: "redis:6379" password: "redis123" db: 1 pool_size: 100 min_idle_conns: 10 max_retries: 3 dial_timeout: "5s" read_timeout: "3s" write_timeout: "3s" pool_timeout: "4s" idle_timeout: "5m" gateway: game_services: discovery: type: "static" static: endpoints: - "game-service:8081" rpc: protocol: "grpc" timeout: "30s" retry_attempts: 3 retry_delay: "1s" circuit_breaker: enabled: true failure_threshold: 5 timeout: "30s" max_requests: 100 load_balancer: strategy: "round_robin" health_check: enabled: true interval: "10s" timeout: "5s" path: "/health" auth_service: base_url: "http://auth-service:8080" timeout: "10s" retry_attempts: 3 retry_delay: "1s" circuit_breaker: enabled: true failure_threshold: 5 timeout: "30s" max_requests: 100 connection: max_connections: 10000 connection_timeout: "30s" idle_timeout: "5m" cleanup_interval: "1m" session: timeout: "24h" cleanup_interval: "1h" store_type: "redis" message_queue: enabled: true provider: "redis" topics: player_events: "gateway.player.events" game_events: "gateway.game.events" system_events: "gateway.system.events" protocol: client: type: "tcp" codec: "binary" compression: false encryption: false game: type: "grpc" codec: "protobuf" compression: true encryption: true routing: rules: - pattern: "player.*" target: "game-service" method: "rpc" - pattern: "battle.*" target: "game-service" method: "rpc" - pattern: "auth.*" target: "auth-service" method: "http" load_balancer: strategy: "round_robin" health_check: true failover: true logging: level: "info" format: "json" output: "stdout" fields: service: "gateway-service" version: "1.0.0" monitoring: health: enabled: true path: "/health" metrics: enabled: false namespace: "gateway_service" profiling: enabled: true host: "0.0.0.0" port: 6062 performance: worker_pool: size: 100 queue_size: 1000 cache: default_ttl: "1h" max_entries: 10000 cleanup_interval: "10m" eviction_policy: "lfu" connection_pool: max_idle: 100 max_open: 200 max_lifetime: "1h" environment: hot_reload: false mock_data: false test_mode: false ================================================ FILE: k8s/local/game-service.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: game-service namespace: gaming labels: app: game-service spec: replicas: 1 selector: matchLabels: app: game-service template: metadata: labels: app: game-service spec: containers: - name: game-service image: greatestworks-game:dev imagePullPolicy: IfNotPresent ports: - containerPort: 8081 env: - name: APP_ENV value: development - name: MONGODB_URI value: "mongodb://admin:admin123@mongodb:27017/admin" - name: MONGODB_DATABASE value: "mmo_game" - name: REDIS_ADDR value: "redis:6379" - name: REDIS_PASSWORD value: "redis123" resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1" memory: "1Gi" livenessProbe: tcpSocket: port: 8081 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: tcpSocket: port: 8081 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: game-service namespace: gaming labels: app: game-service spec: type: ClusterIP selector: app: game-service ports: - name: rpc port: 8081 targetPort: 8081 ================================================ FILE: k8s/local/gateway-service.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: gateway-service namespace: gaming labels: app: gateway-service spec: replicas: 1 selector: matchLabels: app: gateway-service template: metadata: labels: app: gateway-service spec: containers: - name: gateway-service image: greatestworks-gateway:dev imagePullPolicy: IfNotPresent ports: - containerPort: 9090 env: - name: APP_ENV value: development - name: MONGODB_URI value: "mongodb://admin:admin123@mongodb:27017/admin" - name: MONGODB_DATABASE value: "gateway_service" - name: REDIS_ADDR value: "redis:6379" - name: REDIS_PASSWORD value: "redis123" resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1" memory: "1Gi" volumeMounts: - name: gateway-config mountPath: /configs/gateway-service.yaml subPath: gateway-service.yaml livenessProbe: tcpSocket: port: 9090 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: tcpSocket: port: 9090 initialDelaySeconds: 5 periodSeconds: 5 volumes: - name: gateway-config configMap: name: gateway-config items: - key: gateway-service.yaml path: gateway-service.yaml --- apiVersion: v1 kind: Service metadata: name: gateway-service namespace: gaming labels: app: gateway-service spec: type: NodePort selector: app: gateway-service ports: - name: tcp port: 9090 targetPort: 9090 nodePort: 30909 ================================================ FILE: k8s/local/mongodb.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: mongodb namespace: gaming labels: app: mongodb spec: replicas: 1 selector: matchLabels: app: mongodb template: metadata: labels: app: mongodb spec: containers: - name: mongodb image: mongo:7 imagePullPolicy: IfNotPresent ports: - containerPort: 27017 env: - name: MONGO_INITDB_ROOT_USERNAME value: admin - name: MONGO_INITDB_ROOT_PASSWORD value: admin123 resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1" memory: "1Gi" livenessProbe: tcpSocket: port: 27017 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: tcpSocket: port: 27017 initialDelaySeconds: 5 periodSeconds: 5 volumeMounts: - name: mongo-data mountPath: /data/db volumes: - name: mongo-data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: mongodb namespace: gaming labels: app: mongodb spec: type: ClusterIP selector: app: mongodb ports: - name: mongo port: 27017 targetPort: 27017 ================================================ FILE: k8s/local/namespace.yaml ================================================ apiVersion: v1 kind: Namespace metadata: name: gaming labels: app.kubernetes.io/name: greatestworks app.kubernetes.io/part-of: greatestworks ================================================ FILE: k8s/local/overlays/registry/kustomization.yaml ================================================ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization # Use this overlay to replace local image names with registry-qualified ones # After publishing with scripts/publish-images.ps1, edit REPLACE_ME below or generate a copy per your registry resources: - ../../namespace.yaml - ../../mongodb.yaml - ../../redis.yaml - ../../auth-service.yaml - ../../game-service.yaml - ../../gateway-service.yaml - ../../configmap-gateway.yaml images: - name: greatestworks-auth newName: REPLACE_ME/greatestworks-auth newTag: dev - name: greatestworks-game newName: REPLACE_ME/greatestworks-game newTag: dev - name: greatestworks-gateway newName: REPLACE_ME/greatestworks-gateway newTag: dev # Optional: if you also re-tag/push infra images (mongo/redis) with -IncludeInfra in publish script # uncomment and adjust below to your registry path. This can bypass a broken docker.io mirror in your cluster # - name: mongo # newName: REPLACE_ME/mongo # newTag: "7" # - name: redis # newName: REPLACE_ME/redis # newTag: "7" ================================================ FILE: k8s/local/redis.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: redis namespace: gaming labels: app: redis spec: replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7 imagePullPolicy: IfNotPresent args: ["redis-server", "--appendonly", "yes", "--requirepass", "redis123"] ports: - containerPort: 6379 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "512Mi" livenessProbe: tcpSocket: port: 6379 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: tcpSocket: port: 6379 initialDelaySeconds: 5 periodSeconds: 5 volumeMounts: - name: redis-data mountPath: /data volumes: - name: redis-data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: redis namespace: gaming labels: app: redis spec: type: ClusterIP selector: app: redis ports: - name: redis port: 6379 targetPort: 6379 ================================================ FILE: license ================================================ MIT License Copyright (c) 2022 phuhao Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: proto/auth.proto ================================================ syntax = "proto3"; package greatestworks.auth; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/auth"; option csharp_namespace = "GreatestWorks.Auth"; // 认证服务 - 用户登录注册 service AuthService { // 用户注册 rpc Register(RegisterRequest) returns (RegisterResponse); // 用户登录 rpc Login(LoginRequest) returns (LoginResponse); // 用户登出 rpc Logout(LogoutRequest) returns (LogoutResponse); // 心跳检测 rpc HeartBeat(HeartBeatRequest) returns (HeartBeatResponse); // Token 验证 rpc VerifyToken(VerifyTokenRequest) returns (VerifyTokenResponse); // 刷新Token rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse); } // ========== 注册相关 ========== // 注册请求 message RegisterRequest { string username = 1; // 用户名 string password = 2; // 密码(客户端加密后) string email = 3; // 邮箱(可选) string device_id = 4; // 设备ID string client_version = 5; // 客户端版本 } // 注册响应 message RegisterResponse { ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int64 user_id = 3; // 用户ID int64 timestamp = 4; // 时间戳 } // ========== 登录相关 ========== // 登录请求 message LoginRequest { string username = 1; // 用户名 string password = 2; // 密码(客户端加密后) string device_id = 3; // 设备ID string client_version = 4; // 客户端版本 } // 登录响应 message LoginResponse { ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int64 user_id = 3; // 用户ID string session_token = 4; // 会话Token int64 expire_time = 5; // Token过期时间 UserInfo user_info = 6; // 用户信息 int64 timestamp = 7; // 时间戳 } // ========== 登出相关 ========== // 登出请求 message LogoutRequest { int64 user_id = 1; // 用户ID string session_token = 2; // 会话Token } // 登出响应 message LogoutResponse { ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int64 timestamp = 3; // 时间戳 } // ========== 心跳检测 ========== // 心跳请求 message HeartBeatRequest { int64 user_id = 1; // 用户ID string session_token = 2; // 会话Token int64 client_time = 3; // 客户端时间戳 } // 心跳响应 message HeartBeatResponse { ErrorCode error = 1; // 错误码 int64 server_time = 2; // 服务器时间戳 } // ========== Token验证 ========== // Token验证请求 message VerifyTokenRequest { int64 user_id = 1; // 用户ID string session_token = 2; // 会话Token } // Token验证响应 message VerifyTokenResponse { bool valid = 1; // Token是否有效 ErrorCode error = 2; // 错误码 int64 expire_time = 3; // Token过期时间 } // ========== Token刷新 ========== // Token刷新请求 message RefreshTokenRequest { int64 user_id = 1; // 用户ID string session_token = 2; // 当前会话Token } // Token刷新响应 message RefreshTokenResponse { ErrorCode error = 1; // 错误码 string new_token = 2; // 新Token int64 expire_time = 3; // Token过期时间 } // ========== 通用数据结构 ========== // 用户信息 message UserInfo { int64 user_id = 1; // 用户ID string username = 2; // 用户名 string email = 3; // 邮箱 Authority authority = 4; // 权限等级 int64 created_at = 5; // 创建时间 int64 last_login = 6; // 最后登录时间 } // ========== 枚举定义 ========== // 错误码 enum ErrorCode { SUCCESS = 0; // 成功 UNKNOWN_ERROR = 1; // 未知错误 // 用户相关错误 (100-199) INCORRECT_USERNAME_OR_PASSWORD = 100; // 用户名或密码错误 ILLEGAL_USERNAME = 101; // 非法用户名 REPEAT_USERNAME = 102; // 用户名重复 LOGIN_CONFLICT = 103; // 账号已在别处登录 USER_NOT_FOUND = 104; // 用户不存在 USER_BANNED = 105; // 用户被封禁 // Token相关错误 (200-299) INVALID_TOKEN = 200; // 无效Token TOKEN_EXPIRED = 201; // Token已过期 TOKEN_NOT_FOUND = 202; // Token不存在 // 参数相关错误 (300-399) INVALID_PARAMETER = 300; // 无效参数 MISSING_PARAMETER = 301; // 缺少参数 // 服务器相关错误 (900-999) SERVER_MAINTENANCE = 900; // 服务器维护中 SERVER_OVERLOAD = 901; // 服务器过载 DATABASE_ERROR = 902; // 数据库错误 } // 权限等级 enum Authority { PLAYER = 0; // 普通玩家 VIP = 1; // VIP玩家 GM = 2; // GM(游戏管理员) ADMINISTRATOR = 3; // 管理员 SUPER_ADMIN = 4; // 超级管理员 } ================================================ FILE: proto/battle.proto ================================================ syntax = "proto3"; package greatestworks.battle; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/battle"; option csharp_namespace = "GreatestWorks.Battle"; // 战斗服务定义 service BattleService { // 创建战斗 rpc CreateBattle(CreateBattleRequest) returns (CreateBattleResponse); // 加入战斗 rpc JoinBattle(JoinBattleRequest) returns (JoinBattleResponse); // 离开战斗 rpc LeaveBattle(LeaveBattleRequest) returns (LeaveBattleResponse); // 执行战斗动作 rpc ExecuteAction(ExecuteActionRequest) returns (ExecuteActionResponse); // 获取战斗信息 rpc GetBattleInfo(GetBattleInfoRequest) returns (GetBattleInfoResponse); // 获取战斗列表 rpc GetBattleList(GetBattleListRequest) returns (GetBattleListResponse); } // 创建战斗请求 message CreateBattleRequest { string creator_id = 1; string battle_type = 2; int32 max_players = 3; string map_id = 4; map settings = 5; } // 创建战斗响应 message CreateBattleResponse { greatestworks.common.CommonResponse common = 1; string battle_id = 2; BattleInfo battle = 3; } // 加入战斗请求 message JoinBattleRequest { string battle_id = 1; string player_id = 2; string team_id = 3; } // 加入战斗响应 message JoinBattleResponse { greatestworks.common.CommonResponse common = 1; string battle_id = 2; string team_id = 3; int32 position = 4; } // 离开战斗请求 message LeaveBattleRequest { string battle_id = 1; string player_id = 2; } // 离开战斗响应 message LeaveBattleResponse { greatestworks.common.CommonResponse common = 1; string battle_id = 2; } // 执行战斗动作请求 message ExecuteActionRequest { string battle_id = 1; string player_id = 2; string action_type = 3; map parameters = 4; greatestworks.common.Position target_position = 5; } // 执行战斗动作响应 message ExecuteActionResponse { greatestworks.common.CommonResponse common = 1; string action_id = 2; BattleResult result = 3; } // 获取战斗信息请求 message GetBattleInfoRequest { string battle_id = 1; } // 获取战斗信息响应 message GetBattleInfoResponse { greatestworks.common.CommonResponse common = 1; BattleInfo battle = 2; } // 获取战斗列表请求 message GetBattleListRequest { string battle_type = 1; int32 limit = 2; int32 offset = 3; } // 获取战斗列表响应 message GetBattleListResponse { greatestworks.common.CommonResponse common = 1; repeated BattleInfo battles = 2; greatestworks.common.PaginationInfo pagination = 3; } // 战斗信息 message BattleInfo { string battle_id = 1; string battle_type = 2; string map_id = 3; BattleStatus status = 4; int32 max_players = 5; int32 current_players = 6; repeated BattlePlayer players = 7; int64 created_at = 8; int64 started_at = 9; int64 ended_at = 10; } // 战斗状态 enum BattleStatus { BATTLE_STATUS_UNSPECIFIED = 0; BATTLE_STATUS_WAITING = 1; BATTLE_STATUS_STARTING = 2; BATTLE_STATUS_ACTIVE = 3; BATTLE_STATUS_ENDING = 4; BATTLE_STATUS_FINISHED = 5; BATTLE_STATUS_CANCELLED = 6; } // 战斗类型枚举 enum BattleType { BATTLE_TYPE_UNSPECIFIED = 0; BATTLE_TYPE_PVP = 1; // 玩家对战 BATTLE_TYPE_PVE = 2; // 玩家对环境 BATTLE_TYPE_ARENA = 3; // 竞技场 BATTLE_TYPE_RAID = 4; // 团队副本 BATTLE_TYPE_DUNGEON = 5; // 地下城 BATTLE_TYPE_BOSS = 6; // BOSS战 BATTLE_TYPE_TOURNAMENT = 7; // 锦标赛 } // 战斗行动类型枚举 enum BattleActionType { BATTLE_ACTION_TYPE_UNSPECIFIED = 0; BATTLE_ACTION_TYPE_ATTACK = 1; // 攻击 BATTLE_ACTION_TYPE_SKILL = 2; // 技能 BATTLE_ACTION_TYPE_ITEM = 3; // 使用物品 BATTLE_ACTION_TYPE_DEFEND = 4; // 防御 BATTLE_ACTION_TYPE_ESCAPE = 5; // 逃跑 BATTLE_ACTION_TYPE_WAIT = 6; // 等待 } // 战斗结果类型枚举 enum BattleResultType { BATTLE_RESULT_TYPE_UNSPECIFIED = 0; BATTLE_RESULT_TYPE_VICTORY = 1; // 胜利 BATTLE_RESULT_TYPE_DEFEAT = 2; // 失败 BATTLE_RESULT_TYPE_DRAW = 3; // 平局 BATTLE_RESULT_TYPE_ESCAPE = 4; // 逃跑 BATTLE_RESULT_TYPE_TIMEOUT = 5; // 超时 BATTLE_RESULT_TYPE_DISCONNECT = 6; // 断线 } // 战斗事件类型枚举 enum BattleEventType { BATTLE_EVENT_TYPE_UNSPECIFIED = 0; BATTLE_EVENT_TYPE_DAMAGE = 1; // 伤害 BATTLE_EVENT_TYPE_HEAL = 2; // 治疗 BATTLE_EVENT_TYPE_BUFF = 3; // 增益 BATTLE_EVENT_TYPE_DEBUFF = 4; // 减益 BATTLE_EVENT_TYPE_CRITICAL = 5; // 暴击 BATTLE_EVENT_TYPE_MISS = 6; // 未命中 BATTLE_EVENT_TYPE_DODGE = 7; // 闪避 BATTLE_EVENT_TYPE_BLOCK = 8; // 格挡 BATTLE_EVENT_TYPE_DEATH = 9; // 死亡 BATTLE_EVENT_TYPE_REVIVE = 10; // 复活 } // 战斗玩家 message BattlePlayer { string player_id = 1; string name = 2; string team_id = 3; int32 position = 4; PlayerBattleStats stats = 5; bool is_ready = 6; } // 玩家战斗属性 message PlayerBattleStats { int32 health = 1; int32 max_health = 2; int32 mana = 3; int32 max_mana = 4; int32 attack = 5; int32 defense = 6; int32 speed = 7; int32 level = 8; } // 战斗结果 message BattleResult { string action_id = 1; string result_type = 2; map effects = 3; repeated BattleEvent events = 4; int64 timestamp = 5; } // 战斗事件 message BattleEvent { string event_type = 1; string source_id = 2; string target_id = 3; map data = 4; int64 timestamp = 5; } ================================================ FILE: proto/character.proto ================================================ syntax = "proto3"; package greatestworks.character; import "proto/common.proto"; import "proto/auth.proto"; option go_package = "greatestworks/internal/proto/character"; option csharp_namespace = "GreatestWorks.Character"; // 角色服务 service CharacterService { // 创建角色 rpc CreateCharacter(CharacterCreateRequest) returns (CharacterCreateResponse); // 删除角色 rpc DeleteCharacter(CharacterDeleteRequest) returns (CharacterDeleteResponse); // 获取角色列表 rpc GetCharacterList(CharacterListRequest) returns (CharacterListResponse); // 选择角色(进入游戏) rpc SelectCharacter(CharacterSelectRequest) returns (CharacterSelectResponse); // 获取角色详情 rpc GetCharacterInfo(CharacterInfoRequest) returns (CharacterInfoResponse); } // ========== 创建角色 ========== // 创建角色请求 message CharacterCreateRequest { int64 user_id = 1; // 用户ID string name = 2; // 角色名称 int32 unit_id = 3; // 单位定义ID(决定外观、职业等) int32 gender = 4; // 性别 (0:男, 1:女) string session_token = 5; // 会话Token } // 创建角色响应 message CharacterCreateResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 NetCharacter character = 3; // 创建的角色信息 int64 timestamp = 4; // 时间戳 } // ========== 删除角色 ========== // 删除角色请求 message CharacterDeleteRequest { int64 user_id = 1; // 用户ID int64 character_id = 2; // 角色ID string session_token = 3; // 会话Token string confirm_name = 4; // 确认角色名称(防止误删) } // 删除角色响应 message CharacterDeleteResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int64 timestamp = 3; // 时间戳 } // ========== 获取角色列表 ========== // 角色列表请求 message CharacterListRequest { int64 user_id = 1; // 用户ID string session_token = 2; // 会话Token } // 角色列表响应 message CharacterListResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 repeated NetCharacter characters = 3; // 角色列表 int32 max_characters = 4; // 最大角色数量 int64 timestamp = 5; // 时间戳 } // ========== 选择角色 ========== // 选择角色请求 message CharacterSelectRequest { int64 user_id = 1; // 用户ID int64 character_id = 2; // 角色ID string session_token = 3; // 会话Token } // 选择角色响应 message CharacterSelectResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 NetCharacter character = 3; // 角色信息 int32 map_id = 4; // 当前地图ID common.Position position = 5; // 当前位置 int64 timestamp = 6; // 时间戳 } // ========== 获取角色详情 ========== // 角色详情请求 message CharacterInfoRequest { int64 user_id = 1; // 用户ID int64 character_id = 2; // 角色ID string session_token = 3; // 会话Token } // 角色详情响应 message CharacterInfoResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 NetCharacter character = 3; // 角色信息 CharacterAttributes attributes = 4; // 角色属性 int64 timestamp = 5; // 时间戳 } // ========== 数据结构 ========== // 网络角色信息(序列化传输用) message NetCharacter { int64 character_id = 1; // 角色ID int64 user_id = 2; // 所属用户ID string name = 3; // 角色名称 int32 unit_id = 4; // 单位定义ID int32 level = 5; // 等级 int32 exp = 6; // 经验值 int64 gold = 7; // 金币 // 位置信息 int32 map_id = 8; // 当前地图ID float position_x = 9; // X坐标 float position_y = 10; // Y坐标 float position_z = 11; // Z坐标 // 基础属性 int32 hp = 12; // 当前生命值 int32 mp = 13; // 当前魔法值 int32 max_hp = 14; // 最大生命值 int32 max_mp = 15; // 最大魔法值 // 其他信息 int32 gender = 16; // 性别 int64 created_at = 17; // 创建时间 int64 last_login = 18; // 最后登录时间 int32 total_play_time = 19; // 总游戏时长(秒) } // 角色属性 message CharacterAttributes { // 基础属性 BaseAttributes base = 1; // 最终属性(经过装备、Buff等加成后) FinalAttributes final = 2; } // 基础属性 message BaseAttributes { float max_hp = 1; // 最大生命值 float max_mp = 2; // 最大魔法值 float hp_regen = 3; // 生命回复 float mp_regen = 4; // 魔法回复 float ad = 5; // 物理攻击力 float ap = 6; // 法术攻击力 float def = 7; // 物理防御 float mdef = 8; // 法术防御 float cri = 9; // 暴击率 float crd = 10; // 暴击伤害 float hit_rate = 11; // 命中率 float dodge_rate = 12; // 闪避率 float speed = 13; // 移动速度 float attack_speed = 14; // 攻击速度 } // 最终属性 message FinalAttributes { float max_hp = 1; float max_mp = 2; float hp_regen = 3; float mp_regen = 4; float ad = 5; float ap = 6; float def = 7; float mdef = 8; float cri = 9; float crd = 10; float hit_rate = 11; float dodge_rate = 12; float speed = 13; float attack_speed = 14; } // ========== 枚举定义 ========== // 角色性别 enum CharacterGender { MALE = 0; // 男性 FEMALE = 1; // 女性 } // 角色状态 enum CharacterState { IDLE = 0; // 空闲 MOVING = 1; // 移动中 ATTACKING = 2; // 攻击中 CASTING = 3; // 施法中 DEAD = 4; // 死亡 } ================================================ FILE: proto/chat.proto ================================================ syntax = "proto3"; package greatestworks.chat; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/chat"; option csharp_namespace = "GreatestWorks.Chat"; // 聊天服务定义 service ChatService { // 发送消息 rpc SendMessage(SendMessageRequest) returns (SendMessageResponse); // 获取消息历史 rpc GetMessages(GetMessagesRequest) returns (GetMessagesResponse); // 加入聊天频道 rpc JoinChannel(JoinChannelRequest) returns (JoinChannelResponse); // 离开聊天频道 rpc LeaveChannel(LeaveChannelRequest) returns (LeaveChannelResponse); // 创建私聊 rpc CreatePrivateChat(CreatePrivateChatRequest) returns (CreatePrivateChatResponse); // 获取在线用户列表 rpc GetOnlineUsers(GetOnlineUsersRequest) returns (GetOnlineUsersResponse); // 设置用户状态 rpc SetUserStatus(SetUserStatusRequest) returns (SetUserStatusResponse); // 屏蔽用户 rpc BlockUser(BlockUserRequest) returns (BlockUserResponse); // 举报消息 rpc ReportMessage(ReportMessageRequest) returns (ReportMessageResponse); } // 发送消息请求 message SendMessageRequest { string sender_id = 1; string content = 2; greatestworks.common.ChatChannel channel = 3; string target_id = 4; // 私聊目标用户ID或频道ID MessageType message_type = 5; map metadata = 6; } // 发送消息响应 message SendMessageResponse { greatestworks.common.CommonResponse common = 1; string message_id = 2; int64 timestamp = 3; } // 获取消息历史请求 message GetMessagesRequest { greatestworks.common.ChatChannel channel = 1; string channel_id = 2; string user_id = 3; int32 limit = 4; int64 before_timestamp = 5; int64 after_timestamp = 6; } // 获取消息历史响应 message GetMessagesResponse { greatestworks.common.CommonResponse common = 1; repeated ChatMessage messages = 2; greatestworks.common.PaginationInfo pagination = 3; } // 加入聊天频道请求 message JoinChannelRequest { string user_id = 1; greatestworks.common.ChatChannel channel = 2; string channel_id = 3; string password = 4; // 如果是私有频道 } // 加入聊天频道响应 message JoinChannelResponse { greatestworks.common.CommonResponse common = 1; ChannelInfo channel_info = 2; repeated ChatUser online_users = 3; } // 离开聊天频道请求 message LeaveChannelRequest { string user_id = 1; greatestworks.common.ChatChannel channel = 2; string channel_id = 3; } // 离开聊天频道响应 message LeaveChannelResponse { greatestworks.common.CommonResponse common = 1; } // 创建私聊请求 message CreatePrivateChatRequest { string creator_id = 1; repeated string participant_ids = 2; string chat_name = 3; } // 创建私聊响应 message CreatePrivateChatResponse { greatestworks.common.CommonResponse common = 1; string chat_id = 2; ChannelInfo chat_info = 3; } // 获取在线用户列表请求 message GetOnlineUsersRequest { greatestworks.common.ChatChannel channel = 1; string channel_id = 2; int32 limit = 3; int32 offset = 4; } // 获取在线用户列表响应 message GetOnlineUsersResponse { greatestworks.common.CommonResponse common = 1; repeated ChatUser users = 2; greatestworks.common.PaginationInfo pagination = 3; } // 设置用户状态请求 message SetUserStatusRequest { string user_id = 1; UserStatus status = 2; string status_message = 3; } // 设置用户状态响应 message SetUserStatusResponse { greatestworks.common.CommonResponse common = 1; UserStatus new_status = 2; } // 屏蔽用户请求 message BlockUserRequest { string user_id = 1; string target_user_id = 2; bool block = 3; // true=屏蔽, false=解除屏蔽 } // 屏蔽用户响应 message BlockUserResponse { greatestworks.common.CommonResponse common = 1; } // 举报消息请求 message ReportMessageRequest { string reporter_id = 1; string message_id = 2; ReportReason reason = 3; string description = 4; } // 举报消息响应 message ReportMessageResponse { greatestworks.common.CommonResponse common = 1; string report_id = 2; } // 聊天消息 message ChatMessage { string message_id = 1; string sender_id = 2; string sender_name = 3; string content = 4; greatestworks.common.ChatChannel channel = 5; string channel_id = 6; MessageType message_type = 7; int64 timestamp = 8; bool is_edited = 9; int64 edited_timestamp = 10; map metadata = 11; } // 频道信息 message ChannelInfo { string channel_id = 1; string name = 2; string description = 3; greatestworks.common.ChatChannel channel_type = 4; int32 max_users = 5; int32 current_users = 6; bool is_private = 7; string owner_id = 8; repeated string moderator_ids = 9; int64 created_at = 10; } // 聊天用户 message ChatUser { string user_id = 1; string username = 2; string display_name = 3; UserStatus status = 4; string status_message = 5; UserRole role = 6; int64 last_active = 7; bool is_online = 8; } // 消息类型枚举 enum MessageType { MESSAGE_TYPE_UNSPECIFIED = 0; MESSAGE_TYPE_TEXT = 1; // 文本消息 MESSAGE_TYPE_EMOJI = 2; // 表情消息 MESSAGE_TYPE_IMAGE = 3; // 图片消息 MESSAGE_TYPE_FILE = 4; // 文件消息 MESSAGE_TYPE_VOICE = 5; // 语音消息 MESSAGE_TYPE_SYSTEM = 6; // 系统消息 MESSAGE_TYPE_ANNOUNCEMENT = 7; // 公告消息 MESSAGE_TYPE_COMMAND = 8; // 命令消息 } // 用户状态枚举 enum UserStatus { USER_STATUS_UNSPECIFIED = 0; USER_STATUS_ONLINE = 1; // 在线 USER_STATUS_AWAY = 2; // 离开 USER_STATUS_BUSY = 3; // 忙碌 USER_STATUS_INVISIBLE = 4; // 隐身 USER_STATUS_OFFLINE = 5; // 离线 } // 用户角色枚举 enum UserRole { USER_ROLE_UNSPECIFIED = 0; USER_ROLE_MEMBER = 1; // 普通成员 USER_ROLE_MODERATOR = 2; // 版主 USER_ROLE_ADMIN = 3; // 管理员 USER_ROLE_OWNER = 4; // 所有者 } // 举报原因枚举 enum ReportReason { REPORT_REASON_UNSPECIFIED = 0; REPORT_REASON_SPAM = 1; // 垃圾信息 REPORT_REASON_HARASSMENT = 2; // 骚扰 REPORT_REASON_HATE_SPEECH = 3; // 仇恨言论 REPORT_REASON_INAPPROPRIATE = 4; // 不当内容 REPORT_REASON_CHEATING = 5; // 作弊 REPORT_REASON_OTHER = 6; // 其他 } ================================================ FILE: proto/common.proto ================================================ syntax = "proto3"; package greatestworks.common; option go_package = "greatestworks/internal/proto/common"; option csharp_namespace = "GreatestWorks.Common"; // 通用响应结构 message CommonResponse { bool success = 1; string message = 2; int32 code = 3; int64 timestamp = 4; } // 通用请求结构 message CommonRequest { string request_id = 1; int64 timestamp = 2; map metadata = 3; } // 分页信息 message PaginationInfo { int32 page = 1; int32 page_size = 2; int32 total = 3; int32 total_pages = 4; } // 位置信息 message Position { float x = 1; float y = 2; float z = 3; } // 玩家基础信息 message PlayerBasicInfo { string player_id = 1; string name = 2; int32 level = 3; int32 experience = 4; Position position = 5; int64 last_login = 6; int64 created_at = 7; } // 服务器信息 message ServerInfo { string server_id = 1; string name = 2; string version = 3; int32 max_players = 4; int32 current_players = 5; bool is_online = 6; } // 物品类型枚举 enum ItemType { ITEM_TYPE_UNSPECIFIED = 0; ITEM_TYPE_WEAPON = 1; // 武器 ITEM_TYPE_ARMOR = 2; // 护甲 ITEM_TYPE_ACCESSORY = 3; // 饰品 ITEM_TYPE_CONSUMABLE = 4; // 消耗品 ITEM_TYPE_MATERIAL = 5; // 材料 ITEM_TYPE_QUEST = 6; // 任务物品 ITEM_TYPE_CURRENCY = 7; // 货币 ITEM_TYPE_SPECIAL = 8; // 特殊物品 } // 技能类型枚举 enum SkillType { SKILL_TYPE_UNSPECIFIED = 0; SKILL_TYPE_ATTACK = 1; // 攻击技能 SKILL_TYPE_DEFENSE = 2; // 防御技能 SKILL_TYPE_HEAL = 3; // 治疗技能 SKILL_TYPE_BUFF = 4; // 增益技能 SKILL_TYPE_DEBUFF = 5; // 减益技能 SKILL_TYPE_PASSIVE = 6; // 被动技能 SKILL_TYPE_ULTIMATE = 7; // 终极技能 } // 聊天频道枚举 enum ChatChannel { CHAT_CHANNEL_UNSPECIFIED = 0; CHAT_CHANNEL_WORLD = 1; // 世界频道 CHAT_CHANNEL_GUILD = 2; // 公会频道 CHAT_CHANNEL_TEAM = 3; // 队伍频道 CHAT_CHANNEL_PRIVATE = 4; // 私聊频道 CHAT_CHANNEL_SYSTEM = 5; // 系统频道 CHAT_CHANNEL_TRADE = 6; // 交易频道 CHAT_CHANNEL_HELP = 7; // 帮助频道 } // 任务状态枚举 enum QuestStatus { QUEST_STATUS_UNSPECIFIED = 0; QUEST_STATUS_NOT_STARTED = 1; // 未开始 QUEST_STATUS_IN_PROGRESS = 2; // 进行中 QUEST_STATUS_COMPLETED = 3; // 已完成 QUEST_STATUS_FAILED = 4; // 已失败 QUEST_STATUS_CANCELLED = 5; // 已取消 } // 任务类型枚举 enum QuestType { QUEST_TYPE_UNSPECIFIED = 0; QUEST_TYPE_MAIN = 1; // 主线任务 QUEST_TYPE_SIDE = 2; // 支线任务 QUEST_TYPE_DAILY = 3; // 日常任务 QUEST_TYPE_WEEKLY = 4; // 周常任务 QUEST_TYPE_EVENT = 5; // 活动任务 QUEST_TYPE_ACHIEVEMENT = 6; // 成就任务 } // 物品稀有度枚举 enum ItemRarity { ITEM_RARITY_UNSPECIFIED = 0; ITEM_RARITY_COMMON = 1; // 普通 ITEM_RARITY_UNCOMMON = 2; // 不常见 ITEM_RARITY_RARE = 3; // 稀有 ITEM_RARITY_EPIC = 4; // 史诗 ITEM_RARITY_LEGENDARY = 5; // 传说 ITEM_RARITY_MYTHIC = 6; // 神话 } ================================================ FILE: proto/entity.proto ================================================ syntax = "proto3"; package greatestworks.entity; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/entity"; option csharp_namespace = "GreatestWorks.Entity"; // ========== 实体进入/离开场景 ========== // 实体进入场景通知 message EntityEnterResponse { repeated EntityEnterData datas = 1; } // 实体进入数据 message EntityEnterData { int32 entity_id = 1; // 实体ID int32 unit_id = 2; // 单位定义ID EntityType entity_type = 3; // 实体类型 NetTransform transform = 4; // 位置和方向 NetActor actor = 5; // Actor信息(如果是Actor类型) } // 实体离开场景通知 message EntityLeaveResponse { repeated int32 entity_ids = 1; // 离开的实体ID列表 } // ========== 实体Transform同步 ========== // 实体Transform同步请求(客户端→服务器) message EntityTransformSyncRequest { int32 entity_id = 1; // 实体ID NetTransform transform = 2; // 新的Transform int32 state_id = 3; // 状态ID(动画状态) bytes data = 4; // 附加数据 } // 实体Transform同步响应(服务器→客户端广播) message EntityTransformSyncResponse { int32 entity_id = 1; // 实体ID NetTransform transform = 2; // 新的Transform int32 state_id = 3; // 状态ID(动画状态) bytes data = 4; // 附加数据 } // ========== 实体属性同步 ========== // 实体属性同步通知 message EntityAttributeSyncResponse { int32 entity_id = 1; // 实体ID repeated EntityAttributeEntry entries = 2; // 属性条目列表 } // 实体属性条目 message EntityAttributeEntry { EntityAttributeEntryType type = 1; // 属性类型 oneof value { int32 int32_value = 2; // 整数值 float float_value = 3; // 浮点值 string string_value = 4; // 字符串值 } } // ========== 数据结构 ========== // 网络Transform(位置和方向) message NetTransform { NetVector3 position = 1; // 位置 NetVector3 direction = 2; // 方向 } // 网络3D向量 message NetVector3 { float x = 1; float y = 2; float z = 3; } // 网络Actor信息(有战斗属性的实体) message NetActor { FlagStates flag_state = 1; // 状态标志位 int32 level = 2; // 等级 int32 max_hp = 3; // 最大生命值 int32 hp = 4; // 当前生命值 int32 max_mp = 5; // 最大魔法值 int32 mp = 6; // 当前魔法值 int32 exp = 7; // 当前经验值 int32 max_exp = 8; // 升级所需经验值 int32 resurrection_time = 9; // 复活时间(毫秒) string name = 10; // 名称 } // ========== 枚举定义 ========== // 实体类型 enum EntityType { ENTITY_TYPE_PLAYER = 0; // 玩家 ENTITY_TYPE_MONSTER = 1; // 怪物 ENTITY_TYPE_NPC = 2; // NPC ENTITY_TYPE_MISSILE = 3; // 投射物(技能子弹等) ENTITY_TYPE_DROPPED_ITEM = 4; // 掉落物品 ENTITY_TYPE_PET = 5; // 宠物 ENTITY_TYPE_SUMMON = 6; // 召唤物 } // 动画状态 enum AnimationState { ANIMATION_STATE_IDLE = 0; // 空闲 ANIMATION_STATE_MOVE = 1; // 移动 ANIMATION_STATE_SKILL = 2; // 释放技能 ANIMATION_STATE_HURT = 3; // 受伤 ANIMATION_STATE_DEATH = 4; // 死亡 ANIMATION_STATE_JUMP = 5; // 跳跃 ANIMATION_STATE_FALL = 6; // 下落 } // 状态标志位(可组合) enum FlagStates { FLAG_STATE_ZERO = 0; // 无状态 FLAG_STATE_STUN = 1; // 眩晕 FLAG_STATE_ROOT = 2; // 定身 FLAG_STATE_SILENCE = 4; // 沉默 FLAG_STATE_INVINCIBLE = 8; // 无敌 FLAG_STATE_INVISIBLE = 16; // 隐身 FLAG_STATE_DISARM = 32; // 缴械 FLAG_STATE_SLOW = 64; // 减速 } // 实体属性条目类型 enum EntityAttributeEntryType { ATTRIBUTE_NONE = 0; ATTRIBUTE_LEVEL = 1; // 等级 ATTRIBUTE_EXP = 2; // 经验值 ATTRIBUTE_GOLD = 3; // 金币 ATTRIBUTE_HP = 4; // 生命值 ATTRIBUTE_MP = 5; // 魔法值 ATTRIBUTE_MAX_HP = 6; // 最大生命值 ATTRIBUTE_MAX_EXP = 7; // 最大经验值 ATTRIBUTE_MAX_MP = 8; // 最大魔法值 ATTRIBUTE_FLAG_STATE = 9; // 状态标志 ATTRIBUTE_SPEED = 10; // 移动速度 ATTRIBUTE_ATTACK = 11; // 攻击力 ATTRIBUTE_DEFENSE = 12; // 防御力 } ================================================ FILE: proto/errors.proto ================================================ syntax = "proto3"; package greatestworks.errors; option go_package = "greatestworks/internal/proto/errors"; option csharp_namespace = "GreatestWorks.Errors"; // 错误码枚举 - 通用错误 (1000-1999) enum CommonErrorCode { COMMON_ERROR_CODE_UNSPECIFIED = 0; // 成功状态 ERR_SUCCESS = 0; // 成功 // 通用错误 ERR_UNKNOWN = 1000; // 未知错误 ERR_INVALID_MESSAGE = 1001; // 无效消息 ERR_AUTH_FAILED = 1002; // 认证失败 ERR_PLAYER_NOT_FOUND = 1003; // 玩家未找到 ERR_BATTLE_NOT_FOUND = 1004; // 战斗未找到 ERR_UNKNOWN_MESSAGE = 1005; // 未知消息类型 ERR_SERVER_BUSY = 1006; // 服务器繁忙 ERR_INVALID_PLAYER = 1007; // 无效玩家 ERR_PERMISSION_DENIED = 1008; // 权限不足 ERR_RATE_LIMITED = 1009; // 请求过于频繁 ERR_MAINTENANCE = 1010; // 服务器维护 ERR_INVALID_REQUEST = 1011; // 无效请求 ERR_TIMEOUT = 1012; // 请求超时 ERR_CONNECTION_LOST = 1013; // 连接丢失 ERR_INVALID_TOKEN = 1014; // 无效令牌 ERR_SESSION_EXPIRED = 1015; // 会话过期 } // 错误码枚举 - 战斗相关错误 (2000-2999) enum BattleErrorCode { BATTLE_ERROR_CODE_UNSPECIFIED = 0; // 战斗基础错误 ERR_INVALID_CREATOR_ID = 2001; // 无效创建者ID ERR_INVALID_BATTLE_TYPE = 2002; // 无效战斗类型 ERR_INVALID_BATTLE_ID = 2003; // 无效战斗ID ERR_INVALID_PLAYER_ID = 2004; // 无效玩家ID ERR_INVALID_TARGET_ID = 2005; // 无效目标ID ERR_INVALID_SKILL_ID = 2006; // 无效技能ID ERR_INVALID_TEAM = 2007; // 无效队伍 ERR_BATTLE_ALREADY_STARTED = 2008; // 战斗已开始 ERR_BATTLE_NOT_STARTED = 2009; // 战斗未开始 ERR_PLAYER_NOT_IN_BATTLE = 2010; // 玩家不在战斗中 ERR_INSUFFICIENT_MANA = 2011; // MP不足 ERR_SKILL_ON_COOLDOWN = 2012; // 技能冷却中 ERR_INVALID_ACTION = 2013; // 无效行动 ERR_BATTLE_FULL = 2014; // 战斗已满 ERR_BATTLE_ENDED = 2015; // 战斗已结束 ERR_NOT_YOUR_TURN = 2016; // 不是你的回合 ERR_BATTLE_CANCELLED = 2017; // 战斗已取消 ERR_INVALID_BATTLE_STATE = 2018; // 无效战斗状态 ERR_BATTLE_TIMEOUT = 2019; // 战斗超时 } // 错误码枚举 - 宠物相关错误 (3000-3999) enum PetErrorCode { PET_ERROR_CODE_UNSPECIFIED = 0; ERR_PET_NOT_FOUND = 3001; // 宠物未找到 ERR_PET_ALREADY_ACTIVE = 3002; // 宠物已激活 ERR_PET_NOT_ACTIVE = 3003; // 宠物未激活 ERR_PET_LEVEL_TOO_LOW = 3004; // 宠物等级过低 ERR_PET_EVOLUTION_FAIL = 3005; // 宠物进化失败 ERR_PET_INSUFFICIENT_EXP = 3006; // 宠物经验不足 ERR_PET_ALREADY_EVOLVED = 3007; // 宠物已进化 ERR_PET_TRAINING_FAILED = 3008; // 宠物训练失败 ERR_PET_FEEDING_FAILED = 3009; // 宠物喂养失败 ERR_PET_SKILL_NOT_LEARNED = 3010; // 宠物技能未学习 ERR_PET_INSUFFICIENT_ENERGY = 3011; // 宠物能量不足 ERR_PET_SICK = 3012; // 宠物生病 ERR_PET_DEAD = 3013; // 宠物死亡 ERR_PET_BOND_FAILED = 3014; // 宠物羁绊失败 } // 错误码枚举 - 物品相关错误 (4000-4999) enum ItemErrorCode { ITEM_ERROR_CODE_UNSPECIFIED = 0; ERR_ITEM_NOT_FOUND = 4001; // 物品未找到 ERR_ITEM_NOT_USABLE = 4002; // 物品不可使用 ERR_INVENTORY_FULL = 4003; // 背包已满 ERR_INSUFFICIENT_ITEM = 4004; // 物品数量不足 ERR_ITEM_EQUIP_FAILED = 4005; // 装备失败 ERR_ITEM_UNEQUIP_FAILED = 4006; // 卸装失败 ERR_ITEM_CRAFT_FAILED = 4007; // 制作失败 ERR_ITEM_ENHANCE_FAILED = 4008; // 强化失败 ERR_ITEM_TRADE_FAILED = 4009; // 交易失败 ERR_ITEM_DROP_FAILED = 4010; // 丢弃失败 ERR_ITEM_PICKUP_FAILED = 4011; // 拾取失败 ERR_ITEM_STACK_FULL = 4012; // 物品堆叠已满 ERR_ITEM_NOT_TRADEABLE = 4013; // 物品不可交易 ERR_ITEM_BOUND = 4014; // 物品已绑定 ERR_ITEM_LEVEL_TOO_LOW = 4015; // 物品等级过低 } // 错误码枚举 - 建筑相关错误 (5000-5999) enum BuildingErrorCode { BUILDING_ERROR_CODE_UNSPECIFIED = 0; ERR_BUILDING_NOT_FOUND = 5001; // 建筑未找到 ERR_BUILDING_ALREADY_EXISTS = 5002; // 建筑已存在 ERR_BUILDING_INSUFFICIENT_RESOURCES = 5003; // 资源不足 ERR_BUILDING_UPGRADE_FAILED = 5004; // 升级失败 ERR_BUILDING_DESTROY_FAILED = 5005; // 摧毁失败 ERR_BUILDING_PRODUCE_FAILED = 5006; // 生产失败 ERR_BUILDING_COLLECT_FAILED = 5007; // 收集失败 ERR_BUILDING_REPAIR_FAILED = 5008; // 修复失败 ERR_BUILDING_LEVEL_TOO_LOW = 5009; // 建筑等级过低 ERR_BUILDING_LEVEL_TOO_HIGH = 5010; // 建筑等级过高 ERR_BUILDING_NOT_READY = 5011; // 建筑未就绪 ERR_BUILDING_UNDER_CONSTRUCTION = 5012; // 建筑建造中 } // 错误码枚举 - 社交相关错误 (6000-6999) enum SocialErrorCode { SOCIAL_ERROR_CODE_UNSPECIFIED = 0; ERR_FRIEND_NOT_FOUND = 6001; // 好友未找到 ERR_FRIEND_ALREADY_EXISTS = 6002; // 好友已存在 ERR_FRIEND_REQUEST_FAILED = 6003; // 好友请求失败 ERR_FRIEND_ACCEPT_FAILED = 6004; // 接受好友失败 ERR_FRIEND_REJECT_FAILED = 6005; // 拒绝好友失败 ERR_FRIEND_REMOVE_FAILED = 6006; // 删除好友失败 ERR_GUILD_NOT_FOUND = 6007; // 公会未找到 ERR_GUILD_ALREADY_EXISTS = 6008; // 公会已存在 ERR_GUILD_CREATE_FAILED = 6009; // 创建公会失败 ERR_GUILD_JOIN_FAILED = 6010; // 加入公会失败 ERR_GUILD_LEAVE_FAILED = 6011; // 离开公会失败 ERR_GUILD_PERMISSION_DENIED = 6012; // 公会权限不足 ERR_TEAM_NOT_FOUND = 6013; // 队伍未找到 ERR_TEAM_ALREADY_EXISTS = 6014; // 队伍已存在 ERR_TEAM_CREATE_FAILED = 6015; // 创建队伍失败 ERR_TEAM_JOIN_FAILED = 6016; // 加入队伍失败 ERR_TEAM_LEAVE_FAILED = 6017; // 离开队伍失败 ERR_CHAT_MESSAGE_FAILED = 6018; // 聊天消息失败 ERR_CHAT_CHANNEL_NOT_FOUND = 6019; // 聊天频道未找到 ERR_CHAT_PERMISSION_DENIED = 6020; // 聊天权限不足 } // 错误码枚举 - 任务相关错误 (7000-7999) enum QuestErrorCode { QUEST_ERROR_CODE_UNSPECIFIED = 0; ERR_QUEST_NOT_FOUND = 7001; // 任务未找到 ERR_QUEST_ALREADY_ACCEPTED = 7002; // 任务已接受 ERR_QUEST_NOT_ACCEPTED = 7003; // 任务未接受 ERR_QUEST_ALREADY_COMPLETED = 7004; // 任务已完成 ERR_QUEST_NOT_COMPLETED = 7005; // 任务未完成 ERR_QUEST_ACCEPT_FAILED = 7006; // 接受任务失败 ERR_QUEST_COMPLETE_FAILED = 7007; // 完成任务失败 ERR_QUEST_CANCEL_FAILED = 7008; // 取消任务失败 ERR_QUEST_REQUIREMENTS_NOT_MET = 7009; // 任务要求未满足 ERR_QUEST_REWARD_FAILED = 7010; // 任务奖励失败 ERR_QUEST_LEVEL_TOO_LOW = 7011; // 任务等级过低 ERR_QUEST_LEVEL_TOO_HIGH = 7012; // 任务等级过高 ERR_QUEST_PREREQUISITE_NOT_MET = 7013; // 前置任务未完成 } // 错误码枚举 - 系统相关错误 (8000-8999) enum SystemErrorCode { SYSTEM_ERROR_CODE_UNSPECIFIED = 0; ERR_DATABASE_CONNECTION_FAILED = 8001; // 数据库连接失败 ERR_DATABASE_QUERY_FAILED = 8002; // 数据库查询失败 ERR_DATABASE_UPDATE_FAILED = 8003; // 数据库更新失败 ERR_DATABASE_INSERT_FAILED = 8004; // 数据库插入失败 ERR_DATABASE_DELETE_FAILED = 8005; // 数据库删除失败 ERR_CACHE_CONNECTION_FAILED = 8006; // 缓存连接失败 ERR_CACHE_GET_FAILED = 8007; // 缓存获取失败 ERR_CACHE_SET_FAILED = 8008; // 缓存设置失败 ERR_CACHE_DELETE_FAILED = 8009; // 缓存删除失败 ERR_REDIS_CONNECTION_FAILED = 8010; // Redis连接失败 ERR_REDIS_OPERATION_FAILED = 8011; // Redis操作失败 ERR_MONGODB_CONNECTION_FAILED = 8012; // MongoDB连接失败 ERR_MONGODB_OPERATION_FAILED = 8013; // MongoDB操作失败 ERR_CONFIG_LOAD_FAILED = 8014; // 配置加载失败 ERR_LOG_WRITE_FAILED = 8015; // 日志写入失败 } // 错误信息结构 message ErrorInfo { int32 error_code = 1; // 错误码 string error_message = 2; // 错误消息 string error_type = 3; // 错误类型 string details = 4; // 详细信息 int64 timestamp = 5; // 时间戳 string request_id = 6; // 请求ID map context = 7; // 上下文信息 } // 错误响应结构 message ErrorResponse { bool success = 1; // 是否成功 ErrorInfo error = 2; // 错误信息 string message = 3; // 响应消息 int64 timestamp = 4; // 时间戳 string request_id = 5; // 请求ID } ================================================ FILE: proto/fight.proto ================================================ syntax = "proto3"; package greatestworks.fight; import "proto/entity.proto"; option go_package = "greatestworks/internal/proto/fight"; option csharp_namespace = "GreatestWorks.Fight"; // ========== 技能释放 ========== // 技能释放请求(客户端→服务器) message SpellRequest { CastInfo info = 1; } // 技能释放成功响应(服务器→广播所有客户端) message SpellResponse { CastInfo info = 1; } // 技能释放失败响应(服务器→请求的客户端) message SpellFailResponse { int32 skill_id = 1; int32 caster_id = 2; CastResult reason = 3; } // 释放信息 message CastInfo { int32 skill_id = 1; // 技能ID int32 caster_id = 2; // 施法者实体ID NetCastTarget cast_target = 3; // 施法目标 } // 施法目标 message NetCastTarget { int32 target_id = 1; // 目标实体ID(单体目标时使用) entity.NetVector3 target_pos = 2; // 目标位置(范围技能时使用) } // ========== 伤害系统 ========== // 实体受伤通知(服务器→客户端广播) message EntityHurtResponse { DamageInfo info = 1; } // 伤害信息 message DamageInfo { int32 target_id = 1; // 受伤目标ID AttackerInfo attacker_info = 2; // 攻击者信息 int32 amount = 3; // 伤害数值 DamageType damage_type = 4; // 伤害类型 bool is_crit = 5; // 是否暴击 bool is_miss = 6; // 是否未命中 } // 攻击者信息 message AttackerInfo { int32 attacker_id = 1; // 攻击者实体ID AttackerType attacker_type = 2; // 攻击者类型 int32 skill_id = 3; // 技能ID(如果是技能攻击) int32 buff_id = 4; // BuffID(如果是Buff伤害) } // ========== Buff系统 ========== // Buff添加通知 message BuffAddResponse { int32 target_id = 1; // 目标实体ID BuffInfo buff = 2; // Buff信息 } // Buff移除通知 message BuffRemoveResponse { int32 target_id = 1; // 目标实体ID int32 buff_id = 2; // Buff ID int32 buff_instance_id = 3; // Buff实例ID } // Buff更新通知 message BuffUpdateResponse { int32 target_id = 1; // 目标实体ID BuffInfo buff = 2; // 更新后的Buff信息 } // Buff信息 message BuffInfo { int32 buff_id = 1; // Buff定义ID int32 buff_instance_id = 2; // Buff实例ID(同一个Buff可能有多个实例) int32 caster_id = 3; // 施加者实体ID float duration = 4; // 持续时间(秒) float remaining_time = 5; // 剩余时间(秒) int32 layer = 6; // 层数 int32 level = 7; // 等级 BuffType buff_type = 8; // Buff类型 } // ========== 技能信息 ========== // 技能信息 message SkillInfo { int32 skill_id = 1; // 技能ID int32 skill_level = 2; // 技能等级 float cooldown = 3; // 冷却时间(秒) float remaining_cooldown = 4; // 剩余冷却时间(秒) SkillState state = 5; // 技能状态 } // 技能列表请求 message GetSkillListRequest { int32 entity_id = 1; // 实体ID } // 技能列表响应 message GetSkillListResponse { repeated SkillInfo skills = 1; // 技能列表 } // ========== 枚举定义 ========== // 施法结果 enum CastResult { CAST_SUCCESS = 0; // 成功 CAST_NOT_CAST = 1; // 不可释放技能 CAST_TARGET_INVALID = 2; // 无效目标 CAST_ENTITY_DEAD = 3; // 实体已死亡 CAST_OUT_OF_RANGE = 4; // 超出范围 CAST_MP_LACK = 5; // MP不足 CAST_RUNNING = 6; // 进行中 CAST_COOLING = 7; // 冷却中 CAST_INVALID_SKILL_ID = 8; // 无效的技能ID CAST_UNMATCHED_CASTER = 9; // 施法者ID不匹配 CAST_INVALID_CAST_TARGET = 10; // 无效的施法目标 CAST_NOT_ALLOWED = 11; // 不允许释放技能 CAST_SILENCED = 12; // 被沉默 CAST_STUNNED = 13; // 被眩晕 } // 攻击者类型 enum AttackerType { ATTACKER_TYPE_SKILL = 0; // 技能攻击 ATTACKER_TYPE_BUFF = 1; // Buff伤害 ATTACKER_TYPE_NORMAL = 2; // 普通攻击 ATTACKER_TYPE_ENVIRONMENT = 3; // 环境伤害 } // 伤害类型 enum DamageType { DAMAGE_TYPE_UNKNOWN = 0; // 未知 DAMAGE_TYPE_PHYSICAL = 1; // 物理伤害 DAMAGE_TYPE_MAGICAL = 2; // 魔法伤害 DAMAGE_TYPE_REAL = 3; // 真实伤害 DAMAGE_TYPE_HEAL = 4; // 治疗(负伤害) } // Buff类型 enum BuffType { BUFF_TYPE_ATTRIBUTE = 0; // 属性Buff(增减属性) BUFF_TYPE_STATE = 1; // 状态Buff(眩晕、沉默等) BUFF_TYPE_DOT = 2; // 持续伤害 BUFF_TYPE_HOT = 3; // 持续治疗 BUFF_TYPE_SHIELD = 4; // 护盾 BUFF_TYPE_IMMUNE = 5; // 免疫 } // 技能状态 enum SkillState { SKILL_STATE_IDLE = 0; // 空闲(可以释放) SKILL_STATE_READY = 1; // 就绪 SKILL_STATE_INTONATE = 2; // 吟唱中 SKILL_STATE_ACTIVE = 3; // 激活中 SKILL_STATE_COOLING = 4; // 冷却中 } ================================================ FILE: proto/game.proto ================================================ syntax = "proto3"; ================================================ FILE: proto/gateway.proto ================================================ syntax = "proto3"; package greatestworks.gateway; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/gateway"; option csharp_namespace = "GreatestWorks.Gateway"; // 网关服务定义 service GatewayService { // 用户认证 rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse); // 刷新Token rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse); // 登出 rpc Logout(LogoutRequest) returns (LogoutResponse); // 获取服务器列表 rpc GetServerList(GetServerListRequest) returns (GetServerListResponse); // 选择服务器 rpc SelectServer(SelectServerRequest) returns (SelectServerResponse); // 路由请求到后端服务 rpc RouteRequest(RouteRequestMessage) returns (RouteResponseMessage); // 建立WebSocket连接 rpc EstablishConnection(ConnectionRequest) returns (ConnectionResponse); // 心跳检测 rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); // 获取网关状态 rpc GetGatewayStatus(GetGatewayStatusRequest) returns (GetGatewayStatusResponse); // 限流检查 rpc RateLimitCheck(RateLimitRequest) returns (RateLimitResponse); // 获取用户会话信息 rpc GetSessionInfo(GetSessionInfoRequest) returns (GetSessionInfoResponse); } // 认证请求 message AuthenticateRequest { string username = 1; string password = 2; string client_id = 3; string client_version = 4; string device_info = 5; AuthType auth_type = 6; string third_party_token = 7; // 第三方登录token map metadata = 8; } // 认证响应 message AuthenticateResponse { greatestworks.common.CommonResponse common = 1; string access_token = 2; string refresh_token = 3; int64 expires_in = 4; string user_id = 5; string session_id = 6; UserProfile user_profile = 7; repeated string permissions = 8; } // 刷新Token请求 message RefreshTokenRequest { string refresh_token = 1; string user_id = 2; } // 刷新Token响应 message RefreshTokenResponse { greatestworks.common.CommonResponse common = 1; string access_token = 2; string refresh_token = 3; int64 expires_in = 4; } // 登出请求 message LogoutRequest { string user_id = 1; string session_id = 2; string access_token = 3; } // 登出响应 message LogoutResponse { greatestworks.common.CommonResponse common = 1; } // 获取服务器列表请求 message GetServerListRequest { string region = 1; ServerType server_type = 2; bool only_available = 3; } // 获取服务器列表响应 message GetServerListResponse { greatestworks.common.CommonResponse common = 1; repeated ServerInfo servers = 2; string recommended_server_id = 3; } // 选择服务器请求 message SelectServerRequest { string user_id = 1; string server_id = 2; string character_id = 3; } // 选择服务器响应 message SelectServerResponse { greatestworks.common.CommonResponse common = 1; ServerInfo server_info = 2; string connection_token = 3; repeated string service_endpoints = 4; } // 路由请求消息 message RouteRequestMessage { string request_id = 1; string service_name = 2; string method_name = 3; bytes payload = 4; string user_id = 5; string session_id = 6; map headers = 7; int32 timeout = 8; // 超时时间(秒) } // 路由响应消息 message RouteResponseMessage { string request_id = 1; bool success = 2; bytes payload = 3; string error_message = 4; int32 status_code = 5; map headers = 6; int64 processing_time = 7; // 处理时间(毫秒) } // 连接请求 message ConnectionRequest { string user_id = 1; string session_id = 2; string access_token = 3; ConnectionType connection_type = 4; string client_version = 5; map connection_params = 6; } // 连接响应 message ConnectionResponse { greatestworks.common.CommonResponse common = 1; string connection_id = 2; string websocket_url = 3; repeated string supported_protocols = 4; int32 heartbeat_interval = 5; // 心跳间隔(秒) } // 心跳请求 message HeartbeatRequest { string connection_id = 1; string user_id = 2; int64 timestamp = 3; map status_info = 4; } // 心跳响应 message HeartbeatResponse { greatestworks.common.CommonResponse common = 1; int64 server_timestamp = 2; int32 next_heartbeat_interval = 3; GatewayStatus gateway_status = 4; } // 获取网关状态请求 message GetGatewayStatusRequest { string admin_token = 1; bool include_metrics = 2; } // 获取网关状态响应 message GetGatewayStatusResponse { greatestworks.common.CommonResponse common = 1; GatewayStatus status = 2; GatewayMetrics metrics = 3; repeated ServiceStatus backend_services = 4; } // 限流检查请求 message RateLimitRequest { string user_id = 1; string ip_address = 2; string resource = 3; // 请求的资源 string action = 4; // 请求的操作 } // 限流检查响应 message RateLimitResponse { bool allowed = 1; int32 remaining_requests = 2; int64 reset_time = 3; // 限制重置时间 string limit_type = 4; string error_message = 5; } // 获取会话信息请求 message GetSessionInfoRequest { string session_id = 1; string user_id = 2; } // 获取会话信息响应 message GetSessionInfoResponse { greatestworks.common.CommonResponse common = 1; SessionInfo session_info = 2; } // 服务器信息 message ServerInfo { string server_id = 1; string name = 2; string region = 3; ServerType server_type = 4; ServerStatus status = 5; int32 current_players = 6; int32 max_players = 7; float load_percentage = 8; int32 ping = 9; string version = 10; bool is_recommended = 11; bool is_new = 12; int64 last_update = 13; map features = 14; // 服务器特性 } // 用户档案 message UserProfile { string user_id = 1; string username = 2; string email = 3; string display_name = 4; string avatar_url = 5; UserLevel user_level = 6; bool is_premium = 7; int64 created_at = 8; int64 last_login = 9; string preferred_language = 10; string timezone = 11; map preferences = 12; } // 网关状态 message GatewayStatus { bool is_healthy = 1; string version = 2; int64 uptime = 3; // 运行时间(秒) int32 active_connections = 4; int32 total_requests = 5; float cpu_usage = 6; float memory_usage = 7; int32 error_rate = 8; // 错误率(每万次请求) } // 网关指标 message GatewayMetrics { int64 total_requests = 1; int64 successful_requests = 2; int64 failed_requests = 3; float average_response_time = 4; // 平均响应时间(毫秒) int32 active_users = 5; int32 peak_concurrent_users = 6; map requests_per_service = 7; // 各服务请求数 map response_times_per_service = 8; // 各服务响应时间 int64 bandwidth_in = 9; // 入站带宽(字节) int64 bandwidth_out = 10; // 出站带宽(字节) } // 服务状态 message ServiceStatus { string service_name = 1; string service_url = 2; ServiceHealth health = 3; float response_time = 4; // 平均响应时间 int32 active_connections = 5; int64 last_check = 6; string version = 7; map metadata = 8; } // 会话信息 message SessionInfo { string session_id = 1; string user_id = 2; string access_token = 3; int64 created_at = 4; int64 expires_at = 5; int64 last_activity = 6; string client_ip = 7; string user_agent = 8; string current_server = 9; SessionStatus status = 10; map session_data = 11; } // 认证类型枚举 enum AuthType { AUTH_TYPE_UNSPECIFIED = 0; AUTH_TYPE_PASSWORD = 1; // 密码认证 AUTH_TYPE_OAUTH = 2; // OAuth认证 AUTH_TYPE_JWT = 3; // JWT认证 AUTH_TYPE_GUEST = 4; // 游客认证 AUTH_TYPE_FACEBOOK = 5; // Facebook登录 AUTH_TYPE_GOOGLE = 6; // Google登录 AUTH_TYPE_TWITTER = 7; // Twitter登录 AUTH_TYPE_APPLE = 8; // Apple登录 } // 服务器类型枚举 enum ServerType { SERVER_TYPE_UNSPECIFIED = 0; SERVER_TYPE_GAME = 1; // 游戏服务器 SERVER_TYPE_CHAT = 2; // 聊天服务器 SERVER_TYPE_MATCH = 3; // 匹配服务器 SERVER_TYPE_BATTLE = 4; // 战斗服务器 SERVER_TYPE_SOCIAL = 5; // 社交服务器 SERVER_TYPE_TEST = 6; // 测试服务器 } // 服务器状态枚举 enum ServerStatus { SERVER_STATUS_UNSPECIFIED = 0; SERVER_STATUS_ONLINE = 1; // 在线 SERVER_STATUS_OFFLINE = 2; // 离线 SERVER_STATUS_MAINTENANCE = 3; // 维护中 SERVER_STATUS_FULL = 4; // 已满 SERVER_STATUS_RESTRICTED = 5; // 受限制 } // 连接类型枚举 enum ConnectionType { CONNECTION_TYPE_UNSPECIFIED = 0; CONNECTION_TYPE_WEBSOCKET = 1; // WebSocket连接 CONNECTION_TYPE_TCP = 2; // TCP连接 CONNECTION_TYPE_HTTP = 3; // HTTP连接 CONNECTION_TYPE_GRPC = 4; // gRPC连接 } // 用户等级枚举 enum UserLevel { USER_LEVEL_UNSPECIFIED = 0; USER_LEVEL_GUEST = 1; // 游客 USER_LEVEL_REGISTERED = 2; // 注册用户 USER_LEVEL_VERIFIED = 3; // 认证用户 USER_LEVEL_PREMIUM = 4; // 高级用户 USER_LEVEL_VIP = 5; // VIP用户 USER_LEVEL_ADMIN = 6; // 管理员 } // 服务健康状态枚举 enum ServiceHealth { SERVICE_HEALTH_UNSPECIFIED = 0; SERVICE_HEALTH_HEALTHY = 1; // 健康 SERVICE_HEALTH_DEGRADED = 2; // 降级 SERVICE_HEALTH_UNHEALTHY = 3; // 不健康 SERVICE_HEALTH_CRITICAL = 4; // 严重 } // 会话状态枚举 enum SessionStatus { SESSION_STATUS_UNSPECIFIED = 0; SESSION_STATUS_ACTIVE = 1; // 活跃 SESSION_STATUS_IDLE = 2; // 空闲 SESSION_STATUS_EXPIRED = 3; // 已过期 SESSION_STATUS_TERMINATED = 4; // 已终止 SESSION_STATUS_SUSPENDED = 5; // 已暂停 } ================================================ FILE: proto/inventory.proto ================================================ syntax = "proto3"; package greatestworks.inventory; import "proto/common.proto"; import "proto/auth.proto"; option go_package = "greatestworks/internal/proto/inventory"; option csharp_namespace = "GreatestWorks.Inventory"; // 背包服务 service InventoryService { // 查询背包信息 rpc QueryInventory(InventoryQueryRequest) returns (InventoryQueryResponse); // 拾取物品 rpc PickupItem(PickupItemRequest) returns (PickupItemResponse); // 丢弃物品 rpc DiscardItem(DiscardItemRequest) returns (DiscardItemResponse); // 使用物品 rpc UseItem(UseItemRequest) returns (UseItemResponse); // 物品放置/交换 rpc PlacementItem(PlacementItemRequest) returns (PlacementItemResponse); // 整理背包 rpc SortInventory(SortInventoryRequest) returns (SortInventoryResponse); } // ========== 查询背包 ========== // 查询背包请求 message InventoryQueryRequest { int32 entity_id = 1; // 实体ID bool query_knapsack = 2; // 是否查询背包 bool query_warehouse = 3; // 是否查询仓库 bool query_equipment = 4; // 是否查询装备栏 } // 查询背包响应 message InventoryQueryResponse { int32 entity_id = 1; // 实体ID InventoryInfo knapsack_info = 2; // 背包信息 InventoryInfo warehouse_info = 3; // 仓库信息 InventoryInfo equipment_info = 4; // 装备栏信息 } // ========== 拾取物品 ========== // 拾取物品请求 message PickupItemRequest { int32 picker_id = 1; // 拾取者实体ID int32 dropped_item_id = 2; // 掉落物实体ID } // 拾取物品响应 message PickupItemResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int32 item_id = 3; // 物品ID int32 amount = 4; // 拾取数量 } // ========== 丢弃物品 ========== // 丢弃物品请求 message DiscardItemRequest { int32 entity_id = 1; // 实体ID int32 slot_id = 2; // 插槽索引 int32 count = 3; // 丢弃数量 } // 丢弃物品响应 message DiscardItemResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int32 dropped_item_id = 3; // 掉落物实体ID(如果成功丢弃) } // ========== 使用物品 ========== // 使用物品请求 message UseItemRequest { int32 entity_id = 1; // 使用者实体ID int32 slot_id = 2; // 使用哪个插槽的物品 int32 target_id = 3; // 目标ID(部分物品需要) } // 使用物品响应 message UseItemResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 ItemEffect effect = 3; // 物品效果 } // ========== 物品放置/交换 ========== // 物品放置请求 message PlacementItemRequest { int32 entity_id = 1; // 实体ID int32 origin_slot_id = 2; // 原始索引 int32 target_slot_id = 3; // 目标索引 InventoryType inventory_type = 4; // 背包类型 } // 物品放置响应 message PlacementItemResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 } // ========== 整理背包 ========== // 整理背包请求 message SortInventoryRequest { int32 entity_id = 1; // 实体ID InventoryType inventory_type = 2; // 背包类型 } // 整理背包响应 message SortInventoryResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 InventoryInfo inventory = 3; // 整理后的背包信息 } // ========== 数据结构 ========== // 背包信息 message InventoryInfo { int32 capacity = 1; // 格子数量 repeated ItemInfo items = 2; // 物品列表 } // 物品信息 message ItemInfo { int32 item_id = 1; // 物品ID int32 amount = 2; // 物品数量 int32 slot_id = 3; // 所处位置 int64 unique_id = 4; // 唯一ID(装备等需要) int32 quality = 5; // 品质 int32 level = 6; // 等级 int64 expire_time = 7; // 过期时间(0表示永久) bool is_bound = 8; // 是否绑定 } // 物品更新通知 message ItemUpdate { UpdateType type = 1; // 更新类型 ItemInfo item = 2; // 物品信息 } // 物品效果 message ItemEffect { EffectType effect_type = 1; // 效果类型 int32 value = 2; // 效果值 repeated int32 buff_ids = 3; // 添加的Buff ID列表 } // ========== 枚举定义 ========== // 背包类型 enum InventoryType { INVENTORY_TYPE_KNAPSACK = 0; // 背包 INVENTORY_TYPE_WAREHOUSE = 1; // 仓库 INVENTORY_TYPE_EQUIPMENT = 2; // 装备栏 INVENTORY_TYPE_CONSUMABLE = 3; // 消耗品栏 } // 更新类型 enum UpdateType { UPDATE_TYPE_ADD = 0; // 添加 UPDATE_TYPE_DELETE = 1; // 删除 UPDATE_TYPE_UPDATE = 2; // 更新 } // 效果类型 enum EffectType { EFFECT_TYPE_NONE = 0; // 无效果 EFFECT_TYPE_RESTORE_HP = 1; // 恢复生命 EFFECT_TYPE_RESTORE_MP = 2; // 恢复魔法 EFFECT_TYPE_ADD_EXP = 3; // 增加经验 EFFECT_TYPE_ADD_GOLD = 4; // 增加金币 EFFECT_TYPE_ADD_BUFF = 5; // 添加Buff EFFECT_TYPE_TELEPORT = 6; // 传送 } ================================================ FILE: proto/mail.proto ================================================ syntax = "proto3"; package greatestworks.mail; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/mail"; option csharp_namespace = "GreatestWorks.Mail"; // 邮件服务定义 service MailService { // 发送邮件 rpc SendMail(SendMailRequest) returns (SendMailResponse); // 获取邮件列表 rpc GetMailList(GetMailListRequest) returns (GetMailListResponse); // 读取邮件 rpc ReadMail(ReadMailRequest) returns (ReadMailResponse); // 删除邮件 rpc DeleteMail(DeleteMailRequest) returns (DeleteMailResponse); // 批量删除邮件 rpc BatchDeleteMails(BatchDeleteMailsRequest) returns (BatchDeleteMailsResponse); // 领取邮件附件 rpc ClaimAttachment(ClaimAttachmentRequest) returns (ClaimAttachmentResponse); // 批量领取附件 rpc BatchClaimAttachments(BatchClaimAttachmentsRequest) returns (BatchClaimAttachmentsResponse); // 标记邮件 rpc MarkMail(MarkMailRequest) returns (MarkMailResponse); // 搜索邮件 rpc SearchMails(SearchMailsRequest) returns (SearchMailsResponse); // 获取邮件统计 rpc GetMailStats(GetMailStatsRequest) returns (GetMailStatsResponse); // 设置邮件配置 rpc SetMailConfig(SetMailConfigRequest) returns (SetMailConfigResponse); } // 发送邮件请求 message SendMailRequest { string sender_id = 1; repeated string recipient_ids = 2; string subject = 3; string content = 4; MailType mail_type = 5; repeated MailAttachment attachments = 6; int64 expire_at = 7; // 过期时间,0表示不过期 bool is_system_mail = 8; map metadata = 9; } // 发送邮件响应 message SendMailResponse { greatestworks.common.CommonResponse common = 1; repeated string mail_ids = 2; // 为每个收件人创建的邮件ID } // 获取邮件列表请求 message GetMailListRequest { string player_id = 1; MailStatus status = 2; MailType mail_type = 3; int32 limit = 4; int32 offset = 5; bool only_unread = 6; bool only_with_attachments = 7; } // 获取邮件列表响应 message GetMailListResponse { greatestworks.common.CommonResponse common = 1; repeated MailInfo mails = 2; greatestworks.common.PaginationInfo pagination = 3; MailStats stats = 4; } // 读取邮件请求 message ReadMailRequest { string player_id = 1; string mail_id = 2; } // 读取邮件响应 message ReadMailResponse { greatestworks.common.CommonResponse common = 1; MailDetail mail = 2; } // 删除邮件请求 message DeleteMailRequest { string player_id = 1; string mail_id = 2; } // 删除邮件响应 message DeleteMailResponse { greatestworks.common.CommonResponse common = 1; } // 批量删除邮件请求 message BatchDeleteMailsRequest { string player_id = 1; repeated string mail_ids = 2; bool delete_all_read = 3; // 删除所有已读邮件 bool delete_expired = 4; // 删除过期邮件 } // 批量删除邮件响应 message BatchDeleteMailsResponse { greatestworks.common.CommonResponse common = 1; int32 deleted_count = 2; repeated string failed_mail_ids = 3; } // 领取邮件附件请求 message ClaimAttachmentRequest { string player_id = 1; string mail_id = 2; repeated string attachment_ids = 3; // 空则领取所有附件 } // 领取邮件附件响应 message ClaimAttachmentResponse { greatestworks.common.CommonResponse common = 1; repeated ClaimedAttachment claimed_attachments = 2; repeated string failed_attachment_ids = 3; } // 批量领取附件请求 message BatchClaimAttachmentsRequest { string player_id = 1; repeated string mail_ids = 2; bool claim_all_available = 3; // 领取所有可领取的附件 } // 批量领取附件响应 message BatchClaimAttachmentsResponse { greatestworks.common.CommonResponse common = 1; repeated ClaimedAttachment claimed_attachments = 2; int32 total_claimed = 3; repeated string failed_mail_ids = 4; } // 标记邮件请求 message MarkMailRequest { string player_id = 1; repeated string mail_ids = 2; MailMarkType mark_type = 3; } // 标记邮件响应 message MarkMailResponse { greatestworks.common.CommonResponse common = 1; int32 marked_count = 2; } // 搜索邮件请求 message SearchMailsRequest { string player_id = 1; string keyword = 2; // 搜索关键词 string sender_name = 3; // 发件人名称 MailType mail_type = 4; int64 start_date = 5; int64 end_date = 6; int32 limit = 7; int32 offset = 8; } // 搜索邮件响应 message SearchMailsResponse { greatestworks.common.CommonResponse common = 1; repeated MailInfo mails = 2; greatestworks.common.PaginationInfo pagination = 3; } // 获取邮件统计请求 message GetMailStatsRequest { string player_id = 1; } // 获取邮件统计响应 message GetMailStatsResponse { greatestworks.common.CommonResponse common = 1; MailStats stats = 2; } // 设置邮件配置请求 message SetMailConfigRequest { string player_id = 1; MailConfig config = 2; } // 设置邮件配置响应 message SetMailConfigResponse { greatestworks.common.CommonResponse common = 1; MailConfig config = 2; } // 邮件信息 message MailInfo { string mail_id = 1; string sender_id = 2; string sender_name = 3; string recipient_id = 4; string subject = 5; MailType mail_type = 6; MailStatus status = 7; bool has_attachments = 8; bool has_unclaimed_attachments = 9; int64 sent_at = 10; int64 read_at = 11; int64 expire_at = 12; bool is_important = 13; bool is_favorite = 14; } // 邮件详情 message MailDetail { string mail_id = 1; string sender_id = 2; string sender_name = 3; string recipient_id = 4; string subject = 5; string content = 6; MailType mail_type = 7; MailStatus status = 8; repeated MailAttachment attachments = 9; int64 sent_at = 10; int64 read_at = 11; int64 expire_at = 12; bool is_important = 13; bool is_favorite = 14; map metadata = 15; } // 邮件附件 message MailAttachment { string attachment_id = 1; string name = 2; AttachmentType attachment_type = 3; string item_id = 4; // 物品ID int32 quantity = 5; // 数量 int32 gold_amount = 6; // 金币数量 int32 diamond_amount = 7; // 钻石数量 bool is_claimed = 8; int64 claimed_at = 9; map properties = 10; } // 已领取附件 message ClaimedAttachment { string attachment_id = 1; string name = 2; AttachmentType attachment_type = 3; int32 quantity = 4; bool success = 5; string error_message = 6; } // 邮件统计 message MailStats { int32 total_mails = 1; int32 unread_mails = 2; int32 mails_with_attachments = 3; int32 mails_with_unclaimed_attachments = 4; int32 important_mails = 5; int32 favorite_mails = 6; int32 system_mails = 7; int32 player_mails = 8; int64 oldest_mail_date = 9; int64 newest_mail_date = 10; } // 邮件配置 message MailConfig { bool auto_delete_read_mails = 1; int32 auto_delete_days = 2; // 自动删除天数 bool notify_new_mail = 3; bool notify_system_mail = 4; bool auto_claim_attachments = 5; int32 max_mails_per_page = 6; repeated MailType blocked_mail_types = 7; } // 邮件类型枚举 enum MailType { MAIL_TYPE_UNSPECIFIED = 0; MAIL_TYPE_SYSTEM = 1; // 系统邮件 MAIL_TYPE_PLAYER = 2; // 玩家邮件 MAIL_TYPE_REWARD = 3; // 奖励邮件 MAIL_TYPE_NOTIFICATION = 4; // 通知邮件 MAIL_TYPE_PROMOTION = 5; // 推广邮件 MAIL_TYPE_ANNOUNCEMENT = 6; // 公告邮件 MAIL_TYPE_GIFT = 7; // 礼品邮件 MAIL_TYPE_COMPENSATION = 8; // 补偿邮件 MAIL_TYPE_BATTLE_REPORT = 9; // 战斗报告 MAIL_TYPE_GUILD = 10; // 公会邮件 } // 邮件状态枚举 enum MailStatus { MAIL_STATUS_UNSPECIFIED = 0; MAIL_STATUS_UNREAD = 1; // 未读 MAIL_STATUS_READ = 2; // 已读 MAIL_STATUS_ARCHIVED = 3; // 已归档 MAIL_STATUS_DELETED = 4; // 已删除 MAIL_STATUS_EXPIRED = 5; // 已过期 } // 附件类型枚举 enum AttachmentType { ATTACHMENT_TYPE_UNSPECIFIED = 0; ATTACHMENT_TYPE_ITEM = 1; // 物品 ATTACHMENT_TYPE_CURRENCY = 2; // 货币 ATTACHMENT_TYPE_EXPERIENCE = 3; // 经验 ATTACHMENT_TYPE_BUFF = 4; // 增益效果 ATTACHMENT_TYPE_TITLE = 5; // 称号 ATTACHMENT_TYPE_ACHIEVEMENT = 6; // 成就 } // 邮件标记类型枚举 enum MailMarkType { MAIL_MARK_TYPE_UNSPECIFIED = 0; MAIL_MARK_TYPE_READ = 1; // 标记为已读 MAIL_MARK_TYPE_UNREAD = 2; // 标记为未读 MAIL_MARK_TYPE_IMPORTANT = 3; // 标记为重要 MAIL_MARK_TYPE_UNIMPORTANT = 4; // 取消重要标记 MAIL_MARK_TYPE_FAVORITE = 5; // 标记为收藏 MAIL_MARK_TYPE_UNFAVORITE = 6; // 取消收藏标记 } ================================================ FILE: proto/map.proto ================================================ syntax = "proto3"; package greatestworks.map; import "proto/common.proto"; import "proto/entity.proto"; import "google/protobuf/timestamp.proto"; option go_package = "greatestworks/internal/proto/map"; option csharp_namespace = "GreatestWorks.Map"; // 地图服务 service MapService { // 进入地图 rpc EnterMap(EnterMapRequest) returns (EnterMapResponse); // 离开地图 rpc LeaveMap(LeaveMapRequest) returns (LeaveMapResponse); // 获取地图信息 rpc GetMapInfo(GetMapInfoRequest) returns (GetMapInfoResponse); // 提交聊天消息 rpc SubmitChatMessage(SubmitChatMessageRequest) returns (SubmitChatMessageResponse); } // ========== 进入/离开地图 ========== // 进入地图请求 message EnterMapRequest { int64 character_id = 1; // 角色ID int32 map_id = 2; // 地图ID entity.NetVector3 spawn_point = 3; // 出生点(可选) } // 进入地图响应 message EnterMapResponse { bool success = 1; string message = 2; MapInfo map_info = 3; // 地图信息 entity.NetVector3 position = 4; // 实际出生位置 repeated entity.EntityEnterData entities = 5; // 地图中已有的实体 } // 离开地图请求 message LeaveMapRequest { int64 character_id = 1; // 角色ID int32 map_id = 2; // 地图ID } // 离开地图响应 message LeaveMapResponse { bool success = 1; string message = 2; } // ========== 地图信息 ========== // 获取地图信息请求 message GetMapInfoRequest { int32 map_id = 1; // 地图ID } // 获取地图信息响应 message GetMapInfoResponse { bool success = 1; string message = 2; MapInfo map_info = 3; } // 地图信息 message MapInfo { int32 map_id = 1; // 地图ID string name = 2; // 地图名称 string description = 3; // 地图描述 MapType map_type = 4; // 地图类型 int32 width = 5; // 地图宽度 int32 height = 6; // 地图高度 int32 player_count = 7; // 当前玩家数量 int32 max_players = 8; // 最大玩家数量 repeated SpawnPoint spawn_points = 9; // 出生点列表 } // 出生点 message SpawnPoint { int32 spawn_id = 1; // 出生点ID entity.NetVector3 position = 2; // 位置 entity.NetVector3 direction = 3; // 朝向 SpawnPointType spawn_type = 4; // 出生点类型 } // ========== 聊天系统 ========== // 提交聊天消息请求 message SubmitChatMessageRequest { int64 character_id = 1; // 发送者角色ID ChatMessageType message_type = 2; // 消息类型 string message = 3; // 消息内容 int64 target_id = 4; // 目标ID(私聊时使用) } // 提交聊天消息响应 message SubmitChatMessageResponse { bool success = 1; string error_message = 2; google.protobuf.Timestamp timestamp = 3; // 消息时间戳 } // 接收聊天消息通知(服务器广播) message ReceiveChatMessageResponse { int64 character_id = 1; // 发送者角色ID string character_name = 2; // 发送者角色名 ChatMessageType message_type = 3; // 消息类型 string message = 4; // 消息内容 google.protobuf.Timestamp timestamp = 5; // 消息时间戳 } // ========== 枚举定义 ========== // 地图类型 enum MapType { MAP_TYPE_NORMAL = 0; // 普通地图 MAP_TYPE_DUNGEON = 1; // 副本 MAP_TYPE_PVP = 2; // PVP竞技场 MAP_TYPE_RAID = 3; // 团队副本 MAP_TYPE_BATTLE = 4; // 战场 MAP_TYPE_CITY = 5; // 主城 MAP_TYPE_WILD = 6; // 野外 } // 聊天消息类型 enum ChatMessageType { CHAT_MESSAGE_WORLD = 0; // 世界聊天 CHAT_MESSAGE_MAP = 1; // 地图聊天 CHAT_MESSAGE_GROUP = 2; // 组队聊天 CHAT_MESSAGE_GUILD = 3; // 公会聊天 CHAT_MESSAGE_PRIVATE = 4; // 私聊 CHAT_MESSAGE_SYSTEM = 5; // 系统消息 } // 出生点类型 enum SpawnPointType { SPAWN_POINT_DEFAULT = 0; // 默认出生点 SPAWN_POINT_PLAYER = 1; // 玩家出生点 SPAWN_POINT_MONSTER = 2; // 怪物刷新点 SPAWN_POINT_NPC = 3; // NPC出生点 SPAWN_POINT_BOSS = 4; // Boss刷新点 } ================================================ FILE: proto/messages.proto ================================================ syntax = "proto3"; package greatestworks.messages; option go_package = "greatestworks/internal/proto/messages;messages"; option csharp_namespace = "GreatestWorks.Messages"; // 消息号枚举 - 系统消息 (0x0000 - 0x00FF) enum SystemMessageID { SYSTEM_MESSAGE_ID_UNSPECIFIED = 0; MSG_HEARTBEAT = 0x0001; // 心跳消息 MSG_HANDSHAKE = 0x0002; // 握手消息 MSG_AUTH = 0x0003; // 认证消息 MSG_DISCONNECT = 0x0004; // 断开连接 MSG_ERROR = 0x0005; // 错误消息 MSG_PING = 0x0006; // Ping消息 MSG_PONG = 0x0007; // Pong消息 MSG_SYSTEM_INFO = 0x0008; // 系统信息 MSG_SERVER_STATUS = 0x0009; // 服务器状态 MSG_MAINTENANCE = 0x000A; // 维护通知 } // 消息号枚举 - 玩家相关消息 (0x0100 - 0x01FF) enum PlayerMessageID { PLAYER_MESSAGE_ID_UNSPECIFIED = 0; MSG_PLAYER_LOGIN = 0x0101; // 玩家登录 MSG_PLAYER_LOGOUT = 0x0102; // 玩家登出 MSG_PLAYER_INFO = 0x0103; // 玩家信息 MSG_PLAYER_MOVE = 0x0104; // 玩家移动 MSG_PLAYER_CREATE = 0x0105; // 创建玩家 MSG_PLAYER_UPDATE = 0x0106; // 更新玩家 MSG_PLAYER_DELETE = 0x0107; // 删除玩家 MSG_PLAYER_STATUS = 0x0108; // 玩家状态 MSG_PLAYER_STATS = 0x0109; // 玩家属性 MSG_PLAYER_LEVEL = 0x010A; // 玩家升级 MSG_PLAYER_EXP_GAIN = 0x010B; // 玩家经验获得 MSG_PLAYER_SYNC = 0x010C; // 玩家同步 MSG_PLAYER_BAN = 0x010D; // 玩家封禁 MSG_PLAYER_UNBAN = 0x010E; // 玩家解封 MSG_PLAYER_GM_UPDATE = 0x010F; // GM更新玩家 } // 消息号枚举 - 战斗相关消息 (0x0200 - 0x02FF) enum BattleMessageID { BATTLE_MESSAGE_ID_UNSPECIFIED = 0; MSG_CREATE_BATTLE = 0x0201; // 创建战斗 MSG_JOIN_BATTLE = 0x0202; // 加入战斗 MSG_LEAVE_BATTLE = 0x0203; // 离开战斗 MSG_START_BATTLE = 0x0204; // 开始战斗 MSG_END_BATTLE = 0x0205; // 结束战斗 MSG_BATTLE_ACTION = 0x0206; // 战斗行动 MSG_BATTLE_RESULT = 0x0207; // 战斗结果 MSG_BATTLE_STATUS = 0x0208; // 战斗状态 MSG_SKILL_CAST = 0x0209; // 技能释放 MSG_DAMAGE_DEALT = 0x020A; // 伤害计算 MSG_BATTLE_ROUND = 0x020B; // 战斗回合 MSG_BATTLE_SYNC = 0x020C; // 战斗同步 MSG_BATTLE_SPECTATE = 0x020D; // 观战 MSG_BATTLE_REPLAY = 0x020E; // 战斗回放 } // 消息号枚举 - 宠物相关消息 (0x0300 - 0x03FF) enum PetMessageID { PET_MESSAGE_ID_UNSPECIFIED = 0; MSG_PET_SUMMON = 0x0301; // 召唤宠物 MSG_PET_DISMISS = 0x0302; // 收回宠物 MSG_PET_INFO = 0x0303; // 宠物信息 MSG_PET_MOVE = 0x0304; // 宠物移动 MSG_PET_ACTION = 0x0305; // 宠物行动 MSG_PET_LEVEL_UP = 0x0306; // 宠物升级 MSG_PET_EVOLUTION = 0x0307; // 宠物进化 MSG_PET_TRAIN = 0x0308; // 宠物训练 MSG_PET_FEED = 0x0309; // 宠物喂养 MSG_PET_STATUS = 0x030A; // 宠物状态 MSG_PET_SKILL_LEARN = 0x030B; // 宠物技能学习 MSG_PET_SKILL_FORGET = 0x030C; // 宠物技能遗忘 MSG_PET_BOND = 0x030D; // 宠物羁绊 MSG_PET_SYNTHESIS = 0x030E; // 宠物合成 MSG_PET_SKIN_EQUIP = 0x030F; // 宠物皮肤装备 } // 消息号枚举 - 建筑相关消息 (0x0400 - 0x04FF) enum BuildingMessageID { BUILDING_MESSAGE_ID_UNSPECIFIED = 0; MSG_BUILDING_CREATE = 0x0401; // 创建建筑 MSG_BUILDING_UPGRADE = 0x0402; // 升级建筑 MSG_BUILDING_DESTROY = 0x0403; // 摧毁建筑 MSG_BUILDING_INFO = 0x0404; // 建筑信息 MSG_BUILDING_PRODUCE = 0x0405; // 建筑生产 MSG_BUILDING_COLLECT = 0x0406; // 收集资源 MSG_BUILDING_REPAIR = 0x0407; // 修复建筑 MSG_BUILDING_STATUS = 0x0408; // 建筑状态 MSG_BUILDING_SYNC = 0x0409; // 建筑同步 MSG_BUILDING_MOVE = 0x040A; // 建筑移动 MSG_BUILDING_ROTATE = 0x040B; // 建筑旋转 MSG_BUILDING_UPGRADE_CANCEL = 0x040C; // 取消升级 } // 消息号枚举 - 社交相关消息 (0x0500 - 0x05FF) enum SocialMessageID { SOCIAL_MESSAGE_ID_UNSPECIFIED = 0; MSG_CHAT_MESSAGE = 0x0501; // 聊天消息 MSG_FRIEND_REQUEST = 0x0502; // 好友请求 MSG_FRIEND_ACCEPT = 0x0503; // 接受好友 MSG_FRIEND_REJECT = 0x0504; // 拒绝好友 MSG_FRIEND_REMOVE = 0x0505; // 删除好友 MSG_FRIEND_LIST = 0x0506; // 好友列表 MSG_GUILD_CREATE = 0x0507; // 创建公会 MSG_GUILD_JOIN = 0x0508; // 加入公会 MSG_GUILD_LEAVE = 0x0509; // 离开公会 MSG_GUILD_INFO = 0x050A; // 公会信息 MSG_TEAM_CREATE = 0x050B; // 创建队伍 MSG_TEAM_JOIN = 0x050C; // 加入队伍 MSG_TEAM_LEAVE = 0x050D; // 离开队伍 MSG_TEAM_INFO = 0x050E; // 队伍信息 MSG_MAIL_SEND = 0x050F; // 发送邮件 MSG_MAIL_RECEIVE = 0x0510; // 接收邮件 MSG_MAIL_DELETE = 0x0511; // 删除邮件 MSG_MAIL_READ = 0x0512; // 阅读邮件 } // 消息号枚举 - 物品相关消息 (0x0600 - 0x06FF) enum ItemMessageID { ITEM_MESSAGE_ID_UNSPECIFIED = 0; MSG_ITEM_USE = 0x0601; // 使用物品 MSG_ITEM_EQUIP = 0x0602; // 装备物品 MSG_ITEM_UNEQUIP = 0x0603; // 卸下装备 MSG_ITEM_DROP = 0x0604; // 丢弃物品 MSG_ITEM_PICKUP = 0x0605; // 拾取物品 MSG_ITEM_TRADE = 0x0606; // 交易物品 MSG_INVENTORY_INFO = 0x0607; // 背包信息 MSG_ITEM_INFO = 0x0608; // 物品信息 MSG_ITEM_CRAFT = 0x0609; // 制作物品 MSG_ITEM_ENHANCE = 0x060A; // 强化物品 MSG_ITEM_DISASSEMBLE = 0x060B; // 分解物品 MSG_ITEM_SELL = 0x060C; // 出售物品 MSG_ITEM_BUY = 0x060D; // 购买物品 MSG_ITEM_STACK = 0x060E; // 物品堆叠 MSG_ITEM_SPLIT = 0x060F; // 物品拆分 } // 消息号枚举 - 任务相关消息 (0x0700 - 0x07FF) enum QuestMessageID { QUEST_MESSAGE_ID_UNSPECIFIED = 0; MSG_QUEST_ACCEPT = 0x0701; // 接受任务 MSG_QUEST_COMPLETE = 0x0702; // 完成任务 MSG_QUEST_CANCEL = 0x0703; // 取消任务 MSG_QUEST_PROGRESS = 0x0704; // 任务进度 MSG_QUEST_LIST = 0x0705; // 任务列表 MSG_QUEST_INFO = 0x0706; // 任务信息 MSG_QUEST_REWARD = 0x0707; // 任务奖励 MSG_QUEST_UPDATE = 0x0708; // 任务更新 MSG_QUEST_ABANDON = 0x0709; // 放弃任务 MSG_QUEST_SHARE = 0x070A; // 分享任务 MSG_QUEST_TRACK = 0x070B; // 追踪任务 MSG_QUEST_UNTrack = 0x070C; // 取消追踪 } // 消息号枚举 - 查询相关消息 (0x0800 - 0x08FF) enum QueryMessageID { QUERY_MESSAGE_ID_UNSPECIFIED = 0; MSG_GET_PLAYER_INFO = 0x0801; // 获取玩家信息 MSG_GET_ONLINE_PLAYERS = 0x0802; // 获取在线玩家 MSG_GET_BATTLE_INFO = 0x0803; // 获取战斗信息 MSG_GET_RANKING_LIST = 0x0804; // 获取排行榜 MSG_GET_SERVER_INFO = 0x0805; // 获取服务器信息 MSG_GET_PET_INFO = 0x0806; // 获取宠物信息 MSG_GET_BUILDING_INFO = 0x0807; // 获取建筑信息 MSG_GET_ITEM_INFO = 0x0808; // 获取物品信息 MSG_GET_QUEST_INFO = 0x0809; // 获取任务信息 MSG_GET_GUILD_INFO = 0x080A; // 获取公会信息 MSG_GET_TEAM_INFO = 0x080B; // 获取队伍信息 MSG_GET_FRIEND_INFO = 0x080C; // 获取好友信息 MSG_GET_MAIL_INFO = 0x080D; // 获取邮件信息 MSG_GET_STATS = 0x080E; // 获取统计信息 } // 消息号枚举 - 系统管理消息 (0x0900 - 0x09FF) enum AdminMessageID { ADMIN_MESSAGE_ID_UNSPECIFIED = 0; MSG_ADMIN_BAN_PLAYER = 0x0901; // 封禁玩家 MSG_ADMIN_UNBAN_PLAYER = 0x0902; // 解封玩家 MSG_ADMIN_KICK_PLAYER = 0x0903; // 踢出玩家 MSG_ADMIN_MUTE_PLAYER = 0x0904; // 禁言玩家 MSG_ADMIN_UNMUTE_PLAYER = 0x0905; // 取消禁言 MSG_ADMIN_GIVE_ITEM = 0x0906; // 给予物品 MSG_ADMIN_TAKE_ITEM = 0x0907; // 收回物品 MSG_ADMIN_SET_LEVEL = 0x0908; // 设置等级 MSG_ADMIN_SET_EXP = 0x0909; // 设置经验 MSG_ADMIN_SET_GOLD = 0x090A; // 设置金币 MSG_ADMIN_TELEPORT = 0x090B; // 传送玩家 MSG_ADMIN_ANNOUNCE = 0x090C; // 公告 MSG_ADMIN_RELOAD_CONFIG = 0x090D; // 重载配置 MSG_ADMIN_SHUTDOWN = 0x090E; // 关闭服务器 MSG_ADMIN_RESTART = 0x090F; // 重启服务器 } // 消息头结构 message MessageHeader { uint32 magic = 1; // 魔数标识 (0x47574B53 "GWKS") uint32 message_id = 2; // 消息ID uint32 message_type = 3; // 消息类型 uint32 flags = 4; // 标志位 uint64 player_id = 5; // 玩家ID int64 timestamp = 6; // 时间戳 uint32 sequence = 7; // 序列号 uint32 length = 8; // 消息体长度 string request_id = 9; // 请求ID string session_id = 10; // 会话ID } // 消息标志位枚举 enum MessageFlag { MESSAGE_FLAG_UNSPECIFIED = 0; MESSAGE_FLAG_REQUEST = 0x0001; // 请求消息 MESSAGE_FLAG_RESPONSE = 0x0002; // 响应消息 MESSAGE_FLAG_ERROR = 0x0004; // 错误消息 MESSAGE_FLAG_ASYNC = 0x0008; // 异步消息 MESSAGE_FLAG_BROADCAST = 0x0010; // 广播消息 MESSAGE_FLAG_ENCRYPTED = 0x0020; // 加密消息 MESSAGE_FLAG_COMPRESSED = 0x0040; // 压缩消息 MESSAGE_FLAG_PRIORITY = 0x0080; // 高优先级消息 MESSAGE_FLAG_RELIABLE = 0x0100; // 可靠消息 MESSAGE_FLAG_ORDERED = 0x0200; // 有序消息 } // 消息优先级枚举 enum MessagePriority { MESSAGE_PRIORITY_UNSPECIFIED = 0; MESSAGE_PRIORITY_LOW = 1; // 低优先级 MESSAGE_PRIORITY_NORMAL = 2; // 普通优先级 MESSAGE_PRIORITY_HIGH = 3; // 高优先级 MESSAGE_PRIORITY_URGENT = 4; // 紧急优先级 MESSAGE_PRIORITY_CRITICAL = 5; // 关键优先级 } ================================================ FILE: proto/npc.proto ================================================ syntax = "proto3"; package greatestworks.npc; import "proto/common.proto"; import "proto/auth.proto"; option go_package = "greatestworks/internal/proto/npc"; option csharp_namespace = "GreatestWorks.NPC"; // NPC服务 service NPCService { // 与NPC交互 rpc Interact(InteractRequest) returns (InteractResponse); // 查询NPC对话ID rpc QueryDialogue(QueryDialogueIdRequest) returns (QueryDialogueIdResponse); // 开始对话 rpc StartDialogue(StartDialogueRequest) returns (StartDialogueResponse); // 结束对话 rpc EndDialogue(EndDialogueRequest) returns (EndDialogueResponse); // 打开商店 rpc OpenShop(OpenShopRequest) returns (OpenShopResponse); // 购买物品 rpc BuyItem(BuyItemRequest) returns (BuyItemResponse); // 出售物品 rpc SellItem(SellItemRequest) returns (SellItemResponse); } // ========== NPC交互 ========== // 交互请求 message InteractRequest { int32 player_id = 1; // 玩家实体ID int32 npc_id = 2; // NPC实体ID int32 select_idx = 3; // 选择项索引 } // 交互响应 message InteractResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int32 npc_id = 3; // NPC实体ID int32 dialogue_id = 4; // 对话ID } // ========== 查询对话 ========== // 查询对话ID请求 message QueryDialogueIdRequest { int32 player_id = 1; // 玩家实体ID int32 npc_id = 2; // NPC实体ID } // 查询对话ID响应 message QueryDialogueIdResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int32 npc_id = 3; // NPC实体ID int32 dialogue_id = 4; // 对话ID } // ========== 对话系统 ========== // 开始对话请求 message StartDialogueRequest { int32 player_id = 1; // 玩家实体ID int32 npc_id = 2; // NPC实体ID } // 开始对话响应 message StartDialogueResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 DialogueData dialogue = 3; // 对话数据 } // 结束对话请求 message EndDialogueRequest { int32 player_id = 1; // 玩家实体ID int32 npc_id = 2; // NPC实体ID } // 结束对话响应 message EndDialogueResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 } // ========== 商店系统 ========== // 打开商店请求 message OpenShopRequest { int32 player_id = 1; // 玩家实体ID int32 npc_id = 2; // NPC实体ID int32 shop_id = 3; // 商店ID } // 打开商店响应 message OpenShopResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 ShopInfo shop = 3; // 商店信息 } // 购买物品请求 message BuyItemRequest { int32 player_id = 1; // 玩家实体ID int32 shop_id = 2; // 商店ID int32 item_id = 3; // 物品ID int32 count = 4; // 购买数量 } // 购买物品响应 message BuyItemResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int32 total_cost = 3; // 总花费 int32 remaining_gold = 4; // 剩余金币 } // 出售物品请求 message SellItemRequest { int32 player_id = 1; // 玩家实体ID int32 npc_id = 2; // NPC实体ID int32 slot_id = 3; // 物品槽位 int32 count = 4; // 出售数量 } // 出售物品响应 message SellItemResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 int32 total_gain = 3; // 总收益 int32 current_gold = 4; // 当前金币 } // ========== 数据结构 ========== // NPC对话记录 message DialogueRecord { int32 npc_id = 1; // NPC ID int32 dialogue_id = 2; // 对话ID } // NPC对话信息(用于序列化存储) message DialogueInfo { repeated DialogueRecord dialogue_arr = 1; // 对话记录数组 } // 对话数据 message DialogueData { int32 dialogue_id = 1; // 对话ID string npc_name = 2; // NPC名称 string content = 3; // 对话内容 repeated DialogueOption options = 4; // 对话选项 DialogueType dialogue_type = 5; // 对话类型 } // 对话选项 message DialogueOption { int32 option_id = 1; // 选项ID string text = 2; // 选项文本 int32 next_dialogue_id = 3; // 下一个对话ID(-1表示结束) OptionAction action = 4; // 选项动作 } // 选项动作 message OptionAction { ActionType action_type = 1; // 动作类型 int32 action_value = 2; // 动作值(任务ID、商店ID等) } // 商店信息 message ShopInfo { int32 shop_id = 1; // 商店ID string shop_name = 2; // 商店名称 ShopType shop_type = 3; // 商店类型 repeated ShopItem items = 4; // 商品列表 int32 refresh_time = 5; // 刷新时间(秒,0表示不刷新) } // 商品项 message ShopItem { int32 item_id = 1; // 物品ID int32 price = 2; // 价格 int32 stock = 3; // 库存(-1表示无限) int32 max_buy_count = 4; // 最大购买数量(-1表示无限) int32 discount = 5; // 折扣(百分比,100表示无折扣) bool is_limited = 6; // 是否限时 int64 expire_time = 7; // 过期时间 } // ========== 枚举定义 ========== // 对话类型 enum DialogueType { DIALOGUE_TYPE_NORMAL = 0; // 普通对话 DIALOGUE_TYPE_QUEST = 1; // 任务对话 DIALOGUE_TYPE_SHOP = 2; // 商店对话 DIALOGUE_TYPE_TELEPORT = 3; // 传送对话 DIALOGUE_TYPE_CRAFT = 4; // 制作对话 } // 动作类型 enum ActionType { ACTION_NONE = 0; // 无动作 ACTION_ACCEPT_QUEST = 1; // 接受任务 ACTION_SUBMIT_QUEST = 2; // 提交任务 ACTION_OPEN_SHOP = 3; // 打开商店 ACTION_TELEPORT = 4; // 传送 ACTION_LEARN_SKILL = 5; // 学习技能 } // 商店类型 enum ShopType { SHOP_TYPE_GENERAL = 0; // 普通商店 SHOP_TYPE_WEAPON = 1; // 武器商店 SHOP_TYPE_ARMOR = 2; // 护甲商店 SHOP_TYPE_POTION = 3; // 药水商店 SHOP_TYPE_MATERIAL = 4; // 材料商店 SHOP_TYPE_SPECIAL = 5; // 特殊商店 } ================================================ FILE: proto/pet.proto ================================================ syntax = "proto3"; package greatestworks.pet; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/pet"; option csharp_namespace = "GreatestWorks.Pet"; // 宠物服务定义 service PetService { // 创建宠物 rpc CreatePet(CreatePetRequest) returns (CreatePetResponse); // 获取宠物信息 rpc GetPetInfo(GetPetInfoRequest) returns (GetPetInfoResponse); // 更新宠物信息 rpc UpdatePet(UpdatePetRequest) returns (UpdatePetResponse); // 宠物升级 rpc LevelUpPet(LevelUpPetRequest) returns (LevelUpPetResponse); // 宠物进化 rpc EvolvePet(EvolvePetRequest) returns (EvolvePetResponse); // 获取玩家宠物列表 rpc GetPlayerPets(GetPlayerPetsRequest) returns (GetPlayerPetsResponse); } // 创建宠物请求 message CreatePetRequest { string player_id = 1; string species_id = 2; string name = 3; int32 initial_level = 4; string rarity = 5; } // 创建宠物响应 message CreatePetResponse { greatestworks.common.CommonResponse common = 1; PetInfo pet = 2; } // 获取宠物信息请求 message GetPetInfoRequest { string pet_id = 1; } // 获取宠物信息响应 message GetPetInfoResponse { greatestworks.common.CommonResponse common = 1; PetInfo pet = 2; } // 更新宠物信息请求 message UpdatePetRequest { string pet_id = 1; map updates = 2; } // 更新宠物信息响应 message UpdatePetResponse { greatestworks.common.CommonResponse common = 1; PetInfo pet = 2; } // 宠物升级请求 message LevelUpPetRequest { string pet_id = 1; int32 experience_points = 2; } // 宠物升级响应 message LevelUpPetResponse { greatestworks.common.CommonResponse common = 1; PetInfo pet = 2; bool leveled_up = 3; int32 new_level = 4; } // 宠物进化请求 message EvolvePetRequest { string pet_id = 1; string target_species_id = 2; repeated string required_items = 3; } // 宠物进化响应 message EvolvePetResponse { greatestworks.common.CommonResponse common = 1; PetInfo pet = 2; bool evolved = 3; string new_species = 4; } // 获取玩家宠物列表请求 message GetPlayerPetsRequest { string player_id = 1; int32 limit = 2; int32 offset = 3; } // 获取玩家宠物列表响应 message GetPlayerPetsResponse { greatestworks.common.CommonResponse common = 1; repeated PetInfo pets = 2; greatestworks.common.PaginationInfo pagination = 3; } // 宠物信息 message PetInfo { string pet_id = 1; string player_id = 2; string species_id = 3; string name = 4; int32 level = 5; int32 experience = 6; int32 max_experience = 7; string rarity = 8; PetStats stats = 9; PetSkills skills = 10; int64 created_at = 11; int64 last_fed = 12; bool is_active = 13; } // 宠物属性 message PetStats { int32 health = 1; int32 max_health = 2; int32 attack = 3; int32 defense = 4; int32 speed = 5; int32 intelligence = 6; int32 loyalty = 7; int32 happiness = 8; } // 宠物技能 message PetSkills { repeated PetSkill skills = 1; int32 skill_points = 2; } // 宠物技能详情 message PetSkill { string skill_id = 1; string name = 2; string description = 3; int32 level = 4; int32 max_level = 5; PetSkillType skill_type = 6; int32 cooldown = 7; int32 mana_cost = 8; } // 宠物稀有度枚举 enum PetRarity { PET_RARITY_UNSPECIFIED = 0; PET_RARITY_COMMON = 1; // 普通 PET_RARITY_UNCOMMON = 2; // 不常见 PET_RARITY_RARE = 3; // 稀有 PET_RARITY_EPIC = 4; // 史诗 PET_RARITY_LEGENDARY = 5; // 传说 PET_RARITY_MYTHIC = 6; // 神话 } // 宠物品质枚举 enum PetQuality { PET_QUALITY_UNSPECIFIED = 0; PET_QUALITY_POOR = 1; // 劣质 PET_QUALITY_FAIR = 2; // 一般 PET_QUALITY_GOOD = 3; // 良好 PET_QUALITY_EXCELLENT = 4; // 优秀 PET_QUALITY_PERFECT = 5; // 完美 } // 宠物技能类型枚举 enum PetSkillType { PET_SKILL_TYPE_UNSPECIFIED = 0; PET_SKILL_TYPE_ATTACK = 1; // 攻击技能 PET_SKILL_TYPE_DEFENSE = 2; // 防御技能 PET_SKILL_TYPE_HEAL = 3; // 治疗技能 PET_SKILL_TYPE_BUFF = 4; // 增益技能 PET_SKILL_TYPE_DEBUFF = 5; // 减益技能 PET_SKILL_TYPE_PASSIVE = 6; // 被动技能 PET_SKILL_TYPE_ULTIMATE = 7; // 终极技能 } // 宠物心情枚举 enum PetMood { PET_MOOD_UNSPECIFIED = 0; PET_MOOD_VERY_HAPPY = 1; // 非常开心 PET_MOOD_HAPPY = 2; // 开心 PET_MOOD_NORMAL = 3; // 正常 PET_MOOD_SAD = 4; // 难过 PET_MOOD_VERY_SAD = 5; // 非常难过 PET_MOOD_ANGRY = 6; // 愤怒 PET_MOOD_SICK = 7; // 生病 } // 宠物训练类型枚举 enum PetTrainingType { PET_TRAINING_TYPE_UNSPECIFIED = 0; PET_TRAINING_TYPE_STRENGTH = 1; // 力量训练 PET_TRAINING_TYPE_AGILITY = 2; // 敏捷训练 PET_TRAINING_TYPE_INTELLIGENCE = 3; // 智力训练 PET_TRAINING_TYPE_ENDURANCE = 4; // 耐力训练 PET_TRAINING_TYPE_BALANCED = 5; // 平衡训练 } // 宠物训练强度枚举 enum PetTrainingIntensity { PET_TRAINING_INTENSITY_UNSPECIFIED = 0; PET_TRAINING_INTENSITY_LIGHT = 1; // 轻度训练 PET_TRAINING_INTENSITY_MODERATE = 2; // 中度训练 PET_TRAINING_INTENSITY_INTENSE = 3; // 强度训练 PET_TRAINING_INTENSITY_EXTREME = 4; // 极限训练 } ================================================ FILE: proto/player.proto ================================================ syntax = "proto3"; package greatestworks.player; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/player"; option csharp_namespace = "GreatestWorks.Player"; // 玩家服务定义 service PlayerService { // 创建玩家 rpc CreatePlayer(CreatePlayerRequest) returns (CreatePlayerResponse); // 玩家登录 rpc Login(LoginRequest) returns (LoginResponse); // 玩家登出 rpc Logout(LogoutRequest) returns (LogoutResponse); // 获取玩家信息 rpc GetPlayerInfo(GetPlayerInfoRequest) returns (GetPlayerInfoResponse); // 更新玩家信息 rpc UpdatePlayer(UpdatePlayerRequest) returns (UpdatePlayerResponse); // 移动玩家 rpc MovePlayer(MovePlayerRequest) returns (MovePlayerResponse); // 获取在线玩家列表 rpc GetOnlinePlayers(GetOnlinePlayersRequest) returns (GetOnlinePlayersResponse); } // 创建玩家请求 message CreatePlayerRequest { string name = 1; string account_id = 2; int32 initial_level = 3; greatestworks.common.Position initial_position = 4; } // 创建玩家响应 message CreatePlayerResponse { greatestworks.common.CommonResponse common = 1; greatestworks.common.PlayerBasicInfo player = 2; } // 登录请求 message LoginRequest { string player_id = 1; string session_id = 2; string client_version = 3; } // 登录响应 message LoginResponse { greatestworks.common.CommonResponse common = 1; greatestworks.common.PlayerBasicInfo player = 2; string session_token = 3; int64 login_time = 4; } // 登出请求 message LogoutRequest { string player_id = 1; string session_id = 2; } // 登出响应 message LogoutResponse { greatestworks.common.CommonResponse common = 1; int64 logout_time = 2; } // 获取玩家信息请求 message GetPlayerInfoRequest { string player_id = 1; } // 获取玩家信息响应 message GetPlayerInfoResponse { greatestworks.common.CommonResponse common = 1; greatestworks.common.PlayerBasicInfo player = 2; PlayerStats stats = 3; PlayerInventory inventory = 4; } // 更新玩家信息请求 message UpdatePlayerRequest { string player_id = 1; map updates = 2; } // 更新玩家信息响应 message UpdatePlayerResponse { greatestworks.common.CommonResponse common = 1; greatestworks.common.PlayerBasicInfo player = 2; } // 移动玩家请求 message MovePlayerRequest { string player_id = 1; greatestworks.common.Position position = 2; string scene_id = 3; } // 移动玩家响应 message MovePlayerResponse { greatestworks.common.CommonResponse common = 1; greatestworks.common.Position new_position = 2; } // 获取在线玩家列表请求 message GetOnlinePlayersRequest { int32 limit = 1; int32 offset = 2; } // 获取在线玩家列表响应 message GetOnlinePlayersResponse { greatestworks.common.CommonResponse common = 1; repeated greatestworks.common.PlayerBasicInfo players = 2; greatestworks.common.PaginationInfo pagination = 3; } // 玩家属性 message PlayerStats { int32 health = 1; int32 max_health = 2; int32 mana = 3; int32 max_mana = 4; int32 attack = 5; int32 defense = 6; int32 speed = 7; int32 gold = 8; int32 diamonds = 9; } // 玩家背包 message PlayerInventory { int32 capacity = 1; int32 used_slots = 2; repeated InventoryItem items = 3; } // 背包物品 message InventoryItem { string item_id = 1; string name = 2; int32 quantity = 3; int32 max_stack = 4; greatestworks.common.ItemType item_type = 5; greatestworks.common.ItemRarity rarity = 6; } // 玩家性别枚举 enum PlayerGender { PLAYER_GENDER_UNSPECIFIED = 0; PLAYER_GENDER_MALE = 1; // 男性 PLAYER_GENDER_FEMALE = 2; // 女性 PLAYER_GENDER_OTHER = 3; // 其他 } // 玩家职业枚举 enum PlayerClass { PLAYER_CLASS_UNSPECIFIED = 0; PLAYER_CLASS_WARRIOR = 1; // 战士 PLAYER_CLASS_MAGE = 2; // 法师 PLAYER_CLASS_ARCHER = 3; // 弓箭手 PLAYER_CLASS_ASSASSIN = 4; // 刺客 PLAYER_CLASS_PRIEST = 5; // 牧师 PLAYER_CLASS_PALADIN = 6; // 圣骑士 PLAYER_CLASS_DRUID = 7; // 德鲁伊 PLAYER_CLASS_NECROMANCER = 8; // 死灵法师 } // 玩家等级枚举 enum PlayerLevel { PLAYER_LEVEL_UNSPECIFIED = 0; PLAYER_LEVEL_BEGINNER = 1; // 新手 PLAYER_LEVEL_INTERMEDIATE = 2; // 中级 PLAYER_LEVEL_ADVANCED = 3; // 高级 PLAYER_LEVEL_EXPERT = 4; // 专家 PLAYER_LEVEL_MASTER = 5; // 大师 PLAYER_LEVEL_GRANDMASTER = 6; // 宗师 } // 玩家状态枚举 enum PlayerStatus { PLAYER_STATUS_UNSPECIFIED = 0; PLAYER_STATUS_OFFLINE = 1; // 离线 PLAYER_STATUS_ONLINE = 2; // 在线 PLAYER_STATUS_IN_BATTLE = 3; // 战斗中 PLAYER_STATUS_IN_QUEUE = 4; // 排队中 PLAYER_STATUS_AFK = 5; // 离开 PLAYER_STATUS_BANNED = 6; // 被封禁 } ================================================ FILE: proto/protocol.proto ================================================ syntax = "proto3"; package greatestworks.protocol; option go_package = "greatestworks/internal/proto/protocol"; option csharp_namespace = "GreatestWorks.Protocol"; // 消息类型枚举 - 系统消息 (0x0000 - 0x00FF) enum SystemMessageType { SYSTEM_MESSAGE_UNSPECIFIED = 0; MSG_HEARTBEAT = 0x0001; // 心跳消息 MSG_HANDSHAKE = 0x0002; // 握手消息 MSG_AUTH = 0x0003; // 认证消息 MSG_DISCONNECT = 0x0004; // 断开连接 MSG_ERROR = 0x0005; // 错误消息 MSG_PING = 0x0006; // Ping消息 MSG_PONG = 0x0007; // Pong消息 } // 消息类型枚举 - 玩家相关消息 (0x0100 - 0x01FF) enum PlayerMessageType { PLAYER_MESSAGE_UNSPECIFIED = 0; MSG_PLAYER_LOGIN = 0x0101; // 玩家登录 MSG_PLAYER_LOGOUT = 0x0102; // 玩家登出 MSG_PLAYER_INFO = 0x0103; // 玩家信息 MSG_PLAYER_MOVE = 0x0104; // 玩家移动 MSG_PLAYER_CREATE = 0x0105; // 创建玩家 MSG_PLAYER_UPDATE = 0x0106; // 更新玩家 MSG_PLAYER_DELETE = 0x0107; // 删除玩家 MSG_PLAYER_STATUS = 0x0108; // 玩家状态 MSG_PLAYER_STATS = 0x0109; // 玩家属性 MSG_PLAYER_LEVEL = 0x010A; // 玩家升级 } // 消息类型枚举 - 战斗相关消息 (0x0200 - 0x02FF) enum BattleMessageType { BATTLE_MESSAGE_UNSPECIFIED = 0; MSG_CREATE_BATTLE = 0x0201; // 创建战斗 MSG_JOIN_BATTLE = 0x0202; // 加入战斗 MSG_LEAVE_BATTLE = 0x0203; // 离开战斗 MSG_START_BATTLE = 0x0204; // 开始战斗 MSG_END_BATTLE = 0x0205; // 结束战斗 MSG_BATTLE_ACTION = 0x0206; // 战斗行动 MSG_BATTLE_RESULT = 0x0207; // 战斗结果 MSG_BATTLE_STATUS = 0x0208; // 战斗状态 MSG_SKILL_CAST = 0x0209; // 技能释放 MSG_DAMAGE_DEALT = 0x020A; // 伤害计算 } // 消息类型枚举 - 宠物相关消息 (0x0300 - 0x03FF) enum PetMessageType { PET_MESSAGE_UNSPECIFIED = 0; MSG_PET_SUMMON = 0x0301; // 召唤宠物 MSG_PET_DISMISS = 0x0302; // 收回宠物 MSG_PET_INFO = 0x0303; // 宠物信息 MSG_PET_MOVE = 0x0304; // 宠物移动 MSG_PET_ACTION = 0x0305; // 宠物行动 MSG_PET_LEVEL_UP = 0x0306; // 宠物升级 MSG_PET_EVOLUTION = 0x0307; // 宠物进化 MSG_PET_TRAIN = 0x0308; // 宠物训练 MSG_PET_FEED = 0x0309; // 宠物喂养 MSG_PET_STATUS = 0x030A; // 宠物状态 } // 消息类型枚举 - 建筑相关消息 (0x0400 - 0x04FF) enum BuildingMessageType { BUILDING_MESSAGE_UNSPECIFIED = 0; MSG_BUILDING_CREATE = 0x0401; // 创建建筑 MSG_BUILDING_UPGRADE = 0x0402; // 升级建筑 MSG_BUILDING_DESTROY = 0x0403; // 摧毁建筑 MSG_BUILDING_INFO = 0x0404; // 建筑信息 MSG_BUILDING_PRODUCE = 0x0405; // 建筑生产 MSG_BUILDING_COLLECT = 0x0406; // 收集资源 MSG_BUILDING_REPAIR = 0x0407; // 修复建筑 MSG_BUILDING_STATUS = 0x0408; // 建筑状态 } // 消息类型枚举 - 社交相关消息 (0x0500 - 0x05FF) enum SocialMessageType { SOCIAL_MESSAGE_UNSPECIFIED = 0; MSG_CHAT_MESSAGE = 0x0501; // 聊天消息 MSG_FRIEND_REQUEST = 0x0502; // 好友请求 MSG_FRIEND_ACCEPT = 0x0503; // 接受好友 MSG_FRIEND_REJECT = 0x0504; // 拒绝好友 MSG_FRIEND_REMOVE = 0x0505; // 删除好友 MSG_FRIEND_LIST = 0x0506; // 好友列表 MSG_GUILD_CREATE = 0x0507; // 创建公会 MSG_GUILD_JOIN = 0x0508; // 加入公会 MSG_GUILD_LEAVE = 0x0509; // 离开公会 MSG_GUILD_INFO = 0x050A; // 公会信息 MSG_TEAM_CREATE = 0x050B; // 创建队伍 MSG_TEAM_JOIN = 0x050C; // 加入队伍 MSG_TEAM_LEAVE = 0x050D; // 离开队伍 MSG_TEAM_INFO = 0x050E; // 队伍信息 } // 消息类型枚举 - 物品相关消息 (0x0600 - 0x06FF) enum ItemMessageType { ITEM_MESSAGE_UNSPECIFIED = 0; MSG_ITEM_USE = 0x0601; // 使用物品 MSG_ITEM_EQUIP = 0x0602; // 装备物品 MSG_ITEM_UNEQUIP = 0x0603; // 卸下装备 MSG_ITEM_DROP = 0x0604; // 丢弃物品 MSG_ITEM_PICKUP = 0x0605; // 拾取物品 MSG_ITEM_TRADE = 0x0606; // 交易物品 MSG_INVENTORY_INFO = 0x0607; // 背包信息 MSG_ITEM_INFO = 0x0608; // 物品信息 MSG_ITEM_CRAFT = 0x0609; // 制作物品 MSG_ITEM_ENHANCE = 0x060A; // 强化物品 } // 消息类型枚举 - 任务相关消息 (0x0700 - 0x07FF) enum QuestMessageType { QUEST_MESSAGE_UNSPECIFIED = 0; MSG_QUEST_ACCEPT = 0x0701; // 接受任务 MSG_QUEST_COMPLETE = 0x0702; // 完成任务 MSG_QUEST_CANCEL = 0x0703; // 取消任务 MSG_QUEST_PROGRESS = 0x0704; // 任务进度 MSG_QUEST_LIST = 0x0705; // 任务列表 MSG_QUEST_INFO = 0x0706; // 任务信息 MSG_QUEST_REWARD = 0x0707; // 任务奖励 } // 消息类型枚举 - 查询相关消息 (0x0800 - 0x08FF) enum QueryMessageType { QUERY_MESSAGE_UNSPECIFIED = 0; MSG_GET_PLAYER_INFO = 0x0801; // 获取玩家信息 MSG_GET_ONLINE_PLAYERS = 0x0802; // 获取在线玩家 MSG_GET_BATTLE_INFO = 0x0803; // 获取战斗信息 MSG_GET_RANKING_LIST = 0x0804; // 获取排行榜 MSG_GET_SERVER_INFO = 0x0805; // 获取服务器信息 } // 错误码枚举 enum ErrorCode { option allow_alias = true; ERROR_CODE_UNSPECIFIED = 0; // 通用错误 (0-999) ERR_SUCCESS = 0; // 成功 ERR_UNKNOWN = 1000; // 未知错误 ERR_INVALID_MESSAGE = 1001; // 无效消息 ERR_INVALID_PLAYER = 1002; // 无效玩家 ERR_PLAYER_NOT_FOUND = 1003; // 玩家未找到 ERR_PLAYER_OFFLINE = 1004; // 玩家离线 ERR_AUTH_FAILED = 1005; // 认证失败 ERR_PERMISSION_DENIED = 1006; // 权限不足 ERR_RATE_LIMITED = 1007; // 请求过于频繁 ERR_SERVER_BUSY = 1008; // 服务器繁忙 ERR_MAINTENANCE = 1009; // 服务器维护 // 战斗相关错误 (2000-2999) ERR_BATTLE_NOT_FOUND = 2001; // 战斗未找到 ERR_BATTLE_FULL = 2002; // 战斗已满 ERR_BATTLE_STARTED = 2003; // 战斗已开始 ERR_BATTLE_ENDED = 2004; // 战斗已结束 ERR_INVALID_ACTION = 2005; // 无效行动 ERR_NOT_YOUR_TURN = 2006; // 不是你的回合 ERR_SKILL_COOLDOWN = 2007; // 技能冷却中 ERR_INSUFFICIENT_MP = 2008; // MP不足 // 宠物相关错误 (3000-3999) ERR_PET_NOT_FOUND = 3001; // 宠物未找到 ERR_PET_ALREADY_ACTIVE = 3002; // 宠物已激活 ERR_PET_NOT_ACTIVE = 3003; // 宠物未激活 ERR_PET_LEVEL_TOO_LOW = 3004; // 宠物等级过低 ERR_PET_EVOLUTION_FAIL = 3005; // 宠物进化失败 // 物品相关错误 (4000-4999) ERR_ITEM_NOT_FOUND = 4001; // 物品未找到 ERR_ITEM_NOT_USABLE = 4002; // 物品不可使用 ERR_INVENTORY_FULL = 4003; // 背包已满 ERR_INSUFFICIENT_ITEM = 4004; // 物品数量不足 ERR_ITEM_EQUIP_FAILED = 4005; // 装备失败 } // 玩家状态枚举 enum PlayerStatus { PLAYER_STATUS_UNSPECIFIED = 0; PLAYER_STATUS_OFFLINE = 1; // 离线 PLAYER_STATUS_ONLINE = 2; // 在线 PLAYER_STATUS_IN_BATTLE = 3; // 战斗中 PLAYER_STATUS_IN_QUEUE = 4; // 排队中 PLAYER_STATUS_AFK = 5; // 离开 PLAYER_STATUS_BANNED = 6; // 被封禁 } // 战斗状态枚举 enum BattleStatus { BATTLE_STATUS_UNSPECIFIED = 0; BATTLE_STATUS_WAITING = 1; // 等待中 BATTLE_STATUS_STARTING = 2; // 开始中 BATTLE_STATUS_ACTIVE = 3; // 进行中 BATTLE_STATUS_ENDING = 4; // 结束中 BATTLE_STATUS_FINISHED = 5; // 已结束 BATTLE_STATUS_CANCELLED = 6; // 已取消 } // 宠物状态枚举 enum PetStatus { PET_STATUS_UNSPECIFIED = 0; PET_STATUS_IDLE = 1; // 空闲 PET_STATUS_ACTIVE = 2; // 活跃 PET_STATUS_BATTLE = 3; // 战斗中 PET_STATUS_TRAINING = 4; // 训练中 PET_STATUS_SLEEPING = 5; // 睡眠中 PET_STATUS_SICK = 6; // 生病 PET_STATUS_EVOLVING = 7; // 进化中 } // 物品稀有度枚举 enum ItemRarity { ITEM_RARITY_UNSPECIFIED = 0; ITEM_RARITY_COMMON = 1; // 普通 ITEM_RARITY_UNCOMMON = 2; // 不常见 ITEM_RARITY_RARE = 3; // 稀有 ITEM_RARITY_EPIC = 4; // 史诗 ITEM_RARITY_LEGENDARY = 5; // 传说 ITEM_RARITY_MYTHIC = 6; // 神话 } // 宠物稀有度枚举 enum PetRarity { PET_RARITY_UNSPECIFIED = 0; PET_RARITY_COMMON = 1; // 普通 PET_RARITY_UNCOMMON = 2; // 不常见 PET_RARITY_RARE = 3; // 稀有 PET_RARITY_EPIC = 4; // 史诗 PET_RARITY_LEGENDARY = 5; // 传说 PET_RARITY_MYTHIC = 6; // 神话 } // 消息标志位枚举 enum MessageFlag { MESSAGE_FLAG_UNSPECIFIED = 0; MESSAGE_FLAG_REQUEST = 0x0001; // 请求消息 MESSAGE_FLAG_RESPONSE = 0x0002; // 响应消息 MESSAGE_FLAG_ERROR = 0x0004; // 错误消息 MESSAGE_FLAG_ASYNC = 0x0008; // 异步消息 MESSAGE_FLAG_BROADCAST = 0x0010; // 广播消息 MESSAGE_FLAG_ENCRYPTED = 0x0020; // 加密消息 MESSAGE_FLAG_COMPRESSED = 0x0040; // 压缩消息 } // 协议常量 message ProtocolConstants { // 消息魔数 uint32 message_magic = 1; // 0x47574B53 "GWKS" - GreatestWorks // 消息头大小 uint32 message_header_size = 2; // 32字节 // 最大消息体大小 uint32 max_message_body_size = 3; // 1MB // 心跳间隔(秒) uint32 heartbeat_interval = 4; // 30秒 // 连接超时(秒) uint32 connection_timeout = 5; // 300秒 // 重连间隔(秒) uint32 reconnect_interval = 6; // 5秒 // 最大重连次数 uint32 max_reconnect_attempts = 7; // 5次 } // 消息头结构 message MessageHeader { uint32 magic = 1; // 魔数标识 uint32 message_id = 2; // 消息ID(用于请求响应匹配) uint32 message_type = 3; // 消息类型 uint32 flags = 4; // 标志位 uint64 player_id = 5; // 玩家ID int64 timestamp = 6; // 时间戳 uint32 sequence = 7; // 序列号 uint32 length = 8; // 消息体长度 } // 基础响应结构 message BaseResponse { bool success = 1; string message = 2; ErrorCode error_code = 3; int64 timestamp = 4; string request_id = 5; } // 错误响应结构 message ErrorResponse { BaseResponse base = 1; string error_type = 2; string details = 3; int64 error_time = 4; } ================================================ FILE: proto/quest.proto ================================================ syntax = "proto3"; package greatestworks.quest; import "proto/common.proto"; import "proto/auth.proto"; option go_package = "greatestworks/internal/proto/quest"; option csharp_namespace = "GreatestWorks.Quest"; // 任务服务 service QuestService { // 获取任务列表 rpc GetQuestList(GetQuestListRequest) returns (GetQuestListResponse); // 接受任务 rpc AcceptQuest(AcceptQuestRequest) returns (AcceptQuestResponse); // 提交任务 rpc SubmitQuest(SubmitQuestRequest) returns (SubmitQuestResponse); // 放弃任务 rpc AbandonQuest(AbandonQuestRequest) returns (AbandonQuestResponse); // 获取任务详情 rpc GetQuestInfo(GetQuestInfoRequest) returns (GetQuestInfoResponse); } // ========== 获取任务列表 ========== // 获取任务列表请求 message GetQuestListRequest { int64 character_id = 1; // 角色ID common.QuestStatus status = 2; // 任务状态(可选筛选) } // 获取任务列表响应 message GetQuestListResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 repeated TaskRecord tasks = 3; // 任务列表 } // ========== 接受任务 ========== // 接受任务请求 message AcceptQuestRequest { int64 character_id = 1; // 角色ID int32 task_id = 2; // 任务ID int32 npc_id = 3; // NPC ID(如果从NPC接取) } // 接受任务响应 message AcceptQuestResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 TaskRecord task = 3; // 任务记录 } // ========== 提交任务 ========== // 提交任务请求 message SubmitQuestRequest { int64 character_id = 1; // 角色ID int32 task_id = 2; // 任务ID int32 npc_id = 3; // NPC ID(如果向NPC提交) } // 提交任务响应 message SubmitQuestResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 QuestReward reward = 3; // 任务奖励 } // ========== 放弃任务 ========== // 放弃任务请求 message AbandonQuestRequest { int64 character_id = 1; // 角色ID int32 task_id = 2; // 任务ID } // 放弃任务响应 message AbandonQuestResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 } // ========== 获取任务详情 ========== // 获取任务详情请求 message GetQuestInfoRequest { int64 character_id = 1; // 角色ID int32 task_id = 2; // 任务ID } // 获取任务详情响应 message GetQuestInfoResponse { auth.ErrorCode error = 1; // 错误码 string message = 2; // 错误消息 QuestDetail quest_detail = 3; // 任务详情 } // ========== 任务进度更新通知 ========== // 任务进度更新通知(服务器推送) message QuestProgressUpdateNotify { int32 task_id = 1; // 任务ID repeated QuestProgress progress = 2; // 进度列表 } // ========== 数据结构 ========== // 任务记录 message TaskRecord { int32 task_id = 1; // 任务ID common.QuestStatus status = 2; // 任务状态 int64 accept_time = 3; // 接受时间 int64 complete_time = 4; // 完成时间 repeated QuestProgress progress = 5; // 任务进度 } // 任务信息(用于序列化存储) message TaskInfo { repeated TaskRecord task_arr = 1; // 任务数组 } // 任务详情 message QuestDetail { int32 task_id = 1; // 任务ID string name = 2; // 任务名称 string description = 3; // 任务描述 common.QuestType quest_type = 4; // 任务类型 common.QuestStatus status = 5; // 任务状态 int32 required_level = 6; // 需求等级 int32 time_limit = 7; // 时间限制(秒,0表示无限制) repeated QuestObjective objectives = 8; // 任务目标 QuestReward reward = 9; // 任务奖励 repeated int32 prerequisite_quests = 10; // 前置任务ID列表 } // 任务目标 message QuestObjective { ObjectiveType type = 1; // 目标类型 int32 target_id = 2; // 目标ID int32 required_count = 3; // 需求数量 int32 current_count = 4; // 当前数量 string description = 5; // 描述 } // 任务进度 message QuestProgress { int32 objective_index = 1; // 目标索引 int32 current_count = 2; // 当前进度 bool is_completed = 3; // 是否完成 } // 任务奖励 message QuestReward { int32 exp = 1; // 经验奖励 int32 gold = 2; // 金币奖励 repeated RewardItem items = 3; // 物品奖励 repeated int32 optional_items = 4; // 可选物品ID列表 } // 奖励物品 message RewardItem { int32 item_id = 1; // 物品ID int32 count = 2; // 数量 } // ========== 枚举定义 ========== // 任务目标类型 enum ObjectiveType { OBJECTIVE_COLLECT = 0; // 收集物品 OBJECTIVE_KILL = 1; // 击杀怪物 OBJECTIVE_TALK = 2; // 对话NPC OBJECTIVE_REACH = 3; // 到达地点 OBJECTIVE_ESCORT = 4; // 护送NPC OBJECTIVE_USE_ITEM = 5; // 使用物品 OBJECTIVE_LEVEL = 6; // 达到等级 OBJECTIVE_CRAFT = 7; // 制作物品 } ================================================ FILE: proto/room.proto ================================================ syntax = "proto3"; package greatestworks.room; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/room"; option csharp_namespace = "GreatestWorks.Room"; // 房间服务定义 service RoomService { // 创建房间 rpc CreateRoom(CreateRoomRequest) returns (CreateRoomResponse); // 加入房间 rpc JoinRoom(JoinRoomRequest) returns (JoinRoomResponse); // 离开房间 rpc LeaveRoom(LeaveRoomRequest) returns (LeaveRoomResponse); // 获取房间列表 rpc GetRoomList(GetRoomListRequest) returns (GetRoomListResponse); // 获取房间信息 rpc GetRoomInfo(GetRoomInfoRequest) returns (GetRoomInfoResponse); // 更新房间设置 rpc UpdateRoomSettings(UpdateRoomSettingsRequest) returns (UpdateRoomSettingsResponse); // 踢出玩家 rpc KickPlayer(KickPlayerRequest) returns (KickPlayerResponse); // 转移房主 rpc TransferOwnership(TransferOwnershipRequest) returns (TransferOwnershipResponse); // 开始游戏/匹配 rpc StartGame(StartGameRequest) returns (StartGameResponse); // 准备状态 rpc SetReady(SetReadyRequest) returns (SetReadyResponse); // 邀请玩家 rpc InvitePlayer(InvitePlayerRequest) returns (InvitePlayerResponse); // 搜索房间 rpc SearchRooms(SearchRoomsRequest) returns (SearchRoomsResponse); // 发送房间消息 rpc SendRoomMessage(SendRoomMessageRequest) returns (SendRoomMessageResponse); // 设置房间密码 rpc SetRoomPassword(SetRoomPasswordRequest) returns (SetRoomPasswordResponse); } // 创建房间请求 message CreateRoomRequest { string owner_id = 1; string room_name = 2; RoomType room_type = 3; string game_mode = 4; string map_id = 5; int32 max_players = 6; bool is_private = 7; string password = 8; RoomSettings settings = 9; map custom_rules = 10; } // 创建房间响应 message CreateRoomResponse { greatestworks.common.CommonResponse common = 1; string room_id = 2; RoomInfo room_info = 3; } // 加入房间请求 message JoinRoomRequest { string player_id = 1; string room_id = 2; string password = 3; string invitation_code = 4; int32 preferred_team = 5; // 希望加入的队伍 } // 加入房间响应 message JoinRoomResponse { greatestworks.common.CommonResponse common = 1; RoomInfo room_info = 2; RoomPlayer player_info = 3; repeated RoomPlayer other_players = 4; } // 离开房间请求 message LeaveRoomRequest { string player_id = 1; string room_id = 2; } // 离开房间响应 message LeaveRoomResponse { greatestworks.common.CommonResponse common = 1; } // 获取房间列表请求 message GetRoomListRequest { RoomType room_type = 1; string game_mode = 2; bool only_public = 3; bool only_available = 4; // 只显示有空位的房间 int32 limit = 5; int32 offset = 6; RoomSortBy sort_by = 7; } // 获取房间列表响应 message GetRoomListResponse { greatestworks.common.CommonResponse common = 1; repeated RoomInfo rooms = 2; greatestworks.common.PaginationInfo pagination = 3; } // 获取房间信息请求 message GetRoomInfoRequest { string room_id = 1; string player_id = 2; // 查询者ID,用于权限检查 } // 获取房间信息响应 message GetRoomInfoResponse { greatestworks.common.CommonResponse common = 1; RoomDetail room_detail = 2; } // 更新房间设置请求 message UpdateRoomSettingsRequest { string player_id = 1; string room_id = 2; RoomSettings settings = 3; map custom_rules = 4; } // 更新房间设置响应 message UpdateRoomSettingsResponse { greatestworks.common.CommonResponse common = 1; RoomSettings new_settings = 2; } // 踢出玩家请求 message KickPlayerRequest { string kicker_id = 1; string room_id = 2; string target_player_id = 3; string reason = 4; } // 踢出玩家响应 message KickPlayerResponse { greatestworks.common.CommonResponse common = 1; } // 转移房主请求 message TransferOwnershipRequest { string current_owner_id = 1; string room_id = 2; string new_owner_id = 3; } // 转移房主响应 message TransferOwnershipResponse { greatestworks.common.CommonResponse common = 1; RoomPlayer new_owner = 2; } // 开始游戏请求 message StartGameRequest { string owner_id = 1; string room_id = 2; bool force_start = 3; // 强制开始(即使人数不足) } // 开始游戏响应 message StartGameResponse { greatestworks.common.CommonResponse common = 1; string game_session_id = 2; int64 start_time = 3; } // 设置准备状态请求 message SetReadyRequest { string player_id = 1; string room_id = 2; bool is_ready = 3; } // 设置准备状态响应 message SetReadyResponse { greatestworks.common.CommonResponse common = 1; bool is_ready = 2; bool all_players_ready = 3; } // 邀请玩家请求 message InvitePlayerRequest { string inviter_id = 1; string room_id = 2; string target_player_id = 3; string message = 4; } // 邀请玩家响应 message InvitePlayerResponse { greatestworks.common.CommonResponse common = 1; string invitation_id = 2; } // 搜索房间请求 message SearchRoomsRequest { string keyword = 1; string owner_name = 2; RoomType room_type = 3; string game_mode = 4; bool only_public = 5; bool only_available = 6; int32 limit = 7; int32 offset = 8; } // 搜索房间响应 message SearchRoomsResponse { greatestworks.common.CommonResponse common = 1; repeated RoomInfo rooms = 2; greatestworks.common.PaginationInfo pagination = 3; } // 发送房间消息请求 message SendRoomMessageRequest { string sender_id = 1; string room_id = 2; string content = 3; RoomMessageType message_type = 4; } // 发送房间消息响应 message SendRoomMessageResponse { greatestworks.common.CommonResponse common = 1; string message_id = 2; } // 设置房间密码请求 message SetRoomPasswordRequest { string owner_id = 1; string room_id = 2; string new_password = 3; // 空字符串表示移除密码 } // 设置房间密码响应 message SetRoomPasswordResponse { greatestworks.common.CommonResponse common = 1; bool has_password = 2; } // 房间信息 message RoomInfo { string room_id = 1; string room_name = 2; string owner_id = 3; string owner_name = 4; RoomType room_type = 5; string game_mode = 6; string map_id = 7; string map_name = 8; RoomStatus status = 9; int32 current_players = 10; int32 max_players = 11; bool is_private = 12; bool has_password = 13; int32 ping = 14; string region = 15; int64 created_at = 16; RoomSettings settings = 17; } // 房间详情 message RoomDetail { RoomInfo basic_info = 1; repeated RoomPlayer players = 2; repeated RoomTeam teams = 3; repeated RoomMessage recent_messages = 4; map custom_rules = 5; RoomStats stats = 6; } // 房间玩家 message RoomPlayer { string player_id = 1; string player_name = 2; int32 level = 3; int32 rank = 4; PlayerRole role = 5; int32 team_id = 6; bool is_ready = 7; bool is_online = 8; int64 joined_at = 9; PlayerStats stats = 10; greatestworks.common.Position position = 11; } // 房间队伍 message RoomTeam { int32 team_id = 1; string team_name = 2; string team_color = 3; int32 current_members = 4; int32 max_members = 5; repeated string player_ids = 6; bool is_ready = 7; TeamStats team_stats = 8; } // 房间消息 message RoomMessage { string message_id = 1; string sender_id = 2; string sender_name = 3; string content = 4; RoomMessageType message_type = 5; int64 timestamp = 6; } // 房间设置 message RoomSettings { int32 game_duration = 1; // 游戏时长(分钟) int32 preparation_time = 2; // 准备时间(秒) bool friendly_fire = 3; // 友军伤害 bool spectators_allowed = 4; // 允许观战 int32 max_spectators = 5; // 最大观战人数 bool auto_start = 6; // 自动开始 int32 auto_start_countdown = 7; // 自动开始倒计时 bool team_balance = 8; // 队伍平衡 DifficultyLevel difficulty = 9; // 难度等级 map score_limits = 10; // 分数限制 repeated string banned_items = 11; // 禁用物品 repeated string banned_skills = 12; // 禁用技能 } // 玩家统计 message PlayerStats { int32 kills = 1; int32 deaths = 2; int32 assists = 3; float kd_ratio = 4; int32 score = 5; int32 games_played = 6; int32 wins = 7; float win_rate = 8; } // 队伍统计 message TeamStats { int32 total_score = 1; int32 total_kills = 2; int32 total_deaths = 3; float average_kd = 4; int32 objectives_completed = 5; } // 房间统计 message RoomStats { int32 total_games_played = 1; int64 total_playtime = 2; int32 average_players = 3; float average_game_duration = 4; int64 last_active = 5; } // 房间类型枚举 enum RoomType { ROOM_TYPE_UNSPECIFIED = 0; ROOM_TYPE_CASUAL = 1; // 休闲模式 ROOM_TYPE_RANKED = 2; // 排位模式 ROOM_TYPE_CUSTOM = 3; // 自定义模式 ROOM_TYPE_TOURNAMENT = 4; // 锦标赛模式 ROOM_TYPE_PRACTICE = 5; // 练习模式 ROOM_TYPE_SPECTATE = 6; // 观战模式 ROOM_TYPE_PRIVATE_MATCH = 7; // 私人对战 } // 房间状态枚举 enum RoomStatus { ROOM_STATUS_UNSPECIFIED = 0; ROOM_STATUS_WAITING = 1; // 等待玩家 ROOM_STATUS_PREPARING = 2; // 准备中 ROOM_STATUS_IN_GAME = 3; // 游戏中 ROOM_STATUS_FINISHED = 4; // 已结束 ROOM_STATUS_DISBANDED = 5; // 已解散 ROOM_STATUS_PAUSED = 6; // 已暂停 } // 玩家角色枚举 enum PlayerRole { PLAYER_ROLE_UNSPECIFIED = 0; PLAYER_ROLE_OWNER = 1; // 房主 PLAYER_ROLE_MODERATOR = 2; // 管理员 PLAYER_ROLE_PLAYER = 3; // 玩家 PLAYER_ROLE_SPECTATOR = 4; // 观战者 } // 房间消息类型枚举 enum RoomMessageType { ROOM_MESSAGE_TYPE_UNSPECIFIED = 0; ROOM_MESSAGE_TYPE_CHAT = 1; // 聊天消息 ROOM_MESSAGE_TYPE_SYSTEM = 2; // 系统消息 ROOM_MESSAGE_TYPE_JOIN = 3; // 加入消息 ROOM_MESSAGE_TYPE_LEAVE = 4; // 离开消息 ROOM_MESSAGE_TYPE_READY = 5; // 准备消息 ROOM_MESSAGE_TYPE_START = 6; // 开始消息 ROOM_MESSAGE_TYPE_END = 7; // 结束消息 } // 房间排序枚举 enum RoomSortBy { ROOM_SORT_BY_UNSPECIFIED = 0; ROOM_SORT_BY_NAME = 1; // 按名称排序 ROOM_SORT_BY_PLAYERS = 2; // 按玩家数量排序 ROOM_SORT_BY_CREATED_TIME = 3; // 按创建时间排序 ROOM_SORT_BY_PING = 4; // 按延迟排序 ROOM_SORT_BY_POPULARITY = 5; // 按热门程度排序 } // 难度等级枚举 enum DifficultyLevel { DIFFICULTY_LEVEL_UNSPECIFIED = 0; DIFFICULTY_LEVEL_EASY = 1; // 简单 DIFFICULTY_LEVEL_NORMAL = 2; // 普通 DIFFICULTY_LEVEL_HARD = 3; // 困难 DIFFICULTY_LEVEL_EXPERT = 4; // 专家 DIFFICULTY_LEVEL_NIGHTMARE = 5; // 噩梦 } ================================================ FILE: proto/scene.proto ================================================ syntax = "proto3"; package greatestworks.scene; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/scene"; option csharp_namespace = "GreatestWorks.Scene"; // 场景服务定义 service SceneService { // 进入场景 rpc EnterScene(EnterSceneRequest) returns (EnterSceneResponse); // 离开场景 rpc LeaveScene(LeaveSceneRequest) returns (LeaveSceneResponse); // 获取场景信息 rpc GetSceneInfo(GetSceneInfoRequest) returns (GetSceneInfoResponse); // 移动到场景中的位置 rpc MoveToPosition(MoveToPositionRequest) returns (MoveToPositionResponse); // 与场景对象交互 rpc InteractWithObject(InteractWithObjectRequest) returns (InteractWithObjectResponse); // 获取场景中的玩家列表 rpc GetPlayersInScene(GetPlayersInSceneRequest) returns (GetPlayersInSceneResponse); // 获取场景对象列表 rpc GetSceneObjects(GetSceneObjectsRequest) returns (GetSceneObjectsResponse); // 触发场景事件 rpc TriggerSceneEvent(TriggerSceneEventRequest) returns (TriggerSceneEventResponse); // 获取可用场景列表 rpc GetAvailableScenes(GetAvailableScenesRequest) returns (GetAvailableScenesResponse); // 传送到指定场景 rpc TeleportToScene(TeleportToSceneRequest) returns (TeleportToSceneResponse); // 设置场景天气 rpc SetWeather(SetWeatherRequest) returns (SetWeatherResponse); // 获取场景统计 rpc GetSceneStats(GetSceneStatsRequest) returns (GetSceneStatsResponse); } // 进入场景请求 message EnterSceneRequest { string player_id = 1; string scene_id = 2; greatestworks.common.Position spawn_position = 3; string previous_scene_id = 4; string entrance_id = 5; // 入口ID(如传送门、门等) map entry_context = 6; } // 进入场景响应 message EnterSceneResponse { greatestworks.common.CommonResponse common = 1; SceneInfo scene_info = 2; greatestworks.common.Position player_position = 3; repeated ScenePlayer other_players = 4; repeated SceneObject scene_objects = 5; SceneEnvironment environment = 6; } // 离开场景请求 message LeaveSceneRequest { string player_id = 1; string scene_id = 2; string exit_id = 3; // 出口ID } // 离开场景响应 message LeaveSceneResponse { greatestworks.common.CommonResponse common = 1; } // 获取场景信息请求 message GetSceneInfoRequest { string scene_id = 1; string player_id = 2; // 查询者ID,用于权限检查 bool include_players = 3; bool include_objects = 4; } // 获取场景信息响应 message GetSceneInfoResponse { greatestworks.common.CommonResponse common = 1; SceneInfo scene_info = 2; repeated ScenePlayer players = 3; repeated SceneObject objects = 4; SceneEnvironment environment = 5; } // 移动到位置请求 message MoveToPositionRequest { string player_id = 1; string scene_id = 2; greatestworks.common.Position target_position = 3; MovementType movement_type = 4; float speed = 5; } // 移动到位置响应 message MoveToPositionResponse { greatestworks.common.CommonResponse common = 1; greatestworks.common.Position new_position = 2; float actual_speed = 3; int64 movement_time = 4; } // 与对象交互请求 message InteractWithObjectRequest { string player_id = 1; string scene_id = 2; string object_id = 3; InteractionType interaction_type = 4; map parameters = 5; } // 与对象交互响应 message InteractWithObjectResponse { greatestworks.common.CommonResponse common = 1; InteractionResult result = 2; repeated SceneEvent triggered_events = 3; } // 获取场景中玩家列表请求 message GetPlayersInSceneRequest { string scene_id = 1; string requester_id = 2; int32 limit = 3; int32 offset = 4; float radius = 5; // 搜索半径(以请求者为中心) greatestworks.common.Position center_position = 6; // 搜索中心点 } // 获取场景中玩家列表响应 message GetPlayersInSceneResponse { greatestworks.common.CommonResponse common = 1; repeated ScenePlayer players = 2; greatestworks.common.PaginationInfo pagination = 3; } // 获取场景对象请求 message GetSceneObjectsRequest { string scene_id = 1; string player_id = 2; ObjectType object_type = 3; float radius = 4; // 搜索半径 greatestworks.common.Position center_position = 5; bool interactive_only = 6; // 只返回可交互对象 } // 获取场景对象响应 message GetSceneObjectsResponse { greatestworks.common.CommonResponse common = 1; repeated SceneObject objects = 2; } // 触发场景事件请求 message TriggerSceneEventRequest { string player_id = 1; string scene_id = 2; string event_id = 3; string trigger_id = 4; // 触发器ID map event_data = 5; } // 触发场景事件响应 message TriggerSceneEventResponse { greatestworks.common.CommonResponse common = 1; SceneEvent event = 2; repeated SceneEventEffect effects = 3; } // 获取可用场景列表请求 message GetAvailableScenesRequest { string player_id = 1; SceneType scene_type = 2; int32 min_level = 3; int32 max_level = 4; bool only_unlocked = 5; int32 limit = 6; int32 offset = 7; } // 获取可用场景列表响应 message GetAvailableScenesResponse { greatestworks.common.CommonResponse common = 1; repeated SceneInfo scenes = 2; greatestworks.common.PaginationInfo pagination = 3; } // 传送到场景请求 message TeleportToSceneRequest { string player_id = 1; string target_scene_id = 2; string teleport_point_id = 3; // 传送点ID bool use_item = 4; // 是否使用传送道具 string item_id = 5; // 传送道具ID } // 传送到场景响应 message TeleportToSceneResponse { greatestworks.common.CommonResponse common = 1; string target_scene_id = 2; greatestworks.common.Position spawn_position = 3; int32 cost = 4; // 传送费用 } // 设置天气请求 message SetWeatherRequest { string scene_id = 1; string admin_id = 2; // 管理员ID WeatherType weather_type = 3; int32 intensity = 4; // 强度 (0-100) int32 duration = 5; // 持续时间(秒) } // 设置天气响应 message SetWeatherResponse { greatestworks.common.CommonResponse common = 1; SceneEnvironment new_environment = 2; } // 获取场景统计请求 message GetSceneStatsRequest { string scene_id = 1; string admin_id = 2; } // 获取场景统计响应 message GetSceneStatsResponse { greatestworks.common.CommonResponse common = 1; SceneStats stats = 2; } // 场景信息 message SceneInfo { string scene_id = 1; string name = 2; string description = 3; SceneType scene_type = 4; SceneStatus status = 5; int32 min_level = 6; int32 max_level = 7; int32 max_players = 8; int32 current_players = 9; greatestworks.common.Position spawn_point = 10; repeated TeleportPoint teleport_points = 11; SceneSettings settings = 12; int64 created_at = 13; int64 last_updated = 14; string version = 15; map metadata = 16; } // 场景玩家 message ScenePlayer { string player_id = 1; string player_name = 2; int32 level = 3; greatestworks.common.Position position = 4; PlayerState state = 5; bool is_visible = 6; string current_activity = 7; // 当前活动 int64 entered_at = 8; int64 last_update = 9; map player_data = 10; } // 场景对象 message SceneObject { string object_id = 1; string name = 2; ObjectType object_type = 3; ObjectState state = 4; greatestworks.common.Position position = 5; float rotation_y = 6; // Y轴旋转角度 bool is_interactive = 7; bool is_visible = 8; repeated InteractionType available_interactions = 9; map properties = 10; int64 created_at = 11; int64 last_updated = 12; } // 场景环境 message SceneEnvironment { WeatherType weather = 1; int32 weather_intensity = 2; TimeOfDay time_of_day = 3; float ambient_light = 4; // 环境光强度 (0.0-1.0) float temperature = 5; // 温度 float humidity = 6; // 湿度 string background_music = 7; repeated EnvironmentalEffect effects = 8; map custom_settings = 9; } // 传送点 message TeleportPoint { string point_id = 1; string name = 2; greatestworks.common.Position position = 3; bool is_active = 4; bool requires_discovery = 5; // 需要发现才能使用 int32 cost = 6; // 传送费用 repeated string required_items = 7; // 需要的物品 int32 min_level = 8; // 最低等级要求 string description = 9; } // 场景设置 message SceneSettings { bool pvp_enabled = 1; // 是否允许PVP bool respawn_enabled = 2; // 是否允许复活 int32 respawn_time = 3; // 复活时间(秒) bool drop_items_on_death = 4; // 死亡是否掉落物品 float experience_multiplier = 5; // 经验倍率 float drop_rate_multiplier = 6; // 掉落率倍率 bool safe_zone = 7; // 是否为安全区 bool allow_flying = 8; // 是否允许飞行 bool allow_mount = 9; // 是否允许坐骑 int32 idle_timeout = 10; // 闲置超时时间(秒) map movement_modifiers = 11; // 移动修正器 } // 场景事件 message SceneEvent { string event_id = 1; string event_name = 2; EventType event_type = 3; string trigger_player_id = 4; string trigger_object_id = 5; greatestworks.common.Position location = 6; int64 timestamp = 7; map event_data = 8; repeated string affected_player_ids = 9; } // 场景事件效果 message SceneEventEffect { string effect_id = 1; EffectType effect_type = 2; string target_id = 3; // 目标ID(玩家或对象) int32 duration = 4; // 持续时间(秒) float magnitude = 5; // 效果强度 map parameters = 6; } // 交互结果 message InteractionResult { bool success = 1; string message = 2; repeated ItemReward rewards = 3; int32 experience_gained = 4; ObjectState new_object_state = 5; map result_data = 6; } // 物品奖励 message ItemReward { string item_id = 1; string item_name = 2; int32 quantity = 3; greatestworks.common.ItemRarity rarity = 4; } // 环境效果 message EnvironmentalEffect { string effect_id = 1; EffectType effect_type = 2; float intensity = 3; int32 duration = 4; // 持续时间(秒),0表示永久 map parameters = 5; } // 场景统计 message SceneStats { int32 total_visits = 1; int32 unique_visitors = 2; int32 current_online = 3; int32 peak_online = 4; int64 average_session_time = 5; int32 total_interactions = 6; int32 total_events_triggered = 7; map popular_areas = 8; // 热门区域访问量 int64 last_reset = 9; } // 场景类型枚举 enum SceneType { SCENE_TYPE_UNSPECIFIED = 0; SCENE_TYPE_TOWN = 1; // 城镇 SCENE_TYPE_DUNGEON = 2; // 地下城 SCENE_TYPE_WILDERNESS = 3; // 野外 SCENE_TYPE_BATTLE_ARENA = 4; // 战斗竞技场 SCENE_TYPE_INSTANCE = 5; // 副本 SCENE_TYPE_GUILD_HALL = 6; // 公会大厅 SCENE_TYPE_PRIVATE_ROOM = 7; // 私人房间 SCENE_TYPE_EVENT_AREA = 8; // 活动区域 SCENE_TYPE_TRAINING_GROUND = 9; // 训练场 } // 场景状态枚举 enum SceneStatus { SCENE_STATUS_UNSPECIFIED = 0; SCENE_STATUS_ACTIVE = 1; // 活跃 SCENE_STATUS_MAINTENANCE = 2; // 维护中 SCENE_STATUS_CLOSED = 3; // 已关闭 SCENE_STATUS_FULL = 4; // 已满员 SCENE_STATUS_LOADING = 5; // 加载中 } // 玩家状态枚举 enum PlayerState { PLAYER_STATE_UNSPECIFIED = 0; PLAYER_STATE_IDLE = 1; // 空闲 PLAYER_STATE_MOVING = 2; // 移动中 PLAYER_STATE_INTERACTING = 3; // 交互中 PLAYER_STATE_COMBAT = 4; // 战斗中 PLAYER_STATE_TRADING = 5; // 交易中 PLAYER_STATE_AFK = 6; // 暂离 PLAYER_STATE_INVISIBLE = 7; // 隐身 } // 对象类型枚举 enum ObjectType { OBJECT_TYPE_UNSPECIFIED = 0; OBJECT_TYPE_NPC = 1; // NPC OBJECT_TYPE_ITEM = 2; // 物品 OBJECT_TYPE_CHEST = 3; // 宝箱 OBJECT_TYPE_DOOR = 4; // 门 OBJECT_TYPE_PORTAL = 5; // 传送门 OBJECT_TYPE_SIGN = 6; // 标志牌 OBJECT_TYPE_DECORATION = 7; // 装饰物 OBJECT_TYPE_FURNITURE = 8; // 家具 OBJECT_TYPE_VEHICLE = 9; // 载具 OBJECT_TYPE_RESOURCE = 10; // 资源点 OBJECT_TYPE_TRAP = 11; // 陷阱 OBJECT_TYPE_SWITCH = 12; // 开关 } // 对象状态枚举 enum ObjectState { OBJECT_STATE_UNSPECIFIED = 0; OBJECT_STATE_NORMAL = 1; // 正常 OBJECT_STATE_ACTIVATED = 2; // 已激活 OBJECT_STATE_DISABLED = 3; // 已禁用 OBJECT_STATE_BROKEN = 4; // 已损坏 OBJECT_STATE_LOCKED = 5; // 已锁定 OBJECT_STATE_HIDDEN = 6; // 隐藏 } // 交互类型枚举 enum InteractionType { INTERACTION_TYPE_UNSPECIFIED = 0; INTERACTION_TYPE_TALK = 1; // 对话 INTERACTION_TYPE_USE = 2; // 使用 INTERACTION_TYPE_EXAMINE = 3; // 检查 INTERACTION_TYPE_OPEN = 4; // 打开 INTERACTION_TYPE_CLOSE = 5; // 关闭 INTERACTION_TYPE_PICKUP = 6; // 拾取 INTERACTION_TYPE_ACTIVATE = 7; // 激活 INTERACTION_TYPE_REPAIR = 8; // 修理 INTERACTION_TYPE_UPGRADE = 9; // 升级 INTERACTION_TYPE_DESTROY = 10; // 摧毁 } // 移动类型枚举 enum MovementType { MOVEMENT_TYPE_UNSPECIFIED = 0; MOVEMENT_TYPE_WALK = 1; // 走路 MOVEMENT_TYPE_RUN = 2; // 跑步 MOVEMENT_TYPE_TELEPORT = 3; // 传送 MOVEMENT_TYPE_FLY = 4; // 飞行 MOVEMENT_TYPE_SWIM = 5; // 游泳 MOVEMENT_TYPE_MOUNT = 6; // 坐骑 } // 天气类型枚举 enum WeatherType { WEATHER_TYPE_UNSPECIFIED = 0; WEATHER_TYPE_CLEAR = 1; // 晴天 WEATHER_TYPE_CLOUDY = 2; // 多云 WEATHER_TYPE_RAINY = 3; // 雨天 WEATHER_TYPE_STORMY = 4; // 暴风雨 WEATHER_TYPE_SNOWY = 5; // 雪天 WEATHER_TYPE_FOGGY = 6; // 雾天 WEATHER_TYPE_WINDY = 7; // 大风 } // 时间段枚举 enum TimeOfDay { TIME_OF_DAY_UNSPECIFIED = 0; TIME_OF_DAY_DAWN = 1; // 黎明 TIME_OF_DAY_MORNING = 2; // 上午 TIME_OF_DAY_NOON = 3; // 中午 TIME_OF_DAY_AFTERNOON = 4; // 下午 TIME_OF_DAY_EVENING = 5; // 傍晚 TIME_OF_DAY_NIGHT = 6; // 夜晚 TIME_OF_DAY_MIDNIGHT = 7; // 午夜 } // 事件类型枚举 enum EventType { EVENT_TYPE_UNSPECIFIED = 0; EVENT_TYPE_PLAYER_ENTER = 1; // 玩家进入 EVENT_TYPE_PLAYER_LEAVE = 2; // 玩家离开 EVENT_TYPE_OBJECT_INTERACTION = 3; // 对象交互 EVENT_TYPE_COMBAT_START = 4; // 战斗开始 EVENT_TYPE_COMBAT_END = 5; // 战斗结束 EVENT_TYPE_ITEM_PICKUP = 6; // 物品拾取 EVENT_TYPE_QUEST_TRIGGER = 7; // 任务触发 EVENT_TYPE_ACHIEVEMENT_UNLOCK = 8; // 成就解锁 EVENT_TYPE_WEATHER_CHANGE = 9; // 天气变化 EVENT_TYPE_TIME_CHANGE = 10; // 时间变化 } // 效果类型枚举 enum EffectType { EFFECT_TYPE_UNSPECIFIED = 0; EFFECT_TYPE_BUFF = 1; // 增益 EFFECT_TYPE_DEBUFF = 2; // 减益 EFFECT_TYPE_DAMAGE = 3; // 伤害 EFFECT_TYPE_HEAL = 4; // 治疗 EFFECT_TYPE_TELEPORT = 5; // 传送 EFFECT_TYPE_TRANSFORM = 6; // 变形 EFFECT_TYPE_INVISIBILITY = 7; // 隐身 EFFECT_TYPE_SPEED_BOOST = 8; // 速度提升 EFFECT_TYPE_SHIELD = 9; // 护盾 EFFECT_TYPE_STUN = 10; // 眩晕 } ================================================ FILE: proto/team.proto ================================================ syntax = "proto3"; package greatestworks.team; import "proto/common.proto"; option go_package = "greatestworks/internal/proto/team"; option csharp_namespace = "GreatestWorks.Team"; // 团队服务定义 service TeamService { // 创建团队 rpc CreateTeam(CreateTeamRequest) returns (CreateTeamResponse); // 加入团队 rpc JoinTeam(JoinTeamRequest) returns (JoinTeamResponse); // 离开团队 rpc LeaveTeam(LeaveTeamRequest) returns (LeaveTeamResponse); // 邀请玩家 rpc InvitePlayer(InvitePlayerRequest) returns (InvitePlayerResponse); // 踢出玩家 rpc KickPlayer(KickPlayerRequest) returns (KickPlayerResponse); // 转让队长 rpc TransferLeadership(TransferLeadershipRequest) returns (TransferLeadershipResponse); // 获取团队信息 rpc GetTeamInfo(GetTeamInfoRequest) returns (GetTeamInfoResponse); // 搜索团队 rpc SearchTeams(SearchTeamsRequest) returns (SearchTeamsResponse); // 设置团队设置 rpc UpdateTeamSettings(UpdateTeamSettingsRequest) returns (UpdateTeamSettingsResponse); // 处理邀请 rpc HandleInvitation(HandleInvitationRequest) returns (HandleInvitationResponse); // 申请加入团队 rpc ApplyToJoin(ApplyToJoinRequest) returns (ApplyToJoinResponse); // 处理申请 rpc HandleApplication(HandleApplicationRequest) returns (HandleApplicationResponse); } // 创建团队请求 message CreateTeamRequest { string creator_id = 1; string team_name = 2; string description = 3; TeamType team_type = 4; int32 max_members = 5; bool is_public = 6; string password = 7; map settings = 8; } // 创建团队响应 message CreateTeamResponse { greatestworks.common.CommonResponse common = 1; string team_id = 2; TeamInfo team_info = 3; } // 加入团队请求 message JoinTeamRequest { string player_id = 1; string team_id = 2; string password = 3; // 如果是私有团队 string invitation_code = 4; } // 加入团队响应 message JoinTeamResponse { greatestworks.common.CommonResponse common = 1; TeamInfo team_info = 2; TeamMember member_info = 3; } // 离开团队请求 message LeaveTeamRequest { string player_id = 1; string team_id = 2; } // 离开团队响应 message LeaveTeamResponse { greatestworks.common.CommonResponse common = 1; } // 邀请玩家请求 message InvitePlayerRequest { string inviter_id = 1; string team_id = 2; string target_player_id = 3; string message = 4; } // 邀请玩家响应 message InvitePlayerResponse { greatestworks.common.CommonResponse common = 1; string invitation_id = 2; } // 踢出玩家请求 message KickPlayerRequest { string kicker_id = 1; string team_id = 2; string target_player_id = 3; string reason = 4; } // 踢出玩家响应 message KickPlayerResponse { greatestworks.common.CommonResponse common = 1; } // 转让队长请求 message TransferLeadershipRequest { string current_leader_id = 1; string team_id = 2; string new_leader_id = 3; } // 转让队长响应 message TransferLeadershipResponse { greatestworks.common.CommonResponse common = 1; TeamMember new_leader = 2; } // 获取团队信息请求 message GetTeamInfoRequest { string team_id = 1; string player_id = 2; // 查询者ID,用于权限检查 } // 获取团队信息响应 message GetTeamInfoResponse { greatestworks.common.CommonResponse common = 1; TeamInfo team_info = 2; repeated TeamMember members = 3; repeated TeamInvitation pending_invitations = 4; repeated TeamApplication pending_applications = 5; } // 搜索团队请求 message SearchTeamsRequest { string query = 1; TeamType team_type = 2; bool only_public = 3; int32 min_level = 4; int32 max_level = 5; int32 limit = 6; int32 offset = 7; } // 搜索团队响应 message SearchTeamsResponse { greatestworks.common.CommonResponse common = 1; repeated TeamInfo teams = 2; greatestworks.common.PaginationInfo pagination = 3; } // 更新团队设置请求 message UpdateTeamSettingsRequest { string player_id = 1; string team_id = 2; TeamSettings settings = 3; } // 更新团队设置响应 message UpdateTeamSettingsResponse { greatestworks.common.CommonResponse common = 1; TeamSettings new_settings = 2; } // 处理邀请请求 message HandleInvitationRequest { string player_id = 1; string invitation_id = 2; bool accept = 3; } // 处理邀请响应 message HandleInvitationResponse { greatestworks.common.CommonResponse common = 1; TeamInfo team_info = 2; // 如果接受邀请 } // 申请加入团队请求 message ApplyToJoinRequest { string player_id = 1; string team_id = 2; string message = 3; } // 申请加入团队响应 message ApplyToJoinResponse { greatestworks.common.CommonResponse common = 1; string application_id = 2; } // 处理申请请求 message HandleApplicationRequest { string handler_id = 1; string application_id = 2; bool approve = 3; string reason = 4; } // 处理申请响应 message HandleApplicationResponse { greatestworks.common.CommonResponse common = 1; TeamMember new_member = 2; // 如果批准申请 } // 团队信息 message TeamInfo { string team_id = 1; string name = 2; string description = 3; TeamType team_type = 4; TeamStatus status = 5; string leader_id = 6; string leader_name = 7; int32 member_count = 8; int32 max_members = 9; int32 average_level = 10; bool is_public = 11; bool is_recruiting = 12; int64 created_at = 13; int64 last_activity = 14; TeamSettings settings = 15; TeamStats stats = 16; } // 团队成员 message TeamMember { string player_id = 1; string player_name = 2; int32 level = 3; MemberRole role = 4; MemberStatus status = 5; int64 joined_at = 6; int64 last_online = 7; int32 contribution_points = 8; greatestworks.common.Position position = 9; } // 团队邀请 message TeamInvitation { string invitation_id = 1; string team_id = 2; string team_name = 3; string inviter_id = 4; string inviter_name = 5; string invitee_id = 6; string invitee_name = 7; string message = 8; int64 created_at = 9; int64 expires_at = 10; InvitationStatus status = 11; } // 团队申请 message TeamApplication { string application_id = 1; string team_id = 2; string applicant_id = 3; string applicant_name = 4; int32 applicant_level = 5; string message = 6; int64 created_at = 7; ApplicationStatus status = 8; } // 团队设置 message TeamSettings { bool auto_accept_applications = 1; int32 min_level_requirement = 2; int32 max_level_requirement = 3; bool allow_member_invite = 4; bool require_approval = 5; string welcome_message = 6; repeated string tags = 7; map custom_settings = 8; } // 团队统计 message TeamStats { int32 total_battles = 1; int32 wins = 2; int32 losses = 3; float win_rate = 4; int32 total_experience = 5; int32 achievements_unlocked = 6; int64 total_playtime = 7; } // 团队类型枚举 enum TeamType { TEAM_TYPE_UNSPECIFIED = 0; TEAM_TYPE_CASUAL = 1; // 休闲团队 TEAM_TYPE_COMPETITIVE = 2; // 竞技团队 TEAM_TYPE_PVE = 3; // PVE团队 TEAM_TYPE_PVP = 4; // PVP团队 TEAM_TYPE_GUILD = 5; // 公会 TEAM_TYPE_PARTY = 6; // 小队 TEAM_TYPE_RAID = 7; // 团本队伍 TEAM_TYPE_TOURNAMENT = 8; // 锦标赛队伍 } // 团队状态枚举 enum TeamStatus { TEAM_STATUS_UNSPECIFIED = 0; TEAM_STATUS_ACTIVE = 1; // 活跃 TEAM_STATUS_INACTIVE = 2; // 不活跃 TEAM_STATUS_DISBANDED = 3; // 已解散 TEAM_STATUS_SUSPENDED = 4; // 已暂停 TEAM_STATUS_RECRUITING = 5; // 招募中 TEAM_STATUS_FULL = 6; // 已满员 } // 成员角色枚举 enum MemberRole { MEMBER_ROLE_UNSPECIFIED = 0; MEMBER_ROLE_LEADER = 1; // 队长 MEMBER_ROLE_OFFICER = 2; // 副队长 MEMBER_ROLE_VETERAN = 3; // 老队员 MEMBER_ROLE_MEMBER = 4; // 普通成员 MEMBER_ROLE_RECRUIT = 5; // 新成员 } // 成员状态枚举 enum MemberStatus { MEMBER_STATUS_UNSPECIFIED = 0; MEMBER_STATUS_ONLINE = 1; // 在线 MEMBER_STATUS_OFFLINE = 2; // 离线 MEMBER_STATUS_IN_BATTLE = 3; // 战斗中 MEMBER_STATUS_AFK = 4; // 暂离 MEMBER_STATUS_BUSY = 5; // 忙碌 } // 邀请状态枚举 enum InvitationStatus { INVITATION_STATUS_UNSPECIFIED = 0; INVITATION_STATUS_PENDING = 1; // 待处理 INVITATION_STATUS_ACCEPTED = 2; // 已接受 INVITATION_STATUS_REJECTED = 3; // 已拒绝 INVITATION_STATUS_EXPIRED = 4; // 已过期 INVITATION_STATUS_CANCELLED = 5; // 已取消 } // 申请状态枚举 enum ApplicationStatus { APPLICATION_STATUS_UNSPECIFIED = 0; APPLICATION_STATUS_PENDING = 1; // 待审核 APPLICATION_STATUS_APPROVED = 2; // 已批准 APPLICATION_STATUS_REJECTED = 3; // 已拒绝 APPLICATION_STATUS_WITHDRAWN = 4; // 已撤回 } ================================================ FILE: protoc/include/google/protobuf/any.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option go_package = "google.golang.org/protobuf/types/known/anypb"; option java_package = "com.google.protobuf"; option java_outer_classname = "AnyProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // `Any` contains an arbitrary serialized protocol buffer message along with a // URL that describes the type of the serialized message. // // Protobuf library provides support to pack/unpack Any values in the form // of utility functions or additional generated methods of the Any type. // // Example 1: Pack and unpack a message in C++. // // Foo foo = ...; // Any any; // any.PackFrom(foo); // ... // if (any.UnpackTo(&foo)) { // ... // } // // Example 2: Pack and unpack a message in Java. // // Foo foo = ...; // Any any = Any.pack(foo); // ... // if (any.is(Foo.class)) { // foo = any.unpack(Foo.class); // } // // or ... // if (any.isSameTypeAs(Foo.getDefaultInstance())) { // foo = any.unpack(Foo.getDefaultInstance()); // } // // Example 3: Pack and unpack a message in Python. // // foo = Foo(...) // any = Any() // any.Pack(foo) // ... // if any.Is(Foo.DESCRIPTOR): // any.Unpack(foo) // ... // // Example 4: Pack and unpack a message in Go // // foo := &pb.Foo{...} // any, err := anypb.New(foo) // if err != nil { // ... // } // ... // foo := &pb.Foo{} // if err := any.UnmarshalTo(foo); err != nil { // ... // } // // The pack methods provided by protobuf library will by default use // 'type.googleapis.com/full.type.name' as the type URL and the unpack // methods only use the fully qualified type name after the last '/' // in the type URL, for example "foo.bar.com/x/y.z" will yield type // name "y.z". // // JSON // ==== // The JSON representation of an `Any` value uses the regular // representation of the deserialized, embedded message, with an // additional field `@type` which contains the type URL. Example: // // package google.profile; // message Person { // string first_name = 1; // string last_name = 2; // } // // { // "@type": "type.googleapis.com/google.profile.Person", // "firstName": , // "lastName": // } // // If the embedded message type is well-known and has a custom JSON // representation, that representation will be embedded adding a field // `value` which holds the custom JSON in addition to the `@type` // field. Example (for message [google.protobuf.Duration][]): // // { // "@type": "type.googleapis.com/google.protobuf.Duration", // "value": "1.212s" // } // message Any { // A URL/resource name that uniquely identifies the type of the serialized // protocol buffer message. This string must contain at least // one "/" character. The last segment of the URL's path must represent // the fully qualified name of the type (as in // `path/google.protobuf.Duration`). The name should be in a canonical form // (e.g., leading "." is not accepted). // // In practice, teams usually precompile into the binary all types that they // expect it to use in the context of Any. However, for URLs which use the // scheme `http`, `https`, or no scheme, one can optionally set up a type // server that maps type URLs to message definitions as follows: // // * If no scheme is provided, `https` is assumed. // * An HTTP GET on the URL must yield a [google.protobuf.Type][] // value in binary format, or produce an error. // * Applications are allowed to cache lookup results based on the // URL, or have them precompiled into a binary to avoid any // lookup. Therefore, binary compatibility needs to be preserved // on changes to types. (Use versioned type names to manage // breaking changes.) // // Note: this functionality is not currently available in the official // protobuf release, and it is not used for type URLs beginning with // type.googleapis.com. As of May 2023, there are no widely used type server // implementations and no plans to implement one. // // Schemes other than `http`, `https` (or the empty scheme) might be // used with implementation specific semantics. // string type_url = 1; // Must be a valid serialized protocol buffer of the above specified type. bytes value = 2; } ================================================ FILE: protoc/include/google/protobuf/api.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; import "google/protobuf/source_context.proto"; import "google/protobuf/type.proto"; option java_package = "com.google.protobuf"; option java_outer_classname = "ApiProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/apipb"; // Api is a light-weight descriptor for an API Interface. // // Interfaces are also described as "protocol buffer services" in some contexts, // such as by the "service" keyword in a .proto file, but they are different // from API Services, which represent a concrete implementation of an interface // as opposed to simply a description of methods and bindings. They are also // sometimes simply referred to as "APIs" in other contexts, such as the name of // this message itself. See https://cloud.google.com/apis/design/glossary for // detailed terminology. message Api { // The fully qualified name of this interface, including package name // followed by the interface's simple name. string name = 1; // The methods of this interface, in unspecified order. repeated Method methods = 2; // Any metadata attached to the interface. repeated Option options = 3; // A version string for this interface. If specified, must have the form // `major-version.minor-version`, as in `1.10`. If the minor version is // omitted, it defaults to zero. If the entire version field is empty, the // major version is derived from the package name, as outlined below. If the // field is not empty, the version in the package name will be verified to be // consistent with what is provided here. // // The versioning schema uses [semantic // versioning](http://semver.org) where the major version number // indicates a breaking change and the minor version an additive, // non-breaking change. Both version numbers are signals to users // what to expect from different versions, and should be carefully // chosen based on the product plan. // // The major version is also reflected in the package name of the // interface, which must end in `v`, as in // `google.feature.v1`. For major versions 0 and 1, the suffix can // be omitted. Zero major versions must only be used for // experimental, non-GA interfaces. // string version = 4; // Source context for the protocol buffer service represented by this // message. SourceContext source_context = 5; // Included interfaces. See [Mixin][]. repeated Mixin mixins = 6; // The source syntax of the service. Syntax syntax = 7; } // Method represents a method of an API interface. message Method { // The simple name of this method. string name = 1; // A URL of the input message type. string request_type_url = 2; // If true, the request is streamed. bool request_streaming = 3; // The URL of the output message type. string response_type_url = 4; // If true, the response is streamed. bool response_streaming = 5; // Any metadata attached to the method. repeated Option options = 6; // The source syntax of this method. Syntax syntax = 7; } // Declares an API Interface to be included in this interface. The including // interface must redeclare all the methods from the included interface, but // documentation and options are inherited as follows: // // - If after comment and whitespace stripping, the documentation // string of the redeclared method is empty, it will be inherited // from the original method. // // - Each annotation belonging to the service config (http, // visibility) which is not set in the redeclared method will be // inherited. // // - If an http annotation is inherited, the path pattern will be // modified as follows. Any version prefix will be replaced by the // version of the including interface plus the [root][] path if // specified. // // Example of a simple mixin: // // package google.acl.v1; // service AccessControl { // // Get the underlying ACL object. // rpc GetAcl(GetAclRequest) returns (Acl) { // option (google.api.http).get = "/v1/{resource=**}:getAcl"; // } // } // // package google.storage.v2; // service Storage { // rpc GetAcl(GetAclRequest) returns (Acl); // // // Get a data record. // rpc GetData(GetDataRequest) returns (Data) { // option (google.api.http).get = "/v2/{resource=**}"; // } // } // // Example of a mixin configuration: // // apis: // - name: google.storage.v2.Storage // mixins: // - name: google.acl.v1.AccessControl // // The mixin construct implies that all methods in `AccessControl` are // also declared with same name and request/response types in // `Storage`. A documentation generator or annotation processor will // see the effective `Storage.GetAcl` method after inherting // documentation and annotations as follows: // // service Storage { // // Get the underlying ACL object. // rpc GetAcl(GetAclRequest) returns (Acl) { // option (google.api.http).get = "/v2/{resource=**}:getAcl"; // } // ... // } // // Note how the version in the path pattern changed from `v1` to `v2`. // // If the `root` field in the mixin is specified, it should be a // relative path under which inherited HTTP paths are placed. Example: // // apis: // - name: google.storage.v2.Storage // mixins: // - name: google.acl.v1.AccessControl // root: acls // // This implies the following inherited HTTP annotation: // // service Storage { // // Get the underlying ACL object. // rpc GetAcl(GetAclRequest) returns (Acl) { // option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; // } // ... // } message Mixin { // The fully qualified name of the interface which is included. string name = 1; // If non-empty specifies a path under which inherited HTTP paths // are rooted. string root = 2; } ================================================ FILE: protoc/include/google/protobuf/compiler/plugin.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd // Author: kenton@google.com (Kenton Varda) // // protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is // just a program that reads a CodeGeneratorRequest from stdin and writes a // CodeGeneratorResponse to stdout. // // Plugins written using C++ can use google/protobuf/compiler/plugin.h instead // of dealing with the raw protocol defined here. // // A plugin executable needs only to be placed somewhere in the path. The // plugin should be named "protoc-gen-$NAME", and will then be used when the // flag "--${NAME}_out" is passed to protoc. syntax = "proto2"; package google.protobuf.compiler; option java_package = "com.google.protobuf.compiler"; option java_outer_classname = "PluginProtos"; option csharp_namespace = "Google.Protobuf.Compiler"; option go_package = "google.golang.org/protobuf/types/pluginpb"; import "google/protobuf/descriptor.proto"; // The version number of protocol compiler. message Version { optional int32 major = 1; optional int32 minor = 2; optional int32 patch = 3; // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should // be empty for mainline stable releases. optional string suffix = 4; } // An encoded CodeGeneratorRequest is written to the plugin's stdin. message CodeGeneratorRequest { // The .proto files that were explicitly listed on the command-line. The // code generator should generate code only for these files. Each file's // descriptor will be included in proto_file, below. repeated string file_to_generate = 1; // The generator parameter passed on the command-line. optional string parameter = 2; // FileDescriptorProtos for all files in files_to_generate and everything // they import. The files will appear in topological order, so each file // appears before any file that imports it. // // Note: the files listed in files_to_generate will include runtime-retention // options only, but all other files will include source-retention options. // The source_file_descriptors field below is available in case you need // source-retention options for files_to_generate. // // protoc guarantees that all proto_files will be written after // the fields above, even though this is not technically guaranteed by the // protobuf wire format. This theoretically could allow a plugin to stream // in the FileDescriptorProtos and handle them one by one rather than read // the entire set into memory at once. However, as of this writing, this // is not similarly optimized on protoc's end -- it will store all fields in // memory at once before sending them to the plugin. // // Type names of fields and extensions in the FileDescriptorProto are always // fully qualified. repeated FileDescriptorProto proto_file = 15; // File descriptors with all options, including source-retention options. // These descriptors are only provided for the files listed in // files_to_generate. repeated FileDescriptorProto source_file_descriptors = 17; // The version number of protocol compiler. optional Version compiler_version = 3; } // The plugin writes an encoded CodeGeneratorResponse to stdout. message CodeGeneratorResponse { // Error message. If non-empty, code generation failed. The plugin process // should exit with status code zero even if it reports an error in this way. // // This should be used to indicate errors in .proto files which prevent the // code generator from generating correct code. Errors which indicate a // problem in protoc itself -- such as the input CodeGeneratorRequest being // unparseable -- should be reported by writing a message to stderr and // exiting with a non-zero status code. optional string error = 1; // A bitmask of supported features that the code generator supports. // This is a bitwise "or" of values from the Feature enum. optional uint64 supported_features = 2; // Sync with code_generator.h. enum Feature { FEATURE_NONE = 0; FEATURE_PROTO3_OPTIONAL = 1; FEATURE_SUPPORTS_EDITIONS = 2; } // Represents a single generated file. message File { // The file name, relative to the output directory. The name must not // contain "." or ".." components and must be relative, not be absolute (so, // the file cannot lie outside the output directory). "/" must be used as // the path separator, not "\". // // If the name is omitted, the content will be appended to the previous // file. This allows the generator to break large files into small chunks, // and allows the generated text to be streamed back to protoc so that large // files need not reside completely in memory at one time. Note that as of // this writing protoc does not optimize for this -- it will read the entire // CodeGeneratorResponse before writing files to disk. optional string name = 1; // If non-empty, indicates that the named file should already exist, and the // content here is to be inserted into that file at a defined insertion // point. This feature allows a code generator to extend the output // produced by another code generator. The original generator may provide // insertion points by placing special annotations in the file that look // like: // @@protoc_insertion_point(NAME) // The annotation can have arbitrary text before and after it on the line, // which allows it to be placed in a comment. NAME should be replaced with // an identifier naming the point -- this is what other generators will use // as the insertion_point. Code inserted at this point will be placed // immediately above the line containing the insertion point (thus multiple // insertions to the same point will come out in the order they were added). // The double-@ is intended to make it unlikely that the generated code // could contain things that look like insertion points by accident. // // For example, the C++ code generator places the following line in the // .pb.h files that it generates: // // @@protoc_insertion_point(namespace_scope) // This line appears within the scope of the file's package namespace, but // outside of any particular class. Another plugin can then specify the // insertion_point "namespace_scope" to generate additional classes or // other declarations that should be placed in this scope. // // Note that if the line containing the insertion point begins with // whitespace, the same whitespace will be added to every line of the // inserted text. This is useful for languages like Python, where // indentation matters. In these languages, the insertion point comment // should be indented the same amount as any inserted code will need to be // in order to work correctly in that context. // // The code generator that generates the initial file and the one which // inserts into it must both run as part of a single invocation of protoc. // Code generators are executed in the order in which they appear on the // command line. // // If |insertion_point| is present, |name| must also be present. optional string insertion_point = 2; // The file contents. optional string content = 15; // Information describing the file content being inserted. If an insertion // point is used, this information will be appropriately offset and inserted // into the code generation metadata for the generated files. optional GeneratedCodeInfo generated_code_info = 16; } repeated File file = 15; } ================================================ FILE: protoc/include/google/protobuf/descriptor.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Author: kenton@google.com (Kenton Varda) // Based on original Protocol Buffers design by // Sanjay Ghemawat, Jeff Dean, and others. // // The messages in this file describe the definitions found in .proto files. // A valid .proto file can be translated directly to a FileDescriptorProto // without any other information (e.g. without reading its imports). syntax = "proto2"; package google.protobuf; option go_package = "google.golang.org/protobuf/types/descriptorpb"; option java_package = "com.google.protobuf"; option java_outer_classname = "DescriptorProtos"; option csharp_namespace = "Google.Protobuf.Reflection"; option objc_class_prefix = "GPB"; option cc_enable_arenas = true; // descriptor.proto must be optimized for speed because reflection-based // algorithms don't work during bootstrapping. option optimize_for = SPEED; // The protocol compiler can output a FileDescriptorSet containing the .proto // files it parses. message FileDescriptorSet { repeated FileDescriptorProto file = 1; } // The full set of known editions. enum Edition { // A placeholder for an unknown edition value. EDITION_UNKNOWN = 0; // Legacy syntax "editions". These pre-date editions, but behave much like // distinct editions. These can't be used to specify the edition of proto // files, but feature definitions must supply proto2/proto3 defaults for // backwards compatibility. EDITION_PROTO2 = 998; EDITION_PROTO3 = 999; // Editions that have been released. The specific values are arbitrary and // should not be depended on, but they will always be time-ordered for easy // comparison. EDITION_2023 = 1000; // Placeholder editions for testing feature resolution. These should not be // used or relyed on outside of tests. EDITION_1_TEST_ONLY = 1; EDITION_2_TEST_ONLY = 2; EDITION_99997_TEST_ONLY = 99997; EDITION_99998_TEST_ONLY = 99998; EDITION_99999_TEST_ONLY = 99999; } // Describes a complete .proto file. message FileDescriptorProto { optional string name = 1; // file name, relative to root of source tree optional string package = 2; // e.g. "foo", "foo.bar", etc. // Names of files imported by this file. repeated string dependency = 3; // Indexes of the public imported files in the dependency list above. repeated int32 public_dependency = 10; // Indexes of the weak imported files in the dependency list. // For Google-internal migration only. Do not use. repeated int32 weak_dependency = 11; // All top-level definitions in this file. repeated DescriptorProto message_type = 4; repeated EnumDescriptorProto enum_type = 5; repeated ServiceDescriptorProto service = 6; repeated FieldDescriptorProto extension = 7; optional FileOptions options = 8; // This field contains optional information about the original source code. // You may safely remove this entire field without harming runtime // functionality of the descriptors -- the information is needed only by // development tools. optional SourceCodeInfo source_code_info = 9; // The syntax of the proto file. // The supported values are "proto2", "proto3", and "editions". // // If `edition` is present, this value must be "editions". optional string syntax = 12; // The edition of the proto file. optional Edition edition = 14; } // Describes a message type. message DescriptorProto { optional string name = 1; repeated FieldDescriptorProto field = 2; repeated FieldDescriptorProto extension = 6; repeated DescriptorProto nested_type = 3; repeated EnumDescriptorProto enum_type = 4; message ExtensionRange { optional int32 start = 1; // Inclusive. optional int32 end = 2; // Exclusive. optional ExtensionRangeOptions options = 3; } repeated ExtensionRange extension_range = 5; repeated OneofDescriptorProto oneof_decl = 8; optional MessageOptions options = 7; // Range of reserved tag numbers. Reserved tag numbers may not be used by // fields or extension ranges in the same message. Reserved ranges may // not overlap. message ReservedRange { optional int32 start = 1; // Inclusive. optional int32 end = 2; // Exclusive. } repeated ReservedRange reserved_range = 9; // Reserved field names, which may not be used by fields in the same message. // A given name may only be reserved once. repeated string reserved_name = 10; } message ExtensionRangeOptions { // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; message Declaration { // The extension number declared within the extension range. optional int32 number = 1; // The fully-qualified name of the extension field. There must be a leading // dot in front of the full name. optional string full_name = 2; // The fully-qualified type name of the extension field. Unlike // Metadata.type, Declaration.type must have a leading dot for messages // and enums. optional string type = 3; // If true, indicates that the number is reserved in the extension range, // and any extension field with the number will fail to compile. Set this // when a declared extension field is deleted. optional bool reserved = 5; // If true, indicates that the extension must be defined as repeated. // Otherwise the extension must be defined as optional. optional bool repeated = 6; reserved 4; // removed is_repeated } // For external users: DO NOT USE. We are in the process of open sourcing // extension declaration and executing internal cleanups before it can be // used externally. repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; // Any features defined in the specific edition. optional FeatureSet features = 50; // The verification state of the extension range. enum VerificationState { // All the extensions of the range must be declared. DECLARATION = 0; UNVERIFIED = 1; } // The verification state of the range. // TODO: flip the default to DECLARATION once all empty ranges // are marked as UNVERIFIED. optional VerificationState verification = 3 [default = UNVERIFIED]; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } // Describes a field within a message. message FieldDescriptorProto { enum Type { // 0 is reserved for errors. // Order is weird for historical reasons. TYPE_DOUBLE = 1; TYPE_FLOAT = 2; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if // negative values are likely. TYPE_INT64 = 3; TYPE_UINT64 = 4; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if // negative values are likely. TYPE_INT32 = 5; TYPE_FIXED64 = 6; TYPE_FIXED32 = 7; TYPE_BOOL = 8; TYPE_STRING = 9; // Tag-delimited aggregate. // Group type is deprecated and not supported after google.protobuf. However, Proto3 // implementations should still be able to parse the group wire format and // treat group fields as unknown fields. In Editions, the group wire format // can be enabled via the `message_encoding` feature. TYPE_GROUP = 10; TYPE_MESSAGE = 11; // Length-delimited aggregate. // New in version 2. TYPE_BYTES = 12; TYPE_UINT32 = 13; TYPE_ENUM = 14; TYPE_SFIXED32 = 15; TYPE_SFIXED64 = 16; TYPE_SINT32 = 17; // Uses ZigZag encoding. TYPE_SINT64 = 18; // Uses ZigZag encoding. } enum Label { // 0 is reserved for errors LABEL_OPTIONAL = 1; LABEL_REPEATED = 3; // The required label is only allowed in google.protobuf. In proto3 and Editions // it's explicitly prohibited. In Editions, the `field_presence` feature // can be used to get this behavior. LABEL_REQUIRED = 2; } optional string name = 1; optional int32 number = 3; optional Label label = 4; // If type_name is set, this need not be set. If both this and type_name // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. optional Type type = 5; // For message and enum types, this is the name of the type. If the name // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping // rules are used to find the type (i.e. first the nested types within this // message are searched, then within the parent, on up to the root // namespace). optional string type_name = 6; // For extensions, this is the name of the type being extended. It is // resolved in the same manner as type_name. optional string extendee = 2; // For numeric types, contains the original text representation of the value. // For booleans, "true" or "false". // For strings, contains the default text contents (not escaped in any way). // For bytes, contains the C escaped value. All bytes >= 128 are escaped. optional string default_value = 7; // If set, gives the index of a oneof in the containing type's oneof_decl // list. This field is a member of that oneof. optional int32 oneof_index = 9; // JSON name of this field. The value is set by protocol compiler. If the // user has set a "json_name" option on this field, that option's value // will be used. Otherwise, it's deduced from the field's name by converting // it to camelCase. optional string json_name = 10; optional FieldOptions options = 8; // If true, this is a proto3 "optional". When a proto3 field is optional, it // tracks presence regardless of field type. // // When proto3_optional is true, this field must be belong to a oneof to // signal to old proto3 clients that presence is tracked for this field. This // oneof is known as a "synthetic" oneof, and this field must be its sole // member (each proto3 optional field gets its own synthetic oneof). Synthetic // oneofs exist in the descriptor only, and do not generate any API. Synthetic // oneofs must be ordered after all "real" oneofs. // // For message fields, proto3_optional doesn't create any semantic change, // since non-repeated message fields always track presence. However it still // indicates the semantic detail of whether the user wrote "optional" or not. // This can be useful for round-tripping the .proto file. For consistency we // give message fields a synthetic oneof also, even though it is not required // to track presence. This is especially important because the parser can't // tell if a field is a message or an enum, so it must always create a // synthetic oneof. // // Proto2 optional fields do not set this flag, because they already indicate // optional with `LABEL_OPTIONAL`. optional bool proto3_optional = 17; } // Describes a oneof. message OneofDescriptorProto { optional string name = 1; optional OneofOptions options = 2; } // Describes an enum type. message EnumDescriptorProto { optional string name = 1; repeated EnumValueDescriptorProto value = 2; optional EnumOptions options = 3; // Range of reserved numeric values. Reserved values may not be used by // entries in the same enum. Reserved ranges may not overlap. // // Note that this is distinct from DescriptorProto.ReservedRange in that it // is inclusive such that it can appropriately represent the entire int32 // domain. message EnumReservedRange { optional int32 start = 1; // Inclusive. optional int32 end = 2; // Inclusive. } // Range of reserved numeric values. Reserved numeric values may not be used // by enum values in the same enum declaration. Reserved ranges may not // overlap. repeated EnumReservedRange reserved_range = 4; // Reserved enum value names, which may not be reused. A given name may only // be reserved once. repeated string reserved_name = 5; } // Describes a value within an enum. message EnumValueDescriptorProto { optional string name = 1; optional int32 number = 2; optional EnumValueOptions options = 3; } // Describes a service. message ServiceDescriptorProto { optional string name = 1; repeated MethodDescriptorProto method = 2; optional ServiceOptions options = 3; } // Describes a method of a service. message MethodDescriptorProto { optional string name = 1; // Input and output type names. These are resolved in the same way as // FieldDescriptorProto.type_name, but must refer to a message type. optional string input_type = 2; optional string output_type = 3; optional MethodOptions options = 4; // Identifies if client streams multiple client messages optional bool client_streaming = 5 [default = false]; // Identifies if server streams multiple server messages optional bool server_streaming = 6 [default = false]; } // =================================================================== // Options // Each of the definitions above may have "options" attached. These are // just annotations which may cause code to be generated slightly differently // or may contain hints for code that manipulates protocol messages. // // Clients may define custom options as extensions of the *Options messages. // These extensions may not yet be known at parsing time, so the parser cannot // store the values in them. Instead it stores them in a field in the *Options // message called uninterpreted_option. This field must have the same name // across all *Options messages. We then use this field to populate the // extensions when we build a descriptor, at which point all protos have been // parsed and so all extensions are known. // // Extension numbers for custom options may be chosen as follows: // * For options which will only be used within a single application or // organization, or for experimental options, use field numbers 50000 // through 99999. It is up to you to ensure that you do not use the // same number for multiple options. // * For options which will be published and used publicly by multiple // independent entities, e-mail protobuf-global-extension-registry@google.com // to reserve extension numbers. Simply provide your project name (e.g. // Objective-C plugin) and your project website (if available) -- there's no // need to explain how you intend to use them. Usually you only need one // extension number. You can declare multiple options with only one extension // number by putting them in a sub-message. See the Custom Options section of // the docs for examples: // https://developers.google.com/protocol-buffers/docs/proto#options // If this turns out to be popular, a web service will be set up // to automatically assign option numbers. message FileOptions { // Sets the Java package where classes generated from this .proto will be // placed. By default, the proto package is used, but this is often // inappropriate because proto packages do not normally start with backwards // domain names. optional string java_package = 1; // Controls the name of the wrapper Java class generated for the .proto file. // That class will always contain the .proto file's getDescriptor() method as // well as any top-level extensions defined in the .proto file. // If java_multiple_files is disabled, then all the other classes from the // .proto file will be nested inside the single wrapper outer class. optional string java_outer_classname = 8; // If enabled, then the Java code generator will generate a separate .java // file for each top-level message, enum, and service defined in the .proto // file. Thus, these types will *not* be nested inside the wrapper class // named by java_outer_classname. However, the wrapper class will still be // generated to contain the file's getDescriptor() method as well as any // top-level extensions defined in the file. optional bool java_multiple_files = 10 [default = false]; // This option does nothing. optional bool java_generate_equals_and_hash = 20 [deprecated=true]; // If set true, then the Java2 code generator will generate code that // throws an exception whenever an attempt is made to assign a non-UTF-8 // byte sequence to a string field. // Message reflection will do the same. // However, an extension field still accepts non-UTF-8 byte sequences. // This option has no effect on when used with the lite runtime. optional bool java_string_check_utf8 = 27 [default = false]; // Generated classes can be optimized for speed or code size. enum OptimizeMode { SPEED = 1; // Generate complete code for parsing, serialization, // etc. CODE_SIZE = 2; // Use ReflectionOps to implement these methods. LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. } optional OptimizeMode optimize_for = 9 [default = SPEED]; // Sets the Go package where structs generated from this .proto will be // placed. If omitted, the Go package will be derived from the following: // - The basename of the package import path, if provided. // - Otherwise, the package statement in the .proto file, if present. // - Otherwise, the basename of the .proto file, without extension. optional string go_package = 11; // Should generic services be generated in each language? "Generic" services // are not specific to any particular RPC system. They are generated by the // main code generators in each language (without additional plugins). // Generic services were the only kind of service generation supported by // early versions of google.protobuf. // // Generic services are now considered deprecated in favor of using plugins // that generate code specific to your particular RPC system. Therefore, // these default to false. Old code which depends on generic services should // explicitly set them to true. optional bool cc_generic_services = 16 [default = false]; optional bool java_generic_services = 17 [default = false]; optional bool py_generic_services = 18 [default = false]; optional bool php_generic_services = 42 [default = false]; // Is this file deprecated? // Depending on the target platform, this can emit Deprecated annotations // for everything in the file, or it will be completely ignored; in the very // least, this is a formalization for deprecating files. optional bool deprecated = 23 [default = false]; // Enables the use of arenas for the proto messages in this file. This applies // only to generated classes for C++. optional bool cc_enable_arenas = 31 [default = true]; // Sets the objective c class prefix which is prepended to all objective c // generated classes from this .proto. There is no default. optional string objc_class_prefix = 36; // Namespace for generated classes; defaults to the package. optional string csharp_namespace = 37; // By default Swift generators will take the proto package and CamelCase it // replacing '.' with underscore and use that to prefix the types/symbols // defined. When this options is provided, they will use this value instead // to prefix the types/symbols defined. optional string swift_prefix = 39; // Sets the php class prefix which is prepended to all php generated classes // from this .proto. Default is empty. optional string php_class_prefix = 40; // Use this option to change the namespace of php generated classes. Default // is empty. When this option is empty, the package name will be used for // determining the namespace. optional string php_namespace = 41; // Use this option to change the namespace of php generated metadata classes. // Default is empty. When this option is empty, the proto file name will be // used for determining the namespace. optional string php_metadata_namespace = 44; // Use this option to change the package of ruby generated classes. Default // is empty. When this option is not set, the package name will be used for // determining the ruby package. optional string ruby_package = 45; // Any features defined in the specific edition. optional FeatureSet features = 50; // The parser stores options it doesn't recognize here. // See the documentation for the "Options" section above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. // See the documentation for the "Options" section above. extensions 1000 to max; reserved 38; } message MessageOptions { // Set true to use the old proto1 MessageSet wire format for extensions. // This is provided for backwards-compatibility with the MessageSet wire // format. You should not use this for any other reason: It's less // efficient, has fewer features, and is more complicated. // // The message must be defined exactly as follows: // message Foo { // option message_set_wire_format = true; // extensions 4 to max; // } // Note that the message cannot have any defined fields; MessageSets only // have extensions. // // All extensions of your type must be singular messages; e.g. they cannot // be int32s, enums, or repeated messages. // // Because this is an option, the above two restrictions are not enforced by // the protocol compiler. optional bool message_set_wire_format = 1 [default = false]; // Disables the generation of the standard "descriptor()" accessor, which can // conflict with a field of the same name. This is meant to make migration // from proto1 easier; new code should avoid fields named "descriptor". optional bool no_standard_descriptor_accessor = 2 [default = false]; // Is this message deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the message, or it will be completely ignored; in the very least, // this is a formalization for deprecating messages. optional bool deprecated = 3 [default = false]; reserved 4, 5, 6; // NOTE: Do not set the option in .proto files. Always use the maps syntax // instead. The option should only be implicitly set by the proto compiler // parser. // // Whether the message is an automatically generated map entry type for the // maps field. // // For maps fields: // map map_field = 1; // The parsed descriptor looks like: // message MapFieldEntry { // option map_entry = true; // optional KeyType key = 1; // optional ValueType value = 2; // } // repeated MapFieldEntry map_field = 1; // // Implementations may choose not to generate the map_entry=true message, but // use a native map in the target language to hold the keys and values. // The reflection APIs in such implementations still need to work as // if the field is a repeated message field. optional bool map_entry = 7; reserved 8; // javalite_serializable reserved 9; // javanano_as_lite // Enable the legacy handling of JSON field name conflicts. This lowercases // and strips underscored from the fields before comparison in proto3 only. // The new behavior takes `json_name` into account and applies to proto2 as // well. // // This should only be used as a temporary measure against broken builds due // to the change in behavior for JSON field name conflicts. // // TODO This is legacy behavior we plan to remove once downstream // teams have had time to migrate. optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; // Any features defined in the specific edition. optional FeatureSet features = 12; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message FieldOptions { // The ctype option instructs the C++ code generator to use a different // representation of the field than it normally would. See the specific // options below. This option is only implemented to support use of // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of // type "bytes" in the open source release -- sorry, we'll try to include // other types in a future version! optional CType ctype = 1 [default = STRING]; enum CType { // Default mode. STRING = 0; // The option [ctype=CORD] may be applied to a non-repeated field of type // "bytes". It indicates that in C++, the data should be stored in a Cord // instead of a string. For very large strings, this may reduce memory // fragmentation. It may also allow better performance when parsing from a // Cord, or when parsing with aliasing enabled, as the parsed Cord may then // alias the original buffer. CORD = 1; STRING_PIECE = 2; } // The packed option can be enabled for repeated primitive fields to enable // a more efficient representation on the wire. Rather than repeatedly // writing the tag and type for each element, the entire array is encoded as // a single length-delimited blob. In proto3, only explicit setting it to // false will avoid using packed encoding. This option is prohibited in // Editions, but the `repeated_field_encoding` feature can be used to control // the behavior. optional bool packed = 2; // The jstype option determines the JavaScript type used for values of the // field. The option is permitted only for 64 bit integral and fixed types // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING // is represented as JavaScript string, which avoids loss of precision that // can happen when a large value is converted to a floating point JavaScript. // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to // use the JavaScript "number" type. The behavior of the default option // JS_NORMAL is implementation dependent. // // This option is an enum to permit additional types to be added, e.g. // goog.math.Integer. optional JSType jstype = 6 [default = JS_NORMAL]; enum JSType { // Use the default type. JS_NORMAL = 0; // Use JavaScript strings. JS_STRING = 1; // Use JavaScript numbers. JS_NUMBER = 2; } // Should this field be parsed lazily? Lazy applies only to message-type // fields. It means that when the outer message is initially parsed, the // inner message's contents will not be parsed but instead stored in encoded // form. The inner message will actually be parsed when it is first accessed. // // This is only a hint. Implementations are free to choose whether to use // eager or lazy parsing regardless of the value of this option. However, // setting this option true suggests that the protocol author believes that // using lazy parsing on this field is worth the additional bookkeeping // overhead typically needed to implement it. // // This option does not affect the public interface of any generated code; // all method signatures remain the same. Furthermore, thread-safety of the // interface is not affected by this option; const methods remain safe to // call from multiple threads concurrently, while non-const methods continue // to require exclusive access. // // Note that implementations may choose not to check required fields within // a lazy sub-message. That is, calling IsInitialized() on the outer message // may return true even if the inner message has missing required fields. // This is necessary because otherwise the inner message would have to be // parsed in order to perform the check, defeating the purpose of lazy // parsing. An implementation which chooses not to check required fields // must be consistent about it. That is, for any particular sub-message, the // implementation must either *always* check its required fields, or *never* // check its required fields, regardless of whether or not the message has // been parsed. // // As of May 2022, lazy verifies the contents of the byte stream during // parsing. An invalid byte stream will cause the overall parsing to fail. optional bool lazy = 5 [default = false]; // unverified_lazy does no correctness checks on the byte stream. This should // only be used where lazy with verification is prohibitive for performance // reasons. optional bool unverified_lazy = 15 [default = false]; // Is this field deprecated? // Depending on the target platform, this can emit Deprecated annotations // for accessors, or it will be completely ignored; in the very least, this // is a formalization for deprecating fields. optional bool deprecated = 3 [default = false]; // For Google-internal migration only. Do not use. optional bool weak = 10 [default = false]; // Indicate that the field value should not be printed out when using debug // formats, e.g. when the field contains sensitive credentials. optional bool debug_redact = 16 [default = false]; // If set to RETENTION_SOURCE, the option will be omitted from the binary. // Note: as of January 2023, support for this is in progress and does not yet // have an effect (b/264593489). enum OptionRetention { RETENTION_UNKNOWN = 0; RETENTION_RUNTIME = 1; RETENTION_SOURCE = 2; } optional OptionRetention retention = 17; // This indicates the types of entities that the field may apply to when used // as an option. If it is unset, then the field may be freely used as an // option on any kind of entity. Note: as of January 2023, support for this is // in progress and does not yet have an effect (b/264593489). enum OptionTargetType { TARGET_TYPE_UNKNOWN = 0; TARGET_TYPE_FILE = 1; TARGET_TYPE_EXTENSION_RANGE = 2; TARGET_TYPE_MESSAGE = 3; TARGET_TYPE_FIELD = 4; TARGET_TYPE_ONEOF = 5; TARGET_TYPE_ENUM = 6; TARGET_TYPE_ENUM_ENTRY = 7; TARGET_TYPE_SERVICE = 8; TARGET_TYPE_METHOD = 9; } repeated OptionTargetType targets = 19; message EditionDefault { optional Edition edition = 3; optional string value = 2; // Textproto value. } repeated EditionDefault edition_defaults = 20; // Any features defined in the specific edition. optional FeatureSet features = 21; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; reserved 4; // removed jtype reserved 18; // reserve target, target_obsolete_do_not_use } message OneofOptions { // Any features defined in the specific edition. optional FeatureSet features = 1; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message EnumOptions { // Set this option to true to allow mapping different tag names to the same // value. optional bool allow_alias = 2; // Is this enum deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the enum, or it will be completely ignored; in the very least, this // is a formalization for deprecating enums. optional bool deprecated = 3 [default = false]; reserved 5; // javanano_as_lite // Enable the legacy handling of JSON field name conflicts. This lowercases // and strips underscored from the fields before comparison in proto3 only. // The new behavior takes `json_name` into account and applies to proto2 as // well. // TODO Remove this legacy behavior once downstream teams have // had time to migrate. optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; // Any features defined in the specific edition. optional FeatureSet features = 7; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message EnumValueOptions { // Is this enum value deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the enum value, or it will be completely ignored; in the very least, // this is a formalization for deprecating enum values. optional bool deprecated = 1 [default = false]; // Any features defined in the specific edition. optional FeatureSet features = 2; // Indicate that fields annotated with this enum value should not be printed // out when using debug formats, e.g. when the field contains sensitive // credentials. optional bool debug_redact = 3 [default = false]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message ServiceOptions { // Any features defined in the specific edition. optional FeatureSet features = 34; // Note: Field numbers 1 through 32 are reserved for Google's internal RPC // framework. We apologize for hoarding these numbers to ourselves, but // we were already using them long before we decided to release Protocol // Buffers. // Is this service deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the service, or it will be completely ignored; in the very least, // this is a formalization for deprecating services. optional bool deprecated = 33 [default = false]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message MethodOptions { // Note: Field numbers 1 through 32 are reserved for Google's internal RPC // framework. We apologize for hoarding these numbers to ourselves, but // we were already using them long before we decided to release Protocol // Buffers. // Is this method deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the method, or it will be completely ignored; in the very least, // this is a formalization for deprecating methods. optional bool deprecated = 33 [default = false]; // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, // or neither? HTTP based RPC implementation may choose GET verb for safe // methods, and PUT verb for idempotent methods instead of the default POST. enum IdempotencyLevel { IDEMPOTENCY_UNKNOWN = 0; NO_SIDE_EFFECTS = 1; // implies idempotent IDEMPOTENT = 2; // idempotent, but may have side effects } optional IdempotencyLevel idempotency_level = 34 [default = IDEMPOTENCY_UNKNOWN]; // Any features defined in the specific edition. optional FeatureSet features = 35; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } // A message representing a option the parser does not recognize. This only // appears in options protos created by the compiler::Parser class. // DescriptorPool resolves these when building Descriptor objects. Therefore, // options protos in descriptor objects (e.g. returned by Descriptor::options(), // or produced by Descriptor::CopyTo()) will never have UninterpretedOptions // in them. message UninterpretedOption { // The name of the uninterpreted option. Each string represents a segment in // a dot-separated name. is_extension is true iff a segment represents an // extension (denoted with parentheses in options specs in .proto files). // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents // "foo.(bar.baz).moo". message NamePart { required string name_part = 1; required bool is_extension = 2; } repeated NamePart name = 2; // The value of the uninterpreted option, in whatever type the tokenizer // identified it as during parsing. Exactly one of these should be set. optional string identifier_value = 3; optional uint64 positive_int_value = 4; optional int64 negative_int_value = 5; optional double double_value = 6; optional bytes string_value = 7; optional string aggregate_value = 8; } // =================================================================== // Features // TODO Enums in C++ gencode (and potentially other languages) are // not well scoped. This means that each of the feature enums below can clash // with each other. The short names we've chosen maximize call-site // readability, but leave us very open to this scenario. A future feature will // be designed and implemented to handle this, hopefully before we ever hit a // conflict here. message FeatureSet { enum FieldPresence { FIELD_PRESENCE_UNKNOWN = 0; EXPLICIT = 1; IMPLICIT = 2; LEGACY_REQUIRED = 3; } optional FieldPresence field_presence = 1 [ retention = RETENTION_RUNTIME, targets = TARGET_TYPE_FIELD, targets = TARGET_TYPE_FILE, edition_defaults = { edition: EDITION_PROTO2, value: "EXPLICIT" }, edition_defaults = { edition: EDITION_PROTO3, value: "IMPLICIT" }, edition_defaults = { edition: EDITION_2023, value: "EXPLICIT" } ]; enum EnumType { ENUM_TYPE_UNKNOWN = 0; OPEN = 1; CLOSED = 2; } optional EnumType enum_type = 2 [ retention = RETENTION_RUNTIME, targets = TARGET_TYPE_ENUM, targets = TARGET_TYPE_FILE, edition_defaults = { edition: EDITION_PROTO2, value: "CLOSED" }, edition_defaults = { edition: EDITION_PROTO3, value: "OPEN" } ]; enum RepeatedFieldEncoding { REPEATED_FIELD_ENCODING_UNKNOWN = 0; PACKED = 1; EXPANDED = 2; } optional RepeatedFieldEncoding repeated_field_encoding = 3 [ retention = RETENTION_RUNTIME, targets = TARGET_TYPE_FIELD, targets = TARGET_TYPE_FILE, edition_defaults = { edition: EDITION_PROTO2, value: "EXPANDED" }, edition_defaults = { edition: EDITION_PROTO3, value: "PACKED" } ]; enum Utf8Validation { UTF8_VALIDATION_UNKNOWN = 0; NONE = 1; VERIFY = 2; } optional Utf8Validation utf8_validation = 4 [ retention = RETENTION_RUNTIME, targets = TARGET_TYPE_FIELD, targets = TARGET_TYPE_FILE, edition_defaults = { edition: EDITION_PROTO2, value: "NONE" }, edition_defaults = { edition: EDITION_PROTO3, value: "VERIFY" } ]; enum MessageEncoding { MESSAGE_ENCODING_UNKNOWN = 0; LENGTH_PREFIXED = 1; DELIMITED = 2; } optional MessageEncoding message_encoding = 5 [ retention = RETENTION_RUNTIME, targets = TARGET_TYPE_FIELD, targets = TARGET_TYPE_FILE, edition_defaults = { edition: EDITION_PROTO2, value: "LENGTH_PREFIXED" } ]; enum JsonFormat { JSON_FORMAT_UNKNOWN = 0; ALLOW = 1; LEGACY_BEST_EFFORT = 2; } optional JsonFormat json_format = 6 [ retention = RETENTION_RUNTIME, targets = TARGET_TYPE_MESSAGE, targets = TARGET_TYPE_ENUM, targets = TARGET_TYPE_FILE, edition_defaults = { edition: EDITION_PROTO2, value: "LEGACY_BEST_EFFORT" }, edition_defaults = { edition: EDITION_PROTO3, value: "ALLOW" } ]; reserved 999; extensions 1000; // for Protobuf C++ extensions 1001; // for Protobuf Java extensions 9995 to 9999; // For internal testing } // A compiled specification for the defaults of a set of features. These // messages are generated from FeatureSet extensions and can be used to seed // feature resolution. The resolution with this object becomes a simple search // for the closest matching edition, followed by proto merges. message FeatureSetDefaults { // A map from every known edition with a unique set of defaults to its // defaults. Not all editions may be contained here. For a given edition, // the defaults at the closest matching edition ordered at or before it should // be used. This field must be in strict ascending order by edition. message FeatureSetEditionDefault { optional Edition edition = 3; optional FeatureSet features = 2; } repeated FeatureSetEditionDefault defaults = 1; // The minimum supported edition (inclusive) when this was constructed. // Editions before this will not have defaults. optional Edition minimum_edition = 4; // The maximum known edition (inclusive) when this was constructed. Editions // after this will not have reliable defaults. optional Edition maximum_edition = 5; } // =================================================================== // Optional source code info // Encapsulates information about the original source file from which a // FileDescriptorProto was generated. message SourceCodeInfo { // A Location identifies a piece of source code in a .proto file which // corresponds to a particular definition. This information is intended // to be useful to IDEs, code indexers, documentation generators, and similar // tools. // // For example, say we have a file like: // message Foo { // optional string foo = 1; // } // Let's look at just the field definition: // optional string foo = 1; // ^ ^^ ^^ ^ ^^^ // a bc de f ghi // We have the following locations: // span path represents // [a,i) [ 4, 0, 2, 0 ] The whole field definition. // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). // // Notes: // - A location may refer to a repeated field itself (i.e. not to any // particular index within it). This is used whenever a set of elements are // logically enclosed in a single code segment. For example, an entire // extend block (possibly containing multiple extension definitions) will // have an outer location whose path refers to the "extensions" repeated // field without an index. // - Multiple locations may have the same path. This happens when a single // logical declaration is spread out across multiple places. The most // obvious example is the "extend" block again -- there may be multiple // extend blocks in the same scope, each of which will have the same path. // - A location's span is not always a subset of its parent's span. For // example, the "extendee" of an extension declaration appears at the // beginning of the "extend" block and is shared by all extensions within // the block. // - Just because a location's span is a subset of some other location's span // does not mean that it is a descendant. For example, a "group" defines // both a type and a field in a single declaration. Thus, the locations // corresponding to the type and field and their components will overlap. // - Code which tries to interpret locations should probably be designed to // ignore those that it doesn't understand, as more types of locations could // be recorded in the future. repeated Location location = 1; message Location { // Identifies which part of the FileDescriptorProto was defined at this // location. // // Each element is a field number or an index. They form a path from // the root FileDescriptorProto to the place where the definition occurs. // For example, this path: // [ 4, 3, 2, 7, 1 ] // refers to: // file.message_type(3) // 4, 3 // .field(7) // 2, 7 // .name() // 1 // This is because FileDescriptorProto.message_type has field number 4: // repeated DescriptorProto message_type = 4; // and DescriptorProto.field has field number 2: // repeated FieldDescriptorProto field = 2; // and FieldDescriptorProto.name has field number 1: // optional string name = 1; // // Thus, the above path gives the location of a field name. If we removed // the last element: // [ 4, 3, 2, 7 ] // this path refers to the whole field declaration (from the beginning // of the label to the terminating semicolon). repeated int32 path = 1 [packed = true]; // Always has exactly three or four elements: start line, start column, // end line (optional, otherwise assumed same as start line), end column. // These are packed into a single field for efficiency. Note that line // and column numbers are zero-based -- typically you will want to add // 1 to each before displaying to a user. repeated int32 span = 2 [packed = true]; // If this SourceCodeInfo represents a complete declaration, these are any // comments appearing before and after the declaration which appear to be // attached to the declaration. // // A series of line comments appearing on consecutive lines, with no other // tokens appearing on those lines, will be treated as a single comment. // // leading_detached_comments will keep paragraphs of comments that appear // before (but not connected to) the current element. Each paragraph, // separated by empty lines, will be one comment element in the repeated // field. // // Only the comment content is provided; comment markers (e.g. //) are // stripped out. For block comments, leading whitespace and an asterisk // will be stripped from the beginning of each line other than the first. // Newlines are included in the output. // // Examples: // // optional int32 foo = 1; // Comment attached to foo. // // Comment attached to bar. // optional int32 bar = 2; // // optional string baz = 3; // // Comment attached to baz. // // Another line attached to baz. // // // Comment attached to moo. // // // // Another line attached to moo. // optional double moo = 4; // // // Detached comment for corge. This is not leading or trailing comments // // to moo or corge because there are blank lines separating it from // // both. // // // Detached comment for corge paragraph 2. // // optional string corge = 5; // /* Block comment attached // * to corge. Leading asterisks // * will be removed. */ // /* Block comment attached to // * grault. */ // optional int32 grault = 6; // // // ignored detached comments. optional string leading_comments = 3; optional string trailing_comments = 4; repeated string leading_detached_comments = 6; } } // Describes the relationship between generated code and its original source // file. A GeneratedCodeInfo message is associated with only one generated // source file, but may contain references to different source .proto files. message GeneratedCodeInfo { // An Annotation connects some span of text in generated code to an element // of its generating .proto file. repeated Annotation annotation = 1; message Annotation { // Identifies the element in the original source .proto file. This field // is formatted the same as SourceCodeInfo.Location.path. repeated int32 path = 1 [packed = true]; // Identifies the filesystem path to the original source .proto. optional string source_file = 2; // Identifies the starting offset in bytes in the generated code // that relates to the identified object. optional int32 begin = 3; // Identifies the ending offset in bytes in the generated code that // relates to the identified object. The end offset should be one past // the last relevant byte (so the length of the text = end - begin). optional int32 end = 4; // Represents the identified object's effect on the element in the original // .proto file. enum Semantic { // There is no effect or the effect is indescribable. NONE = 0; // The element is set or otherwise mutated. SET = 1; // An alias to the element is returned. ALIAS = 2; } optional Semantic semantic = 5; } } ================================================ FILE: protoc/include/google/protobuf/duration.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option cc_enable_arenas = true; option go_package = "google.golang.org/protobuf/types/known/durationpb"; option java_package = "com.google.protobuf"; option java_outer_classname = "DurationProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // A Duration represents a signed, fixed-length span of time represented // as a count of seconds and fractions of seconds at nanosecond // resolution. It is independent of any calendar and concepts like "day" // or "month". It is related to Timestamp in that the difference between // two Timestamp values is a Duration and it can be added or subtracted // from a Timestamp. Range is approximately +-10,000 years. // // # Examples // // Example 1: Compute Duration from two Timestamps in pseudo code. // // Timestamp start = ...; // Timestamp end = ...; // Duration duration = ...; // // duration.seconds = end.seconds - start.seconds; // duration.nanos = end.nanos - start.nanos; // // if (duration.seconds < 0 && duration.nanos > 0) { // duration.seconds += 1; // duration.nanos -= 1000000000; // } else if (duration.seconds > 0 && duration.nanos < 0) { // duration.seconds -= 1; // duration.nanos += 1000000000; // } // // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. // // Timestamp start = ...; // Duration duration = ...; // Timestamp end = ...; // // end.seconds = start.seconds + duration.seconds; // end.nanos = start.nanos + duration.nanos; // // if (end.nanos < 0) { // end.seconds -= 1; // end.nanos += 1000000000; // } else if (end.nanos >= 1000000000) { // end.seconds += 1; // end.nanos -= 1000000000; // } // // Example 3: Compute Duration from datetime.timedelta in Python. // // td = datetime.timedelta(days=3, minutes=10) // duration = Duration() // duration.FromTimedelta(td) // // # JSON Mapping // // In JSON format, the Duration type is encoded as a string rather than an // object, where the string ends in the suffix "s" (indicating seconds) and // is preceded by the number of seconds, with nanoseconds expressed as // fractional seconds. For example, 3 seconds with 0 nanoseconds should be // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 // microsecond should be expressed in JSON format as "3.000001s". // message Duration { // Signed seconds of the span of time. Must be from -315,576,000,000 // to +315,576,000,000 inclusive. Note: these bounds are computed from: // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years int64 seconds = 1; // Signed fractions of a second at nanosecond resolution of the span // of time. Durations less than one second are represented with a 0 // `seconds` field and a positive or negative `nanos` field. For durations // of one second or more, a non-zero value for the `nanos` field must be // of the same sign as the `seconds` field. Must be from -999,999,999 // to +999,999,999 inclusive. int32 nanos = 2; } ================================================ FILE: protoc/include/google/protobuf/empty.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option go_package = "google.golang.org/protobuf/types/known/emptypb"; option java_package = "com.google.protobuf"; option java_outer_classname = "EmptyProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; // A generic empty message that you can re-use to avoid defining duplicated // empty messages in your APIs. A typical example is to use it as the request // or the response type of an API method. For instance: // // service Foo { // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); // } // message Empty {} ================================================ FILE: protoc/include/google/protobuf/field_mask.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option java_package = "com.google.protobuf"; option java_outer_classname = "FieldMaskProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; option cc_enable_arenas = true; // `FieldMask` represents a set of symbolic field paths, for example: // // paths: "f.a" // paths: "f.b.d" // // Here `f` represents a field in some root message, `a` and `b` // fields in the message found in `f`, and `d` a field found in the // message in `f.b`. // // Field masks are used to specify a subset of fields that should be // returned by a get operation or modified by an update operation. // Field masks also have a custom JSON encoding (see below). // // # Field Masks in Projections // // When used in the context of a projection, a response message or // sub-message is filtered by the API to only contain those fields as // specified in the mask. For example, if the mask in the previous // example is applied to a response message as follows: // // f { // a : 22 // b { // d : 1 // x : 2 // } // y : 13 // } // z: 8 // // The result will not contain specific values for fields x,y and z // (their value will be set to the default, and omitted in proto text // output): // // // f { // a : 22 // b { // d : 1 // } // } // // A repeated field is not allowed except at the last position of a // paths string. // // If a FieldMask object is not present in a get operation, the // operation applies to all fields (as if a FieldMask of all fields // had been specified). // // Note that a field mask does not necessarily apply to the // top-level response message. In case of a REST get operation, the // field mask applies directly to the response, but in case of a REST // list operation, the mask instead applies to each individual message // in the returned resource list. In case of a REST custom method, // other definitions may be used. Where the mask applies will be // clearly documented together with its declaration in the API. In // any case, the effect on the returned resource/resources is required // behavior for APIs. // // # Field Masks in Update Operations // // A field mask in update operations specifies which fields of the // targeted resource are going to be updated. The API is required // to only change the values of the fields as specified in the mask // and leave the others untouched. If a resource is passed in to // describe the updated values, the API ignores the values of all // fields not covered by the mask. // // If a repeated field is specified for an update operation, new values will // be appended to the existing repeated field in the target resource. Note that // a repeated field is only allowed in the last position of a `paths` string. // // If a sub-message is specified in the last position of the field mask for an // update operation, then new value will be merged into the existing sub-message // in the target resource. // // For example, given the target message: // // f { // b { // d: 1 // x: 2 // } // c: [1] // } // // And an update message: // // f { // b { // d: 10 // } // c: [2] // } // // then if the field mask is: // // paths: ["f.b", "f.c"] // // then the result will be: // // f { // b { // d: 10 // x: 2 // } // c: [1, 2] // } // // An implementation may provide options to override this default behavior for // repeated and message fields. // // In order to reset a field's value to the default, the field must // be in the mask and set to the default value in the provided resource. // Hence, in order to reset all fields of a resource, provide a default // instance of the resource and set all fields in the mask, or do // not provide a mask as described below. // // If a field mask is not present on update, the operation applies to // all fields (as if a field mask of all fields has been specified). // Note that in the presence of schema evolution, this may mean that // fields the client does not know and has therefore not filled into // the request will be reset to their default. If this is unwanted // behavior, a specific service may require a client to always specify // a field mask, producing an error if not. // // As with get operations, the location of the resource which // describes the updated values in the request message depends on the // operation kind. In any case, the effect of the field mask is // required to be honored by the API. // // ## Considerations for HTTP REST // // The HTTP kind of an update operation which uses a field mask must // be set to PATCH instead of PUT in order to satisfy HTTP semantics // (PUT must only be used for full updates). // // # JSON Encoding of Field Masks // // In JSON, a field mask is encoded as a single string where paths are // separated by a comma. Fields name in each path are converted // to/from lower-camel naming conventions. // // As an example, consider the following message declarations: // // message Profile { // User user = 1; // Photo photo = 2; // } // message User { // string display_name = 1; // string address = 2; // } // // In proto a field mask for `Profile` may look as such: // // mask { // paths: "user.display_name" // paths: "photo" // } // // In JSON, the same mask is represented as below: // // { // mask: "user.displayName,photo" // } // // # Field Masks and Oneof Fields // // Field masks treat fields in oneofs just as regular fields. Consider the // following message: // // message SampleMessage { // oneof test_oneof { // string name = 4; // SubMessage sub_message = 9; // } // } // // The field mask can be: // // mask { // paths: "name" // } // // Or: // // mask { // paths: "sub_message" // } // // Note that oneof type names ("test_oneof" in this case) cannot be used in // paths. // // ## Field Mask Verification // // The implementation of any API method which has a FieldMask type field in the // request should verify the included field paths, and return an // `INVALID_ARGUMENT` error if any path is unmappable. message FieldMask { // The set of field mask paths. repeated string paths = 1; } ================================================ FILE: protoc/include/google/protobuf/source_context.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option java_package = "com.google.protobuf"; option java_outer_classname = "SourceContextProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; // `SourceContext` represents information about the source of a // protobuf element, like the file in which it is defined. message SourceContext { // The path-qualified name of the .proto file that contained the associated // protobuf element. For example: `"google/protobuf/source_context.proto"`. string file_name = 1; } ================================================ FILE: protoc/include/google/protobuf/struct.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option cc_enable_arenas = true; option go_package = "google.golang.org/protobuf/types/known/structpb"; option java_package = "com.google.protobuf"; option java_outer_classname = "StructProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // `Struct` represents a structured data value, consisting of fields // which map to dynamically typed values. In some languages, `Struct` // might be supported by a native representation. For example, in // scripting languages like JS a struct is represented as an // object. The details of that representation are described together // with the proto support for the language. // // The JSON representation for `Struct` is JSON object. message Struct { // Unordered map of dynamically typed values. map fields = 1; } // `Value` represents a dynamically typed value which can be either // null, a number, a string, a boolean, a recursive struct value, or a // list of values. A producer of value is expected to set one of these // variants. Absence of any variant indicates an error. // // The JSON representation for `Value` is JSON value. message Value { // The kind of value. oneof kind { // Represents a null value. NullValue null_value = 1; // Represents a double value. double number_value = 2; // Represents a string value. string string_value = 3; // Represents a boolean value. bool bool_value = 4; // Represents a structured value. Struct struct_value = 5; // Represents a repeated `Value`. ListValue list_value = 6; } } // `NullValue` is a singleton enumeration to represent the null value for the // `Value` type union. // // The JSON representation for `NullValue` is JSON `null`. enum NullValue { // Null value. NULL_VALUE = 0; } // `ListValue` is a wrapper around a repeated field of values. // // The JSON representation for `ListValue` is JSON array. message ListValue { // Repeated field of dynamically typed values. repeated Value values = 1; } ================================================ FILE: protoc/include/google/protobuf/timestamp.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option cc_enable_arenas = true; option go_package = "google.golang.org/protobuf/types/known/timestamppb"; option java_package = "com.google.protobuf"; option java_outer_classname = "TimestampProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // A Timestamp represents a point in time independent of any time zone or local // calendar, encoded as a count of seconds and fractions of seconds at // nanosecond resolution. The count is relative to an epoch at UTC midnight on // January 1, 1970, in the proleptic Gregorian calendar which extends the // Gregorian calendar backwards to year one. // // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap // second table is needed for interpretation, using a [24-hour linear // smear](https://developers.google.com/time/smear). // // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By // restricting to that range, we ensure that we can convert to and from [RFC // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. // // # Examples // // Example 1: Compute Timestamp from POSIX `time()`. // // Timestamp timestamp; // timestamp.set_seconds(time(NULL)); // timestamp.set_nanos(0); // // Example 2: Compute Timestamp from POSIX `gettimeofday()`. // // struct timeval tv; // gettimeofday(&tv, NULL); // // Timestamp timestamp; // timestamp.set_seconds(tv.tv_sec); // timestamp.set_nanos(tv.tv_usec * 1000); // // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. // // FILETIME ft; // GetSystemTimeAsFileTime(&ft); // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. // Timestamp timestamp; // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); // // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. // // long millis = System.currentTimeMillis(); // // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) // .setNanos((int) ((millis % 1000) * 1000000)).build(); // // Example 5: Compute Timestamp from Java `Instant.now()`. // // Instant now = Instant.now(); // // Timestamp timestamp = // Timestamp.newBuilder().setSeconds(now.getEpochSecond()) // .setNanos(now.getNano()).build(); // // Example 6: Compute Timestamp from current time in Python. // // timestamp = Timestamp() // timestamp.GetCurrentTime() // // # JSON Mapping // // In JSON format, the Timestamp type is encoded as a string in the // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" // where {year} is always expressed using four digits while {month}, {day}, // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone // is required. A proto3 JSON serializer should always use UTC (as indicated by // "Z") when printing the Timestamp type and a proto3 JSON parser should be // able to accept both UTC and other timezones (as indicated by an offset). // // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past // 01:30 UTC on January 15, 2017. // // In JavaScript, one can convert a Date object to this format using the // standard // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) // method. In Python, a standard `datetime.datetime` object can be converted // to this format using // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use // the Joda Time's [`ISODateTimeFormat.dateTime()`]( // http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() // ) to obtain a formatter capable of generating timestamps in this format. // message Timestamp { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. int64 seconds = 1; // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. int32 nanos = 2; } ================================================ FILE: protoc/include/google/protobuf/type.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; import "google/protobuf/any.proto"; import "google/protobuf/source_context.proto"; option cc_enable_arenas = true; option java_package = "com.google.protobuf"; option java_outer_classname = "TypeProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/typepb"; // A protocol buffer message type. message Type { // The fully qualified message name. string name = 1; // The list of fields. repeated Field fields = 2; // The list of types appearing in `oneof` definitions in this type. repeated string oneofs = 3; // The protocol buffer options. repeated Option options = 4; // The source context. SourceContext source_context = 5; // The source syntax. Syntax syntax = 6; // The source edition string, only valid when syntax is SYNTAX_EDITIONS. string edition = 7; } // A single field of a message type. message Field { // Basic field types. enum Kind { // Field type unknown. TYPE_UNKNOWN = 0; // Field type double. TYPE_DOUBLE = 1; // Field type float. TYPE_FLOAT = 2; // Field type int64. TYPE_INT64 = 3; // Field type uint64. TYPE_UINT64 = 4; // Field type int32. TYPE_INT32 = 5; // Field type fixed64. TYPE_FIXED64 = 6; // Field type fixed32. TYPE_FIXED32 = 7; // Field type bool. TYPE_BOOL = 8; // Field type string. TYPE_STRING = 9; // Field type group. Proto2 syntax only, and deprecated. TYPE_GROUP = 10; // Field type message. TYPE_MESSAGE = 11; // Field type bytes. TYPE_BYTES = 12; // Field type uint32. TYPE_UINT32 = 13; // Field type enum. TYPE_ENUM = 14; // Field type sfixed32. TYPE_SFIXED32 = 15; // Field type sfixed64. TYPE_SFIXED64 = 16; // Field type sint32. TYPE_SINT32 = 17; // Field type sint64. TYPE_SINT64 = 18; } // Whether a field is optional, required, or repeated. enum Cardinality { // For fields with unknown cardinality. CARDINALITY_UNKNOWN = 0; // For optional fields. CARDINALITY_OPTIONAL = 1; // For required fields. Proto2 syntax only. CARDINALITY_REQUIRED = 2; // For repeated fields. CARDINALITY_REPEATED = 3; } // The field type. Kind kind = 1; // The field cardinality. Cardinality cardinality = 2; // The field number. int32 number = 3; // The field name. string name = 4; // The field type URL, without the scheme, for message or enumeration // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. string type_url = 6; // The index of the field type in `Type.oneofs`, for message or enumeration // types. The first type has index 1; zero means the type is not in the list. int32 oneof_index = 7; // Whether to use alternative packed wire representation. bool packed = 8; // The protocol buffer options. repeated Option options = 9; // The field JSON name. string json_name = 10; // The string value of the default value of this field. Proto2 syntax only. string default_value = 11; } // Enum type definition. message Enum { // Enum type name. string name = 1; // Enum value definitions. repeated EnumValue enumvalue = 2; // Protocol buffer options. repeated Option options = 3; // The source context. SourceContext source_context = 4; // The source syntax. Syntax syntax = 5; // The source edition string, only valid when syntax is SYNTAX_EDITIONS. string edition = 6; } // Enum value definition. message EnumValue { // Enum value name. string name = 1; // Enum value number. int32 number = 2; // Protocol buffer options. repeated Option options = 3; } // A protocol buffer option, which can be attached to a message, field, // enumeration, etc. message Option { // The option's name. For protobuf built-in options (options defined in // descriptor.proto), this is the short name. For example, `"map_entry"`. // For custom options, it should be the fully-qualified name. For example, // `"google.api.http"`. string name = 1; // The option's value packed in an Any message. If the value is a primitive, // the corresponding wrapper type defined in google/protobuf/wrappers.proto // should be used. If the value is an enum, it should be stored as an int32 // value using the google.protobuf.Int32Value type. Any value = 2; } // The syntax in which a protocol buffer element is defined. enum Syntax { // Syntax `proto2`. SYNTAX_PROTO2 = 0; // Syntax `proto3`. SYNTAX_PROTO3 = 1; // Syntax `editions`. SYNTAX_EDITIONS = 2; } ================================================ FILE: protoc/include/google/protobuf/wrappers.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Wrappers for primitive (non-message) types. These types are useful // for embedding primitives in the `google.protobuf.Any` type and for places // where we need to distinguish between the absence of a primitive // typed field and its default value. // // These wrappers have no meaningful use within repeated fields as they lack // the ability to detect presence on individual elements. // These wrappers have no meaningful use within a map or a oneof since // individual entries of a map or fields of a oneof can already detect presence. syntax = "proto3"; package google.protobuf; option cc_enable_arenas = true; option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; option java_package = "com.google.protobuf"; option java_outer_classname = "WrappersProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // Wrapper message for `double`. // // The JSON representation for `DoubleValue` is JSON number. message DoubleValue { // The double value. double value = 1; } // Wrapper message for `float`. // // The JSON representation for `FloatValue` is JSON number. message FloatValue { // The float value. float value = 1; } // Wrapper message for `int64`. // // The JSON representation for `Int64Value` is JSON string. message Int64Value { // The int64 value. int64 value = 1; } // Wrapper message for `uint64`. // // The JSON representation for `UInt64Value` is JSON string. message UInt64Value { // The uint64 value. uint64 value = 1; } // Wrapper message for `int32`. // // The JSON representation for `Int32Value` is JSON number. message Int32Value { // The int32 value. int32 value = 1; } // Wrapper message for `uint32`. // // The JSON representation for `UInt32Value` is JSON number. message UInt32Value { // The uint32 value. uint32 value = 1; } // Wrapper message for `bool`. // // The JSON representation for `BoolValue` is JSON `true` and `false`. message BoolValue { // The bool value. bool value = 1; } // Wrapper message for `string`. // // The JSON representation for `StringValue` is JSON string. message StringValue { // The string value. string value = 1; } // Wrapper message for `bytes`. // // The JSON representation for `BytesValue` is JSON string. message BytesValue { // The bytes value. bytes value = 1; } ================================================ FILE: protoc/readme.txt ================================================ Protocol Buffers - Google's data interchange format Copyright 2008 Google Inc. https://developers.google.com/protocol-buffers/ This package contains a precompiled binary version of the protocol buffer compiler (protoc). This binary is intended for users who want to use Protocol Buffers in languages other than C++ but do not want to compile protoc themselves. To install, simply place this binary somewhere in your PATH. If you intend to use the included well known types then don't forget to copy the contents of the 'include' directory somewhere as well, for example into '/usr/local/include/'. Please refer to our official github site for more installation instructions: https://github.com/protocolbuffers/protobuf ================================================ FILE: scripts/build-images.ps1 ================================================ Param( [string]$Tag = "dev" ) $ErrorActionPreference = 'Stop' Write-Host "Building Docker images for greatestworks (tag=$Tag)" # Resolve repo root $RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path | Split-Path -Parent Set-Location $RepoRoot function Build-ServiceImage { param( [Parameter(Mandatory=$true)][string]$ServiceName, [Parameter(Mandatory=$true)][string]$ServicePackage ) $imageName = "greatestworks-${ServiceName}:${Tag}" Write-Host " -> Building $imageName from package $ServicePackage" $buildTime = Get-Date -Format o $gitCommit = git rev-parse --short HEAD 2>$null docker build ` --build-arg SERVICE_PACKAGE=$ServicePackage ` --build-arg BUILD_VERSION=$Tag ` --build-arg BUILD_TIME=$buildTime ` --build-arg GIT_COMMIT=$gitCommit ` -t $imageName ` -f Dockerfile . } Build-ServiceImage -ServiceName "auth" -ServicePackage "./cmd/auth-service" Build-ServiceImage -ServiceName "game" -ServicePackage "./cmd/game-service" Build-ServiceImage -ServiceName "gateway" -ServicePackage "./cmd/gateway-service" Write-Host "Done. Images:" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | Select-String greatestworks- ================================================ FILE: scripts/build.sh ================================================ #!/bin/bash # Greatest Works - 构建脚本 # 用于编译Go项目,支持多平台交叉编译 set -e # 项目信息 PROJECT_NAME="greatestworks" VERSION=${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")} BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") # 构建参数 LDFLAGS="-s -w -X main.version=${VERSION} -X main.buildTime=${BUILD_TIME} -X main.gitCommit=${GIT_COMMIT}" BUILD_DIR="./bin" SOURCE_DIR="./cmd/server" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 清理构建目录 clean_build_dir() { log_info "清理构建目录..." rm -rf ${BUILD_DIR} mkdir -p ${BUILD_DIR} } # 检查Go环境 check_go_env() { if ! command -v go &> /dev/null; then log_error "Go未安装或不在PATH中" exit 1 fi GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//') log_info "Go版本: ${GO_VERSION}" # 检查Go版本是否满足要求 REQUIRED_VERSION="1.21" if ! printf '%s\n%s\n' "$REQUIRED_VERSION" "$GO_VERSION" | sort -V -C; then log_warning "建议使用Go ${REQUIRED_VERSION}或更高版本" fi } # 下载依赖 download_deps() { log_info "下载Go模块依赖..." go mod download go mod tidy } # 运行测试 run_tests() { if [ "$SKIP_TESTS" != "true" ]; then log_info "运行单元测试..." go test -v ./... -timeout=30s if [ $? -ne 0 ]; then log_error "测试失败,构建中止" exit 1 fi log_success "所有测试通过" else log_warning "跳过测试" fi } # 代码质量检查 run_lint() { if [ "$SKIP_LINT" != "true" ]; then if command -v golangci-lint &> /dev/null; then log_info "运行代码质量检查..." golangci-lint run if [ $? -ne 0 ]; then log_error "代码质量检查失败,构建中止" exit 1 fi log_success "代码质量检查通过" else log_warning "golangci-lint未安装,跳过代码质量检查" fi else log_warning "跳过代码质量检查" fi } # 构建单个平台 build_single() { local goos=$1 local goarch=$2 local output_name="${PROJECT_NAME}" if [ "$goos" = "windows" ]; then output_name="${output_name}.exe" fi local output_path="${BUILD_DIR}/${goos}-${goarch}/${output_name}" log_info "构建 ${goos}/${goarch}..." mkdir -p "$(dirname "$output_path")" CGO_ENABLED=0 GOOS=$goos GOARCH=$goarch go build \ -ldflags "${LDFLAGS}" \ -o "$output_path" \ "$SOURCE_DIR" if [ $? -eq 0 ]; then local file_size=$(du -h "$output_path" | cut -f1) log_success "构建完成: $output_path (${file_size})" else log_error "构建失败: ${goos}/${goarch}" return 1 fi } # 构建所有平台 build_all() { log_info "开始多平台构建..." # 定义支持的平台 declare -a platforms=( "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64" ) for platform in "${platforms[@]}"; do IFS='/' read -r goos goarch <<< "$platform" build_single "$goos" "$goarch" if [ $? -ne 0 ]; then log_error "构建失败,中止" exit 1 fi done log_success "所有平台构建完成" } # 构建当前平台 build_current() { local goos=$(go env GOOS) local goarch=$(go env GOARCH) log_info "构建当前平台 ${goos}/${goarch}..." local output_name="${PROJECT_NAME}" if [ "$goos" = "windows" ]; then output_name="${output_name}.exe" fi local output_path="${BUILD_DIR}/${output_name}" go build -ldflags "${LDFLAGS}" -o "$output_path" "$SOURCE_DIR" if [ $? -eq 0 ]; then local file_size=$(du -h "$output_path" | cut -f1) log_success "构建完成: $output_path (${file_size})" # 创建符号链接到项目根目录 ln -sf "$output_path" "./server" log_info "创建符号链接: ./server -> $output_path" else log_error "构建失败" exit 1 fi } # 显示帮助信息 show_help() { echo "Greatest Works 构建脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " -h, --help 显示帮助信息" echo " -a, --all 构建所有支持的平台" echo " -c, --current 构建当前平台 (默认)" echo " --skip-tests 跳过单元测试" echo " --skip-lint 跳过代码质量检查" echo " --clean 清理构建目录" echo "" echo "环境变量:" echo " VERSION 版本号 (默认: git describe)" echo " SKIP_TESTS 跳过测试 (true/false)" echo " SKIP_LINT 跳过代码检查 (true/false)" echo "" echo "示例:" echo " $0 # 构建当前平台" echo " $0 --all # 构建所有平台" echo " $0 --skip-tests # 跳过测试构建" echo " VERSION=v1.0.0 $0 # 指定版本号构建" } # 主函数 main() { local build_all_platforms=false local clean_only=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -a|--all) build_all_platforms=true shift ;; -c|--current) build_all_platforms=false shift ;; --skip-tests) export SKIP_TESTS=true shift ;; --skip-lint) export SKIP_LINT=true shift ;; --clean) clean_only=true shift ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done log_info "Greatest Works 构建脚本启动" log_info "版本: ${VERSION}" log_info "构建时间: ${BUILD_TIME}" log_info "Git提交: ${GIT_COMMIT}" # 清理构建目录 clean_build_dir if [ "$clean_only" = true ]; then log_success "构建目录已清理" exit 0 fi # 检查环境 check_go_env # 下载依赖 download_deps # 运行测试 run_tests # 代码质量检查 run_lint # 构建 if [ "$build_all_platforms" = true ]; then build_all else build_current fi log_success "构建完成!" } # 执行主函数 main "$@" ================================================ FILE: scripts/clean.sh ================================================ #!/bin/bash # Greatest Works - 清理脚本 # 清理构建产物、临时文件、日志文件等 set -e # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 清理构建产物 clean_build_artifacts() { log_info "清理构建产物..." local cleaned_items=() # 清理bin目录 if [ -d "bin" ]; then rm -rf bin/* cleaned_items+=("bin目录") fi # 清理可执行文件 local executables=("server" "greatestworks" "greatestworks.exe") for exe in "${executables[@]}"; do if [ -f "$exe" ]; then rm -f "$exe" cleaned_items+=("可执行文件: $exe") fi done # 清理Go构建缓存 if command -v go >/dev/null 2>&1; then go clean -cache go clean -modcache cleaned_items+=("Go构建缓存") fi if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "构建产物清理完成: ${cleaned_items[*]}" else log_info "没有找到构建产物" fi } # 清理临时文件 clean_temp_files() { log_info "清理临时文件..." local cleaned_items=() local temp_patterns=( "*.tmp" "*.temp" "*.swp" "*.swo" "*~" ".DS_Store" "Thumbs.db" "*.pid" "*.lock" ) for pattern in "${temp_patterns[@]}"; do local files=$(find . -name "$pattern" -type f 2>/dev/null || true) if [ -n "$files" ]; then echo "$files" | xargs rm -f cleaned_items+=("$pattern") fi done # 清理tmp目录 if [ -d "tmp" ]; then rm -rf tmp/* cleaned_items+=("tmp目录") fi # 清理系统临时文件 local temp_dirs=("/tmp/greatestworks_*" "/tmp/build_*" "/tmp/test_*") for temp_dir in "${temp_dirs[@]}"; do if ls $temp_dir 1> /dev/null 2>&1; then rm -rf $temp_dir cleaned_items+=("系统临时文件") fi done if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "临时文件清理完成: ${cleaned_items[*]}" else log_info "没有找到临时文件" fi } # 清理日志文件 clean_log_files() { log_info "清理日志文件..." local cleaned_items=() # 清理logs目录 if [ -d "logs" ]; then local log_count=$(find logs -name "*.log" -type f | wc -l) if [ $log_count -gt 0 ]; then find logs -name "*.log" -type f -delete cleaned_items+=("$log_count 个日志文件") fi # 清理压缩的日志文件 local gz_count=$(find logs -name "*.log.gz" -type f | wc -l) if [ $gz_count -gt 0 ]; then find logs -name "*.log.gz" -type f -delete cleaned_items+=("$gz_count 个压缩日志文件") fi fi # 清理根目录的日志文件 local root_logs=("*.log" "nohup.out" "error.log" "access.log") for log_pattern in "${root_logs[@]}"; do if ls $log_pattern 1> /dev/null 2>&1; then rm -f $log_pattern cleaned_items+=("根目录日志: $log_pattern") fi done if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "日志文件清理完成: ${cleaned_items[*]}" else log_info "没有找到日志文件" fi } # 清理测试文件 clean_test_files() { log_info "清理测试文件..." local cleaned_items=() # 清理测试结果目录 if [ -d "test-results" ]; then rm -rf test-results/* cleaned_items+=("test-results目录") fi # 清理覆盖率文件 if [ -d "coverage" ]; then rm -rf coverage/* cleaned_items+=("coverage目录") fi # 清理Go测试缓存 if command -v go >/dev/null 2>&1; then go clean -testcache cleaned_items+=("Go测试缓存") fi # 清理测试相关文件 local test_files=("*.test" "*.out" "*.prof" "cpu.prof" "mem.prof") for test_file in "${test_files[@]}"; do if ls $test_file 1> /dev/null 2>&1; then rm -f $test_file cleaned_items+=("测试文件: $test_file") fi done if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "测试文件清理完成: ${cleaned_items[*]}" else log_info "没有找到测试文件" fi } # 清理Docker资源 clean_docker_resources() { log_info "清理Docker资源..." if ! command -v docker >/dev/null 2>&1; then log_warning "Docker未安装,跳过Docker清理" return 0 fi if ! docker info >/dev/null 2>&1; then log_warning "Docker未运行,跳过Docker清理" return 0 fi local cleaned_items=() # 清理悬空镜像 local dangling_images=$(docker images -f "dangling=true" -q) if [ -n "$dangling_images" ]; then echo "$dangling_images" | xargs docker rmi cleaned_items+=("悬空镜像") fi # 清理未使用的镜像 docker image prune -f >/dev/null 2>&1 cleaned_items+=("未使用镜像") # 清理停止的容器 local stopped_containers=$(docker ps -a -q -f status=exited) if [ -n "$stopped_containers" ]; then echo "$stopped_containers" | xargs docker rm cleaned_items+=("停止的容器") fi # 清理未使用的网络 docker network prune -f >/dev/null 2>&1 cleaned_items+=("未使用网络") # 清理未使用的卷 docker volume prune -f >/dev/null 2>&1 cleaned_items+=("未使用卷") # 清理构建缓存 if command -v docker >/dev/null 2>&1 && docker buildx version >/dev/null 2>&1; then docker buildx prune -f >/dev/null 2>&1 cleaned_items+=("构建缓存") fi if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "Docker资源清理完成: ${cleaned_items[*]}" else log_info "没有找到需要清理的Docker资源" fi } # 清理依赖缓存 clean_dependency_cache() { log_info "清理依赖缓存..." local cleaned_items=() # 清理Go模块缓存 if command -v go >/dev/null 2>&1; then go clean -modcache cleaned_items+=("Go模块缓存") fi # 清理npm缓存 if command -v npm >/dev/null 2>&1; then npm cache clean --force >/dev/null 2>&1 cleaned_items+=("npm缓存") fi # 清理yarn缓存 if command -v yarn >/dev/null 2>&1; then yarn cache clean >/dev/null 2>&1 cleaned_items+=("yarn缓存") fi # 清理pip缓存 if command -v pip >/dev/null 2>&1; then pip cache purge >/dev/null 2>&1 cleaned_items+=("pip缓存") fi if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "依赖缓存清理完成: ${cleaned_items[*]}" else log_info "没有找到依赖缓存" fi } # 清理IDE和编辑器文件 clean_ide_files() { log_info "清理IDE和编辑器文件..." local cleaned_items=() local ide_patterns=( ".vscode/settings.json" ".idea/workspace.xml" ".idea/tasks.xml" "*.sublime-workspace" ".project" ".classpath" ) for pattern in "${ide_patterns[@]}"; do if ls $pattern 1> /dev/null 2>&1; then rm -f $pattern cleaned_items+=("IDE文件: $pattern") fi done # 清理编辑器备份文件 find . -name "*~" -type f -delete 2>/dev/null || true find . -name "*.swp" -type f -delete 2>/dev/null || true find . -name "*.swo" -type f -delete 2>/dev/null || true if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "IDE文件清理完成: ${cleaned_items[*]}" else log_info "没有找到IDE文件" fi } # 清理备份文件 clean_backup_files() { local keep_days="${1:-7}" log_info "清理 $keep_days 天前的备份文件..." local cleaned_items=() # 清理数据库备份 if [ -d "backups" ]; then local old_backups=$(find backups -name "*.tar.gz" -type f -mtime +$keep_days 2>/dev/null || true) if [ -n "$old_backups" ]; then echo "$old_backups" | xargs rm -f local count=$(echo "$old_backups" | wc -l) cleaned_items+=("$count 个旧备份文件") fi fi # 清理日志备份 if [ -d "logs" ]; then local old_log_backups=$(find logs -name "*.log.*.gz" -type f -mtime +$keep_days 2>/dev/null || true) if [ -n "$old_log_backups" ]; then echo "$old_log_backups" | xargs rm -f local count=$(echo "$old_log_backups" | wc -l) cleaned_items+=("$count 个旧日志备份") fi fi if [ ${#cleaned_items[@]} -gt 0 ]; then log_success "备份文件清理完成: ${cleaned_items[*]}" else log_info "没有找到需要清理的备份文件" fi } # 显示磁盘使用情况 show_disk_usage() { log_info "磁盘使用情况:" # 显示项目目录大小 local project_size=$(du -sh . 2>/dev/null | cut -f1) echo " 项目总大小: $project_size" # 显示各个子目录大小 local dirs=("bin" "logs" "tmp" "test-results" "coverage" "backups" "node_modules" ".git") for dir in "${dirs[@]}"; do if [ -d "$dir" ]; then local dir_size=$(du -sh "$dir" 2>/dev/null | cut -f1) echo " $dir: $dir_size" fi done # 显示系统磁盘使用情况 echo "" echo "系统磁盘使用情况:" df -h . | tail -1 | awk '{print " 可用空间: " $4 " / " $2 " (" $5 " 已使用)"}' } # 显示帮助信息 show_help() { echo "Greatest Works 清理脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " -h, --help 显示帮助信息" echo " -a, --all 清理所有内容 (默认)" echo " -b, --build 只清理构建产物" echo " -t, --temp 只清理临时文件" echo " -l, --logs 只清理日志文件" echo " --test 只清理测试文件" echo " --docker 只清理Docker资源" echo " --cache 只清理依赖缓存" echo " --ide 只清理IDE文件" echo " --backup [DAYS] 清理N天前的备份文件 (默认7天)" echo " --dry-run 显示将要清理的内容,但不执行" echo " --usage 显示磁盘使用情况" echo "" echo "示例:" echo " $0 # 清理所有内容" echo " $0 --build # 只清理构建产物" echo " $0 --backup 30 # 清理30天前的备份" echo " $0 --dry-run # 预览清理内容" echo " $0 --usage # 显示磁盘使用情况" } # 主函数 main() { local clean_all=true local clean_build=false local clean_temp=false local clean_logs=false local clean_test=false local clean_docker=false local clean_cache=false local clean_ide=false local clean_backup=false local backup_days=7 local dry_run=false local show_usage=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -a|--all) clean_all=true shift ;; -b|--build) clean_all=false clean_build=true shift ;; -t|--temp) clean_all=false clean_temp=true shift ;; -l|--logs) clean_all=false clean_logs=true shift ;; --test) clean_all=false clean_test=true shift ;; --docker) clean_all=false clean_docker=true shift ;; --cache) clean_all=false clean_cache=true shift ;; --ide) clean_all=false clean_ide=true shift ;; --backup) clean_all=false clean_backup=true if [[ $2 =~ ^[0-9]+$ ]]; then backup_days=$2 shift 2 else shift fi ;; --dry-run) dry_run=true shift ;; --usage) show_usage=true shift ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done log_info "Greatest Works 清理脚本启动" # 显示磁盘使用情况 if [ "$show_usage" = true ]; then show_disk_usage exit 0 fi # 干运行模式 if [ "$dry_run" = true ]; then log_warning "干运行模式:只显示将要清理的内容,不执行实际清理" # 在干运行模式下,可以添加检查逻辑 exit 0 fi # 执行清理操作 if [ "$clean_all" = true ]; then clean_build_artifacts clean_temp_files clean_log_files clean_test_files clean_docker_resources clean_dependency_cache clean_ide_files clean_backup_files 7 else if [ "$clean_build" = true ]; then clean_build_artifacts fi if [ "$clean_temp" = true ]; then clean_temp_files fi if [ "$clean_logs" = true ]; then clean_log_files fi if [ "$clean_test" = true ]; then clean_test_files fi if [ "$clean_docker" = true ]; then clean_docker_resources fi if [ "$clean_cache" = true ]; then clean_dependency_cache fi if [ "$clean_ide" = true ]; then clean_ide_files fi if [ "$clean_backup" = true ]; then clean_backup_files "$backup_days" fi fi # 显示清理后的磁盘使用情况 echo "" show_disk_usage log_success "清理完成!" } # 执行主函数 main "$@" ================================================ FILE: scripts/db-migrate.sh ================================================ #!/bin/bash # Greatest Works - 数据库迁移脚本 # 管理MongoDB和Redis的数据迁移和初始化 set -e # 默认配置 DEFAULT_MONGODB_URI="mongodb://localhost:27017" DEFAULT_DATABASE="gamedb" MIGRATIONS_DIR="./migrations" SEEDS_DIR="./seeds" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 检查MongoDB连接 check_mongodb() { local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" log_info "检查MongoDB连接: $uri" if command -v mongosh >/dev/null 2>&1; then if mongosh "$uri" --eval "db.adminCommand('ping')" >/dev/null 2>&1; then log_success "MongoDB连接正常" return 0 else log_error "MongoDB连接失败" return 1 fi elif command -v mongo >/dev/null 2>&1; then if mongo "$uri" --eval "db.adminCommand('ping')" >/dev/null 2>&1; then log_success "MongoDB连接正常" return 0 else log_error "MongoDB连接失败" return 1 fi else log_error "MongoDB客户端未安装 (mongosh 或 mongo)" return 1 fi } # 检查Redis连接 check_redis() { local addr="${REDIS_ADDR:-localhost:6379}" log_info "检查Redis连接: $addr" if command -v redis-cli >/dev/null 2>&1; then if redis-cli -h "${addr%:*}" -p "${addr#*:}" ping >/dev/null 2>&1; then log_success "Redis连接正常" return 0 else log_error "Redis连接失败" return 1 fi else log_error "Redis客户端未安装 (redis-cli)" return 1 fi } # 创建迁移目录 setup_migration_dirs() { log_info "创建迁移目录..." mkdir -p "$MIGRATIONS_DIR" mkdir -p "$SEEDS_DIR" # 创建迁移记录集合的初始化脚本 if [ ! -f "$MIGRATIONS_DIR/000_init_migrations.js" ]; then cat > "$MIGRATIONS_DIR/000_init_migrations.js" << 'EOF' // 初始化迁移记录集合 db.migrations.createIndex({ "version": 1 }, { unique: true }); db.migrations.createIndex({ "applied_at": 1 }); print("迁移记录集合初始化完成"); EOF log_info "创建初始化迁移文件" fi log_success "迁移目录设置完成" } # 生成新的迁移文件 generate_migration() { local name="$1" if [ -z "$name" ]; then log_error "请提供迁移文件名" return 1 fi local timestamp=$(date +"%Y%m%d%H%M%S") local filename="${timestamp}_${name}.js" local filepath="$MIGRATIONS_DIR/$filename" cat > "$filepath" << EOF // 迁移: $name // 创建时间: $(date) // 版本: $timestamp // 执行迁移 function up() { print("执行迁移: $name"); // 在这里添加迁移逻辑 // 例如: // db.collection.createIndex({ "field": 1 }); // db.collection.updateMany({}, { \$set: { "new_field": "default_value" } }); print("迁移 $name 完成"); } // 回滚迁移 function down() { print("回滚迁移: $name"); // 在这里添加回滚逻辑 // 例如: // db.collection.dropIndex({ "field": 1 }); // db.collection.updateMany({}, { \$unset: { "new_field": "" } }); print("回滚 $name 完成"); } // 执行迁移 up(); // 记录迁移 db.migrations.insertOne({ version: "$timestamp", name: "$name", filename: "$filename", applied_at: new Date() }); EOF log_success "生成迁移文件: $filepath" } # 获取已应用的迁移 get_applied_migrations() { local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" if command -v mongosh >/dev/null 2>&1; then mongosh "$uri/$database" --quiet --eval "db.migrations.find({}, {version: 1, _id: 0}).sort({version: 1}).forEach(function(doc) { print(doc.version); })" else mongo "$uri/$database" --quiet --eval "db.migrations.find({}, {version: 1, _id: 0}).sort({version: 1}).forEach(function(doc) { print(doc.version); })" fi } # 获取待应用的迁移 get_pending_migrations() { local applied_migrations=() # 获取已应用的迁移 while IFS= read -r version; do if [ -n "$version" ]; then applied_migrations+=("$version") fi done < <(get_applied_migrations) # 获取所有迁移文件 local all_migrations=() for file in "$MIGRATIONS_DIR"/*.js; do if [ -f "$file" ]; then local basename=$(basename "$file") local version=$(echo "$basename" | cut -d'_' -f1) all_migrations+=("$version:$file") fi done # 排序 IFS=$'\n' all_migrations=($(sort <<<"${all_migrations[*]}")) unset IFS # 找出待应用的迁移 for migration in "${all_migrations[@]}"; do local version=$(echo "$migration" | cut -d':' -f1) local file=$(echo "$migration" | cut -d':' -f2) local is_applied=false for applied in "${applied_migrations[@]}"; do if [ "$version" = "$applied" ]; then is_applied=true break fi done if [ "$is_applied" = false ]; then echo "$file" fi done } # 应用单个迁移 apply_migration() { local file="$1" local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" log_info "应用迁移: $(basename "$file")" if command -v mongosh >/dev/null 2>&1; then if mongosh "$uri/$database" "$file"; then log_success "迁移应用成功: $(basename "$file")" return 0 else log_error "迁移应用失败: $(basename "$file")" return 1 fi else if mongo "$uri/$database" "$file"; then log_success "迁移应用成功: $(basename "$file")" return 0 else log_error "迁移应用失败: $(basename "$file")" return 1 fi fi } # 运行所有待应用的迁移 run_migrations() { log_info "开始数据库迁移..." local pending_migrations=() while IFS= read -r file; do if [ -n "$file" ]; then pending_migrations+=("$file") fi done < <(get_pending_migrations) if [ ${#pending_migrations[@]} -eq 0 ]; then log_info "没有待应用的迁移" return 0 fi log_info "找到 ${#pending_migrations[@]} 个待应用的迁移" for file in "${pending_migrations[@]}"; do if ! apply_migration "$file"; then log_error "迁移失败,停止执行" return 1 fi done log_success "所有迁移应用完成" } # 回滚迁移 rollback_migration() { local steps="${1:-1}" local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" log_info "回滚最近 $steps 个迁移..." # 获取最近应用的迁移 local recent_migrations if command -v mongosh >/dev/null 2>&1; then recent_migrations=$(mongosh "$uri/$database" --quiet --eval "db.migrations.find({}).sort({applied_at: -1}).limit($steps).forEach(function(doc) { print(doc.filename); })") else recent_migrations=$(mongo "$uri/$database" --quiet --eval "db.migrations.find({}).sort({applied_at: -1}).limit($steps).forEach(function(doc) { print(doc.filename); })") fi if [ -z "$recent_migrations" ]; then log_warning "没有可回滚的迁移" return 0 fi # 回滚每个迁移 echo "$recent_migrations" | while IFS= read -r filename; do if [ -n "$filename" ]; then local filepath="$MIGRATIONS_DIR/$filename" if [ -f "$filepath" ]; then log_info "回滚迁移: $filename" # 创建临时回滚脚本 local rollback_script="/tmp/rollback_$(basename "$filename")" # 提取回滚函数并执行 cat > "$rollback_script" << EOF $(cat "$filepath" | sed -n '/function down()/,/^}/p') // 执行回滚 down(); // 删除迁移记录 db.migrations.deleteOne({filename: "$filename"}); EOF if command -v mongosh >/dev/null 2>&1; then mongosh "$uri/$database" "$rollback_script" else mongo "$uri/$database" "$rollback_script" fi rm "$rollback_script" log_success "回滚完成: $filename" else log_warning "迁移文件不存在: $filepath" fi fi done log_success "迁移回滚完成" } # 显示迁移状态 show_migration_status() { log_info "迁移状态:" # 显示已应用的迁移 local applied_count=0 echo "已应用的迁移:" while IFS= read -r version; do if [ -n "$version" ]; then echo " ✓ $version" ((applied_count++)) fi done < <(get_applied_migrations) # 显示待应用的迁移 local pending_count=0 echo "待应用的迁移:" while IFS= read -r file; do if [ -n "$file" ]; then echo " ○ $(basename "$file")" ((pending_count++)) fi done < <(get_pending_migrations) echo "" echo "统计: 已应用 $applied_count 个,待应用 $pending_count 个" } # 运行种子数据 run_seeds() { local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" log_info "运行种子数据..." if [ ! -d "$SEEDS_DIR" ]; then log_warning "种子数据目录不存在: $SEEDS_DIR" return 0 fi local seed_files=("$SEEDS_DIR"/*.js) if [ ! -f "${seed_files[0]}" ]; then log_warning "没有找到种子数据文件" return 0 fi for file in "${seed_files[@]}"; do if [ -f "$file" ]; then log_info "运行种子数据: $(basename "$file")" if command -v mongosh >/dev/null 2>&1; then mongosh "$uri/$database" "$file" else mongo "$uri/$database" "$file" fi log_success "种子数据运行完成: $(basename "$file")" fi done log_success "所有种子数据运行完成" } # 创建数据库索引 create_indexes() { local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" log_info "创建数据库索引..." # 创建索引脚本 local index_script="/tmp/create_indexes.js" cat > "$index_script" << 'EOF' // Greatest Works - 数据库索引创建脚本 print("开始创建索引..."); // 玩家集合索引 db.players.createIndex({ "user_id": 1 }, { unique: true }); db.players.createIndex({ "name": 1 }, { unique: true }); db.players.createIndex({ "level": -1 }); db.players.createIndex({ "created_at": 1 }); db.players.createIndex({ "last_login": -1 }); print("玩家集合索引创建完成"); // 公会集合索引 db.guilds.createIndex({ "name": 1 }, { unique: true }); db.guilds.createIndex({ "leader_id": 1 }); db.guilds.createIndex({ "level": -1 }); db.guilds.createIndex({ "created_at": 1 }); print("公会集合索引创建完成"); // 战斗记录索引 db.battles.createIndex({ "player_id": 1, "created_at": -1 }); db.battles.createIndex({ "battle_type": 1, "created_at": -1 }); db.battles.createIndex({ "created_at": -1 }); print("战斗记录索引创建完成"); // 排行榜索引 db.rankings.createIndex({ "type": 1, "score": -1 }); db.rankings.createIndex({ "player_id": 1, "type": 1 }); db.rankings.createIndex({ "updated_at": -1 }); print("排行榜索引创建完成"); // 聊天记录索引 db.chats.createIndex({ "channel_id": 1, "created_at": -1 }); db.chats.createIndex({ "player_id": 1, "created_at": -1 }); db.chats.createIndex({ "created_at": -1 }, { expireAfterSeconds: 2592000 }); // 30天过期 print("聊天记录索引创建完成"); // 日志集合索引 db.player_logs.createIndex({ "player_id": 1, "created_at": -1 }); db.player_logs.createIndex({ "action": 1, "created_at": -1 }); db.player_logs.createIndex({ "created_at": -1 }, { expireAfterSeconds: 7776000 }); // 90天过期 print("日志集合索引创建完成"); print("所有索引创建完成"); EOF if command -v mongosh >/dev/null 2>&1; then mongosh "$uri/$database" "$index_script" else mongo "$uri/$database" "$index_script" fi rm "$index_script" log_success "数据库索引创建完成" } # 备份数据库 backup_database() { local backup_dir="./backups" local timestamp=$(date +"%Y%m%d_%H%M%S") local backup_path="$backup_dir/backup_$timestamp" local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" log_info "备份数据库到: $backup_path" mkdir -p "$backup_dir" if command -v mongodump >/dev/null 2>&1; then mongodump --uri="$uri" --db="$database" --out="$backup_path" # 压缩备份 tar -czf "$backup_path.tar.gz" -C "$backup_dir" "backup_$timestamp" rm -rf "$backup_path" log_success "数据库备份完成: $backup_path.tar.gz" else log_error "mongodump未安装,无法备份数据库" return 1 fi } # 恢复数据库 restore_database() { local backup_file="$1" local uri="${MONGODB_URI:-$DEFAULT_MONGODB_URI}" local database="${DATABASE:-$DEFAULT_DATABASE}" if [ -z "$backup_file" ]; then log_error "请指定备份文件" return 1 fi if [ ! -f "$backup_file" ]; then log_error "备份文件不存在: $backup_file" return 1 fi log_info "从备份恢复数据库: $backup_file" # 解压备份文件 local temp_dir="/tmp/restore_$(date +%s)" mkdir -p "$temp_dir" if [[ "$backup_file" == *.tar.gz ]]; then tar -xzf "$backup_file" -C "$temp_dir" else cp -r "$backup_file" "$temp_dir/" fi if command -v mongorestore >/dev/null 2>&1; then mongorestore --uri="$uri" --db="$database" --drop "$temp_dir"/* rm -rf "$temp_dir" log_success "数据库恢复完成" else log_error "mongorestore未安装,无法恢复数据库" rm -rf "$temp_dir" return 1 fi } # 显示帮助信息 show_help() { echo "Greatest Works 数据库迁移脚本" echo "" echo "用法: $0 [命令] [选项]" echo "" echo "命令:" echo " migrate 运行所有待应用的迁移" echo " rollback [N] 回滚最近N个迁移 (默认1个)" echo " status 显示迁移状态" echo " generate NAME 生成新的迁移文件" echo " seed 运行种子数据" echo " index 创建数据库索引" echo " backup 备份数据库" echo " restore FILE 从备份恢复数据库" echo " setup 初始化迁移环境" echo "" echo "选项:" echo " -h, --help 显示帮助信息" echo " --mongodb-uri URI MongoDB连接URI" echo " --database NAME 数据库名称" echo " --redis-addr ADDR Redis地址" echo "" echo "环境变量:" echo " MONGODB_URI MongoDB连接URI" echo " DATABASE 数据库名称" echo " REDIS_ADDR Redis地址" echo "" echo "示例:" echo " $0 migrate # 运行迁移" echo " $0 generate add_user_index # 生成迁移文件" echo " $0 rollback 2 # 回滚2个迁移" echo " $0 backup # 备份数据库" } # 主函数 main() { local command="" # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; --mongodb-uri) export MONGODB_URI="$2" shift 2 ;; --database) export DATABASE="$2" shift 2 ;; --redis-addr) export REDIS_ADDR="$2" shift 2 ;; migrate|rollback|status|generate|seed|index|backup|restore|setup) command="$1" shift break ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done if [ -z "$command" ]; then log_error "请指定命令" show_help exit 1 fi log_info "Greatest Works 数据库迁移脚本启动" log_info "命令: $command" # 执行命令 case $command in "setup") setup_migration_dirs ;; "migrate") if check_mongodb; then setup_migration_dirs run_migrations fi ;; "rollback") if check_mongodb; then local steps="${1:-1}" rollback_migration "$steps" fi ;; "status") if check_mongodb; then show_migration_status fi ;; "generate") local name="$1" generate_migration "$name" ;; "seed") if check_mongodb; then run_seeds fi ;; "index") if check_mongodb; then create_indexes fi ;; "backup") if check_mongodb; then backup_database fi ;; "restore") if check_mongodb; then local backup_file="$1" restore_database "$backup_file" fi ;; *) log_error "未知命令: $command" show_help exit 1 ;; esac log_success "操作完成" } # 执行主函数 main "$@" ================================================ FILE: scripts/deploy.sh ================================================ #!/bin/bash # Greatest Works - 部署脚本 # 支持多环境自动化部署 set -e # 默认配置 DEFAULT_ENV="development" DEFAULT_REGISTRY="registry.example.com" DEFAULT_NAMESPACE="gaming" PROJECT_NAME="greatestworks" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 检查必要的工具 check_dependencies() { local missing_tools=() # 检查Docker if ! command -v docker &> /dev/null; then missing_tools+=("docker") fi # 根据部署类型检查其他工具 case $DEPLOY_TYPE in "kubernetes") if ! command -v kubectl &> /dev/null; then missing_tools+=("kubectl") fi ;; "docker-compose") if ! command -v docker-compose &> /dev/null; then missing_tools+=("docker-compose") fi ;; esac if [ ${#missing_tools[@]} -ne 0 ]; then log_error "缺少必要工具: ${missing_tools[*]}" exit 1 fi } # 构建Docker镜像 build_docker_image() { local tag="$1" log_info "构建Docker镜像: $tag" # 检查Dockerfile是否存在 if [ ! -f "Dockerfile" ]; then log_error "Dockerfile不存在" exit 1 fi # 构建镜像 docker build -t "$tag" . if [ $? -eq 0 ]; then log_success "Docker镜像构建完成: $tag" else log_error "Docker镜像构建失败" exit 1 fi } # 推送Docker镜像 push_docker_image() { local tag="$1" log_info "推送Docker镜像: $tag" # 登录到镜像仓库 if [ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ]; then echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin fi # 推送镜像 docker push "$tag" if [ $? -eq 0 ]; then log_success "Docker镜像推送完成: $tag" else log_error "Docker镜像推送失败" exit 1 fi } # Docker Compose部署 deploy_docker_compose() { local env="$1" local compose_file="docker-compose.yml" # 检查环境特定的compose文件 if [ -f "docker-compose.${env}.yml" ]; then compose_file="docker-compose.${env}.yml" fi log_info "使用Docker Compose部署 (环境: $env)" log_info "Compose文件: $compose_file" # 检查compose文件是否存在 if [ ! -f "$compose_file" ]; then log_error "Docker Compose文件不存在: $compose_file" exit 1 fi # 设置环境变量 export DEPLOY_ENV="$env" export IMAGE_TAG="${IMAGE_TAG:-latest}" # 停止现有服务 log_info "停止现有服务..." docker-compose -f "$compose_file" down # 拉取最新镜像 log_info "拉取最新镜像..." docker-compose -f "$compose_file" pull # 启动服务 log_info "启动服务..." docker-compose -f "$compose_file" up -d # 等待服务启动 sleep 10 # 检查服务状态 log_info "检查服务状态..." docker-compose -f "$compose_file" ps log_success "Docker Compose部署完成" } # Kubernetes部署 deploy_kubernetes() { local env="$1" local namespace="${NAMESPACE:-$DEFAULT_NAMESPACE}" local k8s_dir="k8s" log_info "使用Kubernetes部署 (环境: $env, 命名空间: $namespace)" # 检查k8s目录是否存在 if [ ! -d "$k8s_dir" ]; then log_error "Kubernetes配置目录不存在: $k8s_dir" exit 1 fi # 创建命名空间(如果不存在) kubectl create namespace "$namespace" --dry-run=client -o yaml | kubectl apply -f - # 设置当前上下文的命名空间 kubectl config set-context --current --namespace="$namespace" # 应用配置文件 local config_files=() # 查找环境特定的配置文件 if [ -d "$k8s_dir/$env" ]; then config_files=("$k8s_dir/$env"/*.yaml "$k8s_dir/$env"/*.yml) else config_files=("$k8s_dir"/*.yaml "$k8s_dir"/*.yml) fi # 替换环境变量 export DEPLOY_ENV="$env" export IMAGE_TAG="${IMAGE_TAG:-latest}" export NAMESPACE="$namespace" for config_file in "${config_files[@]}"; do if [ -f "$config_file" ]; then log_info "应用配置: $config_file" envsubst < "$config_file" | kubectl apply -f - fi done # 等待部署完成 log_info "等待部署完成..." kubectl rollout status deployment/$PROJECT_NAME -n "$namespace" --timeout=300s # 显示部署状态 log_info "部署状态:" kubectl get pods -n "$namespace" -l app=$PROJECT_NAME log_success "Kubernetes部署完成" } # 健康检查 health_check() { local url="$1" local max_attempts=30 local attempt=1 log_info "执行健康检查: $url" while [ $attempt -le $max_attempts ]; do if curl -f -s "$url" > /dev/null; then log_success "健康检查通过" return 0 fi log_info "健康检查失败,重试 ($attempt/$max_attempts)..." sleep 10 ((attempt++)) done log_error "健康检查失败,服务可能未正常启动" return 1 } # 回滚部署 rollback_deployment() { local env="$1" log_warning "开始回滚部署..." case $DEPLOY_TYPE in "kubernetes") local namespace="${NAMESPACE:-$DEFAULT_NAMESPACE}" kubectl rollout undo deployment/$PROJECT_NAME -n "$namespace" kubectl rollout status deployment/$PROJECT_NAME -n "$namespace" ;; "docker-compose") # Docker Compose回滚需要手动处理 log_warning "Docker Compose回滚需要手动操作" ;; esac log_success "回滚完成" } # 显示帮助信息 show_help() { echo "Greatest Works 部署脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " -h, --help 显示帮助信息" echo " -e, --env ENV 部署环境 (development/staging/production)" echo " -t, --type TYPE 部署类型 (docker-compose/kubernetes)" echo " -r, --registry URL Docker镜像仓库地址" echo " -n, --namespace NAME Kubernetes命名空间" echo " --tag TAG Docker镜像标签" echo " --build 构建Docker镜像" echo " --push 推送Docker镜像" echo " --no-health-check 跳过健康检查" echo " --rollback 回滚部署" echo "" echo "环境变量:" echo " REGISTRY_USERNAME 镜像仓库用户名" echo " REGISTRY_PASSWORD 镜像仓库密码" echo " HEALTH_CHECK_URL 健康检查URL" echo "" echo "示例:" echo " $0 -e development -t docker-compose" echo " $0 -e production -t kubernetes --build --push" echo " $0 --rollback -e production -t kubernetes" } # 主函数 main() { local env="$DEFAULT_ENV" local deploy_type="docker-compose" local registry="$DEFAULT_REGISTRY" local namespace="$DEFAULT_NAMESPACE" local image_tag="latest" local build_image=false local push_image=false local skip_health_check=false local rollback=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -e|--env) env="$2" shift 2 ;; -t|--type) deploy_type="$2" shift 2 ;; -r|--registry) registry="$2" shift 2 ;; -n|--namespace) namespace="$2" shift 2 ;; --tag) image_tag="$2" shift 2 ;; --build) build_image=true shift ;; --push) push_image=true shift ;; --no-health-check) skip_health_check=true shift ;; --rollback) rollback=true shift ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done # 设置全局变量 export DEPLOY_TYPE="$deploy_type" export REGISTRY="$registry" export NAMESPACE="$namespace" export IMAGE_TAG="$image_tag" log_info "Greatest Works 部署脚本启动" log_info "环境: $env" log_info "部署类型: $deploy_type" log_info "镜像标签: $image_tag" # 检查依赖 check_dependencies # 处理回滚 if [ "$rollback" = true ]; then rollback_deployment "$env" exit 0 fi # 构建镜像 if [ "$build_image" = true ]; then local full_tag="$registry/$PROJECT_NAME:$image_tag" build_docker_image "$full_tag" # 推送镜像 if [ "$push_image" = true ]; then push_docker_image "$full_tag" fi fi # 执行部署 case $deploy_type in "docker-compose") deploy_docker_compose "$env" ;; "kubernetes") deploy_kubernetes "$env" ;; *) log_error "不支持的部署类型: $deploy_type" exit 1 ;; esac # 健康检查 if [ "$skip_health_check" != true ]; then local health_url="${HEALTH_CHECK_URL:-http://localhost:8080/health}" if ! health_check "$health_url"; then log_error "部署可能失败,请检查服务状态" exit 1 fi fi log_success "部署完成!" } # 执行主函数 main "$@" ================================================ FILE: scripts/docker-build.sh ================================================ #!/bin/bash # Greatest Works - Docker构建和推送脚本 # 支持多架构构建和镜像推送 set -e # 默认配置 DEFAULT_REGISTRY="registry.example.com" DEFAULT_NAMESPACE="greatestworks" PROJECT_NAME="greatestworks" DEFAULT_TAG="latest" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 检查Docker环境 check_docker() { if ! command -v docker &> /dev/null; then log_error "Docker未安装" exit 1 fi if ! docker info >/dev/null 2>&1; then log_error "Docker未运行,请启动Docker服务" exit 1 fi log_success "Docker环境检查通过" } # 检查buildx支持 check_buildx() { if ! docker buildx version >/dev/null 2>&1; then log_error "Docker Buildx未安装,无法进行多架构构建" return 1 fi # 创建buildx构建器(如果不存在) if ! docker buildx ls | grep -q "multiarch"; then log_info "创建多架构构建器..." docker buildx create --name multiarch --driver docker-container --use docker buildx inspect --bootstrap else docker buildx use multiarch fi log_success "Docker Buildx准备完成" } # 获取版本信息 get_version_info() { # 从git获取版本信息 if git rev-parse --git-dir >/dev/null 2>&1; then VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev") GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") else VERSION="dev" GIT_COMMIT="unknown" GIT_BRANCH="unknown" fi BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') log_info "版本信息:" log_info " 版本: $VERSION" log_info " 提交: $GIT_COMMIT" log_info " 分支: $GIT_BRANCH" log_info " 构建时间: $BUILD_TIME" } # 构建单架构镜像 build_single_arch() { local registry="$1" local namespace="$2" local tag="$3" local platform="$4" local push="$5" local image_name="$registry/$namespace/$PROJECT_NAME:$tag" log_info "构建镜像: $image_name (平台: $platform)" local build_args=() build_args+=("--platform=$platform") build_args+=("--build-arg=VERSION=$VERSION") build_args+=("--build-arg=GIT_COMMIT=$GIT_COMMIT") build_args+=("--build-arg=GIT_BRANCH=$GIT_BRANCH") build_args+=("--build-arg=BUILD_TIME=$BUILD_TIME") build_args+=("-t=$image_name") if [ "$push" = true ]; then build_args+=("--push") else build_args+=("--load") fi build_args+=(".") if docker buildx build "${build_args[@]}"; then log_success "镜像构建成功: $image_name" return 0 else log_error "镜像构建失败: $image_name" return 1 fi } # 构建多架构镜像 build_multi_arch() { local registry="$1" local namespace="$2" local tag="$3" local platforms="$4" local push="$5" local image_name="$registry/$namespace/$PROJECT_NAME:$tag" log_info "构建多架构镜像: $image_name" log_info "支持平台: $platforms" local build_args=() build_args+=("--platform=$platforms") build_args+=("--build-arg=VERSION=$VERSION") build_args+=("--build-arg=GIT_COMMIT=$GIT_COMMIT") build_args+=("--build-arg=GIT_BRANCH=$GIT_BRANCH") build_args+=("--build-arg=BUILD_TIME=$BUILD_TIME") build_args+=("-t=$image_name") if [ "$push" = true ]; then build_args+=("--push") fi build_args+=(".") if docker buildx build "${build_args[@]}"; then log_success "多架构镜像构建成功: $image_name" return 0 else log_error "多架构镜像构建失败: $image_name" return 1 fi } # 构建开发镜像 build_dev_image() { local tag="dev" log_info "构建开发镜像..." # 使用开发Dockerfile(如果存在) local dockerfile="Dockerfile" if [ -f "Dockerfile.dev" ]; then dockerfile="Dockerfile.dev" fi local image_name="$PROJECT_NAME:$tag" docker build \ -f "$dockerfile" \ --build-arg VERSION="$VERSION" \ --build-arg GIT_COMMIT="$GIT_COMMIT" \ --build-arg BUILD_TIME="$BUILD_TIME" \ -t "$image_name" \ . if [ $? -eq 0 ]; then log_success "开发镜像构建成功: $image_name" return 0 else log_error "开发镜像构建失败" return 1 fi } # 登录到镜像仓库 login_registry() { local registry="$1" if [ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ]; then log_info "登录到镜像仓库: $registry" echo "$REGISTRY_PASSWORD" | docker login "$registry" -u "$REGISTRY_USERNAME" --password-stdin if [ $? -eq 0 ]; then log_success "镜像仓库登录成功" return 0 else log_error "镜像仓库登录失败" return 1 fi else log_warning "未设置镜像仓库凭据,跳过登录" return 0 fi } # 推送镜像 push_image() { local image_name="$1" log_info "推送镜像: $image_name" if docker push "$image_name"; then log_success "镜像推送成功: $image_name" return 0 else log_error "镜像推送失败: $image_name" return 1 fi } # 标记镜像 tag_image() { local source_tag="$1" local target_tag="$2" log_info "标记镜像: $source_tag -> $target_tag" if docker tag "$source_tag" "$target_tag"; then log_success "镜像标记成功" return 0 else log_error "镜像标记失败" return 1 fi } # 清理构建缓存 clean_build_cache() { log_info "清理Docker构建缓存..." # 清理buildx缓存 docker buildx prune -f # 清理未使用的镜像 docker image prune -f # 清理悬空镜像 docker image prune -a -f --filter "until=24h" log_success "构建缓存清理完成" } # 显示镜像信息 show_image_info() { local image_name="$1" log_info "镜像信息: $image_name" if docker image inspect "$image_name" >/dev/null 2>&1; then local size=$(docker image inspect "$image_name" --format='{{.Size}}' | numfmt --to=iec) local created=$(docker image inspect "$image_name" --format='{{.Created}}') local arch=$(docker image inspect "$image_name" --format='{{.Architecture}}') echo " 大小: $size" echo " 创建时间: $created" echo " 架构: $arch" # 显示层信息 echo " 层数: $(docker history "$image_name" --quiet | wc -l)" else log_warning "镜像不存在: $image_name" fi } # 运行安全扫描 run_security_scan() { local image_name="$1" log_info "运行安全扫描: $image_name" # 检查是否安装了安全扫描工具 if command -v trivy >/dev/null 2>&1; then log_info "使用Trivy进行安全扫描..." trivy image "$image_name" elif command -v docker >/dev/null 2>&1 && docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest --version >/dev/null 2>&1; then log_info "使用Docker版Trivy进行安全扫描..." docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image "$image_name" else log_warning "未找到安全扫描工具,跳过安全扫描" fi } # 显示帮助信息 show_help() { echo "Greatest Works Docker构建脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " -h, --help 显示帮助信息" echo " -r, --registry URL 镜像仓库地址" echo " -n, --namespace NAME 命名空间" echo " -t, --tag TAG 镜像标签" echo " --platform PLATFORM 目标平台 (linux/amd64,linux/arm64)" echo " --multi-arch 构建多架构镜像" echo " --dev 构建开发镜像" echo " --push 构建后推送镜像" echo " --no-cache 不使用构建缓存" echo " --scan 运行安全扫描" echo " --clean 清理构建缓存" echo "" echo "环境变量:" echo " REGISTRY_USERNAME 镜像仓库用户名" echo " REGISTRY_PASSWORD 镜像仓库密码" echo " DOCKER_BUILDKIT 启用BuildKit (1/0)" echo "" echo "示例:" echo " $0 # 构建本地镜像" echo " $0 --multi-arch --push # 构建多架构镜像并推送" echo " $0 --dev # 构建开发镜像" echo " $0 --tag v1.0.0 --push # 构建指定版本并推送" echo " $0 --clean # 清理构建缓存" } # 主函数 main() { local registry="$DEFAULT_REGISTRY" local namespace="$DEFAULT_NAMESPACE" local tag="$DEFAULT_TAG" local platforms="linux/amd64" local multi_arch=false local dev_build=false local push=false local no_cache=false local scan=false local clean_only=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; -r|--registry) registry="$2" shift 2 ;; -n|--namespace) namespace="$2" shift 2 ;; -t|--tag) tag="$2" shift 2 ;; --platform) platforms="$2" shift 2 ;; --multi-arch) multi_arch=true platforms="linux/amd64,linux/arm64" shift ;; --dev) dev_build=true shift ;; --push) push=true shift ;; --no-cache) no_cache=true shift ;; --scan) scan=true shift ;; --clean) clean_only=true shift ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done log_info "Greatest Works Docker构建脚本启动" # 检查Docker环境 check_docker # 清理缓存 if [ "$clean_only" = true ]; then clean_build_cache exit 0 fi # 获取版本信息 get_version_info # 启用BuildKit export DOCKER_BUILDKIT=1 # 设置no-cache参数 if [ "$no_cache" = true ]; then export DOCKER_BUILDKIT_NO_CACHE=1 fi # 开发镜像构建 if [ "$dev_build" = true ]; then build_dev_image if [ "$scan" = true ]; then run_security_scan "$PROJECT_NAME:dev" fi exit 0 fi # 登录镜像仓库 if [ "$push" = true ]; then login_registry "$registry" fi # 多架构构建 if [ "$multi_arch" = true ]; then check_buildx build_multi_arch "$registry" "$namespace" "$tag" "$platforms" "$push" else # 单架构构建 if [ "$push" = true ]; then check_buildx build_single_arch "$registry" "$namespace" "$tag" "$platforms" "$push" else # 本地构建 local image_name="$registry/$namespace/$PROJECT_NAME:$tag" docker build \ --build-arg VERSION="$VERSION" \ --build-arg GIT_COMMIT="$GIT_COMMIT" \ --build-arg GIT_BRANCH="$GIT_BRANCH" \ --build-arg BUILD_TIME="$BUILD_TIME" \ -t "$image_name" \ . if [ $? -eq 0 ]; then log_success "镜像构建成功: $image_name" # 显示镜像信息 show_image_info "$image_name" # 安全扫描 if [ "$scan" = true ]; then run_security_scan "$image_name" fi else log_error "镜像构建失败" exit 1 fi fi fi log_success "Docker构建完成!" # 显示后续操作提示 if [ "$push" = false ]; then local image_name="$registry/$namespace/$PROJECT_NAME:$tag" log_info "后续操作:" log_info " 运行镜像: docker run -p 8080:8080 $image_name" log_info " 推送镜像: docker push $image_name" log_info " 安全扫描: $0 --scan" fi } # 执行主函数 main "$@" ================================================ FILE: scripts/generate_proto.bat ================================================ @echo off REM Proto文件生成脚本 (Windows版本) REM 支持Go和C#代码生成 setlocal enabledelayedexpansion echo 开始生成Proto文件... REM 优先使用仓库自带的protoc set PROTOC_PATH=%CD%\protoc\bin\protoc.exe if exist "%PROTOC_PATH%" ( set "PROTOC=%PROTOC_PATH%" ) else ( where protoc >nul 2>nul if %errorlevel% equ 0 ( for /f "delims=" %%i in ('where protoc') do set "PROTOC=%%i" ) else ( echo 错误: 未找到protoc,请安装或将其添加到PATH,或将protoc放置在repo的protoc\bin目录下。 exit /b 1 ) ) REM 检查Go插件 where protoc-gen-go >nul 2>nul if %errorlevel% neq 0 ( echo 安装Go插件... go install google.golang.org/protobuf/cmd/protoc-gen-go@latest ) REM 创建输出目录 if not exist "internal\proto\player" mkdir "internal\proto\player" if not exist "internal\proto\battle" mkdir "internal\proto\battle" if not exist "internal\proto\pet" mkdir "internal\proto\pet" if not exist "internal\proto\common" mkdir "internal\proto\common" if not exist "internal\proto\chat" mkdir "internal\proto\chat" if not exist "internal\proto\team" mkdir "internal\proto\team" if not exist "internal\proto\mail" mkdir "internal\proto\mail" if not exist "internal\proto\room" mkdir "internal\proto\room" if not exist "internal\proto\scene" mkdir "internal\proto\scene" if not exist "internal\proto\gateway" mkdir "internal\proto\gateway" if not exist "csharp\GreatestWorks\Player" mkdir "csharp\GreatestWorks\Player" if not exist "csharp\GreatestWorks\Battle" mkdir "csharp\GreatestWorks\Battle" if not exist "csharp\GreatestWorks\Pet" mkdir "csharp\GreatestWorks\Pet" if not exist "csharp\GreatestWorks\Common" mkdir "csharp\GreatestWorks\Common" if not exist "csharp\GreatestWorks\Chat" mkdir "csharp\GreatestWorks\Chat" if not exist "csharp\GreatestWorks\Team" mkdir "csharp\GreatestWorks\Team" if not exist "csharp\GreatestWorks\Mail" mkdir "csharp\GreatestWorks\Mail" if not exist "csharp\GreatestWorks\Room" mkdir "csharp\GreatestWorks\Room" if not exist "csharp\GreatestWorks\Scene" mkdir "csharp\GreatestWorks\Scene" if not exist "csharp\GreatestWorks\Gateway" mkdir "csharp\GreatestWorks\Gateway" echo 使用的protoc: %PROTOC% echo 生成Go代码... REM 生成Go代码 "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\common.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\player.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\battle.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\pet.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\chat.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\team.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\mail.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\room.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\scene.proto "%PROTOC%" --go_out=. --go_opt=paths=source_relative proto\gateway.proto echo 移动生成的文件到正确位置... REM 移动生成的文件到正确位置 move proto\common.pb.go internal\proto\common\ >nul 2>nul move proto\player.pb.go internal\proto\player\ >nul 2>nul move proto\battle.pb.go internal\proto\battle\ >nul 2>nul move proto\pet.pb.go internal\proto\pet\ >nul 2>nul move proto\chat.pb.go internal\proto\chat\ >nul 2>nul move proto\team.pb.go internal\proto\team\ >nul 2>nul move proto\mail.pb.go internal\proto\mail\ >nul 2>nul move proto\room.pb.go internal\proto\room\ >nul 2>nul move proto\scene.pb.go internal\proto\scene\ >nul 2>nul move proto\gateway.pb.go internal\proto\gateway\ >nul 2>nul echo 生成C#代码... REM 生成C#代码 "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\common.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\player.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\battle.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\pet.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\chat.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\team.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\mail.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\room.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\scene.proto "%PROTOC%" --csharp_out=csharp --csharp_opt=file_extension=.g.cs proto\gateway.proto echo Proto文件生成完成! echo 生成的文件: echo Go代码: internal\proto\ echo C#代码: csharp\ REM 显示生成的文件 echo Go生成的文件: dir /s /b internal\proto\*.pb.go echo C#生成的文件: dir /s /b csharp\*.g.cs echo. pause ================================================ FILE: scripts/generate_proto.sh ================================================ #!/bin/bash # Proto文件生成脚本 # 支持Go和C#代码生成 set -e # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color echo -e "${GREEN}开始生成Proto文件...${NC}" # 检查protoc是否安装 if ! command -v protoc &> /dev/null; then echo -e "${RED}错误: protoc未安装,请先安装Protocol Buffers编译器${NC}" echo "安装方法:" echo " macOS: brew install protobuf" echo " Ubuntu: sudo apt-get install protobuf-compiler" echo " CentOS: sudo yum install protobuf-compiler" exit 1 fi # 检查Go插件 if ! command -v protoc-gen-go &> /dev/null; then echo -e "${YELLOW}安装Go插件...${NC}" go install google.golang.org/protobuf/cmd/protoc-gen-go@latest fi # 检查C#插件 if ! command -v protoc-gen-csharp &> /dev/null; then echo -e "${YELLOW}安装C#插件...${NC}" # C#插件通常随protoc一起安装 echo "请确保已安装C#支持" fi # 创建输出目录 mkdir -p internal/proto/player mkdir -p internal/proto/battle mkdir -p internal/proto/pet mkdir -p internal/proto/common mkdir -p csharp/GreatestWorks/Player mkdir -p csharp/GreatestWorks/Battle mkdir -p csharp/GreatestWorks/Pet mkdir -p csharp/GreatestWorks/Common echo -e "${GREEN}生成Go代码...${NC}" # 生成Go代码 protoc \ --go_out=. \ --go_opt=paths=source_relative \ proto/common.proto protoc \ --go_out=. \ --go_opt=paths=source_relative \ proto/player.proto protoc \ --go_out=. \ --go_opt=paths=source_relative \ proto/battle.proto protoc \ --go_out=. \ --go_opt=paths=source_relative \ proto/pet.proto echo -e "${GREEN}移动生成的文件到正确位置...${NC}" # 移动生成的文件到正确位置 mv proto/common.pb.go internal/proto/common/ 2>/dev/null || true mv proto/player.pb.go internal/proto/player/ 2>/dev/null || true mv proto/battle.pb.go internal/proto/battle/ 2>/dev/null || true mv proto/pet.pb.go internal/proto/pet/ 2>/dev/null || true echo -e "${GREEN}生成C#代码...${NC}" # 生成C#代码 protoc \ --csharp_out=csharp \ --csharp_opt=file_extension=.g.cs \ proto/common.proto protoc \ --csharp_out=csharp \ --csharp_opt=file_extension=.g.cs \ proto/player.proto protoc \ --csharp_out=csharp \ --csharp_opt=file_extension=.g.cs \ proto/battle.proto protoc \ --csharp_out=csharp \ --csharp_opt=file_extension=.g.cs \ proto/pet.proto echo -e "${GREEN}Proto文件生成完成!${NC}" echo -e "${YELLOW}生成的文件:${NC}" echo " Go代码: internal/proto/" echo " C#代码: csharp/" # 显示生成的文件 echo -e "${YELLOW}Go生成的文件:${NC}" find internal/proto -name "*.pb.go" -type f echo -e "${YELLOW}C#生成的文件:${NC}" find csharp -name "*.g.cs" -type f ================================================ FILE: scripts/health-check.sh ================================================ #!/bin/bash # 健康检查脚本 set -e echo "检查服务健康状态..." # 检查HTTP服务器 echo "检查HTTP服务器..." if curl -f http://localhost:8080/health > /dev/null 2>&1; then echo "✓ HTTP服务器正常" else echo "✗ HTTP服务器异常" exit 1 fi # 检查MongoDB echo "检查MongoDB..." if docker exec mmo-server mongosh --host mongodb:27017 --username admin --password admin123 --authenticationDatabase admin --eval "db.runCommand('ping')" > /dev/null 2>&1; then echo "✓ MongoDB正常" else echo "✗ MongoDB异常" exit 1 fi # 检查Redis echo "检查Redis..." if docker exec mmo-server redis-cli -h redis -p 6379 -a redis123 ping > /dev/null 2>&1; then echo "✓ Redis正常" else echo "✗ Redis异常" exit 1 fi # 检查NATS echo "检查NATS..." if nc -z localhost 4222; then echo "✓ NATS正常" else echo "✗ NATS异常" exit 1 fi echo "所有服务健康检查通过!" ================================================ FILE: scripts/init-db.sh ================================================ #!/bin/bash # 数据库初始化脚本 set -e echo "开始初始化数据库..." # 等待MongoDB启动 echo "等待MongoDB启动..." until mongosh --host mongodb:27017 --eval "print('MongoDB is ready')" > /dev/null 2>&1; do echo "等待MongoDB启动..." sleep 2 done # 等待Redis启动 echo "等待Redis启动..." until redis-cli -h redis -p 6379 -a redis123 ping > /dev/null 2>&1; do echo "等待Redis启动..." sleep 2 done # 等待NATS启动 echo "等待NATS启动..." until nc -z nats 4222; do echo "等待NATS启动..." sleep 2 done echo "所有数据库服务已启动" # 执行MongoDB初始化脚本 echo "执行MongoDB初始化..." mongosh --host mongodb:27017 --username admin --password admin123 --authenticationDatabase admin < /scripts/mongo-init.js # 初始化Redis数据 echo "初始化Redis数据..." redis-cli -h redis -p 6379 -a redis123 << EOF FLUSHDB SET server:status "running" SET server:start_time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" SET players:count 0 SET battles:count 0 SET items:count 0 EOF echo "数据库初始化完成" ================================================ FILE: scripts/k8s-deploy.ps1 ================================================ Param( [switch]$UseMinikube = $false, [string]$Namespace = "gaming", [string]$Tag = "dev" ) $ErrorActionPreference = 'Stop' # Repo root and kubectl presence $RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path | Split-Path -Parent Set-Location $RepoRoot if (-not (Get-Command kubectl -ErrorAction SilentlyContinue)) { throw "kubectl is required but not found in PATH" } # Ensure namespace and base infra Write-Host "Applying namespace and base infra (MongoDB, Redis)" kubectl apply -f k8s/local/namespace.yaml | Out-Host kubectl apply -n $Namespace -f k8s/local/mongodb.yaml | Out-Host kubectl apply -n $Namespace -f k8s/local/redis.yaml | Out-Host # Optionally load local images into minikube if ($UseMinikube) { if (-not (Get-Command minikube -ErrorAction SilentlyContinue)) { throw "--UseMinikube specified but 'minikube' command not found" } Write-Host "Loading local images into minikube: greatestworks-auth:$Tag, greatestworks-game:$Tag, greatestworks-gateway:$Tag" minikube image load "greatestworks-auth:$Tag" minikube image load "greatestworks-game:$Tag" minikube image load "greatestworks-gateway:$Tag" } # Apply ConfigMaps and services Write-Host "Applying ConfigMaps and service deployments" kubectl apply -n $Namespace -f k8s/local/configmap-gateway.yaml | Out-Host kubectl apply -n $Namespace -f k8s/local/game-service.yaml | Out-Host kubectl apply -n $Namespace -f k8s/local/auth-service.yaml | Out-Host kubectl apply -n $Namespace -f k8s/local/gateway-service.yaml | Out-Host # Wait for pods readiness Write-Host "Waiting for deployments to be ready..." kubectl -n $Namespace rollout status deployment/mongodb --timeout=120s | Out-Host kubectl -n $Namespace rollout status deployment/redis --timeout=120s | Out-Host kubectl -n $Namespace rollout status deployment/game-service --timeout=180s | Out-Host kubectl -n $Namespace rollout status deployment/auth-service --timeout=180s | Out-Host kubectl -n $Namespace rollout status deployment/gateway-service --timeout=180s | Out-Host Write-Host "Services (NodePort)" kubectl -n $Namespace get svc | Out-Host Write-Host "Done. You can reach:" Write-Host " - Auth HTTP: http://localhost:30080" Write-Host " - Gateway TCP: :30909" ================================================ FILE: scripts/load-images-to-k8s.ps1 ================================================ param( [string]$Tag = "dev" ) $ErrorActionPreference = "Stop" $services = @("auth", "game", "gateway") $images = $services | ForEach-Object { "greatestworks-${_}:${Tag}" } # Add infra images $images += @("mongo:7", "redis:7") Write-Host "Saving images to tar archives..." -ForegroundColor Cyan foreach ($img in $images) { $safeName = $img -replace ":", "_" $tarFile = "tmp-${safeName}.tar" Write-Host " Saving $img -> $tarFile" -ForegroundColor Gray docker save -o $tarFile $img } Write-Host "`nLoading images into kubernetes nodes..." -ForegroundColor Cyan $nodes = kubectl get nodes -o jsonpath='{.items[*].metadata.name}' foreach ($node in $nodes.Split(" ")) { Write-Host " Node: $node" -ForegroundColor Yellow foreach ($img in $images) { $safeName = $img -replace ":", "_" $tarFile = "tmp-${safeName}.tar" Write-Host " Loading $img" -ForegroundColor Gray docker cp $tarFile "${node}:/var/lib/${tarFile}" docker exec $node ctr -n k8s.io images import "/var/lib/${tarFile}" docker exec $node rm "/var/lib/${tarFile}" } } Write-Host "`nCleaning up local tar files..." -ForegroundColor Cyan foreach ($img in $images) { $safeName = $img -replace ":", "_" $tarFile = "tmp-${safeName}.tar" Remove-Item $tarFile -ErrorAction SilentlyContinue } Write-Host "Done! Images are now available in the kubernetes cluster." -ForegroundColor Green Write-Host "You can now deploy with imagePullPolicy: Never or IfNotPresent" -ForegroundColor Green ================================================ FILE: scripts/mongo-init.js ================================================ // MongoDB 初始化脚本 // 创建游戏数据库和基础集合 // 切换到游戏数据库 db = db.getSiblingDB('mmo_game'); // 创建用户集合 db.createCollection('players'); db.createCollection('battles'); db.createCollection('items'); db.createCollection('buildings'); db.createCollection('pets'); db.createCollection('npcs'); db.createCollection('quests'); db.createCollection('rankings'); // 创建索引 db.players.createIndex({ "player_id": 1 }, { unique: true }); db.players.createIndex({ "username": 1 }, { unique: true }); db.players.createIndex({ "email": 1 }, { unique: true }); db.players.createIndex({ "level": -1 }); db.players.createIndex({ "experience": -1 }); db.players.createIndex({ "created_at": 1 }); db.battles.createIndex({ "battle_id": 1 }, { unique: true }); db.battles.createIndex({ "participants": 1 }); db.battles.createIndex({ "status": 1 }); db.battles.createIndex({ "created_at": 1 }); db.items.createIndex({ "item_id": 1 }, { unique: true }); db.items.createIndex({ "type": 1 }); db.items.createIndex({ "rarity": 1 }); db.buildings.createIndex({ "building_id": 1 }, { unique: true }); db.buildings.createIndex({ "owner_id": 1 }); db.buildings.createIndex({ "type": 1 }); db.pets.createIndex({ "pet_id": 1 }, { unique: true }); db.pets.createIndex({ "owner_id": 1 }); db.pets.createIndex({ "type": 1 }); db.npcs.createIndex({ "npc_id": 1 }, { unique: true }); db.npcs.createIndex({ "scene_id": 1 }); db.npcs.createIndex({ "type": 1 }); db.quests.createIndex({ "quest_id": 1 }, { unique: true }); db.quests.createIndex({ "player_id": 1 }); db.quests.createIndex({ "status": 1 }); db.rankings.createIndex({ "type": 1, "rank": 1 }); db.rankings.createIndex({ "player_id": 1 }); // 插入一些基础数据 db.players.insertOne({ player_id: "admin", username: "admin", email: "admin@example.com", level: 1, experience: 0, gold: 1000, created_at: new Date(), updated_at: new Date() }); print("MongoDB 初始化完成"); ================================================ FILE: scripts/publish-images.ps1 ================================================ param( [Parameter(Mandatory = $true)] [string]$Registry, # e.g. docker.io [Parameter(Mandatory = $true)] [string]$Namespace, # e.g. your-dockerhub-username [string]$Tag = "dev", # image tag to publish [switch]$IncludeInfra, # also re-tag/push mongo/redis [string]$MongoImage = "mongo:7", # infra image (optional) [string]$RedisImage = "redis:7" # infra image (optional) ) $ErrorActionPreference = "Stop" function Ensure-LoggedIn() { try { docker info | Out-Null } catch { Write-Host "Docker doesn't seem to be running. Please start Docker Desktop and try again." -ForegroundColor Yellow throw } } function Publish-ServiceImage([string]$service) { $local = "greatestworks-${service}:${Tag}" $remote = "${Registry}/${Namespace}/greatestworks-${service}:${Tag}" Write-Host "Pushing $local -> $remote" -ForegroundColor Cyan # Ensure local exists docker image inspect $local | Out-Null # Re-tag and push docker tag $local $remote docker push $remote } function Publish-InfraImage([string]$image) { # $image like: mongo:7 or redis:7 $parts = $image.Split(":") $name = $parts[0] $tag = if ($parts.Length -gt 1) { $parts[1] } else { "latest" } $remote = "${Registry}/${Namespace}/${name}:${tag}" Write-Host "Re-tagging infra $image -> $remote" -ForegroundColor Cyan # Pull if missing locally (pull may fail if your Docker daemon has an invalid mirror) try { docker image inspect $image | Out-Null } catch { Write-Host "Local image $image not found; attempting to pull..." -ForegroundColor Yellow docker pull $image } docker tag $image $remote docker push $remote } Ensure-LoggedIn $services = @("auth","game","gateway") foreach ($svc in $services) { Publish-ServiceImage $svc } if ($IncludeInfra) { Publish-InfraImage $MongoImage Publish-InfraImage $RedisImage } Write-Host "Done. Use the kustomize overlay at k8s/local/overlays/registry to deploy with these image names." -ForegroundColor Green ================================================ FILE: scripts/setup-dev.sh ================================================ #!/bin/bash # Greatest Works - 开发环境设置脚本 # 一键设置完整的开发环境 set -e # 默认配置 GO_VERSION="1.21" NODE_VERSION="18" DOCKER_COMPOSE_VERSION="2.20.0" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 检测操作系统 detect_os() { if [[ "$OSTYPE" == "linux-gnu"* ]]; then OS="linux" if [ -f /etc/debian_version ]; then DISTRO="debian" elif [ -f /etc/redhat-release ]; then DISTRO="redhat" else DISTRO="unknown" fi elif [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" DISTRO="macos" elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then OS="windows" DISTRO="windows" else OS="unknown" DISTRO="unknown" fi log_info "检测到操作系统: $OS ($DISTRO)" } # 检查命令是否存在 command_exists() { command -v "$1" >/dev/null 2>&1 } # 检查Go环境 check_go() { log_info "检查Go环境..." if command_exists go; then local current_version=$(go version | awk '{print $3}' | sed 's/go//') log_info "当前Go版本: $current_version" # 检查版本是否满足要求 if printf '%s\n%s\n' "$GO_VERSION" "$current_version" | sort -V -C; then log_success "Go版本满足要求" return 0 else log_warning "Go版本过低,建议升级到 $GO_VERSION 或更高版本" fi else log_warning "Go未安装" return 1 fi } # 安装Go install_go() { log_info "安装Go $GO_VERSION..." case $OS in "linux") local arch=$(uname -m) case $arch in "x86_64") arch="amd64" ;; "aarch64") arch="arm64" ;; *) log_error "不支持的架构: $arch"; return 1 ;; esac local go_url="https://golang.org/dl/go${GO_VERSION}.linux-${arch}.tar.gz" local go_file="/tmp/go${GO_VERSION}.linux-${arch}.tar.gz" curl -L "$go_url" -o "$go_file" sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf "$go_file" rm "$go_file" # 添加到PATH if ! grep -q "/usr/local/go/bin" ~/.bashrc; then echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc fi ;; "macos") if command_exists brew; then brew install go else log_error "请先安装Homebrew或手动安装Go" return 1 fi ;; "windows") log_error "Windows环境请手动安装Go" return 1 ;; *) log_error "不支持的操作系统: $OS" return 1 ;; esac log_success "Go安装完成" } # 检查Docker环境 check_docker() { log_info "检查Docker环境..." if command_exists docker; then local docker_version=$(docker --version | awk '{print $3}' | sed 's/,//') log_info "当前Docker版本: $docker_version" # 检查Docker是否运行 if docker info >/dev/null 2>&1; then log_success "Docker运行正常" else log_warning "Docker未运行,请启动Docker服务" fi else log_warning "Docker未安装" return 1 fi # 检查Docker Compose if command_exists docker-compose; then local compose_version=$(docker-compose --version | awk '{print $3}' | sed 's/,//') log_info "当前Docker Compose版本: $compose_version" log_success "Docker Compose已安装" else log_warning "Docker Compose未安装" return 1 fi } # 安装Docker install_docker() { log_info "安装Docker..." case $OS in "linux") case $DISTRO in "debian") # 安装依赖 sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release # 添加Docker GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加Docker仓库 echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 添加用户到docker组 sudo usermod -aG docker $USER ;; "redhat") sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin sudo systemctl start docker sudo systemctl enable docker sudo usermod -aG docker $USER ;; *) log_error "不支持的Linux发行版: $DISTRO" return 1 ;; esac ;; "macos") if command_exists brew; then brew install --cask docker log_info "请手动启动Docker Desktop" else log_error "请先安装Homebrew或手动安装Docker Desktop" return 1 fi ;; "windows") log_error "Windows环境请手动安装Docker Desktop" return 1 ;; *) log_error "不支持的操作系统: $OS" return 1 ;; esac log_success "Docker安装完成" log_warning "请重新登录以使用户组更改生效" } # 检查开发工具 check_dev_tools() { log_info "检查开发工具..." local missing_tools=() # 检查git if ! command_exists git; then missing_tools+=("git") fi # 检查make if ! command_exists make; then missing_tools+=("make") fi # 检查curl if ! command_exists curl; then missing_tools+=("curl") fi if [ ${#missing_tools[@]} -ne 0 ]; then log_warning "缺少开发工具: ${missing_tools[*]}" return 1 else log_success "开发工具检查通过" return 0 fi } # 安装开发工具 install_dev_tools() { log_info "安装开发工具..." case $OS in "linux") case $DISTRO in "debian") sudo apt-get update sudo apt-get install -y git make curl wget unzip build-essential ;; "redhat") sudo yum groupinstall -y "Development Tools" sudo yum install -y git make curl wget unzip ;; *) log_error "不支持的Linux发行版: $DISTRO" return 1 ;; esac ;; "macos") if command_exists brew; then brew install git make curl wget else # 安装Xcode命令行工具 xcode-select --install fi ;; "windows") log_error "Windows环境请手动安装开发工具" return 1 ;; *) log_error "不支持的操作系统: $OS" return 1 ;; esac log_success "开发工具安装完成" } # 安装Go开发工具 install_go_tools() { log_info "安装Go开发工具..." # 检查Go是否可用 if ! command_exists go; then log_error "Go未安装,请先安装Go" return 1 fi # 安装常用Go工具 local go_tools=( "github.com/golangci/golangci-lint/cmd/golangci-lint@latest" "github.com/air-verse/air@latest" "github.com/swaggo/swag/cmd/swag@latest" "github.com/golang/mock/mockgen@latest" "golang.org/x/tools/cmd/goimports@latest" "golang.org/x/tools/gopls@latest" ) for tool in "${go_tools[@]}"; do log_info "安装 $tool..." if go install "$tool"; then log_success "$tool 安装成功" else log_warning "$tool 安装失败" fi done # 确保GOPATH/bin在PATH中 local gopath=$(go env GOPATH) if [ -n "$gopath" ] && [[ ":$PATH:" != *":$gopath/bin:"* ]]; then echo "export PATH=\$PATH:$gopath/bin" >> ~/.bashrc log_info "已添加 $gopath/bin 到PATH" fi log_success "Go开发工具安装完成" } # 设置项目环境 setup_project() { log_info "设置项目环境..." # 检查是否在项目根目录 if [ ! -f "go.mod" ]; then log_error "请在项目根目录运行此脚本" return 1 fi # 下载Go依赖 log_info "下载Go模块依赖..." go mod download go mod tidy # 创建必要的目录 local dirs=("bin" "logs" "tmp" "test-results" "coverage") for dir in "${dirs[@]}"; do if [ ! -d "$dir" ]; then mkdir -p "$dir" log_info "创建目录: $dir" fi done # 复制配置文件模板 if [ ! -f "config.yaml" ]; then if [ -f "configs/config.dev.yaml.example" ]; then cp "configs/config.dev.yaml.example" "config.yaml" log_info "创建开发配置文件: config.yaml" fi fi # 创建.env文件 if [ ! -f ".env" ]; then cat > .env << EOF # Greatest Works 开发环境变量 # 服务器配置 SERVER_PORT=8080 SERVER_HOST=localhost # 数据库配置 MONGODB_URI=mongodb://localhost:27017/gamedb_dev REDIS_ADDR=localhost:6379 REDIS_PASSWORD= # 消息队列 NATS_URL=nats://localhost:4222 # 认证配置 JWT_SECRET=dev-secret-key-change-in-production # 日志配置 LOG_LEVEL=debug LOG_FORMAT=text # 开发模式 DEV_MODE=true HOT_RELOAD=true EOF log_info "创建环境变量文件: .env" fi # 设置Git hooks if [ -d ".git" ]; then setup_git_hooks fi log_success "项目环境设置完成" } # 设置Git hooks setup_git_hooks() { log_info "设置Git hooks..." local hooks_dir=".git/hooks" # pre-commit hook cat > "$hooks_dir/pre-commit" << 'EOF' #!/bin/bash # Greatest Works pre-commit hook set -e echo "运行pre-commit检查..." # 格式化代码 echo "格式化Go代码..." gofmt -w . # 运行代码质量检查 if command -v golangci-lint >/dev/null 2>&1; then echo "运行golangci-lint..." golangci-lint run fi # 运行测试 echo "运行单元测试..." go test -short ./... echo "pre-commit检查通过" EOF chmod +x "$hooks_dir/pre-commit" log_info "设置pre-commit hook" log_success "Git hooks设置完成" } # 启动开发服务 start_dev_services() { log_info "启动开发服务..." # 检查docker-compose文件 local compose_file="docker-compose.dev.yml" if [ ! -f "$compose_file" ]; then compose_file="docker-compose.yml" fi if [ -f "$compose_file" ]; then log_info "启动Docker服务..." docker-compose -f "$compose_file" up -d # 等待服务启动 log_info "等待服务启动..." sleep 10 # 检查服务状态 docker-compose -f "$compose_file" ps log_success "开发服务启动完成" else log_warning "未找到docker-compose文件,跳过服务启动" fi } # 显示帮助信息 show_help() { echo "Greatest Works 开发环境设置脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " -h, --help 显示帮助信息" echo " --check-only 只检查环境,不安装" echo " --skip-docker 跳过Docker安装" echo " --skip-go-tools 跳过Go工具安装" echo " --skip-services 跳过开发服务启动" echo " --force-install 强制重新安装所有组件" echo "" echo "功能:" echo " - 检查和安装Go环境" echo " - 检查和安装Docker环境" echo " - 安装开发工具和Go工具" echo " - 设置项目环境" echo " - 启动开发服务" echo "" echo "示例:" echo " $0 # 完整设置开发环境" echo " $0 --check-only # 只检查环境状态" echo " $0 --skip-docker # 跳过Docker相关设置" } # 主函数 main() { local check_only=false local skip_docker=false local skip_go_tools=false local skip_services=false local force_install=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; --check-only) check_only=true shift ;; --skip-docker) skip_docker=true shift ;; --skip-go-tools) skip_go_tools=true shift ;; --skip-services) skip_services=true shift ;; --force-install) force_install=true shift ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done log_info "Greatest Works 开发环境设置脚本启动" # 检测操作系统 detect_os # 检查基础开发工具 if ! check_dev_tools; then if [ "$check_only" = false ]; then install_dev_tools fi fi # 检查Go环境 if ! check_go || [ "$force_install" = true ]; then if [ "$check_only" = false ]; then install_go fi fi # 检查Docker环境 if [ "$skip_docker" = false ]; then if ! check_docker || [ "$force_install" = true ]; then if [ "$check_only" = false ]; then install_docker fi fi fi if [ "$check_only" = true ]; then log_info "环境检查完成" exit 0 fi # 安装Go开发工具 if [ "$skip_go_tools" = false ]; then install_go_tools fi # 设置项目环境 setup_project # 启动开发服务 if [ "$skip_services" = false ]; then start_dev_services fi log_success "开发环境设置完成!" log_info "请运行以下命令使环境变量生效:" log_info " source ~/.bashrc" log_info "" log_info "然后可以使用以下命令:" log_info " make dev # 启动开发服务器" log_info " make test # 运行测试" log_info " make build # 构建项目" } # 执行主函数 main "$@" ================================================ FILE: scripts/start-services.bat ================================================ @echo off REM 启动分布式游戏服务脚本 REM 基于DDD架构的分布式多节点服务 echo 启动分布式游戏服务... REM 检查Go环境 go version >nul 2>&1 if %errorlevel% neq 0 ( echo 错误: 未找到Go环境,请先安装Go pause exit /b 1 ) REM 创建日志目录 if not exist "logs" mkdir logs REM 启动认证服务 echo 启动认证服务... start "Auth Service" cmd /k "cd /d %~dp0.. && go run cmd/auth-service/main.go" REM 等待认证服务启动 timeout /t 3 /nobreak >nul REM 启动游戏服务 echo 启动游戏服务... start "Game Service" cmd /k "cd /d %~dp0.. && go run cmd/game-service/main.go" REM 等待游戏服务启动 timeout /t 3 /nobreak >nul REM 启动网关服务 echo 启动网关服务... start "Gateway Service" cmd /k "cd /d %~dp0.. && go run cmd/gateway-service/main.go" echo. echo 所有服务已启动! echo. echo 服务地址: echo - 认证服务: http://localhost:8080 echo - 游戏服务: rpc://localhost:8081 echo - 网关服务: tcp://localhost:9090 echo. echo 按任意键关闭所有服务... pause >nul REM 关闭所有服务 echo 正在关闭所有服务... taskkill /f /im go.exe >nul 2>&1 taskkill /f /im cmd.exe >nul 2>&1 echo 所有服务已关闭 pause ================================================ FILE: scripts/start-services.sh ================================================ #!/bin/bash # 启动服务脚本 set -e echo "启动GreatestWorks MMO游戏服务器..." # 检查Docker是否运行 if ! docker info > /dev/null 2>&1; then echo "错误: Docker未运行,请先启动Docker" exit 1 fi # 检查Docker Compose是否可用 if ! command -v docker-compose > /dev/null 2>&1; then echo "错误: Docker Compose未安装" exit 1 fi # 创建必要的目录 mkdir -p logs mkdir -p configs mkdir -p data/mongodb mkdir -p data/redis mkdir -p data/nats # 设置权限 chmod +x scripts/*.sh # 复制环境变量文件(如果不存在) if [ ! -f .env ]; then echo "创建环境变量文件..." cat > .env << EOF # 构建配置 BUILD_TARGET=final BUILD_VERSION=1.0.0 BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) GIT_COMMIT=dev IMAGE_TAG=latest # 应用配置 APP_ENV=development GIN_MODE=debug LOG_LEVEL=info LOG_FORMAT=json # 服务器端口配置 SERVER_HTTP_PORT=8080 SERVER_WS_PORT=8081 SERVER_METRICS_PORT=9090 # 数据库配置 MONGODB_USER=admin MONGODB_PASSWORD=admin123 MONGODB_DATABASE=mmo_game REDIS_PASSWORD=redis123 # 消息队列配置 NATS_CLUSTER_ID=mmo-cluster # 安全配置 JWT_SECRET=your-super-secret-jwt-key-change-in-production ENCRYPTION_KEY=32-character-encryption-key-here # 性能配置 MAX_CONNECTIONS=10000 WORKER_POOL_SIZE=100 CACHE_TTL=3600 # 资源限制 SERVER_CPU_LIMIT=2.0 SERVER_MEMORY_LIMIT=2G SERVER_CPU_RESERVATION=0.5 SERVER_MEMORY_RESERVATION=512M EOF fi # 启动服务 echo "启动Docker Compose服务..." docker-compose up -d # 等待服务启动 echo "等待服务启动..." sleep 10 # 检查服务状态 echo "检查服务状态..." docker-compose ps # 显示日志 echo "显示服务日志..." docker-compose logs --tail=50 echo "服务启动完成!" echo "HTTP服务器: http://localhost:8080" echo "健康检查: http://localhost:8080/health" echo "指标监控: http://localhost:8080/metrics" echo "MongoDB管理: http://localhost:8081" echo "Redis管理: http://localhost:8082" ================================================ FILE: scripts/stop-services.sh ================================================ #!/bin/bash # 停止服务脚本 set -e echo "停止GreatestWorks MMO游戏服务器..." # 停止所有服务 echo "停止Docker Compose服务..." docker-compose down # 清理数据卷(可选) read -p "是否清理数据卷?这将删除所有数据 (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "清理数据卷..." docker-compose down -v docker volume prune -f fi # 清理镜像(可选) read -p "是否清理构建镜像? (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "清理构建镜像..." docker image prune -f fi echo "服务已停止" ================================================ FILE: tools/simclient/README_E2E.md ================================================ # SimClient E2E 测试使用说明 ## 概述 `tools/simclient` 提供了端到端(E2E)测试能力,用于验证网关服务的完整功能链路: - **登录认证**(可选):通过 HTTP 认证服务获取 token - **TCP 连接**:连接到网关 TCP 服务器 - **游戏协议**:发送登录、移动、技能释放、登出等消息 - **AOI 验证**:观察服务器响应与广播 ## 快速开始 ### 1. 准备服务器 确保以下服务正在运行: ```powershell # 启动网关服务(默认端口 9090) go run ./cmd/gateway-service # (可选)启动认证服务(默认端口 8080) go run ./cmd/auth-service ``` ### 2. 运行单次 E2E 测试 ```powershell # 使用提供的 E2E 配置 go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e.yaml -mode=integration # 或使用命令行参数覆盖 go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e.yaml -mode=integration -debug ``` **预期输出示例**: ``` Scenario: e2e-login-move-skill Duration: 1.234s Success: true Actions: gateway.connect 123ms OK gateway.msg.login 45ms OK login.response 12ms OK gateway.msg.move 8ms OK move.response 10ms OK gateway.msg.skill 9ms OK skill.response 11ms OK gateway.msg.move 7ms OK move2.response 10ms OK gateway.msg.logout 6ms OK logout.response 5ms OK ``` ### 3. 运行压力测试 ```powershell # 50 个虚拟玩家,并发 10,每个玩家执行 3 次完整流程 go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e_load.yaml -mode=load # 命令行自定义参数 go run ./tools/simclient/cmd/simclient ^ -config=tools/simclient/e2e_load.yaml ^ -mode=load ^ -users=100 ^ -concurrency=20 ^ -iterations=5 ``` **预期输出示例**: ``` Load Scenario: e2e-load-test Users: 50 Concurrency: 10 Iterations/User: 3 Total Duration: 12.456s Scenarios: 150 (success: 148, failures: 2) Action Metrics: gateway.connect count= 150 success= 150 fail= 0 min= 45ms avg= 123ms p95= 234ms max= 456ms gateway.msg.login count= 150 success= 150 fail= 0 min= 5ms avg= 12ms p95= 23ms max= 45ms gateway.msg.move count= 300 success= 300 fail= 0 min= 3ms avg= 8ms p95= 15ms max= 34ms gateway.msg.skill count= 150 success= 150 fail= 0 min= 4ms avg= 9ms p95= 18ms max= 28ms ... ``` ## E2E 场景流程 新增的 `E2EScenario` (`tools/simclient/e2e_scenario.go`) 执行以下步骤: 1. **认证**(如果 `auth.enabled=true`) - 向 HTTP 认证服务发送登录请求 - 获取并记录 token(当前未附加到后续消息,可扩展) 2. **连接网关** - TCP 连接到 `gateway.host:gateway.port` - 设置读写超时 3. **发送登录包** ```json { "player_id": "123456", "map_id": 1 } ``` - 消息类型:`MsgPlayerLogin` 4. **发送移动包** ```json { "position": {"x": 100.0, "y": 50.0, "z": 10.0} } ``` - 消息类型:`MsgPlayerMove` 5. **发送技能释放包** ```json { "skill_id": 1001, "target_id": 2001 } ``` - 消息类型:`MsgBattleSkill` 6. **再次移动** - 验证多次操作 7. **发送登出包** - 消息类型:`MsgPlayerLogout` 每个步骤后会尝试读取服务器响应(200ms 超时),记录接收到的字节数或超时情况。 ## 配置说明 ### scenario 配置 - `type: e2e` - 使用 E2E 场景(必须) - `name` - 场景名称(用于报告) - `player_prefix` - 玩家名前缀(用于生成唯一 player_id) - `action_interval` - 动作间隔(E2E 场景自动控制,可忽略) - `stop_on_error` - 遇到错误是否立即停止 ### auth 配置 - `enabled` - 是否启用 HTTP 认证 - `base_url` - 认证服务地址(如 `http://localhost:8080`) - `login_path` - 登录路径(如 `/api/v1/auth/login`) - `username/password` - 认证凭据 ### gateway 配置 - `host` - 网关 TCP 服务器地址 - `port` - 网关 TCP 端口(默认 9090) - `connect_timeout` - 连接超时 - `read_timeout/write_timeout` - 读写超时 ### load 配置(压测模式) - `enabled: true` - 启用压测 - `virtual_users` - 虚拟玩家总数 - `concurrency` - 并发执行数 - `iterations` - 每个玩家执行完整流程的次数 - `ramp_up` - 启动所有玩家的时间(平滑启动) - `stop_on_error` - 遇到错误是否停止整个压测 ## 高级用法 ### 自定义场景 如需修改流程,编辑 `tools/simclient/e2e_scenario.go`: ```go // 修改技能 ID 或目标 ID func (s *E2EScenario) Execute(ctx context.Context, client *SimulatorClient) (*ScenarioResult, error) { // ... 现有代码 ... // 自定义:释放多个技能 s.sendSkillCast(result, client, conn, 1001, 2001) s.sendSkillCast(result, client, conn, 1002, 2001) // ... 继续 ... } ``` ### 验证 AOI 广播 当多个 simclient 同时运行时,可观察是否收到来自其他玩家的广播消息: ```powershell # 终端 1 go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e.yaml -mode=integration -debug # 终端 2(稍后启动) go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e.yaml -mode=integration -debug ``` 在 debug 日志中查看是否收到 `entity_move`、`entity_appear` 等 AOI 消息。 ### 集成到 CI/CD ```yaml # .github/workflows/test.yml - name: Run E2E Test run: | go run ./cmd/gateway-service & sleep 5 go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e.yaml -mode=integration killall gateway-service ``` ## 故障排查 ### 连接失败 - **错误**:`dial gateway localhost:9090: connection refused` - **解决**:确保网关服务已启动并监听正确端口 ### 认证失败 - **错误**:`auth request returned 401` - **解决**:检查 `auth.username` 和 `password` 是否正确;或设置 `auth.enabled: false` 跳过认证 ### 超时 - **错误**:大量 `timeout: true` 记录 - **原因**:服务器未及时响应 - **解决**: - 增加 `gateway.read_timeout` - 检查服务器日志是否有错误 - 确认服务器正常处理消息 ### 编译错误 ```powershell # 重新编译 simclient cd c:\Users\HHaou\greatestworks go build -o simclient.exe ./tools/simclient/cmd/simclient ./simclient.exe -config=tools/simclient/e2e.yaml ``` ## 扩展与贡献 欢迎扩展 E2E 场景,如: - 添加队伍组队测试 - 添加聊天消息测试 - 添加背包/交易测试 - 验证 AOI 广播正确性(多玩家视野同步) 修改 `tools/simclient/e2e_scenario.go` 并提交 PR。 --- **相关文件**: - `tools/simclient/e2e_scenario.go` - E2E 场景实现 - `tools/simclient/e2e.yaml` - 单次测试配置 - `tools/simclient/e2e_load.yaml` - 压测配置 - `tools/simclient/cmd/simclient/main.go` - CLI 入口 ================================================ FILE: tools/simclient/action_scenario.go ================================================ package simclient import ( "context" "fmt" "net" "sort" "strconv" "strings" "time" "greatestworks/internal/infrastructure/logging" tcpProtocol "greatestworks/internal/interfaces/tcp/protocol" ) // ActionScenario executes a predefined list of message actions for targeted testing. type ActionScenario struct { cfg ScenarioConfig logger logging.Logger actions []resolvedAction } type resolvedAction struct { name string messageType uint32 flags uint16 expectResponse bool pause time.Duration repeat int } // NewActionScenario constructs an action-driven scenario. func NewActionScenario(cfg ScenarioConfig, logger logging.Logger) (*ActionScenario, error) { localCfg := cfg features := make([]string, 0, len(localCfg.Features)) for _, feature := range localCfg.Features { if feature == "" { continue } features = append(features, normalizeKey(feature)) } // Allow specifying a single feature via type when no explicit features are given. scenarioType := normalizeKey(localCfg.Type) if len(features) == 0 && scenarioType != "" && scenarioType != "basic" { features = append(features, scenarioType) } actions := make([]resolvedAction, 0) for _, feature := range features { steps, ok := featureLibrary[feature] if !ok { return nil, fmt.Errorf("unknown feature %q", feature) } for _, step := range steps { action, err := resolveAction(step, &localCfg) if err != nil { return nil, fmt.Errorf("feature %s: %w", feature, err) } actions = append(actions, action) } } for _, rawAction := range localCfg.Actions { action, err := resolveAction(rawAction, &localCfg) if err != nil { return nil, err } actions = append(actions, action) } if len(actions) == 0 { return nil, fmt.Errorf("no feature actions configured") } return &ActionScenario{ cfg: localCfg, logger: logger, actions: actions, }, nil } // Execute runs each configured action sequentially. func (s *ActionScenario) Execute(ctx context.Context, client *SimulatorClient) (*ScenarioResult, error) { result := &ScenarioResult{ScenarioName: s.cfg.Name, StartedAt: time.Now()} defer func() { result.CompletedAt = time.Now() }() if token, err := authenticateAndRecord(ctx, client, result, s.logger); err != nil { if s.cfg.StopOnError { return result, err } s.logger.Warn("authentication failed but continuing", logging.Fields{"error": err}) } else if token != "" { s.logger.Debug("received auth token", logging.Fields{"token_length": len(token)}) } start := time.Now() conn, err := client.ConnectGateway(ctx) connectLatency := time.Since(start) fields := map[string]interface{}{} if err == nil && conn != nil { fields["remote"] = conn.RemoteAddr().String() } result.Record("gateway.connect", connectLatency, err, fields) if err != nil { return result, err } defer conn.Close() for _, action := range s.actions { for i := 0; i < action.repeat; i++ { select { case <-ctx.Done(): err := ctx.Err() result.Record("scenario.cancelled", 0, err, nil) return result, err default: } stepName := action.name if action.repeat > 1 { stepName = fmt.Sprintf("%s#%d", stepName, i+1) } if err := s.executeAction(result, client, conn, stepName, action); err != nil { if s.cfg.StopOnError { return result, err } s.logger.Warn("action execution failed", logging.Fields{ "action": stepName, "error": err, }) } if action.pause > 0 { select { case <-ctx.Done(): err := ctx.Err() result.Record("scenario.cancelled", 0, err, nil) return result, err case <-time.After(action.pause): } } } } return result, nil } // Name returns the scenario name for logging and metrics. func (s *ActionScenario) Name() string { return s.cfg.Name } func (s *ActionScenario) executeAction(result *ScenarioResult, client *SimulatorClient, conn net.Conn, name string, action resolvedAction) error { start := time.Now() fields := map[string]interface{}{ "message_type": action.messageType, "flags": describeFlags(action.flags), } messageID, err := client.SendGatewayMessage(conn, action.messageType, action.flags) if err == nil { fields["message_id"] = messageID if action.expectResponse { received, respErr := client.TryRead(conn) switch { case respErr != nil: err = respErr case !received: err = fmt.Errorf("no response received") default: fields["response"] = "received" } } } result.Record(name, time.Since(start), err, fields) return err } func resolveAction(cfg ScenarioActionConfig, scenario *ScenarioConfig) (resolvedAction, error) { actionName := cfg.Name if actionName == "" { actionName = cfg.Message } if actionName == "" { return resolvedAction{}, fmt.Errorf("action name or message must be provided") } msgType, err := resolveMessageType(cfg.Message) if err != nil { return resolvedAction{}, fmt.Errorf("action %s: %w", actionName, err) } flags, err := resolveFlags(cfg.Flags) if err != nil { return resolvedAction{}, fmt.Errorf("action %s: %w", actionName, err) } expect := false if cfg.ExpectResponse != nil { expect = *cfg.ExpectResponse } pause := cfg.Pause.AsDuration() if pause == 0 { pause = scenario.ActionInterval.AsDuration() } repeat := cfg.Repeat if repeat <= 0 { repeat = 1 } return resolvedAction{ name: actionName, messageType: msgType, flags: flags, expectResponse: expect, pause: pause, repeat: repeat, }, nil } func resolveMessageType(name string) (uint32, error) { key := normalizeKey(name) if key == "" { return 0, fmt.Errorf("message name is required") } if id, ok := messageNameToID[key]; ok { return id, nil } if strings.HasPrefix(key, "0x") { value, err := strconv.ParseUint(key, 0, 32) if err != nil { return 0, fmt.Errorf("invalid hex message %q: %w", name, err) } return uint32(value), nil } if value, err := strconv.ParseUint(key, 10, 32); err == nil { return uint32(value), nil } return 0, fmt.Errorf("unknown message name %q", name) } func resolveFlags(flagNames []string) (uint16, error) { if len(flagNames) == 0 { return tcpProtocol.FlagRequest, nil } var mask uint16 for _, name := range flagNames { key := normalizeKey(name) if key == "" { continue } value, ok := messageFlagNameToValue[key] if !ok { return 0, fmt.Errorf("unknown flag %q", name) } mask |= value } if mask == 0 { mask = tcpProtocol.FlagRequest } return mask, nil } func describeFlags(mask uint16) string { if mask == 0 { return "" } names := make([]string, 0, len(messageFlagNameToValue)) for name, value := range messageFlagNameToValue { if mask&value != 0 { names = append(names, name) } } sort.Strings(names) return strings.Join(names, ",") } func normalizeKey(value string) string { return strings.TrimSpace(strings.ToLower(value)) } var messageNameToID = map[string]uint32{ "system.heartbeat": tcpProtocol.MsgHeartbeat, "system.handshake": tcpProtocol.MsgHandshake, "system.auth": tcpProtocol.MsgAuth, "player.login": tcpProtocol.MsgPlayerLogin, "player.logout": tcpProtocol.MsgPlayerLogout, "player.move": tcpProtocol.MsgPlayerMove, "player.info": tcpProtocol.MsgPlayerInfo, "player.create": tcpProtocol.MsgPlayerCreate, "player.update": tcpProtocol.MsgPlayerUpdate, "player.delete": tcpProtocol.MsgPlayerDelete, "player.level": tcpProtocol.MsgPlayerLevelUp, "player.exp": tcpProtocol.MsgPlayerExpGain, "player.status": tcpProtocol.MsgPlayerStatus, "player.stats": tcpProtocol.MsgPlayerStats, "player.sync": tcpProtocol.MsgPlayerStatusSync, "battle.create": tcpProtocol.MsgCreateBattle, "battle.join": tcpProtocol.MsgJoinBattle, "battle.start": tcpProtocol.MsgStartBattle, "battle.action": tcpProtocol.MsgBattleAction, "battle.leave": tcpProtocol.MsgLeaveBattle, "battle.result": tcpProtocol.MsgBattleResult, "battle.status": tcpProtocol.MsgBattleStatus, "battle.round": tcpProtocol.MsgBattleRound, "battle.skill": tcpProtocol.MsgBattleSkill, "battle.damage": tcpProtocol.MsgBattleDamage, "query.player_info": tcpProtocol.MsgGetPlayerInfo, "query.online_players": tcpProtocol.MsgGetOnlinePlayers, "query.battle_info": tcpProtocol.MsgGetBattleInfo, "query.player_stats": tcpProtocol.MsgGetPlayerStats, "query.battle_list": tcpProtocol.MsgGetBattleList, "query.rankings": tcpProtocol.MsgGetRankings, "query.server_info": tcpProtocol.MsgGetServerInfo, "pet.summon": tcpProtocol.MsgPetSummon, "pet.dismiss": tcpProtocol.MsgPetDismiss, "pet.info": tcpProtocol.MsgPetInfo, "pet.move": tcpProtocol.MsgPetMove, "pet.action": tcpProtocol.MsgPetAction, "pet.level_up": tcpProtocol.MsgPetLevelUp, "pet.evolution": tcpProtocol.MsgPetEvolution, "pet.train": tcpProtocol.MsgPetTrain, "pet.feed": tcpProtocol.MsgPetFeed, "pet.status": tcpProtocol.MsgPetStatus, "building.create": tcpProtocol.MsgBuildingCreate, "building.upgrade": tcpProtocol.MsgBuildingUpgrade, "building.destroy": tcpProtocol.MsgBuildingDestroy, "building.info": tcpProtocol.MsgBuildingInfo, "building.produce": tcpProtocol.MsgBuildingProduce, "building.collect": tcpProtocol.MsgBuildingCollect, "building.repair": tcpProtocol.MsgBuildingRepair, "building.status": tcpProtocol.MsgBuildingStatus, "social.chat": tcpProtocol.MsgChatMessage, "social.friend_request": tcpProtocol.MsgFriendRequest, "social.friend_accept": tcpProtocol.MsgFriendAccept, "social.friend_reject": tcpProtocol.MsgFriendReject, "social.friend_remove": tcpProtocol.MsgFriendRemove, "social.friend_list": tcpProtocol.MsgFriendList, "social.guild_create": tcpProtocol.MsgGuildCreate, "social.guild_join": tcpProtocol.MsgGuildJoin, "social.guild_leave": tcpProtocol.MsgGuildLeave, "social.guild_info": tcpProtocol.MsgGuildInfo, "social.team_create": tcpProtocol.MsgTeamCreate, "social.team_join": tcpProtocol.MsgTeamJoin, "social.team_leave": tcpProtocol.MsgTeamLeave, "social.team_info": tcpProtocol.MsgTeamInfo, "item.use": tcpProtocol.MsgItemUse, "item.equip": tcpProtocol.MsgItemEquip, "item.unequip": tcpProtocol.MsgItemUnequip, "item.drop": tcpProtocol.MsgItemDrop, "item.pickup": tcpProtocol.MsgItemPickup, "item.trade": tcpProtocol.MsgItemTrade, "item.inventory": tcpProtocol.MsgInventoryInfo, "item.info": tcpProtocol.MsgItemInfo, "item.craft": tcpProtocol.MsgItemCraft, "item.enhance": tcpProtocol.MsgItemEnhance, "quest.accept": tcpProtocol.MsgQuestAccept, "quest.complete": tcpProtocol.MsgQuestComplete, "quest.cancel": tcpProtocol.MsgQuestCancel, "quest.progress": tcpProtocol.MsgQuestProgress, "quest.list": tcpProtocol.MsgQuestList, "quest.info": tcpProtocol.MsgQuestInfo, "quest.reward": tcpProtocol.MsgQuestReward, } var messageFlagNameToValue = map[string]uint16{ "request": tcpProtocol.FlagRequest, "response": tcpProtocol.FlagResponse, "error": tcpProtocol.FlagError, "async": tcpProtocol.FlagAsync, "broadcast": tcpProtocol.FlagBroadcast, "encrypted": tcpProtocol.FlagEncrypted, "compressed": tcpProtocol.FlagCompressed, } ================================================ FILE: tools/simclient/client.go ================================================ package simclient import ( "bytes" "context" "encoding/binary" "encoding/json" "fmt" "hash/fnv" "io" "math/rand" "net" "net/http" "strings" "sync/atomic" "time" "greatestworks/internal/infrastructure/logging" tcpProtocol "greatestworks/internal/interfaces/tcp/protocol" ) var messageIDCounter uint32 func init() { rand.Seed(time.Now().UnixNano()) messageIDCounter = uint32(rand.Int31()) } func nextMessageID() uint32 { return atomic.AddUint32(&messageIDCounter, 1) } // SimulatorClient coordinates HTTP authentication and TCP gateway traffic for a single virtual player. type SimulatorClient struct { id int cfg *Config logger logging.Logger httpClient *http.Client playerName string playerID uint64 seq uint32 } // NewSimulatorClient constructs a simulator client with per-player logging context. func NewSimulatorClient(id int, cfg *Config, baseLogger logging.Logger) *SimulatorClient { cfg.Normalize() httpTimeout := cfg.Auth.Timeout.AsDuration() if httpTimeout <= 0 { httpTimeout = 5 * time.Second } name := fmt.Sprintf("%s_%06d", cfg.Scenario.PlayerPrefix, id) playerID := hashToUint64(name) logger := baseLogger.WithFields(logging.Fields{ "client_id": id, "player": name, "player_id": playerID, "scenario": cfg.Scenario.Name, "gateway": fmt.Sprintf("%s:%d", cfg.Gateway.Host, cfg.Gateway.Port), "auth_enabled": cfg.Auth.Enabled, }) return &SimulatorClient{ id: id, cfg: cfg, logger: logger, httpClient: &http.Client{Timeout: httpTimeout}, playerName: name, playerID: playerID, } } // PlayerName returns the human readable identifier used for hashing. func (c *SimulatorClient) PlayerName() string { return c.playerName } // PlayerID returns the hashed numeric player identifier encoded in gateway headers. func (c *SimulatorClient) PlayerID() uint64 { return c.playerID } // Login authenticates the player via the auth service and returns a bearer token. func (c *SimulatorClient) Login(ctx context.Context) (string, error) { if !c.cfg.Auth.Enabled { c.logger.Debug("auth skipped because auth.enabled=false") return "", nil } payload := map[string]string{ "username": c.cfg.Auth.Username, "password": c.cfg.Auth.Password, } body, err := json.Marshal(payload) if err != nil { return "", fmt.Errorf("marshal login payload: %w", err) } base := strings.TrimRight(c.cfg.Auth.BaseURL, "/") path := c.cfg.Auth.LoginPath if !strings.HasPrefix(path, "/") { path = "/" + path } endpoint := base + path req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { return "", fmt.Errorf("create auth request: %w", err) } req.Header.Set("Content-Type", "application/json") start := time.Now() resp, err := c.httpClient.Do(req) latency := time.Since(start) if err != nil { return "", fmt.Errorf("auth request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 400 { raw, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) return "", fmt.Errorf("auth request returned %d: %s", resp.StatusCode, strings.TrimSpace(string(raw))) } var loginResp struct { Token string `json:"token"` } if err := json.NewDecoder(resp.Body).Decode(&loginResp); err != nil { return "", fmt.Errorf("decode auth response: %w", err) } if loginResp.Token == "" { return "", fmt.Errorf("auth response missing token") } c.logger.Info("authenticated successfully", logging.Fields{ "latency_ms": latency.Milliseconds(), }) return loginResp.Token, nil } // ConnectGateway establishes a TCP connection to the gateway service. func (c *SimulatorClient) ConnectGateway(ctx context.Context) (net.Conn, error) { address := fmt.Sprintf("%s:%d", c.cfg.Gateway.Host, c.cfg.Gateway.Port) dialer := &net.Dialer{Timeout: c.cfg.Gateway.ConnectTimeout.AsDuration()} conn, err := dialer.DialContext(ctx, "tcp", address) if err != nil { return nil, fmt.Errorf("dial gateway %s: %w", address, err) } if timeout := c.cfg.Gateway.ReadTimeout.AsDuration(); timeout > 0 { _ = conn.SetReadDeadline(time.Now().Add(timeout)) } if timeout := c.cfg.Gateway.WriteTimeout.AsDuration(); timeout > 0 { _ = conn.SetWriteDeadline(time.Now().Add(timeout)) } c.logger.Info("connected to gateway", logging.Fields{ "remote": conn.RemoteAddr().String(), }) return conn, nil } // SendGatewayMessage writes a framed header-only message to the gateway. func (c *SimulatorClient) SendGatewayMessage(conn net.Conn, msgType uint32, flags uint16) (uint32, error) { messageID := nextMessageID() seq := atomic.AddUint32(&c.seq, 1) header := buildHeader(messageID, msgType, flags, c.playerID, time.Now().Unix(), seq) if err := conn.SetWriteDeadline(time.Now().Add(c.cfg.Gateway.WriteTimeout.AsDuration())); err != nil { c.logger.Warn("failed to set write deadline", logging.Fields{"error": err}) } if _, err := conn.Write(header); err != nil { return 0, fmt.Errorf("write message to gateway: %w", err) } return messageID, nil } // TryRead attempts to consume a header-sized response, ignoring timeouts to avoid blocking. func (c *SimulatorClient) TryRead(conn net.Conn) (bool, error) { timeout := c.cfg.Gateway.ReadTimeout.AsDuration() if timeout <= 0 { timeout = 250 * time.Millisecond } if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil { return false, err } buf := make([]byte, tcpProtocol.MessageHeaderSize) n, err := conn.Read(buf) if err != nil { if ne, ok := err.(net.Error); ok && ne.Timeout() { return false, nil } if err == io.EOF { return false, io.EOF } return false, fmt.Errorf("read gateway response: %w", err) } if n == 0 { return false, nil } return true, nil } func buildHeader(messageID, messageType uint32, flags uint16, playerID uint64, timestamp int64, sequence uint32) []byte { header := make([]byte, tcpProtocol.MessageHeaderSize) binary.BigEndian.PutUint32(header[0:], tcpProtocol.MessageMagic) binary.BigEndian.PutUint32(header[4:], messageID) binary.BigEndian.PutUint32(header[8:], messageType) binary.BigEndian.PutUint16(header[12:], flags) binary.BigEndian.PutUint64(header[14:], playerID) binary.BigEndian.PutUint64(header[22:], uint64(timestamp)) // Sequence is defined as uint32 but wire format currently uses two bytes; keep lower 16 bits for compatibility. binary.BigEndian.PutUint16(header[30:], uint16(sequence&0xFFFF)) return header } func hashToUint64(value string) uint64 { h := fnv.New64a() _, _ = h.Write([]byte(value)) return h.Sum64() } ================================================ FILE: tools/simclient/cmd/simclient/main.go ================================================ package main import ( "context" "flag" "fmt" "os" "os/signal" "strings" "syscall" "time" "greatestworks/internal/infrastructure/logging" simclient "greatestworks/tools/simclient" ) func main() { var ( configPath string mode string users int concurrency int iterations int enableAuth bool disableAuth bool debug bool ) flag.StringVar(&configPath, "config", "", "Path to simulator YAML configuration") flag.StringVar(&mode, "mode", "integration", "Mode: integration or load") flag.IntVar(&users, "users", 0, "Override virtual user count for load mode") flag.IntVar(&concurrency, "concurrency", 0, "Override concurrency for load mode") flag.IntVar(&iterations, "iterations", 0, "Override scenario iterations per user in load mode") flag.BoolVar(&enableAuth, "auth", false, "Force enable auth flow") flag.BoolVar(&disableAuth, "no-auth", false, "Force disable auth flow") flag.BoolVar(&debug, "debug", false, "Enable verbose debug logging") flag.Parse() cfg, err := simclient.LoadConfigFromFile(configPath) if err != nil { fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err) os.Exit(1) } if enableAuth { cfg.Auth.Enabled = true } if disableAuth { cfg.Auth.Enabled = false } if strings.EqualFold(mode, "load") && !cfg.Load.Enabled { cfg.Load.Enabled = true } if users > 0 { cfg.Load.VirtualUsers = users } if concurrency > 0 { cfg.Load.Concurrency = concurrency } if iterations > 0 { cfg.Load.Iterations = iterations } level := logging.InfoLevel if debug { level = logging.DebugLevel } logger := logging.NewBaseLogger(level) ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() runner, err := simclient.NewRunner(cfg, logger) if err != nil { fmt.Fprintf(os.Stderr, "failed to build scenario: %v\n", err) os.Exit(1) } switch strings.ToLower(mode) { case "integration", "single", "once": result, err := runner.RunOnce(ctx) printScenarioResult(result, err) if err != nil { os.Exit(1) } case "load", "stress": report, err := runner.RunLoad(ctx) if err != nil { fmt.Fprintf(os.Stderr, "load test failed: %v\n", err) os.Exit(1) } printLoadReport(report) if report.Overall.Failures > 0 || len(report.Errors) > 0 { os.Exit(1) } default: fmt.Fprintf(os.Stderr, "unknown mode %q\n", mode) os.Exit(2) } } func printScenarioResult(result *simclient.ScenarioResult, execErr error) { if result == nil { fmt.Println("scenario did not produce a result") if execErr != nil { fmt.Printf("error: %v\n", execErr) } return } duration := result.CompletedAt.Sub(result.StartedAt) fmt.Printf("Scenario: %s\n", result.ScenarioName) fmt.Printf("Duration: %s\n", duration) fmt.Printf("Success: %t\n", result.Success() && execErr == nil) if execErr != nil { fmt.Printf("Error: %v\n", execErr) } fmt.Println("Actions:") for _, action := range result.Actions { status := "OK" if action.Err != nil { status = "ERR" } fmt.Printf(" %-26s %8s %s", action.Name, action.Duration, status) if action.Err != nil { fmt.Printf(" (%v)", action.Err) } fmt.Println() } } func printLoadReport(report *simclient.LoadReport) { if report == nil { fmt.Println("load test produced no report") return } totalDuration := report.CompletedAt.Sub(report.StartedAt) fmt.Printf("Load Scenario: %s\n", report.Scenario) fmt.Printf("Users: %d Concurrency: %d Iterations/User: %d\n", report.Users, report.Concurrency, report.Iterations) fmt.Printf("Total Duration: %s\n", totalDuration) fmt.Printf("Scenarios: %d (success: %d, failures: %d)\n", report.Overall.Scenarios, report.Overall.Successes, report.Overall.Failures) fmt.Println("Action Metrics:") for _, metric := range report.Metrics { fmt.Printf(" %-24s count=%4d success=%4d fail=%3d", metric.Action, metric.Count, metric.Successes, metric.Failures) if metric.Count > 0 { fmt.Printf(" min=%8s avg=%8s p95=%8s max=%8s", metric.Min, metric.Avg, metric.P95, metric.Max) } fmt.Println() } if len(report.Errors) > 0 { fmt.Println("Errors:") for _, err := range report.Errors { fmt.Printf(" %s\n", err) } } fmt.Printf("Completed at %s\n", report.CompletedAt.Format(time.RFC3339)) } ================================================ FILE: tools/simclient/config.example.yaml ================================================ auth: enabled: true base_url: "http://localhost:8080" login_path: "/api/v1/auth/login" username: "tester" password: "tester123" timeout: "5s" gateway: host: "127.0.0.1" port: 9090 connect_timeout: "3s" read_timeout: "2s" write_timeout: "2s" scenario: name: "feature-check" type: "feature" duration: "15s" action_interval: "1s" player_prefix: "loadtester" stop_on_error: true features: - "player.basic" - "pet.basic" - "social.chat" - "social.team_basic" actions: - name: "quest.accept" message: "quest.accept" expect_response: true pause: "2s" load: enabled: true virtual_users: 100 concurrency: 20 ramp_up: "10s" iterations: 2 stop_on_error: false metrics: output: "console" ================================================ FILE: tools/simclient/config.go ================================================ package simclient import ( "fmt" "os" "path/filepath" "time" "gopkg.in/yaml.v3" ) // Duration wraps time.Duration to support YAML unmarshalling from strings like "5s". type Duration struct { time.Duration } // NewDuration constructs a Duration from a time.Duration value. func NewDuration(d time.Duration) Duration { return Duration{Duration: d} } // UnmarshalYAML parses a duration value expressed as a string. func (d *Duration) UnmarshalYAML(value *yaml.Node) error { if value == nil { d.Duration = 0 return nil } switch value.Kind { case yaml.ScalarNode: var raw string if err := value.Decode(&raw); err != nil { return fmt.Errorf("invalid duration value: %w", err) } if raw == "" { d.Duration = 0 return nil } parsed, err := time.ParseDuration(raw) if err != nil { return fmt.Errorf("could not parse duration %q: %w", raw, err) } d.Duration = parsed return nil default: return fmt.Errorf("unsupported YAML node kind for duration: %v", value.Kind) } } // MarshalYAML renders the duration as a human-readable string. func (d Duration) MarshalYAML() (interface{}, error) { return d.Duration.String(), nil } // AsDuration returns the embedded time.Duration, guarding zero defaults. func (d Duration) AsDuration() time.Duration { return d.Duration } // Config contains runtime options for the simulator client. type Config struct { Auth AuthConfig `yaml:"auth"` Gateway GatewayConfig `yaml:"gateway"` Scenario ScenarioConfig `yaml:"scenario"` Load LoadConfig `yaml:"load"` Metrics MetricsConfig `yaml:"metrics"` } // AuthConfig controls optional authentication against the auth service. type AuthConfig struct { Enabled bool `yaml:"enabled"` BaseURL string `yaml:"base_url"` LoginPath string `yaml:"login_path"` Username string `yaml:"username"` Password string `yaml:"password"` Timeout Duration `yaml:"timeout"` } // GatewayConfig holds TCP gateway connection information. type GatewayConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` ConnectTimeout Duration `yaml:"connect_timeout"` ReadTimeout Duration `yaml:"read_timeout"` WriteTimeout Duration `yaml:"write_timeout"` } // ScenarioConfig tunes how long and how frequently simulated actions run. type ScenarioConfig struct { Name string `yaml:"name"` Type string `yaml:"type"` Duration Duration `yaml:"duration"` ActionInterval Duration `yaml:"action_interval"` PlayerPrefix string `yaml:"player_prefix"` StopOnError bool `yaml:"stop_on_error"` Features []string `yaml:"features"` Actions []ScenarioActionConfig `yaml:"actions"` } // ScenarioActionConfig represents a single action step in a feature scenario. type ScenarioActionConfig struct { Name string `yaml:"name"` Message string `yaml:"message"` Flags []string `yaml:"flags"` ExpectResponse *bool `yaml:"expect_response"` Pause Duration `yaml:"pause"` Repeat int `yaml:"repeat"` } // LoadConfig enables multi-user execution for pressure testing. type LoadConfig struct { Enabled bool `yaml:"enabled"` VirtualUsers int `yaml:"virtual_users"` Concurrency int `yaml:"concurrency"` RampUp Duration `yaml:"ramp_up"` Iterations int `yaml:"iterations"` StopOnError bool `yaml:"stop_on_error"` } // MetricsConfig configures how metrics are reported. type MetricsConfig struct { Output string `yaml:"output"` } // DefaultConfig returns a config pre-populated with sensible defaults. func DefaultConfig() Config { return Config{ Auth: AuthConfig{ Enabled: false, BaseURL: "http://localhost:8080", LoginPath: "/api/v1/auth/login", Username: "tester", Password: "tester123", Timeout: NewDuration(5 * time.Second), }, Gateway: GatewayConfig{ Host: "127.0.0.1", Port: 9090, ConnectTimeout: NewDuration(3 * time.Second), ReadTimeout: NewDuration(2 * time.Second), WriteTimeout: NewDuration(2 * time.Second), }, Scenario: ScenarioConfig{ Name: "basic", Type: "basic", Duration: NewDuration(10 * time.Second), ActionInterval: NewDuration(1 * time.Second), PlayerPrefix: "player", StopOnError: true, Features: []string{}, Actions: nil, }, Load: LoadConfig{ Enabled: false, VirtualUsers: 50, Concurrency: 10, RampUp: NewDuration(5 * time.Second), Iterations: 1, StopOnError: false, }, Metrics: MetricsConfig{ Output: "console", }, } } // LoadConfigFromFile loads a configuration file and merges it with defaults. func LoadConfigFromFile(path string) (Config, error) { cfg := DefaultConfig() if path == "" { cfg.Normalize() return cfg, nil } data, err := os.ReadFile(filepath.Clean(path)) if err != nil { return cfg, fmt.Errorf("read config: %w", err) } if err := yaml.Unmarshal(data, &cfg); err != nil { return cfg, fmt.Errorf("parse config: %w", err) } cfg.Normalize() return cfg, nil } // Normalize ensures required fields fall back to defaults when unset. func (c *Config) Normalize() { if c.Gateway.Host == "" { c.Gateway.Host = "127.0.0.1" } if c.Gateway.Port == 0 { c.Gateway.Port = 9090 } if c.Gateway.ConnectTimeout.AsDuration() == 0 { c.Gateway.ConnectTimeout = NewDuration(3 * time.Second) } if c.Gateway.ReadTimeout.AsDuration() == 0 { c.Gateway.ReadTimeout = NewDuration(2 * time.Second) } if c.Gateway.WriteTimeout.AsDuration() == 0 { c.Gateway.WriteTimeout = NewDuration(2 * time.Second) } if c.Scenario.Name == "" { c.Scenario.Name = "basic" } if c.Scenario.Type == "" { c.Scenario.Type = "basic" } if c.Scenario.Duration.AsDuration() == 0 { c.Scenario.Duration = NewDuration(10 * time.Second) } if c.Scenario.ActionInterval.AsDuration() == 0 { c.Scenario.ActionInterval = NewDuration(1 * time.Second) } if c.Scenario.PlayerPrefix == "" { c.Scenario.PlayerPrefix = "player" } if c.Scenario.Features == nil { c.Scenario.Features = []string{} } if c.Load.Concurrency <= 0 { c.Load.Concurrency = 10 } if c.Load.VirtualUsers <= 0 { c.Load.VirtualUsers = c.Load.Concurrency } if c.Load.Iterations <= 0 { c.Load.Iterations = 1 } if c.Load.RampUp.AsDuration() < 0 { c.Load.RampUp = NewDuration(0) } } ================================================ FILE: tools/simclient/e2e.yaml ================================================ # E2E 端到端验证场景配置 # 用法: go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e.yaml -mode=integration scenario: type: e2e name: e2e-login-move-skill player_prefix: test_player action_interval: 500ms duration: 0s # E2E 场景自动完成 stop_on_error: true auth: enabled: false # 如需测试认证,改为 true 并配置 base_url base_url: http://localhost:8080 login_path: /api/v1/auth/login username: testuser password: testpass123 timeout: 5s gateway: host: localhost port: 9090 connect_timeout: 5s read_timeout: 2s write_timeout: 2s load: enabled: false virtual_users: 1 concurrency: 1 iterations: 1 ramp_up: 0s stop_on_error: false ================================================ FILE: tools/simclient/e2e_load.yaml ================================================ # E2E 压力测试配置 # 用法: go run ./tools/simclient/cmd/simclient -config=tools/simclient/e2e_load.yaml -mode=load scenario: type: e2e name: e2e-load-test player_prefix: load_player action_interval: 1s duration: 0s stop_on_error: false auth: enabled: false gateway: host: localhost port: 9090 connect_timeout: 10s read_timeout: 3s write_timeout: 3s load: enabled: true virtual_users: 50 # 模拟50个玩家 concurrency: 10 # 并发10个 iterations: 3 # 每个玩家执行3次完整流程 ramp_up: 5s # 5秒内逐步启动所有玩家 stop_on_error: false ================================================ FILE: tools/simclient/e2e_scenario.go ================================================ package simclient import ( "context" "encoding/binary" "encoding/json" "errors" "fmt" "io" "net" "time" "greatestworks/internal/infrastructure/logging" tcpProtocol "greatestworks/internal/interfaces/tcp/protocol" ) // E2EScenario 端到端场景:登录→移动→技能释放→登出,验证 AOI 广播 type E2EScenario struct { cfg ScenarioConfig logger logging.Logger } // NewE2EScenario 创建端到端测试场景 func NewE2EScenario(cfg ScenarioConfig, logger logging.Logger) *E2EScenario { if cfg.ActionInterval.AsDuration() <= 0 { cfg.ActionInterval = NewDuration(1 * time.Second) } return &E2EScenario{cfg: cfg, logger: logger} } // Name 返回场景名称 func (s *E2EScenario) Name() string { if s.cfg.Name != "" { return s.cfg.Name } return "e2e-workflow" } // Execute 执行端到端场景 func (s *E2EScenario) Execute(ctx context.Context, client *SimulatorClient) (*ScenarioResult, error) { result := &ScenarioResult{ScenarioName: s.Name(), StartedAt: time.Now()} defer func() { result.CompletedAt = time.Now() }() // 步骤1:认证(如果启用) token, err := authenticateAndRecord(ctx, client, result, s.logger) if err != nil && s.cfg.StopOnError { return result, err } _ = token // 可选:后续可用于附加到 TCP 消息 // 步骤2:连接网关 start := time.Now() conn, err := client.ConnectGateway(ctx) connectLatency := time.Since(start) fields := map[string]interface{}{} if err == nil && conn != nil { fields["remote"] = conn.RemoteAddr().String() } result.Record("gateway.connect", connectLatency, err, fields) if err != nil { return result, err } defer conn.Close() // 步骤3:发送 TCP 登录包(PlayerLogin 消息) if err := s.sendLogin(result, client, conn); err != nil { if s.cfg.StopOnError { return result, err } } // 短暂等待登录响应 time.Sleep(100 * time.Millisecond) s.tryReadResponse(result, client, conn, "login.response") // 步骤4:发送移动包(模拟移动到新位置) if err := s.sendMove(result, client, conn, 100.0, 50.0, 10.0); err != nil { if s.cfg.StopOnError { return result, err } } time.Sleep(100 * time.Millisecond) s.tryReadResponse(result, client, conn, "move.response") // 步骤5:发送技能释放包 if err := s.sendSkillCast(result, client, conn, 1001, 2001); err != nil { if s.cfg.StopOnError { return result, err } } time.Sleep(100 * time.Millisecond) s.tryReadResponse(result, client, conn, "skill.response") // 步骤6:再次移动(验证多次操作) if err := s.sendMove(result, client, conn, 120.0, 55.0, 10.0); err != nil { if s.cfg.StopOnError { return result, err } } time.Sleep(100 * time.Millisecond) s.tryReadResponse(result, client, conn, "move2.response") // 步骤7:发送登出包 if err := s.sendLogout(result, client, conn); err != nil { if s.cfg.StopOnError { return result, err } } time.Sleep(100 * time.Millisecond) s.tryReadResponse(result, client, conn, "logout.response") return result, nil } // sendLogin 发送 PlayerLogin 消息(带 JSON payload) func (s *E2EScenario) sendLogin(result *ScenarioResult, client *SimulatorClient, conn net.Conn) error { payload := map[string]interface{}{ "player_id": fmt.Sprintf("%d", client.PlayerID()), "map_id": 1, } return s.sendMessageWithPayload(result, client, conn, "gateway.msg.login", tcpProtocol.MsgPlayerLogin, payload) } // sendMove 发送 PlayerMove 消息 func (s *E2EScenario) sendMove(result *ScenarioResult, client *SimulatorClient, conn net.Conn, x, y, z float64) error { payload := map[string]interface{}{ "position": map[string]interface{}{ "x": x, "y": y, "z": z, }, } return s.sendMessageWithPayload(result, client, conn, "gateway.msg.move", tcpProtocol.MsgPlayerMove, payload) } // sendSkillCast 发送技能释放消息 func (s *E2EScenario) sendSkillCast(result *ScenarioResult, client *SimulatorClient, conn net.Conn, skillID, targetID int32) error { payload := map[string]interface{}{ "skill_id": skillID, "target_id": targetID, } return s.sendMessageWithPayload(result, client, conn, "gateway.msg.skill", tcpProtocol.MsgBattleSkill, payload) } // sendLogout 发送登出消息 func (s *E2EScenario) sendLogout(result *ScenarioResult, client *SimulatorClient, conn net.Conn) error { return s.sendMessageWithPayload(result, client, conn, "gateway.msg.logout", tcpProtocol.MsgPlayerLogout, nil) } // sendMessageWithPayload 发送带 JSON payload 的消息 func (s *E2EScenario) sendMessageWithPayload( result *ScenarioResult, client *SimulatorClient, conn net.Conn, action string, messageType uint32, payload interface{}, ) error { start := time.Now() var payloadBytes []byte var err error if payload != nil { payloadBytes, err = json.Marshal(payload) if err != nil { result.Record(action, time.Since(start), fmt.Errorf("marshal payload: %w", err), nil) return err } } messageID := nextMessageID() seq := client.seq + 1 client.seq = seq // 构造完整消息:Header + Payload header := make([]byte, tcpProtocol.MessageHeaderSize) binary.BigEndian.PutUint32(header[0:], tcpProtocol.MessageMagic) binary.BigEndian.PutUint32(header[4:], messageID) binary.BigEndian.PutUint32(header[8:], messageType) binary.BigEndian.PutUint16(header[12:], tcpProtocol.FlagRequest) binary.BigEndian.PutUint64(header[14:], client.PlayerID()) binary.BigEndian.PutUint64(header[22:], uint64(time.Now().Unix())) binary.BigEndian.PutUint16(header[30:], uint16(seq&0xFFFF)) // 发送 header + payload if err := conn.SetWriteDeadline(time.Now().Add(client.cfg.Gateway.WriteTimeout.AsDuration())); err != nil { s.logger.Warn("failed to set write deadline", logging.Fields{"error": err}) } if _, err := conn.Write(header); err != nil { result.Record(action, time.Since(start), fmt.Errorf("write header: %w", err), nil) return err } if len(payloadBytes) > 0 { if _, err := conn.Write(payloadBytes); err != nil { result.Record(action, time.Since(start), fmt.Errorf("write payload: %w", err), nil) return err } } fields := map[string]interface{}{ "message_type": messageType, "message_id": messageID, "payload_length": len(payloadBytes), } result.Record(action, time.Since(start), nil, fields) return nil } // tryReadResponse 尝试读取响应(不阻塞太久) func (s *E2EScenario) tryReadResponse(result *ScenarioResult, client *SimulatorClient, conn net.Conn, action string) { timeout := 200 * time.Millisecond if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil { return } buf := make([]byte, 4096) start := time.Now() n, err := conn.Read(buf) duration := time.Since(start) if err != nil { if ne, ok := err.(net.Error); ok && ne.Timeout() { // 超时不记录为错误(可能服务器不立即响应) result.Record(action, duration, nil, map[string]interface{}{"timeout": true}) return } if errors.Is(err, io.EOF) { result.Record(action, duration, err, map[string]interface{}{"eof": true}) return } result.Record(action, duration, err, nil) return } result.Record(action, duration, nil, map[string]interface{}{"bytes": n}) } ================================================ FILE: tools/simclient/feature_library.go ================================================ package simclient // helper returns pointer to bool literal func boolPtr(v bool) *bool { return &v } // featureLibrary maps feature identifiers to reusable action sequences. var featureLibrary = map[string][]ScenarioActionConfig{ "system.heartbeat": { {Name: "system.heartbeat", Message: "system.heartbeat", ExpectResponse: boolPtr(true)}, }, "player.login": { {Name: "player.login", Message: "player.login", ExpectResponse: boolPtr(true)}, }, "player.logout": { {Name: "player.logout", Message: "player.logout", ExpectResponse: boolPtr(true)}, }, "player.move": { {Name: "player.move", Message: "player.move", ExpectResponse: boolPtr(true)}, }, "player.info": { {Name: "player.info", Message: "player.info", ExpectResponse: boolPtr(true)}, }, "player.stats": { {Name: "player.stats", Message: "player.stats", ExpectResponse: boolPtr(true)}, }, "player.basic": { {Name: "player.login", Message: "player.login", ExpectResponse: boolPtr(true)}, {Name: "player.info", Message: "player.info", ExpectResponse: boolPtr(true)}, {Name: "player.stats", Message: "player.stats", ExpectResponse: boolPtr(true)}, }, "battle.create": { {Name: "battle.create", Message: "battle.create", ExpectResponse: boolPtr(true)}, }, "battle.join": { {Name: "battle.join", Message: "battle.join", ExpectResponse: boolPtr(true)}, }, "battle.action": { {Name: "battle.action", Message: "battle.action", ExpectResponse: boolPtr(true)}, }, "battle.status": { {Name: "battle.status", Message: "battle.status", ExpectResponse: boolPtr(true)}, }, "battle.basic": { {Name: "battle.create", Message: "battle.create", ExpectResponse: boolPtr(true)}, {Name: "battle.status", Message: "battle.status", ExpectResponse: boolPtr(true)}, }, "pet.summon": { {Name: "pet.summon", Message: "pet.summon", ExpectResponse: boolPtr(true)}, }, "pet.dismiss": { {Name: "pet.dismiss", Message: "pet.dismiss", ExpectResponse: boolPtr(true)}, }, "pet.info": { {Name: "pet.info", Message: "pet.info", ExpectResponse: boolPtr(true)}, }, "pet.status": { {Name: "pet.status", Message: "pet.status", ExpectResponse: boolPtr(true)}, }, "pet.basic": { {Name: "pet.info", Message: "pet.info", ExpectResponse: boolPtr(true)}, {Name: "pet.status", Message: "pet.status", ExpectResponse: boolPtr(true)}, }, "building.create": { {Name: "building.create", Message: "building.create", ExpectResponse: boolPtr(true)}, }, "building.upgrade": { {Name: "building.upgrade", Message: "building.upgrade", ExpectResponse: boolPtr(true)}, }, "building.status": { {Name: "building.status", Message: "building.status", ExpectResponse: boolPtr(true)}, }, "building.basic": { {Name: "building.status", Message: "building.status", ExpectResponse: boolPtr(true)}, }, "social.friend_list": { {Name: "social.friend_list", Message: "social.friend_list", ExpectResponse: boolPtr(true)}, }, "social.friend_remove": { {Name: "social.friend_remove", Message: "social.friend_remove", ExpectResponse: boolPtr(true)}, }, "social.chat": { {Name: "social.chat", Message: "social.chat", ExpectResponse: boolPtr(true)}, }, "social.team_basic": { {Name: "social.team_create", Message: "social.team_create", ExpectResponse: boolPtr(true)}, {Name: "social.team_info", Message: "social.team_info", ExpectResponse: boolPtr(true)}, {Name: "social.team_join", Message: "social.team_join", ExpectResponse: boolPtr(true)}, {Name: "social.team_leave", Message: "social.team_leave", ExpectResponse: boolPtr(true)}, }, "item.use": { {Name: "item.use", Message: "item.use", ExpectResponse: boolPtr(true)}, }, "quest.accept": { {Name: "quest.accept", Message: "quest.accept", ExpectResponse: boolPtr(true)}, }, "quest.progress": { {Name: "quest.progress", Message: "quest.progress", ExpectResponse: boolPtr(true)}, }, "quest.complete": { {Name: "quest.complete", Message: "quest.complete", ExpectResponse: boolPtr(true)}, }, } ================================================ FILE: tools/simclient/main.go ================================================ package simclient // Package simclient contains helper utilities for building simulated clients. // // The runnable CLI lives under cmd/simclient. This file remains solely to // provide package documentation and avoids conflicting package names when running tests. ================================================ FILE: tools/simclient/metrics.go ================================================ package simclient import ( "sort" "sync" "time" ) // MetricsRecorder stores aggregated action timings across many scenarios. type MetricsRecorder struct { mu sync.Mutex totals map[string]*metricAccumulator overall overallStats } type metricAccumulator struct { count int failures int successes int total time.Duration min time.Duration max time.Duration samples []time.Duration } type overallStats struct { scenarios int successes int failures int } // MetricSummary exposes derived statistics for reporting. type MetricSummary struct { Action string Count int Successes int Failures int Min time.Duration Max time.Duration Avg time.Duration P95 time.Duration } // OverallSummary provides coarse scenario-level counts. type OverallSummary struct { Scenarios int Successes int Failures int } // NewMetricsRecorder creates an empty recorder. func NewMetricsRecorder() *MetricsRecorder { return &MetricsRecorder{ totals: make(map[string]*metricAccumulator), } } // AddScenario captures metrics for a scenario execution. func (r *MetricsRecorder) AddScenario(result *ScenarioResult) { if result == nil { return } r.mu.Lock() defer r.mu.Unlock() r.overall.scenarios++ if result.Success() { r.overall.successes++ } else { r.overall.failures++ } for _, action := range result.Actions { acc := r.totals[action.Name] if acc == nil { acc = &metricAccumulator{} r.totals[action.Name] = acc } acc.count++ if action.Err != nil { acc.failures++ } else { acc.successes++ } acc.total += action.Duration acc.samples = append(acc.samples, action.Duration) if acc.count == 1 || action.Duration < acc.min { acc.min = action.Duration } if action.Duration > acc.max { acc.max = action.Duration } } } // Snapshot returns the aggregated metrics summaries. func (r *MetricsRecorder) Snapshot() ([]MetricSummary, OverallSummary) { r.mu.Lock() defer r.mu.Unlock() summaries := make([]MetricSummary, 0, len(r.totals)) for action, acc := range r.totals { if acc.count == 0 { continue } summary := MetricSummary{ Action: action, Count: acc.count, Successes: acc.successes, Failures: acc.failures, } if len(acc.samples) > 0 { sort.Slice(acc.samples, func(i, j int) bool { return acc.samples[i] < acc.samples[j] }) summary.Min = acc.min summary.Max = acc.max summary.Avg = time.Duration(0) divisor := acc.successes + acc.failures if divisor > 0 { summary.Avg = acc.total / time.Duration(divisor) } summary.P95 = percentile(acc.samples, 0.95) } summaries = append(summaries, summary) } sort.Slice(summaries, func(i, j int) bool { return summaries[i].Action < summaries[j].Action }) return summaries, OverallSummary{ Scenarios: r.overall.scenarios, Successes: r.overall.successes, Failures: r.overall.failures, } } func percentile(data []time.Duration, p float64) time.Duration { if len(data) == 0 { return 0 } if p <= 0 { return data[0] } if p >= 1 { return data[len(data)-1] } idx := int(float64(len(data)-1) * p) return data[idx] } ================================================ FILE: tools/simclient/runner.go ================================================ package simclient import ( "context" "fmt" "strings" "sync" "sync/atomic" "time" "greatestworks/internal/infrastructure/logging" ) // Runner orchestrates scenarios for integration or load testing modes. type Runner struct { cfg Config logger logging.Logger scenario Scenario } // NewRunner constructs a runner using the basic scenario by default. func NewRunner(cfg Config, logger logging.Logger) (*Runner, error) { cfg.Normalize() scenarioLogger := logger.WithField("component", "scenario") scenarioCfg := cfg.Scenario scenario, err := buildScenario(&scenarioCfg, scenarioLogger) if err != nil { return nil, err } cfg.Scenario = scenarioCfg return &Runner{ cfg: cfg, logger: logger, scenario: scenario, }, nil } // Config returns a copy of the runner configuration. func (r *Runner) Config() Config { return r.cfg } // RunOnce executes the scenario a single time for integration testing. func (r *Runner) RunOnce(ctx context.Context) (*ScenarioResult, error) { client := NewSimulatorClient(1, &r.cfg, r.logger.WithField("mode", "integration")) result, err := r.scenario.Execute(ctx, client) return result, err } // LoadReport summarises the outcome of a load test run. type LoadReport struct { Scenario string StartedAt time.Time CompletedAt time.Time Users int Concurrency int Iterations int Metrics []MetricSummary Overall OverallSummary Errors []string } // RunLoad executes multiple scenario instances concurrently. func (r *Runner) RunLoad(ctx context.Context) (*LoadReport, error) { if !r.cfg.Load.Enabled { return nil, fmt.Errorf("load testing disabled in configuration") } ctx, cancel := context.WithCancel(ctx) defer cancel() recorder := NewMetricsRecorder() report := &LoadReport{ Scenario: r.scenario.Name(), StartedAt: time.Now(), Users: r.cfg.Load.VirtualUsers, Concurrency: r.cfg.Load.Concurrency, Iterations: r.cfg.Load.Iterations, } semaphore := make(chan struct{}, r.cfg.Load.Concurrency) var wg sync.WaitGroup var errOnce atomic.Bool var errorsMu sync.Mutex outer: for user := 0; user < r.cfg.Load.VirtualUsers; user++ { select { case <-ctx.Done(): r.logger.Warn("load test cancelled before completion", logging.Fields{"error": ctx.Err()}) break outer default: } semaphore <- struct{}{} wg.Add(1) go func(userIndex int) { defer wg.Done() defer func() { <-semaphore }() clientLogger := r.logger.WithFields(logging.Fields{ "mode": "load", "user_index": userIndex, }) client := NewSimulatorClient(userIndex+1, &r.cfg, clientLogger) for iteration := 0; iteration < r.cfg.Load.Iterations; iteration++ { if ctx.Err() != nil { return } result, err := r.scenario.Execute(ctx, client) recorder.AddScenario(result) if err != nil { errorsMu.Lock() if len(report.Errors) < 50 { report.Errors = append(report.Errors, fmt.Sprintf("user %d iteration %d: %v", userIndex+1, iteration+1, err)) } errorsMu.Unlock() if r.cfg.Load.StopOnError && !errOnce.Load() { errOnce.Store(true) cancel() } if r.cfg.Load.StopOnError { return } } } }(user) if ramp := r.cfg.Load.RampUp.AsDuration(); ramp > 0 { perUserDelay := ramp / time.Duration(r.cfg.Load.VirtualUsers) if perUserDelay > 0 { select { case <-ctx.Done(): break outer case <-time.After(perUserDelay): } } } } wg.Wait() report.CompletedAt = time.Now() report.Metrics, report.Overall = recorder.Snapshot() return report, nil } func buildScenario(cfg *ScenarioConfig, logger logging.Logger) (Scenario, error) { scenarioType := strings.TrimSpace(strings.ToLower(cfg.Type)) hasCustomActions := len(cfg.Actions) > 0 || len(cfg.Features) > 0 // E2E 场景优先 if scenarioType == "e2e" { cfg.Type = "e2e" return NewE2EScenario(*cfg, logger), nil } if (scenarioType == "" || scenarioType == "basic") && !hasCustomActions { cfg.Type = "basic" return NewBasicScenario(*cfg, logger), nil } if scenarioType == "" || scenarioType == "basic" { cfg.Type = "feature" } if len(cfg.Features) == 0 { if _, ok := featureLibrary[scenarioType]; ok { cfg.Features = append(cfg.Features, scenarioType) } } scenario, err := NewActionScenario(*cfg, logger) if err != nil { return nil, err } return scenario, nil } ================================================ FILE: tools/simclient/scenario.go ================================================ package simclient import ( "context" "errors" "io" "net" "time" "greatestworks/internal/infrastructure/logging" tcpProtocol "greatestworks/internal/interfaces/tcp/protocol" ) // Scenario represents an executable workflow for a simulated player. type Scenario interface { Name() string Execute(ctx context.Context, client *SimulatorClient) (*ScenarioResult, error) } // ScenarioResult captures per-action latencies and errors for analysis. type ScenarioResult struct { ScenarioName string StartedAt time.Time CompletedAt time.Time Actions []ActionRecord } // ActionRecord stores the outcome of a single simulated step. type ActionRecord struct { Name string Duration time.Duration Err error Data map[string]interface{} } // Record appends an action result to the scenario. func (r *ScenarioResult) Record(name string, duration time.Duration, err error, fields map[string]interface{}) { r.Actions = append(r.Actions, ActionRecord{ Name: name, Duration: duration, Err: err, Data: fields, }) } // Success reports whether every recorded action completed successfully. func (r *ScenarioResult) Success() bool { for _, action := range r.Actions { if action.Err != nil { return false } } return true } // Errors returns a flattened slice of non-nil action errors. func (r *ScenarioResult) Errors() []error { var errs []error for _, action := range r.Actions { if action.Err != nil { errs = append(errs, action.Err) } } return errs } func authenticateAndRecord(ctx context.Context, client *SimulatorClient, result *ScenarioResult, logger logging.Logger) (string, error) { start := time.Now() token, err := client.Login(ctx) result.Record("auth.login", time.Since(start), err, nil) if err == nil && token != "" { logger.Debug("authentication token acquired", logging.Fields{"token_length": len(token)}) } return token, err } // BasicScenario drives a minimal end-to-end flow against the gateway. type BasicScenario struct { cfg ScenarioConfig logger logging.Logger } // NewBasicScenario creates the default scenario implementation. func NewBasicScenario(cfg ScenarioConfig, logger logging.Logger) *BasicScenario { if cfg.ActionInterval.AsDuration() <= 0 { cfg.ActionInterval = NewDuration(1 * time.Second) } if cfg.Duration.AsDuration() <= 0 { cfg.Duration = NewDuration(10 * time.Second) } return &BasicScenario{cfg: cfg, logger: logger} } // Name returns the configured scenario name. func (s *BasicScenario) Name() string { return s.cfg.Name } // Execute runs the scenario for the supplied client. func (s *BasicScenario) Execute(ctx context.Context, client *SimulatorClient) (*ScenarioResult, error) { result := &ScenarioResult{ScenarioName: s.cfg.Name, StartedAt: time.Now()} defer func() { result.CompletedAt = time.Now() }() if _, err := authenticateAndRecord(ctx, client, result, s.logger); err != nil { if s.cfg.StopOnError { return result, err } s.logger.Warn("authentication failed but continuing", logging.Fields{"error": err}) } start := time.Now() conn, err := client.ConnectGateway(ctx) connectLatency := time.Since(start) fields := map[string]interface{}{} if err == nil && conn != nil { fields["remote"] = conn.RemoteAddr().String() } result.Record("gateway.connect", connectLatency, err, fields) if err != nil { return result, err } defer conn.Close() // Login handshake. if err := s.sendMessage(result, client, conn, "gateway.msg.login", tcpProtocol.MsgPlayerLogin); err != nil { if s.cfg.StopOnError { return result, err } } duration := s.cfg.Duration.AsDuration() interval := s.cfg.ActionInterval.AsDuration() if interval <= 0 { interval = time.Second } deadline := time.Now().Add(duration) ticker := time.NewTicker(interval) defer ticker.Stop() for iteration := 0; ; iteration++ { if duration > 0 && time.Now().After(deadline) { break } select { case <-ctx.Done(): err := ctx.Err() result.Record("scenario.cancelled", 0, err, nil) return result, err case <-ticker.C: if err := s.sendMessage(result, client, conn, "gateway.msg.move", tcpProtocol.MsgPlayerMove); err != nil && s.cfg.StopOnError { return result, err } if err := s.sendMessage(result, client, conn, "gateway.msg.heartbeat", tcpProtocol.MsgHeartbeat); err != nil && s.cfg.StopOnError { return result, err } if ok, readErr := client.TryRead(conn); readErr != nil { if errors.Is(readErr, io.EOF) { result.Record("gateway.connection.closed", 0, readErr, nil) return result, readErr } result.Record("gateway.read", 0, readErr, nil) if s.cfg.StopOnError { return result, readErr } } else if ok { result.Record("gateway.read", 0, nil, map[string]interface{}{"bytes": tcpProtocol.MessageHeaderSize}) } } if duration <= 0 && iteration >= 1 { break } } if err := s.sendMessage(result, client, conn, "gateway.msg.logout", tcpProtocol.MsgPlayerLogout); err != nil && s.cfg.StopOnError { return result, err } return result, nil } func (s *BasicScenario) sendMessage(result *ScenarioResult, client *SimulatorClient, conn net.Conn, action string, messageType uint32) error { start := time.Now() messageID, err := client.SendGatewayMessage(conn, messageType, tcpProtocol.FlagRequest) fields := map[string]interface{}{ "message_type": messageType, "message_id": messageID, } result.Record(action, time.Since(start), err, fields) return err } ================================================ FILE: tools/simclient/simclient_test.go ================================================ package simclient import ( "context" "os" "testing" "time" "greatestworks/internal/infrastructure/logging" ) func TestBasicScenarioSmoke(t *testing.T) { if os.Getenv("SIMCLIENT_E2E") != "1" { t.Skip("set SIMCLIENT_E2E=1 to run simulator integration smoke test") } cfg := DefaultConfig() cfg.Auth.Enabled = false cfg.Scenario.Duration = NewDuration(3 * time.Second) cfg.Scenario.ActionInterval = NewDuration(500 * time.Millisecond) logger := logging.NewBaseLogger(logging.DebugLevel) runner, err := NewRunner(cfg, logger) if err != nil { t.Fatalf("failed to build scenario: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() result, err := runner.RunOnce(ctx) if err != nil { t.Fatalf("scenario execution returned error: %v", err) } if result == nil { t.Fatalf("scenario returned nil result") } if !result.Success() { t.Fatalf("scenario actions reported errors: %+v", result.Errors()) } }