├── .github └── workflows │ ├── auto-assign.yml │ ├── codeql-analysis.yml │ ├── go.yml │ ├── push.yaml │ └── reviewdog.yml ├── .gitignore ├── LICENSE ├── README.md ├── api ├── analytics │ └── analytics.proto ├── auth │ └── auth.proto ├── buddy │ ├── buddy.proto │ └── buddy_common.proto ├── chat │ └── chat.proto ├── gen │ ├── analytics │ │ └── api │ │ │ ├── analytics.pb.go │ │ │ ├── analytics.pb.gw.go │ │ │ └── analytics_grpc.pb.go │ ├── auth │ │ └── api │ │ │ ├── auth.pb.go │ │ │ ├── auth.pb.gw.go │ │ │ └── auth_grpc.pb.go │ ├── buddy │ │ └── api │ │ │ ├── buddy.pb.go │ │ │ ├── buddy.pb.gw.go │ │ │ ├── buddy_common.pb.go │ │ │ └── buddy_grpc.pb.go │ ├── chat │ │ └── api │ │ │ ├── chat.pb.go │ │ │ ├── chat.pb.gw.go │ │ │ └── chat_grpc.pb.go │ ├── knapsack │ │ └── api │ │ │ ├── knapsacks.pb.go │ │ │ ├── knapsacks.pb.gw.go │ │ │ └── knapsacks_grpc.pb.go │ ├── leaderboard │ │ └── api │ │ │ ├── leaderboard.pb.go │ │ │ ├── leaderboard.pb.gw.go │ │ │ └── leaderboard_grpc.pb.go │ ├── mail │ │ └── api │ │ │ ├── mail.pb.go │ │ │ ├── mail.pb.gw.go │ │ │ └── mail_grpc.pb.go │ ├── matchmaking │ │ └── api │ │ │ ├── matchmaking.pb.go │ │ │ ├── matchmaking.pb.gw.go │ │ │ └── matchmaking_grpc.pb.go │ ├── party │ │ └── api │ │ │ ├── party.pb.go │ │ │ ├── party.pb.gw.go │ │ │ └── party_grpc.pb.go │ ├── profile │ │ └── api │ │ │ ├── profiles.pb.go │ │ │ ├── profiles.pb.gw.go │ │ │ └── profiles_grpc.pb.go │ └── room │ │ └── api │ │ ├── room_common.pb.go │ │ ├── room_msgid.pb.go │ │ ├── room_notice.pb.go │ │ └── room_req.pb.go ├── google │ ├── README.md │ └── api │ │ ├── annotations.proto │ │ └── http.proto ├── knapsack │ └── knapsacks.proto ├── leaderboard │ └── leaderboard.proto ├── mail │ └── mail.proto ├── matchmaking │ └── matchmaking.proto ├── party │ └── party.proto ├── profile │ └── profiles.proto └── room │ ├── room_common.proto │ ├── room_msgid.proto │ ├── room_notice.proto │ └── room_req.proto ├── buf.gen.yaml ├── buf.lock ├── buf.yaml ├── build └── package │ └── docker │ └── Dockerfile ├── cmd ├── analytics │ └── service │ │ └── main.go ├── auth │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── buddy │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── chat │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── knapsack │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── leaderboard │ └── service │ │ └── main.go ├── mail │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── matchmaking │ └── service │ │ └── main.go ├── party │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── platform │ ├── client │ │ └── main.go │ └── service │ │ └── main.go ├── profile │ ├── client │ │ └── main.go │ └── service │ │ └── main.go └── room │ ├── client │ └── main.go │ └── service │ └── main.go ├── deployment └── k8s │ └── readme.md ├── draws ├── auth-validate.drawio.png ├── auth.drawio.png └── knapsack.drawio.png ├── go.mod ├── go.sum ├── services ├── analytics │ ├── README.md │ ├── client │ │ └── analytics.go │ ├── internal │ │ └── service │ │ │ ├── analytics.go │ │ │ ├── bi │ │ │ ├── clickhouse │ │ │ │ ├── factory.go │ │ │ │ └── internal │ │ │ │ │ ├── processor.go │ │ │ │ │ └── processor_test.go │ │ │ ├── errors.go │ │ │ ├── events.go │ │ │ ├── interfaces.go │ │ │ ├── local │ │ │ │ ├── factory.go │ │ │ │ └── internal │ │ │ │ │ └── processor.go │ │ │ ├── mixpanel │ │ │ │ ├── factory.go │ │ │ │ └── internal │ │ │ │ │ ├── event.go │ │ │ │ │ ├── processor.go │ │ │ │ │ ├── templates.go │ │ │ │ │ └── topics.go │ │ │ └── thinkingdata │ │ │ │ ├── factory.go │ │ │ │ └── internal │ │ │ │ └── processor.go │ │ │ └── service.go │ └── pkg │ │ ├── analyfx │ │ ├── analytics_client.go │ │ └── analytics_settings.go │ │ ├── global │ │ ├── analytics.go │ │ └── mock.go │ │ └── module │ │ └── service.go ├── auth │ ├── README.md │ ├── client │ │ └── auth.go │ ├── internal │ │ ├── db │ │ │ ├── factory.go │ │ │ ├── model │ │ │ │ ├── dao.go │ │ │ │ ├── data.go │ │ │ │ └── keys.go │ │ │ └── redis │ │ │ │ ├── keys.go │ │ │ │ └── status.go │ │ ├── errors.go │ │ ├── handlers.go │ │ ├── service.go │ │ └── utils │ │ │ └── utils.go │ └── pkg │ │ ├── afx │ │ ├── auth_client.go │ │ ├── auth_middleware.go │ │ ├── auth_settings.go │ │ ├── auth_supabase_middleware.go │ │ └── supabase_settings.go │ │ └── module │ │ └── module.go ├── buddy │ ├── README.md │ ├── client │ │ └── buddy.go │ ├── internal │ │ ├── db │ │ │ ├── factory.go │ │ │ └── model │ │ │ │ ├── dao.go │ │ │ │ ├── data │ │ │ │ ├── blocked_list.go │ │ │ │ ├── buddy.go │ │ │ │ └── recent_met.go │ │ │ │ └── keys.go │ │ ├── errors │ │ │ └── errors.go │ │ ├── service │ │ │ ├── handler.go │ │ │ └── service.go │ │ └── utils │ │ │ └── utils.go │ └── pkg │ │ ├── bfx │ │ ├── buddy_client.go │ │ └── buddy_settings.go │ │ └── module │ │ └── service.go ├── chat │ ├── README.md │ ├── internal │ │ └── service │ │ │ ├── db │ │ │ ├── database.go │ │ │ └── keys.go │ │ │ ├── errors │ │ │ └── errors.go │ │ │ ├── private │ │ │ ├── handlers.go │ │ │ └── service.go │ │ │ └── public │ │ │ ├── chatter.go │ │ │ └── service.go │ └── pkg │ │ ├── cfx │ │ ├── chat_client.go │ │ ├── chat_private_client.go │ │ └── chat_settings.go │ │ └── module │ │ └── service.go ├── knapsack │ ├── README.md │ ├── changes │ │ └── topics.go │ ├── errors │ │ └── errors.go │ ├── internal │ │ ├── db │ │ │ ├── factory.go │ │ │ └── model │ │ │ │ ├── dao.go │ │ │ │ ├── data.go │ │ │ │ └── keys.go │ │ └── service │ │ │ ├── private │ │ │ ├── handlers.go │ │ │ └── service.go │ │ │ └── public │ │ │ ├── handlers.go │ │ │ └── service.go │ └── pkg │ │ ├── kfx │ │ ├── knapsack_client.go │ │ └── knapsack_settings.go │ │ └── module │ │ └── module.go ├── leaderboard │ ├── README.md │ ├── internal │ │ ├── db │ │ │ ├── db_redis.go │ │ │ ├── keys.go │ │ │ └── model │ │ │ │ └── playerInfo.go │ │ └── service │ │ │ ├── errors │ │ │ └── errors.go │ │ │ ├── private │ │ │ ├── handlers.go │ │ │ └── service.go │ │ │ └── public │ │ │ ├── handlers.go │ │ │ └── service.go │ └── pkg │ │ ├── lbfx │ │ ├── leaderboard_client_private.go │ │ ├── leaderboard_client_public.go │ │ └── leaderboard_setting.go │ │ └── module │ │ └── module.go ├── mail │ ├── README.md │ ├── internal │ │ └── service │ │ │ ├── common │ │ │ ├── common.go │ │ │ ├── encrypt.go │ │ │ ├── encrypt_test.go │ │ │ └── topics.go │ │ │ ├── db │ │ │ ├── database.go │ │ │ └── keys.go │ │ │ ├── errors │ │ │ └── errors.go │ │ │ ├── private │ │ │ ├── mail.go │ │ │ └── service.go │ │ │ └── public │ │ │ ├── mail.go │ │ │ └── service.go │ └── pkg │ │ ├── mailfx │ │ ├── mail_client.go │ │ ├── mail_client_private.go │ │ └── mail_settings.go │ │ └── module │ │ └── module.go ├── matchmaking │ ├── client │ │ └── client.go │ ├── internal │ │ ├── agones │ │ │ └── allocator.go │ │ ├── director.go │ │ └── service.go │ └── pkg │ │ ├── mmfx │ │ ├── client.go │ │ └── setting.go │ │ └── module │ │ └── module.go ├── party │ ├── README.md │ ├── errors │ │ └── errors.go │ ├── internal │ │ ├── db │ │ │ ├── database.go │ │ │ ├── keys.go │ │ │ └── model.go │ │ └── service │ │ │ ├── common.go │ │ │ └── public │ │ │ ├── handlers.go │ │ │ └── service.go │ └── pkg │ │ ├── module │ │ └── module.go │ │ └── ptfx │ │ ├── party_client.go │ │ └── party_settings.go ├── profile │ ├── README.md │ ├── changes │ │ └── topics.go │ ├── errors │ │ └── errors.go │ ├── internal │ │ ├── db │ │ │ ├── factory.go │ │ │ ├── model │ │ │ │ ├── dao.go │ │ │ │ ├── data.go │ │ │ │ ├── keys.go │ │ │ │ └── privateDao.go │ │ │ └── redis │ │ │ │ ├── basic.go │ │ │ │ ├── keys.go │ │ │ │ ├── name.go │ │ │ │ └── status.go │ │ ├── private │ │ │ ├── handlers.go │ │ │ └── service.go │ │ └── public │ │ │ ├── handlers.go │ │ │ └── service.go │ └── pkg │ │ ├── module │ │ └── module.go │ │ └── pfx │ │ ├── profile_client.go │ │ └── profile_settings.go └── room │ ├── README.md │ ├── client │ └── room.go │ ├── internal │ ├── agones.go │ ├── common │ │ ├── consts.go │ │ └── response.go │ ├── room │ │ ├── factory.go │ │ ├── frames.go │ │ ├── handlers.go │ │ ├── msgsender.go │ │ ├── player │ │ │ ├── error.go │ │ │ └── players.go │ │ ├── riface │ │ │ ├── ihandler.go │ │ │ └── iroom.go │ │ └── room.go │ ├── roommgr.go │ └── service.go │ └── pkg │ ├── module │ └── module.go │ └── rfx │ └── room_settings.go └── tests ├── auth └── auth.js ├── common └── common.js └── readme.md /.github/workflows/auto-assign.yml: -------------------------------------------------------------------------------- 1 | name: Auto Assign 2 | on: 3 | issues: 4 | types: [ opened ] 5 | pull_request: 6 | types: [ opened ] 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - name: 'Auto-assign issue' 15 | uses: pozil/auto-assign-issue@v1 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | assignees: GStones 19 | numOfAssignee: 1 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | pull_request: 5 | schedule: 6 | - cron: '0 10 * * 6' 7 | 8 | jobs: 9 | CodeQL-Build: 10 | 11 | permissions: 12 | actions: read 13 | contents: read 14 | security-events: write 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | 21 | # Initializes the CodeQL tools for scanning. 22 | - name: Initialize CodeQL 23 | uses: github/codeql-action/init@v2 24 | with: 25 | languages: go 26 | 27 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 28 | # If this step fails, then you should remove it and run the build manually (see below) 29 | - name: Autobuild 30 | uses: github/codeql-action/autobuild@v2 31 | 32 | - name: Perform CodeQL Analysis 33 | uses: github/codeql-action/analyze@v2 34 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [pull_request] 3 | jobs: 4 | 5 | test: 6 | permissions: 7 | contents: read 8 | name: Test 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 50 # Need git history for testing. 15 | 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version-file: "go.mod" 19 | 20 | - name: Test 21 | run: go test -v -race -coverpkg=./... -coverprofile=coverage.txt ./... 22 | 23 | - uses: codecov/codecov-action@v3 24 | -------------------------------------------------------------------------------- /.github/workflows/push.yaml: -------------------------------------------------------------------------------- 1 | name: buf-push 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: bufbuild/buf-setup-action@v1 12 | # - uses: bufbuild/buf-lint-action@v1 13 | - uses: bufbuild/buf-breaking-action@v1 14 | with: 15 | # The 'main' branch of the GitHub repository that defines the module. 16 | against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,ref=HEAD~1" 17 | - uses: bufbuild/buf-push-action@v1 18 | with: 19 | buf_token: ${{ secrets.BUF_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /third_party/ 2 | /.idea/* 3 | /.vscode/* 4 | 5 | /logs/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 moke-game 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/analytics/analytics.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Analytics service for sending analytics events, 4 | // support multi delivery type: local,thinkingdata,clickhouse,mixpanel etc. 5 | // 分析服务用于发送分析事件 6 | // 支持多种投递方式: local,thinkingdata,clickhouse,mixpanel等 7 | package analytics.v1; 8 | option go_package = "analytics/api;v1"; 9 | import "google/api/annotations.proto"; 10 | 11 | service AnalyticsService { 12 | // Analytics send a batch of analytics events to the analytics service,return Nothing 13 | // Recommend to use async/multi events at once 14 | // 发送一批分析事件到分析服务,返回Nothing 15 | // 建议使用异步+批量事件一次发送 16 | rpc Analytics(AnalyticsEvents) returns (Nothing) { 17 | option (google.api.http) = { 18 | post: "/v1/analytics" 19 | body: "*" 20 | }; 21 | } 22 | } 23 | 24 | message AnalyticsEvents { 25 | repeated Event events = 1; 26 | } 27 | 28 | enum DeliveryType { 29 | // Deliver to the local file 30 | Local = 0; 31 | 32 | // Deliver to ThinkingData 33 | // https://www.thinkingdata.cn/ 34 | ThinkingData = 1; 35 | 36 | //Deliver to clickhouse 37 | ClickHouse = 2; 38 | // Deliver to Mixpanel 39 | // https://mixpanel.com/ 40 | Mixpanel = 3; 41 | } 42 | 43 | // AnalyticsEvent is a single analytics event to capture. 44 | message Event { 45 | // The unique name for this event. Be pragmatic with event names and store additional properties in the 46 | // properties field. 47 | //NOTE: only contain: number,letter(ignoring case) and underscore"_" ,no spaces in the configuration 48 | string event = 1; 49 | 50 | // Generic JSON property key/value pairs. {"id":"fun","age":10} 51 | bytes properties = 2; 52 | 53 | // Where to deliver this event to, defaults to Local. 54 | DeliveryType deliver_to = 3; 55 | 56 | // user_id is the unique identifier for the user. 57 | // if use thinkingdata ,distinct_id /user_id is required 58 | string user_id = 4; 59 | // distinct_id is the unique identifier for the user/visitor. 60 | // if use thinkingdata ,distinct_id /user_id is required 61 | string distinct_id = 5; 62 | } 63 | 64 | // Nothing is an empty message. Used when there's nothing to send. 65 | message Nothing {} 66 | -------------------------------------------------------------------------------- /api/buddy/buddy_common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package buddy.v1; 3 | option go_package = "buddy/api;buddy"; 4 | 5 | // Nothing is used when there is no data to be sent. 6 | message Nothing { 7 | } 8 | 9 | message ProfileId { 10 | string profile_id = 1; 11 | } 12 | // Buddy contains state associated with a buddy. 13 | message Buddy { 14 | string uid = 1; 15 | int32 receive_reward = 2; 16 | bool is_favorite = 3; 17 | string remark = 4; 18 | int64 act_time=5; 19 | } 20 | 21 | message Inviter{ 22 | string uid = 1; 23 | string req_info = 2; 24 | int64 req_time = 3; 25 | } 26 | 27 | message Blocked{ 28 | string uid = 1; 29 | int64 add_time = 2; 30 | } 31 | 32 | message Buddies { 33 | map buddies = 1; 34 | map inviters = 2; 35 | map inviter_sends = 3; 36 | map blocked = 4; 37 | } 38 | 39 | message ProfileIds { 40 | repeated ProfileId profile_ids = 1; 41 | } -------------------------------------------------------------------------------- /api/chat/chat.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | // Chat Service is used for real-time communication 3 | // 聊天服务 用于实时通讯 4 | package chat.v1; 5 | 6 | option go_package = "chat/api;chat"; 7 | 8 | // ChatService provides chat service. 9 | service ChatService { 10 | // Chat create a stream to send and receive chat information. 11 | rpc Chat(stream ChatRequest) returns (stream ChatResponse); 12 | } 13 | 14 | service ChatPrivateService { 15 | // add blocked list 16 | rpc AddBlocked(AddBlockedRequest) returns (AddBlockedResponse); 17 | } 18 | 19 | message Destination { 20 | int32 channel = 1; 21 | string id = 2; 22 | } 23 | 24 | message ChatMessage { 25 | //message we send or receive chat information, It carries the necessary information 26 | message Message { 27 | //user id 28 | string profile_id = 1; 29 | //who started the chat 30 | string nickname = 2; 31 | //user avatar 32 | string avatar = 3; 33 | // user avatar frame 34 | int32 avatar_frame = 4; 35 | // user gender 36 | int32 gender = 5; 37 | //content 38 | string content = 6; 39 | //emoji 40 | int32 emoji = 7; 41 | //send time 42 | int64 timestamp = 8; 43 | } 44 | 45 | //send where 46 | Destination destination = 1; 47 | 48 | //chat all content 49 | Message message = 2; 50 | } 51 | 52 | message ChatRequest { 53 | message Subscribe { 54 | //who started the chat 55 | string profile_id = 1; 56 | //send where 57 | Destination destination = 2; 58 | } 59 | 60 | message UnSubscribe { 61 | //who started the chat 62 | string profile_id = 1; 63 | //send where 64 | Destination destination = 2; 65 | } 66 | 67 | oneof kind { 68 | Subscribe subscribe = 1; 69 | UnSubscribe unsubscribe = 3; 70 | ChatMessage message = 2; 71 | } 72 | } 73 | 74 | message ChatError { 75 | enum Code { 76 | CODE_NONE = 0; 77 | CODE_INTERVAL = 1; 78 | CODE_BLOCKED = 2; 79 | } 80 | Code code = 1; 81 | } 82 | 83 | message ChatResponse { 84 | oneof kind { 85 | ChatMessage message = 1; 86 | ChatError error = 2; 87 | } 88 | } 89 | 90 | message AddBlockedRequest { 91 | // profile id 92 | string profile_id = 1; 93 | bool is_blocked = 2; // true: add blocked list, false: remove blocked list 94 | int64 duration = 3; // block time, 0: permanent (s) 95 | } 96 | 97 | message AddBlockedResponse {} 98 | -------------------------------------------------------------------------------- /api/google/README.md: -------------------------------------------------------------------------------- 1 | # GOOGLE API 2 | 3 | 当前依赖只是用于 tests文件夹中的测试脚本运行使用,脚本生成使用buf工具 -------------------------------------------------------------------------------- /api/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /api/matchmaking/matchmaking.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // MatchService is a service for match,need custom match function 4 | // 匹配服务,用于匹配玩家,需要自定义匹配函数 5 | package matchmaking.v1; 6 | 7 | option go_package = "matchmaking/api;matchmaking"; 8 | 9 | service MatchService{ 10 | rpc Match(MatchRequest) returns (stream MatchResponse); 11 | } 12 | 13 | message MatchRequest{ 14 | string userId = 1; 15 | string gameId = 2; 16 | } 17 | 18 | message MatchResponse{ 19 | string matchId = 1; 20 | repeated Ticket userIds = 2; 21 | string gameId = 3; 22 | } 23 | 24 | message Ticket{ 25 | string ticketId = 1; 26 | string userId = 2; 27 | string gameId = 3; 28 | string matchId = 4; 29 | string status = 5; 30 | } 31 | -------------------------------------------------------------------------------- /api/room/room_common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package room.v1; 3 | 4 | option go_package = "room/api;room"; 5 | 6 | // Room error code 7 | enum RoomErrorCode { 8 | ROOM_ERROR_CODE_OK = 0; 9 | ROOM_ERROR_CODE_INVALID = 1; 10 | ROOM_ERROR_CODE_NOT_FOUND = 2; 11 | ROOM_ERROR_CODE_FULL = 3; 12 | ROOM_ERROR_CODE_ALREADY_IN = 4; 13 | ROOM_ERROR_CODE_NOT_IN = 5; 14 | ROOM_ERROR_CODE_NOT_READY = 6; 15 | ROOM_ERROR_CODE_ALREADY_READY = 7; 16 | ROOM_ERROR_CODE_NOT_STARTED = 8; 17 | ROOM_ERROR_CODE_ALREADY_STARTED = 9; 18 | ROOM_ERROR_CODE_NOT_ENDED = 10; 19 | ROOM_ERROR_CODE_ALREADY_ENDED = 11; 20 | ROOM_ERROR_CODE_NOT_SYNC = 12; 21 | ROOM_ERROR_CODE_ALREADY_SYNC = 13; 22 | } 23 | 24 | // Common response 25 | // Common response message, 26 | // all Response messages should be returned to the client as the message field of this message 27 | // 通用响应消息,所有Response消息都应该作为此消息的message字段返回给客户端 28 | message Response { 29 | RoomErrorCode error_code = 1; 30 | bytes message = 2; 31 | } 32 | 33 | 34 | // Command data 35 | // Operation frame data, used for client to send to the server, and the server broadcasts to all clients in the room 36 | // 操作帧数据, 用于客户端发送给服务器,服务器广播给房间内所有客户端 37 | message CmdData { 38 | // player uid 39 | string uid = 1; // player uid 40 | // joystick x axis value 41 | float x = 2; // x position 42 | // joystick y axis value 43 | float y = 3; // y position 44 | // player action 45 | int32 action = 4; // action 46 | // custom data 47 | bytes custom = 5; // custom data 48 | } 49 | 50 | // Frame data 51 | message FrameData{ 52 | // frame index 53 | uint32 frame_index = 1; 54 | // operation data 55 | repeated CmdData cmds = 2; 56 | } 57 | 58 | 59 | // Player info 60 | message Player{ 61 | string uid = 1; 62 | string nickname = 2; 63 | string avatar = 3; 64 | } -------------------------------------------------------------------------------- /api/room/room_msgid.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package room.v1; 4 | 5 | option go_package = "room/api;room"; 6 | 7 | // MsgID 8 | enum MsgID { 9 | MSG_ID_INVALID = 0; 10 | MSG_ID_HEARTBEAT = 1; 11 | 12 | MSG_ID_ROOM_JOIN = 1000; 13 | MSG_ID_ROOM_EXIT = 1001; 14 | MSG_ID_ROOM_SYNC = 1002; 15 | } 16 | 17 | // NoticeID 18 | enum NoticeID{ 19 | NOTICE_ID_INVALID = 0; 20 | 21 | NOTICE_ID_ROOM_JOINED = 100; 22 | NOTICE_ID_ROOM_EXIT = 101; 23 | NOTICE_ID_ROOM_SYNC = 102; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /api/room/room_notice.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package room.v1; 4 | import "room/room_common.proto"; 5 | option go_package = "room/api;room"; 6 | 7 | 8 | // Notice client to join room 9 | // when client join room, server will send this notice to all clients in the room 10 | // if client is already in the room, notice will not be sent 11 | // 当客户端加入房间时,服务器会向房间内所有客户端发送此通知 12 | message NtfRoomJoined { 13 | // need add player info 14 | // 需要添加的玩家信息 15 | repeated Player players = 1; 16 | } 17 | 18 | // Notice client to exit room 19 | // when client exit room, server will send this notice to all clients in the room 20 | // 当客户端退出房间时,服务器会向房间内所有客户端发送此通知 21 | message NtfRoomExit { 22 | // need remove player uids 23 | // 需要移除的玩家uid 24 | repeated string uids = 1; 25 | } 26 | 27 | // Notice client to sync frame data 28 | // Room will send this notice every frame to all clients in the room 29 | // 房间会每帧向房间内所有客户端发送此同步信息 30 | message NtfFrame { 31 | // frame data 32 | // 帧数据 33 | repeated FrameData frames = 1; 34 | } 35 | -------------------------------------------------------------------------------- /api/room/room_req.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Room is a lockstep mode game room, clients in the room synchronize frame data through the server to achieve game synchronization 4 | // 房间是lockstep模式的游戏房间,房间内的客户端通过服务器同步帧数据,实现游戏同步 5 | package room.v1; 6 | import "room/room_common.proto"; 7 | option go_package = "room/api;room"; 8 | 9 | // Client join room 10 | message ReqJoin { 11 | // room jwt token contains: uid,room_id 12 | // 房间jwt token包含: uid,room_id 13 | string token = 1; 14 | // last frame index: default 0, 15 | // if client has cache data and want to reconnect from a frame index, 16 | // set this value to the corresponding frame index 17 | // 最后一帧的索引: 默认为0, 18 | // 如果客户端本地有缓存数据,想要从某个帧索引重新连接,设置这个值为对应的帧索引 19 | uint32 last_frame_index = 2; 20 | } 21 | 22 | // Server response to client join room 23 | message RspJoin { 24 | // room random seed 25 | // 房间随机种子 26 | int64 random_seed = 1; 27 | } 28 | 29 | // Client leave room 30 | message ReqExit { 31 | } 32 | 33 | // Server response to client leave room 34 | message RspExit { 35 | } 36 | 37 | // Client sync frame data 38 | message ReqSync { 39 | CmdData cmd = 1; 40 | } 41 | 42 | // Server response to client sync frame data 43 | message RspSync { 44 | 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | managed: 3 | enabled: true 4 | plugins: 5 | - remote: buf.build/protocolbuffers/go 6 | out: api/gen/ 7 | - remote: buf.build/grpc/go 8 | out: api/gen/ 9 | opt: require_unimplemented_servers=false 10 | - remote: buf.build/grpc-ecosystem/gateway 11 | out: api/gen/ 12 | opt: generate_unbound_methods=true 13 | - remote: buf.build/grpc-ecosystem/openapiv2 14 | out: third_party/OpenAPI -------------------------------------------------------------------------------- /buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/googleapis/googleapis 5 | commit: ee48893a270147348e3edc6c1a03de0e 6 | digest: b5:ef3c9849abc866b9625eb0f652bda8ea6a1477230eaeeb320c8cb63b16562575c0d3129524643e842c35156b40796aff79d048116b670ae95e9034b0c5a868c0 7 | - name: buf.build/grpc-ecosystem/grpc-gateway 8 | commit: 3f42134f4c564983838425bc43c7a65f 9 | digest: b5:291b947d8ac09492517557e4e72e294788cb8201afc7d0df7bda80fa10931adb60d4d669208a7696bf24f1ecb2a33a16d4c1e766e6f31809248b00343119569b 10 | -------------------------------------------------------------------------------- /buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: api/ 4 | name: buf.build/moke-game/platform 5 | excludes: [ "api/google" ] 6 | deps: 7 | - buf.build/googleapis/googleapis 8 | - buf.build/grpc-ecosystem/grpc-gateway 9 | lint: 10 | use: 11 | - DEFAULT 12 | except: 13 | - FIELD_NOT_REQUIRED 14 | - PACKAGE_DIRECTORY_MATCH 15 | - PACKAGE_NO_IMPORT_CYCLE 16 | - PACKAGE_VERSION_SUFFIX 17 | disallow_comment_ignores: true 18 | breaking: 19 | use: 20 | - FILE 21 | except: 22 | - EXTENSION_NO_DELETE 23 | - FIELD_SAME_DEFAULT 24 | -------------------------------------------------------------------------------- /build/package/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG APP_NAME=platform 2 | # Step 1: Modules caching 3 | FROM golang:1.22.5-alpine as modules 4 | RUN apk add --no-cache ca-certificates git 5 | COPY go.mod go.sum /modules/ 6 | WORKDIR /modules 7 | ENV GO111MODULE="on" 8 | ENV GOPROXY="https://goproxy.cn,https://gocenter.io,direct" 9 | RUN go mod download 10 | 11 | # Step 2: Builder 12 | FROM golang:1.22.5-alpine as builder 13 | ARG APP_NAME 14 | COPY --from=modules /go/pkg /go/pkg 15 | COPY . /app 16 | WORKDIR /app 17 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ 18 | go build -o /bin/app ./cmd/${APP_NAME}/service 19 | 20 | # Step 3: Final 21 | FROM alpine 22 | RUN #sed -i -e 's/http:/https:/' /etc/apk/repositories 23 | RUN apk --no-cache add tzdata 24 | # you can fix it to your TZ 25 | ENV TZ=Asia/Shanghai 26 | COPY --from=builder /bin/app /app 27 | CMD ["/app"] -------------------------------------------------------------------------------- /cmd/analytics/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | 6 | "github.com/moke-game/platform/services/analytics/pkg/module" 7 | ) 8 | 9 | func main() { 10 | fxmain.Main( 11 | module.AnalyticsModule, 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/auth/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/abiosoft/ishell" 7 | "github.com/spf13/cobra" 8 | 9 | auth "github.com/moke-game/platform/services/auth/client" 10 | ) 11 | 12 | var options struct { 13 | host string 14 | tcpHost string 15 | } 16 | 17 | const ( 18 | DefaultHost = "localhost:8081" 19 | ) 20 | 21 | func main() { 22 | rootCmd := &cobra.Command{ 23 | Use: "shell ", 24 | Short: "auth client", 25 | Aliases: []string{"cli"}, 26 | } 27 | rootCmd.PersistentFlags().StringVar( 28 | &options.host, 29 | "host", 30 | DefaultHost, 31 | "grpc http service (:)", 32 | ) 33 | 34 | sGrpc := &cobra.Command{ 35 | Use: "shell", 36 | Short: "Run an interactive grpc client", 37 | Run: func(cmd *cobra.Command, args []string) { 38 | initShell(cmd) 39 | }, 40 | } 41 | 42 | rootCmd.AddCommand(sGrpc) 43 | _ = rootCmd.ExecuteContext(context.Background()) 44 | } 45 | 46 | func initShell(cmd *cobra.Command) { 47 | shell := ishell.New() 48 | authShell, err := auth.CreateAuthClient(options.host) 49 | if err != nil { 50 | cmd.Println(err) 51 | return 52 | } 53 | shell.AddCmd(authShell) 54 | shell.Run() 55 | } 56 | -------------------------------------------------------------------------------- /cmd/auth/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/orm/pkg/ofx" 6 | 7 | "github.com/moke-game/platform/services/auth/pkg/module" 8 | ) 9 | 10 | func main() { 11 | fxmain.Main( 12 | ofx.RedisCacheModule, 13 | module.AuthModule, 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/buddy/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/abiosoft/ishell" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/moke-game/platform/services/buddy/client" 10 | ) 11 | 12 | var options struct { 13 | host string 14 | tcpHost string 15 | } 16 | 17 | const ( 18 | // DefaultHost default host 19 | DefaultHost = "localhost:8081" 20 | ) 21 | 22 | func main() { 23 | rootCmd := &cobra.Command{ 24 | Use: "shell ", 25 | Short: "buddy client", 26 | Aliases: []string{"cli"}, 27 | } 28 | rootCmd.PersistentFlags().StringVar( 29 | &options.host, 30 | "host", 31 | DefaultHost, 32 | "grpc http service (:)", 33 | ) 34 | 35 | sGrpc := &cobra.Command{ 36 | Use: "shell", 37 | Short: "Run an interactive grpc client", 38 | Run: func(cmd *cobra.Command, args []string) { 39 | shell := ishell.New() 40 | buddyClient, err := client.CreateBuddyClient(options.host) 41 | if err != nil { 42 | cmd.Println(err) 43 | return 44 | } 45 | shell.AddCmd(buddyClient) 46 | shell.Run() 47 | }, 48 | } 49 | 50 | rootCmd.AddCommand(sGrpc) 51 | _ = rootCmd.ExecuteContext(context.Background()) 52 | } 53 | -------------------------------------------------------------------------------- /cmd/buddy/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | 8 | auth "github.com/moke-game/platform/services/auth/pkg/module" 9 | "github.com/moke-game/platform/services/buddy/pkg/module" 10 | ) 11 | 12 | func main() { 13 | fxmain.Main( 14 | module.BuddyModule, 15 | ofx.RedisCacheModule, 16 | mfx.NatsModule, 17 | auth.AuthMiddlewareModule, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/chat/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var options struct { 10 | host string 11 | tcpHost string 12 | } 13 | 14 | const ( 15 | DefaultHost = "localhost:8081" 16 | ) 17 | 18 | func main() { 19 | rootCmd := &cobra.Command{ 20 | Use: "shell ", 21 | Short: "buddy client", 22 | Aliases: []string{"cli"}, 23 | } 24 | rootCmd.PersistentFlags().StringVar( 25 | &options.host, 26 | "host", 27 | DefaultHost, 28 | "grpc http service (:)", 29 | ) 30 | 31 | sGrpc := &cobra.Command{ 32 | Use: "shell", 33 | Short: "Run an interactive grpc client", 34 | Run: func(cmd *cobra.Command, args []string) { 35 | }, 36 | } 37 | 38 | rootCmd.AddCommand(sGrpc) 39 | _ = rootCmd.ExecuteContext(context.Background()) 40 | } 41 | -------------------------------------------------------------------------------- /cmd/chat/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | 7 | auth "github.com/moke-game/platform/services/auth/pkg/module" 8 | "github.com/moke-game/platform/services/chat/pkg/module" 9 | ) 10 | 11 | func main() { 12 | fxmain.Main( 13 | mfx.NatsModule, 14 | module.ChatModule, 15 | auth.AuthMiddlewareModule, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/knapsack/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var options struct { 10 | host string 11 | tcpHost string 12 | } 13 | 14 | const ( 15 | DefaultHost = "localhost:8081" 16 | ) 17 | 18 | func main() { 19 | rootCmd := &cobra.Command{ 20 | Use: "shell ", 21 | Short: "buddy client", 22 | Aliases: []string{"cli"}, 23 | } 24 | rootCmd.PersistentFlags().StringVar( 25 | &options.host, 26 | "host", 27 | DefaultHost, 28 | "grpc http service (:)", 29 | ) 30 | 31 | sGrpc := &cobra.Command{ 32 | Use: "shell", 33 | Short: "Run an interactive grpc client", 34 | Run: func(cmd *cobra.Command, args []string) { 35 | 36 | }, 37 | } 38 | 39 | rootCmd.AddCommand(sGrpc) 40 | _ = rootCmd.ExecuteContext(context.Background()) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/knapsack/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | 8 | auth "github.com/moke-game/platform/services/auth/pkg/module" 9 | "github.com/moke-game/platform/services/knapsack/pkg/module" 10 | ) 11 | 12 | func main() { 13 | fxmain.Main( 14 | ofx.RedisCacheModule, 15 | module.KnapsackModule, 16 | mfx.NatsModule, 17 | auth.AuthMiddlewareModule, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/leaderboard/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/orm/pkg/ofx" 6 | 7 | auth "github.com/moke-game/platform/services/auth/pkg/module" 8 | "github.com/moke-game/platform/services/leaderboard/pkg/module" 9 | ) 10 | 11 | func main() { 12 | fxmain.Main( 13 | ofx.RedisCacheModule, 14 | module.LeaderboardModule, 15 | auth.AuthMiddlewareModule, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/mail/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | defaultMail = "localhost:8081" 9 | defaultUsername = "test" 10 | ) 11 | 12 | var options struct { 13 | mail string 14 | username string 15 | } 16 | 17 | func main() { 18 | 19 | rootCmd := &cobra.Command{ 20 | Use: "cond_cli", 21 | Short: "Run a mail CLI", 22 | } 23 | 24 | rootCmd.PersistentFlags().StringVar(&options.mail, "mail", defaultMail, "mail service (:)") 25 | rootCmd.PersistentFlags().StringVar(&options.username, "username", defaultUsername, "username for authentication") 26 | { 27 | shell := &cobra.Command{ 28 | Use: "shell", 29 | Short: "Run an interactive mail service client", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | //TODO add client 32 | }, 33 | } 34 | rootCmd.AddCommand(shell) 35 | } 36 | _ = rootCmd.Execute() 37 | } 38 | -------------------------------------------------------------------------------- /cmd/mail/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | 8 | auth "github.com/moke-game/platform/services/auth/pkg/module" 9 | "github.com/moke-game/platform/services/mail/pkg/module" 10 | ) 11 | 12 | func main() { 13 | fxmain.Main( 14 | mfx.NatsModule, 15 | module.MailModule, 16 | ofx.RedisCacheModule, 17 | auth.AuthMiddlewareModule, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/matchmaking/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | 6 | mm "github.com/moke-game/platform/services/matchmaking/pkg/module" 7 | ) 8 | 9 | func main() { 10 | fxmain.Main( 11 | mm.MatchmakingModule, 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/party/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var options struct { 10 | host string 11 | tcpHost string 12 | } 13 | 14 | const ( 15 | DefaultHost = "localhost:8081" 16 | ) 17 | 18 | func main() { 19 | rootCmd := &cobra.Command{ 20 | Use: "shell ", 21 | Short: "party client", 22 | Aliases: []string{"cli"}, 23 | } 24 | rootCmd.PersistentFlags().StringVar( 25 | &options.host, 26 | "host", 27 | DefaultHost, 28 | "grpc http service (:)", 29 | ) 30 | 31 | sGrpc := &cobra.Command{ 32 | Use: "shell", 33 | Short: "Run an interactive grpc client", 34 | Run: func(cmd *cobra.Command, args []string) { 35 | 36 | }, 37 | } 38 | 39 | rootCmd.AddCommand(sGrpc) 40 | _ = rootCmd.ExecuteContext(context.Background()) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/party/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | 8 | auth "github.com/moke-game/platform/services/auth/pkg/module" 9 | "github.com/moke-game/platform/services/party/pkg/module" 10 | ) 11 | 12 | func main() { 13 | fxmain.Main( 14 | ofx.RedisCacheModule, 15 | module.PartyModule, 16 | mfx.NatsModule, 17 | auth.AuthMiddlewareModule, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/platform/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/abiosoft/ishell" 5 | "github.com/spf13/cobra" 6 | 7 | analytics "github.com/moke-game/platform/services/analytics/client" 8 | auth "github.com/moke-game/platform/services/auth/client" 9 | buddy "github.com/moke-game/platform/services/buddy/client" 10 | "github.com/moke-game/platform/services/matchmaking/client" 11 | ) 12 | 13 | const ( 14 | defaultURL = "localhost:8081" 15 | defaultUsername = "test" 16 | ) 17 | 18 | var options struct { 19 | url string 20 | username string 21 | } 22 | 23 | func main() { 24 | rootCmd := &cobra.Command{ 25 | Short: "Run a platform service client", 26 | } 27 | rootCmd.PersistentFlags().StringVar(&options.url, "url", defaultURL, "service url") 28 | rootCmd.PersistentFlags().StringVar(&options.username, "username", defaultUsername, "username for authentication") 29 | { 30 | cmd := &cobra.Command{ 31 | Use: "shell", 32 | Short: "Run an interactive service client", 33 | Run: func(cmd *cobra.Command, args []string) { 34 | initShells(cmd) 35 | }, 36 | } 37 | rootCmd.AddCommand(cmd) 38 | } 39 | _ = rootCmd.Execute() 40 | } 41 | 42 | func initShells(cmd *cobra.Command) { 43 | shell := ishell.New() 44 | aShell, err := analytics.CreateAnalyticsClient(options.url, options.username) 45 | if err != nil { 46 | cmd.Println(err) 47 | return 48 | } 49 | authShell, err := auth.CreateAuthClient(options.url) 50 | if err != nil { 51 | cmd.Println(err) 52 | return 53 | } 54 | buddyShell, err := buddy.CreateBuddyClient(options.url) 55 | if err != nil { 56 | cmd.Println(err) 57 | return 58 | } 59 | 60 | matchmakingShell, err := client.CreateClient(options.url) 61 | if err != nil { 62 | cmd.Println(err) 63 | return 64 | } 65 | shell.AddCmd(matchmakingShell) 66 | shell.AddCmd(buddyShell) 67 | shell.AddCmd(authShell) 68 | shell.AddCmd(aShell) 69 | shell.Run() 70 | } 71 | -------------------------------------------------------------------------------- /cmd/platform/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | 8 | analytics "github.com/moke-game/platform/services/analytics/pkg/module" 9 | auth "github.com/moke-game/platform/services/auth/pkg/module" 10 | buddy "github.com/moke-game/platform/services/buddy/pkg/module" 11 | chat "github.com/moke-game/platform/services/chat/pkg/module" 12 | knapsack "github.com/moke-game/platform/services/knapsack/pkg/module" 13 | leaderboard "github.com/moke-game/platform/services/leaderboard/pkg/module" 14 | mail "github.com/moke-game/platform/services/mail/pkg/module" 15 | matchmaking "github.com/moke-game/platform/services/matchmaking/pkg/module" 16 | party "github.com/moke-game/platform/services/party/pkg/module" 17 | profile "github.com/moke-game/platform/services/profile/pkg/module" 18 | ) 19 | 20 | func main() { 21 | fxmain.Main( 22 | // infrastructure 23 | mfx.NatsModule, 24 | ofx.RedisCacheModule, 25 | 26 | mail.MailModule, 27 | analytics.AnalyticsModule, 28 | auth.AuthAllModule, 29 | profile.ProfileModule, 30 | knapsack.KnapsackModule, 31 | party.PartyModule, 32 | buddy.BuddyModule, 33 | leaderboard.LeaderboardModule, 34 | chat.ChatModule, 35 | matchmaking.MatchmakingModule, 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/profile/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var options struct { 10 | host string 11 | tcpHost string 12 | } 13 | 14 | const ( 15 | DefaultHost = "localhost:8081" 16 | ) 17 | 18 | func main() { 19 | rootCmd := &cobra.Command{ 20 | Use: "shell ", 21 | Short: "buddy client", 22 | Aliases: []string{"cli"}, 23 | } 24 | rootCmd.PersistentFlags().StringVar( 25 | &options.host, 26 | "host", 27 | DefaultHost, 28 | "grpc http service (:)", 29 | ) 30 | 31 | sGrpc := &cobra.Command{ 32 | Use: "shell", 33 | Short: "Run an interactive grpc client", 34 | Run: func(cmd *cobra.Command, args []string) { 35 | 36 | }, 37 | } 38 | 39 | rootCmd.AddCommand(sGrpc) 40 | _ = rootCmd.ExecuteContext(context.Background()) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/profile/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | 8 | auth "github.com/moke-game/platform/services/auth/pkg/module" 9 | profile "github.com/moke-game/platform/services/profile/pkg/module" 10 | ) 11 | 12 | func main() { 13 | fxmain.Main( 14 | ofx.RedisCacheModule, 15 | profile.ProfileModule, 16 | mfx.NatsModule, 17 | auth.AuthMiddlewareModule, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/room/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/abiosoft/ishell" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/moke-game/platform/services/room/client" 10 | ) 11 | 12 | var options struct { 13 | host string 14 | port int 15 | } 16 | 17 | const ( 18 | DefaultHost = "localhost" 19 | DefaultPort = 8888 20 | ) 21 | 22 | func main() { 23 | rootCmd := &cobra.Command{ 24 | Use: "shell", 25 | Short: "room client", 26 | Aliases: []string{"S"}, 27 | } 28 | rootCmd.PersistentFlags().StringVar( 29 | &options.host, 30 | "host", 31 | DefaultHost, 32 | "grpc http service (:)", 33 | ) 34 | 35 | rootCmd.PersistentFlags().IntVar( 36 | &options.port, 37 | "port", 38 | DefaultPort, 39 | "grpc http service (:)", 40 | ) 41 | 42 | sGrpc := &cobra.Command{ 43 | Use: "shell", 44 | Short: "Run an interactive grpc client", 45 | Run: func(cmd *cobra.Command, args []string) { 46 | shell := ishell.New() 47 | if sh, err := client.CreateRoomWSClient(options.host, options.port); err != nil { 48 | panic(err) 49 | } else { 50 | shell.AddCmd(sh) 51 | } 52 | shell.Run() 53 | }, 54 | } 55 | 56 | rootCmd.AddCommand(sGrpc) 57 | _ = rootCmd.ExecuteContext(context.Background()) 58 | } 59 | -------------------------------------------------------------------------------- /cmd/room/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | agones "github.com/gstones/moke-kit/3rd/agones/pkg/module" 5 | "github.com/gstones/moke-kit/fxmain" 6 | 7 | "github.com/moke-game/platform/services/room/pkg/module" 8 | ) 9 | 10 | func main() { 11 | fxmain.Main( 12 | agones.AgonesSDKModule, 13 | module.RoomModule, 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /deployment/k8s/readme.md: -------------------------------------------------------------------------------- 1 | # Run with K8s 2 | 3 | Please visit this repository for more information: 4 | TODO - Add link to ops repository 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /draws/auth-validate.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moke-game/platform/5e99e8ee1e09b623cc99e7a963005f80c847f841/draws/auth-validate.drawio.png -------------------------------------------------------------------------------- /draws/auth.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moke-game/platform/5e99e8ee1e09b623cc99e7a963005f80c847f841/draws/auth.drawio.png -------------------------------------------------------------------------------- /draws/knapsack.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moke-game/platform/5e99e8ee1e09b623cc99e7a963005f80c847f841/draws/knapsack.drawio.png -------------------------------------------------------------------------------- /services/analytics/README.md: -------------------------------------------------------------------------------- 1 | # Analytics Service 2 | 3 | BI 日志收集服务 4 | 5 | ## 支持的第三方BI类型: 6 | 7 | * localFile 8 | * clickhouse 9 | * thinkingData 10 | * mixpanel 11 | 12 | ## 流程图 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /services/analytics/client/analytics.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/abiosoft/ishell" 7 | "github.com/gstones/moke-kit/logging/slogger" 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | 10 | pb "github.com/moke-game/platform/api/gen/analytics/api" 11 | "github.com/moke-game/platform/services/analytics/pkg/analyfx" 12 | ) 13 | 14 | type AnalyticsClient struct { 15 | cmd *ishell.Cmd 16 | 17 | username string 18 | client pb.AnalyticsServiceClient 19 | } 20 | 21 | func (ac *AnalyticsClient) initShell() { 22 | ac.cmd = &ishell.Cmd{ 23 | Name: "analytics", 24 | Help: "analytics interactive shell", 25 | Aliases: []string{"AS"}, 26 | } 27 | ac.initSubCmd() 28 | } 29 | 30 | func (ac *AnalyticsClient) initSubCmd() { 31 | ac.cmd.AddCmd(&ishell.Cmd{ 32 | Name: "send", 33 | Help: "send analytics events", 34 | Aliases: []string{"S"}, 35 | Func: ac.sendAnalytic, 36 | }) 37 | } 38 | 39 | func (ac *AnalyticsClient) sendAnalytic(c *ishell.Context) { 40 | eventName := slogger.ReadLine(c, "event name: ") 41 | js := slogger.ReadLine(c, "properties: ") 42 | userID := slogger.ReadLine(c, "user id: ") 43 | deliverTo := []string{"Local", "ThinkingData", "ClickHouse", "Mixpanel"} 44 | selected := c.Checklist(deliverTo, "choose your deliver to bi", []int{0, 2}) 45 | if len(selected) == 0 { 46 | return 47 | } 48 | var chose []string 49 | for _, i := range selected { 50 | chose = append(chose, deliverTo[i]) 51 | } 52 | var events []*pb.Event 53 | for _, v := range chose { 54 | events = append(events, &pb.Event{ 55 | Event: eventName, 56 | UserId: userID, 57 | Properties: []byte(js), 58 | DeliverTo: pb.DeliveryType(pb.DeliveryType_value[v]), 59 | }) 60 | } 61 | 62 | req := &pb.AnalyticsEvents{ 63 | Events: events, 64 | } 65 | if resp, err := ac.client.Analytics(context.Background(), req); err != nil { 66 | slogger.Warn(c, err) 67 | } else { 68 | slogger.Infof(c, "send analytic events result: %v", resp) 69 | } 70 | 71 | } 72 | 73 | func CreateAnalyticsClient(url string, username string) (*ishell.Cmd, error) { 74 | if c, err := analyfx.NewAnalyticsClient(url, sfx.SecuritySettingsParams{}); err != nil { 75 | return nil, err 76 | } else { 77 | aClient := &AnalyticsClient{ 78 | cmd: &ishell.Cmd{}, 79 | username: username, 80 | client: c, 81 | } 82 | aClient.initShell() 83 | return aClient.cmd, nil 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /services/analytics/internal/service/analytics.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "go.uber.org/zap" 7 | 8 | pb "github.com/moke-game/platform/api/gen/analytics/api" 9 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 10 | ) 11 | 12 | func (s *Service) Analytics( 13 | ctx context.Context, 14 | events *pb.AnalyticsEvents, 15 | ) (*pb.Nothing, error) { 16 | for _, v := range events.Events { 17 | eventType := bi.EventType(v.Event) 18 | p, ok := s.processes[v.DeliverTo] 19 | if !ok { 20 | s.logger.Warn("no processor found", zap.String("deliverTo", v.DeliverTo.String())) 21 | continue 22 | } 23 | if err := p.Handle(eventType, v.UserId, v.DistinctId, v.Properties); err != nil { 24 | s.logger.Error("bi data handle error", zap.Error(err)) 25 | } 26 | } 27 | return &pb.Nothing{}, nil 28 | } 29 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/clickhouse/factory.go: -------------------------------------------------------------------------------- 1 | package clickhouse 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 7 | "github.com/moke-game/platform/services/analytics/internal/service/bi/clickhouse/internal" 8 | ) 9 | 10 | func NewDataProcessor( 11 | logger *zap.Logger, rootPath, addr, dbname, uname, passwd string, 12 | ) (processor bi.DataProcessor, err error) { 13 | p := new(internal.Processor) 14 | if e := p.Init(logger, rootPath, addr, dbname, uname, passwd); e != nil { 15 | err = e 16 | } else { 17 | processor = p 18 | } 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/clickhouse/internal/processor_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestProcessor_getType(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | args any 11 | want string 12 | }{ 13 | { 14 | name: "str", 15 | args: "1", 16 | want: "String", 17 | }, 18 | { 19 | name: "int", 20 | args: 1, 21 | want: "Int32", 22 | }, 23 | { 24 | name: "f64", 25 | args: 10.2, 26 | want: "Float64", 27 | }, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | p := &Processor{} 32 | if got := p.getType(tt.args); got != tt.want { 33 | t.Errorf("getType() = %v, want %v", got, tt.want) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/errors.go: -------------------------------------------------------------------------------- 1 | package bi 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNotFoundEventType = status.Error(codes.NotFound, "ErrNotFoundEventType") 10 | ErrInvalidProperties = status.Error(codes.InvalidArgument, "ErrInvalidProperties") 11 | ) 12 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/events.go: -------------------------------------------------------------------------------- 1 | package bi 2 | 3 | const ( 4 | // EventTypeUserSet MixPanel: https://developer.mixpanel.com/docs/http#section--set 5 | //ThinkingData:overwriting one or more user attributes, 6 | // overwriting the previous value if the attribute already exists 7 | EventTypeUserSet EventType = "#user.set" 8 | // EventTypeUserSetOnce MixPanel:https://developer.mixpanel.com/docs/http#section--set_once 9 | //ThinkingData: initialize one or more user attributes, if the attribute already exists, ignore the operation 10 | EventTypeUserSetOnce EventType = "#user.setOnce" 11 | //MixPanel:https://developer.mixpanel.com/docs/http#section--add 12 | //ThinkingData: adding calculations to one or more numeric user attributes 13 | EventTypeUserAdd EventType = "#user.add" 14 | //MixPanel:https://developer.mixpanel.com/docs/http#section--delete 15 | //ThinkingData: delete this user table 16 | EventTypeUserDel EventType = "#user.delete" 17 | 18 | // EventTypeUserAppend (Unimplemented) 19 | // MixPanel:https://developer.mixpanel.com/docs/http#section--append 20 | // /ThinkingData: not support 21 | EventTypeUserAppend EventType = "#user.append" 22 | // EventTypeUserUnion (Unimplemented) 23 | // MixPanel:https://developer.mixpanel.com/docs/http#section--union 24 | // ThinkingData: not support 25 | EventTypeUserUnion EventType = "#user.union" 26 | // EventTypeUserRemove (Unimplemented) 27 | // MixPanel: https://developer.mixpanel.com/docs/http#section--remove 28 | // ThinkingData: not support 29 | EventTypeUserRemove EventType = "#user.remove" 30 | //(Unimplemented) 31 | // MixPanel: https://developer.mixpanel.com/docs/http#section--unset 32 | // ThinkingData:not support 33 | EventTypeUserUnset EventType = "#user.unset" 34 | ) 35 | 36 | type EventType string 37 | 38 | func (et EventType) String() string { 39 | return string(et) 40 | } 41 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/interfaces.go: -------------------------------------------------------------------------------- 1 | package bi 2 | 3 | type DataProcessor interface { 4 | Handle(name EventType, userID string, distinct string, properties []byte) error 5 | } 6 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/local/factory.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 7 | "github.com/moke-game/platform/services/analytics/internal/service/bi/local/internal" 8 | ) 9 | 10 | func NewDataProcessor( 11 | logger *zap.Logger, 12 | hostname string, 13 | rootPath string, 14 | ) (processor bi.DataProcessor, err error) { 15 | p := new(internal.Processor) 16 | if e := p.Init(logger, hostname, rootPath); e != nil { 17 | err = e 18 | } else { 19 | processor = p 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/local/internal/processor.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | 9 | "go.uber.org/zap" 10 | 11 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 12 | ) 13 | 14 | type Processor struct { 15 | logger *zap.Logger 16 | rootPath string 17 | hostname string 18 | } 19 | 20 | func (p *Processor) Init( 21 | logger *zap.Logger, 22 | hostname, rootPath string, 23 | ) error { 24 | p.logger = logger.With(zap.String("data_processor", "local_bi")) 25 | p.hostname = hostname 26 | p.rootPath = rootPath 27 | return nil 28 | } 29 | 30 | func (p *Processor) Handle(event bi.EventType, userId, distinct string, properties []byte) error { 31 | if err := p.deliver(event.String(), properties); err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | func (p *Processor) makeLogPath(eventName string) string { 38 | now := time.Now() 39 | t := now.Format("2006-01-02") 40 | h := now.Hour() 41 | return filepath.Join(p.rootPath, fmt.Sprintf("mta-%s_%s_%d.log", eventName, t, h)) 42 | } 43 | 44 | func (p *Processor) deliver(eventName string, data []byte) error { 45 | p.logger.Info("deliver local file bi logs", zap.String("data", string(data))) 46 | fileName := p.makeLogPath(eventName) 47 | if err := os.MkdirAll(filepath.Dir(fileName), os.ModePerm); err != nil { 48 | return err 49 | } 50 | 51 | file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 52 | if err != nil { 53 | return err 54 | } 55 | defer file.Close() 56 | if _, err := file.Write(data); err != nil { 57 | return err 58 | } 59 | if _, err := file.WriteString("\n"); err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/mixpanel/factory.go: -------------------------------------------------------------------------------- 1 | package mixpanel 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/mq/miface" 5 | "go.uber.org/zap" 6 | 7 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 8 | "github.com/moke-game/platform/services/analytics/internal/service/bi/mixpanel/internal" 9 | ) 10 | 11 | func NewDataProcessor( 12 | logger *zap.Logger, 13 | mq miface.MessageQueue, 14 | userId, 15 | ip, 16 | token string) (processor bi.DataProcessor, err error) { 17 | p := new(internal.Processor) 18 | if e := p.Init(logger, mq, userId, ip, token); e != nil { 19 | err = e 20 | } else { 21 | processor = p 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/mixpanel/internal/event.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 10 | ) 11 | 12 | var EventTypeName = map[bi.EventType]string{ 13 | bi.EventTypeUserSet: "$set", 14 | bi.EventTypeUserSetOnce: "$set_once", 15 | bi.EventTypeUserAdd: "$add", 16 | bi.EventTypeUserDel: "$delete", 17 | } 18 | 19 | func CreateEvent(eventType bi.EventType, token, userID, ip string, properties []byte) (event Event, err error) { 20 | err = event.init(eventType, token, userID, ip, properties) 21 | return 22 | } 23 | 24 | type Event struct { 25 | UserID string 26 | Token string 27 | IP string 28 | Time string 29 | Topic MPTopicType 30 | EventName string 31 | Properties string 32 | } 33 | 34 | func (e *Event) init(event bi.EventType, token, userID, ip string, properties []byte) error { 35 | eventStr := event.String() 36 | e.UserID = userID 37 | e.Token = token 38 | e.IP = ip 39 | e.Time = time.Now().Format("2006-01-02 15:04:05") 40 | if strings.HasPrefix(eventStr, "#") { 41 | tp, ok := EventTypeName[event] 42 | if !ok { 43 | err := errors.Wrap(bi.ErrNotFoundEventType, eventStr) 44 | return err 45 | } 46 | e.EventName = tp 47 | e.Topic = AnalyticsTopicMPUserProfiles 48 | } else { 49 | e.Topic = AnalyticsTopicMPTrack 50 | e.EventName = eventStr 51 | } 52 | 53 | e.Properties = string(properties) 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/mixpanel/internal/processor.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/gstones/moke-kit/mq/miface" 7 | jsoniter "github.com/json-iterator/go" 8 | "github.com/pkg/errors" 9 | "go.uber.org/zap" 10 | 11 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 12 | ) 13 | 14 | type Processor struct { 15 | logger *zap.Logger 16 | mq miface.MessageQueue 17 | userId string 18 | ip string 19 | token string 20 | } 21 | 22 | func (p *Processor) Init(logger *zap.Logger, mq miface.MessageQueue, userId, ip, token string) error { 23 | p.logger = logger.With(zap.String("data_processor", "MixPanel")) 24 | p.userId = userId 25 | p.ip = ip 26 | p.mq = mq 27 | p.token = token 28 | return nil 29 | } 30 | 31 | func (p *Processor) Handle(name bi.EventType, UserId, distinct string, properties []byte) error { 32 | if event, err := CreateEvent(name, p.token, p.userId, p.ip, properties); err != nil { 33 | return err 34 | } else if data, err := p.pack(event); err != nil { 35 | return err 36 | } else if err := p.deliver(event.Topic.String(), data); err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | func (p *Processor) pack(event Event) ([]byte, error) { 43 | if event.Topic == AnalyticsTopicMPUserProfiles { 44 | return p.packageUserProfiles(event) 45 | } 46 | return p.packageTrackEvents(event) 47 | } 48 | 49 | func (p *Processor) deliver(topic string, data []byte) error { 50 | 51 | p.logger.Info("deliver bi logs", zap.String("data", string(data))) 52 | return nil 53 | //size := base64.StdEncoding.EncodedLen(len(data)) 54 | //payload := make([]byte, size) 55 | //base64.StdEncoding.Encode(payload, data) 56 | // 57 | // 58 | //return p.mq.Publish(mq.NsqProtocol+topic, mq.WithBytes(payload)) 59 | } 60 | 61 | func (p *Processor) packageTrackEvents(event Event) ([]byte, error) { 62 | var tpl bytes.Buffer 63 | if err := mpTrackTemplate.Execute(&tpl, event); err != nil { 64 | return nil, err 65 | } else if ok := jsoniter.Valid(tpl.Bytes()); !ok { 66 | return nil, errors.Wrap(bi.ErrInvalidProperties, event.Properties) 67 | } else { 68 | return tpl.Bytes(), nil 69 | } 70 | } 71 | 72 | func (p *Processor) packageUserProfiles(event Event) ([]byte, error) { 73 | var tpl bytes.Buffer 74 | if err := mpEngageTemplate.Execute(&tpl, event); err != nil { 75 | return nil, err 76 | } else if ok := jsoniter.Valid(tpl.Bytes()); !ok { 77 | return nil, errors.Wrap(bi.ErrInvalidProperties, event.Properties) 78 | } 79 | return tpl.Bytes(), nil 80 | } 81 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/mixpanel/internal/templates.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "text/template" 4 | 5 | const ( 6 | mpTrackTpl = `{ 7 | "event": "{{.EventName}}", 8 | "properties":{ 9 | "distinct_id": "{{.UserID}}", 10 | "token":"{{.Token}}", 11 | "time":"{{.Time}}" 12 | {{if .Properties}},{{.Properties}}{{end}} 13 | }}` 14 | mpEngageTpl = `{ 15 | "$token": "{{.Token}}", 16 | "$distinct_id": "{{.UserID}}", 17 | "$ip": "{{.IP}}", 18 | "{{.EventName}}":{ 19 | "$name": "{{.UserID}}" 20 | {{if .Properties}},{{.Properties}}{{end}} 21 | }}` 22 | ) 23 | 24 | var mpTrackTemplate, mpEngageTemplate *template.Template 25 | 26 | func init() { 27 | mpTrackTemplate = template.Must(template.New("mixpanel_track").Parse(mpTrackTpl)) 28 | mpEngageTemplate = template.Must(template.New("mixpanel_engage").Parse(mpEngageTpl)) 29 | } 30 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/mixpanel/internal/topics.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type MPTopicType string 4 | 5 | const ( 6 | AnalyticsTopicMPTrack MPTopicType = "analytics.mp.track" 7 | AnalyticsTopicMPUserProfiles MPTopicType = "analytics.mp.engage" 8 | ) 9 | 10 | func (mt MPTopicType) String() string { 11 | return string(mt) 12 | } 13 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/thinkingdata/factory.go: -------------------------------------------------------------------------------- 1 | package thinkingdata 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 7 | "github.com/moke-game/platform/services/analytics/internal/service/bi/thinkingdata/internal" 8 | ) 9 | 10 | func NewDataProcessor( 11 | logger *zap.Logger, 12 | path string, 13 | ) (processor bi.DataProcessor, err error) { 14 | p := new(internal.Processor) 15 | if e := p.Init(logger, path); e != nil { 16 | err = e 17 | } else { 18 | processor = p 19 | } 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /services/analytics/internal/service/bi/thinkingdata/internal/processor.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/ThinkingDataAnalytics/go-sdk/v2/src/thinkingdata" 5 | jsoniter "github.com/json-iterator/go" 6 | "go.uber.org/zap" 7 | 8 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 9 | ) 10 | 11 | type Processor struct { 12 | logger *zap.Logger 13 | tda thinkingdata.TDAnalytics 14 | } 15 | 16 | func (p *Processor) Init(logger *zap.Logger, path string) error { 17 | p.logger = logger.With(zap.String("data_processor", "ThinkingData")) 18 | consumer, err := thinkingdata.NewLogConsumerWithConfig(thinkingdata.TDLogConsumerConfig{ 19 | Directory: path, 20 | }) 21 | if err != nil { 22 | return err 23 | } 24 | p.tda = thinkingdata.New(consumer) 25 | return nil 26 | } 27 | 28 | func (p *Processor) Handle(event bi.EventType, userID string, distinct string, properties []byte) error { 29 | return p.handleWithEventType(event, userID, distinct, properties) 30 | } 31 | 32 | func (p *Processor) handleWithEventType(event bi.EventType, userID string, distinct string, properties []byte) error { 33 | proper := map[string]interface{}{} 34 | if err := jsoniter.Unmarshal(properties, &proper); err != nil { 35 | return err 36 | } 37 | p.logger.Debug( 38 | "bi data handle", 39 | zap.String("event", event.String()), 40 | zap.String("userID", userID), 41 | zap.String("distinct", distinct), 42 | zap.Any("properties", proper), 43 | ) 44 | switch event { 45 | case bi.EventTypeUserSet: 46 | return p.tda.UserSet(userID, distinct, proper) 47 | case bi.EventTypeUserSetOnce: 48 | return p.tda.UserSetOnce(userID, distinct, proper) 49 | case bi.EventTypeUserAdd: 50 | return p.tda.UserAdd(userID, distinct, proper) 51 | case bi.EventTypeUserDel: 52 | return p.tda.UserDelete(userID, distinct) 53 | default: 54 | return p.tda.Track(userID, distinct, event.String(), proper) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /services/analytics/internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/gstones/moke-kit/mq/miface" 8 | "github.com/gstones/moke-kit/mq/pkg/mfx" 9 | "github.com/gstones/moke-kit/server/pkg/sfx" 10 | "github.com/gstones/moke-kit/server/siface" 11 | "github.com/gstones/moke-kit/utility" 12 | "go.uber.org/fx" 13 | "go.uber.org/zap" 14 | 15 | pb "github.com/moke-game/platform/api/gen/analytics/api" 16 | "github.com/moke-game/platform/services/analytics/internal/service/bi" 17 | "github.com/moke-game/platform/services/analytics/internal/service/bi/clickhouse" 18 | "github.com/moke-game/platform/services/analytics/internal/service/bi/local" 19 | "github.com/moke-game/platform/services/analytics/internal/service/bi/thinkingdata" 20 | "github.com/moke-game/platform/services/analytics/pkg/analyfx" 21 | ) 22 | 23 | type Service struct { 24 | utility.WithoutAuth 25 | logger *zap.Logger 26 | mq miface.MessageQueue 27 | processes map[pb.DeliveryType]bi.DataProcessor 28 | hostName string 29 | url string 30 | } 31 | 32 | func (s *Service) RegisterWithGatewayServer(server siface.IGatewayServer) error { 33 | return pb.RegisterAnalyticsServiceHandlerFromEndpoint( 34 | context.Background(), server.GatewayRuntimeMux(), s.url, server.GatewayOption(), 35 | ) 36 | } 37 | 38 | func NewService( 39 | l *zap.Logger, 40 | mq miface.MessageQueue, 41 | settings analyfx.AnalyticsSettingParams, 42 | ) (result *Service, err error) { 43 | processors := make(map[pb.DeliveryType]bi.DataProcessor) 44 | hostname := os.Getenv("HOST_NAME") 45 | if proc, e := local.NewDataProcessor(l, hostname, settings.LocalBiPath); e != nil { 46 | return nil, e 47 | } else { 48 | processors[pb.DeliveryType_Local] = proc 49 | } 50 | if settings.CKAddr != "" && settings.CKDB != "" && settings.CKUser != "" && settings.CKPasswd != "" { 51 | if proc, e := clickhouse.NewDataProcessor( 52 | l, settings.LocalBiPath, settings.CKAddr, 53 | settings.CKDB, settings.CKUser, settings.CKPasswd, 54 | ); e != nil { 55 | return nil, e 56 | } else { 57 | processors[pb.DeliveryType_ClickHouse] = proc 58 | } 59 | } 60 | if proc, e := thinkingdata.NewDataProcessor( 61 | l, 62 | settings.TDPath, 63 | ); e != nil { 64 | return nil, e 65 | } else { 66 | processors[pb.DeliveryType_ThinkingData] = proc 67 | } 68 | 69 | result = &Service{ 70 | logger: l, 71 | mq: mq, 72 | hostName: hostname, 73 | processes: processors, 74 | url: settings.AnalyticsUrl, 75 | } 76 | return 77 | } 78 | 79 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 80 | pb.RegisterAnalyticsServiceServer(server.GrpcServer(), s) 81 | return nil 82 | } 83 | 84 | var ServiceModule = fx.Provide( 85 | func( 86 | l *zap.Logger, 87 | mq mfx.MessageQueueParams, 88 | settings analyfx.AnalyticsSettingParams, 89 | ) (out sfx.GrpcServiceResult, gw sfx.GatewayServiceResult, err error) { 90 | if svc, e := NewService(l, mq.MessageQueue, settings); e != nil { 91 | err = e 92 | } else { 93 | out.GrpcService = svc 94 | gw.GatewayService = svc 95 | } 96 | return 97 | }, 98 | ) 99 | -------------------------------------------------------------------------------- /services/analytics/pkg/analyfx/analytics_client.go: -------------------------------------------------------------------------------- 1 | package analyfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/tools" 8 | 9 | pb "github.com/moke-game/platform/api/gen/analytics/api" 10 | "github.com/moke-game/platform/services/analytics/pkg/global" 11 | ) 12 | 13 | type AnalyticsClientParams struct { 14 | fx.In 15 | 16 | AnalyticsClient pb.AnalyticsServiceClient `name:"AnalyticsClient"` 17 | } 18 | 19 | type AnalyticsClientResult struct { 20 | fx.Out 21 | 22 | AnalyticsClient pb.AnalyticsServiceClient `name:"AnalyticsClient"` 23 | } 24 | 25 | func NewAnalyticsClient(host string, sSetting sfx.SecuritySettingsParams) (pb.AnalyticsServiceClient, error) { 26 | if sSetting.MTLSEnable { 27 | if conn, err := tools.DialWithSecurity( 28 | host, 29 | sSetting.ClientCert, 30 | sSetting.ClientKey, 31 | sSetting.ServerName, 32 | sSetting.ServerCaCert, 33 | ); err != nil { 34 | return nil, err 35 | } else { 36 | return pb.NewAnalyticsServiceClient(conn), nil 37 | } 38 | } else { 39 | if conn, err := tools.DialInsecure(host); err != nil { 40 | return nil, err 41 | } else { 42 | return pb.NewAnalyticsServiceClient(conn), nil 43 | } 44 | } 45 | } 46 | 47 | var AnalyticsClientModule = fx.Invoke( 48 | func( 49 | setting AnalyticsSettingParams, 50 | sSetting sfx.SecuritySettingsParams, 51 | ) (out AnalyticsClientResult, err error) { 52 | if cli, e := NewAnalyticsClient(setting.AnalyticsUrl, sSetting); e != nil { 53 | err = e 54 | } else { 55 | out.AnalyticsClient = cli 56 | // set global analytics client 57 | global.SetAnalyticsClient(cli) 58 | } 59 | return 60 | }, 61 | ) 62 | -------------------------------------------------------------------------------- /services/analytics/pkg/analyfx/analytics_settings.go: -------------------------------------------------------------------------------- 1 | package analyfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type AnalyticsSettingParams struct { 9 | fx.In 10 | AnalyticsUrl string `name:"AnalyticsUrl"` 11 | 12 | // LocalBiPath is the path of the local bi logs, default is ./logs/bi. 13 | LocalBiPath string `name:"LocalBiPath"` 14 | 15 | // ClickHouse settings. 16 | CKAddr string `name:"CKAddr"` 17 | CKDB string `name:"CKDB"` 18 | CKUser string `name:"CKUser"` 19 | CKPasswd string `name:"CKPasswd"` 20 | 21 | // ThinkingData settings. 22 | TDPath string `name:"TDPath"` 23 | } 24 | 25 | type AnalyticsSettingsResult struct { 26 | fx.Out 27 | // AnalyticsUrl is the url of the analytics service, default is localhost:8081. 28 | // 当前统计服务器地址, 默认为localhost:8081 29 | AnalyticsUrl string `name:"AnalyticsUrl" envconfig:"ANALYTICS_URL" default:"localhost:8081"` 30 | // LocalBiPath is the path of the local bi logs, default is ./logs/bi. 31 | // 本地bi日志路径, 默认为./logs/bi 32 | LocalBiPath string `name:"LocalBiPath" envconfig:"LOCAL_BI_PATH" default:"./logs/bi"` 33 | // ClickHouse settings. 34 | CKAddr string `name:"CKAddr" envconfig:"CK_ADDR" default:""` 35 | CKDB string `name:"CKDB" envconfig:"CK_DB" default:"default"` 36 | CKUser string `name:"CKUser" envconfig:"CK_USER" default:""` 37 | CKPasswd string `name:"CKPasswd" envconfig:"CK_PASSWD" default:""` 38 | 39 | // ThinkingData settings. 40 | TDPath string `name:"TDPath" envconfig:"TD_PATH" default:"./logs/td"` 41 | } 42 | 43 | func (g *AnalyticsSettingsResult) LoadFromEnv() (err error) { 44 | err = utility.Load(g) 45 | return 46 | } 47 | 48 | var SettingsModule = fx.Provide( 49 | func() (out AnalyticsSettingsResult, err error) { 50 | err = out.LoadFromEnv() 51 | return 52 | }, 53 | ) 54 | -------------------------------------------------------------------------------- /services/analytics/pkg/global/analytics.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import pb "github.com/moke-game/platform/api/gen/analytics/api" 4 | 5 | var ( 6 | analyticsClient pb.AnalyticsServiceClient 7 | ) 8 | 9 | // SetAnalyticsClient sets the global analytics client 10 | func SetAnalyticsClient(cli pb.AnalyticsServiceClient) { 11 | analyticsClient = cli 12 | } 13 | 14 | // GetAnalyticsSender returns the global analytics client 15 | func GetAnalyticsSender() pb.AnalyticsServiceClient { 16 | if analyticsClient == nil { 17 | return &Noop{} 18 | } 19 | return analyticsClient 20 | } 21 | -------------------------------------------------------------------------------- /services/analytics/pkg/global/mock.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | 8 | pb "github.com/moke-game/platform/api/gen/analytics/api" 9 | ) 10 | 11 | type Noop struct { 12 | grpc.ClientStream 13 | } 14 | 15 | func (c *Noop) Analytics(_ context.Context, _ *pb.AnalyticsEvents, _ ...grpc.CallOption) (*pb.Nothing, error) { 16 | 17 | return &pb.Nothing{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/analytics/pkg/module/service.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/analytics/internal/service" 7 | "github.com/moke-game/platform/services/analytics/pkg/analyfx" 8 | ) 9 | 10 | // AnalyticsModule provides service for analytics 11 | var AnalyticsModule = fx.Module("analytics", 12 | service.ServiceModule, 13 | analyfx.SettingsModule, 14 | ) 15 | 16 | // AnalyticsClientModule provides client for analytics 17 | var AnalyticsClientModule = fx.Module("analytics-client", 18 | analyfx.AnalyticsClientModule, 19 | analyfx.SettingsModule, 20 | ) 21 | 22 | // AnalyticsAllModule provides client, service and middleware for analytics 23 | var AnalyticsAllModule = fx.Module("analytics-all", 24 | service.ServiceModule, 25 | analyfx.AnalyticsClientModule, 26 | analyfx.SettingsModule, 27 | ) 28 | -------------------------------------------------------------------------------- /services/auth/README.md: -------------------------------------------------------------------------------- 1 | # Auth Service 2 | 3 | token认证服务器,提供用户认证服务。[为什么需要token认证?](https://www.okta.com/identity-101/what-is-token-based-authentication/) 4 | 5 | ## 流程图 6 | 7 | ![Workflow](../../draws/auth.drawio.png) 8 | 9 | ## 服务说明 10 | 11 | * 所有服务的`public`类型都需要认证,认证通过后才能访问。 12 | 13 | ![validate](../../draws/auth-validate.drawio.png) 14 | 15 | 16 | -------------------------------------------------------------------------------- /services/auth/internal/db/factory.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strconv" 7 | 8 | "go.uber.org/zap" 9 | 10 | "github.com/gstones/moke-kit/orm/nerrors" 11 | "github.com/gstones/moke-kit/orm/nosql/diface" 12 | "github.com/gstones/moke-kit/orm/nosql/key" 13 | 14 | "github.com/moke-game/platform/services/auth/internal/db/model" 15 | ) 16 | 17 | type Database struct { 18 | logger *zap.Logger 19 | appName string 20 | coll diface.ICollection 21 | cache diface.ICache 22 | } 23 | 24 | func OpenDatabase( 25 | l *zap.Logger, 26 | appName string, 27 | coll diface.ICollection, 28 | cache diface.ICache, 29 | ) *Database { 30 | return &Database{ 31 | logger: l, 32 | coll: coll, 33 | cache: cache, 34 | appName: appName, 35 | } 36 | } 37 | 38 | const uidStart = 10000 39 | 40 | func (db *Database) generateId() (string, error) { 41 | if k, err := model.NewUidKey(db.appName); err != nil { 42 | return "", err 43 | } else if uid, err := db.coll.Incr(context.Background(), k, "uid", 1); err != nil { 44 | if errors.Is(err, nerrors.ErrNotFound) { 45 | return strconv.FormatInt(uidStart, 10), nil 46 | } 47 | return "", err 48 | } else { 49 | return strconv.FormatInt(uidStart+uid, 10), nil 50 | } 51 | } 52 | 53 | func (db *Database) Delete(id string) (err error) { 54 | var index key.Key 55 | if index, err = model.NewAuthKey(id); err != nil { 56 | return 57 | } 58 | db.cache.DeleteCache(context.Background(), index) 59 | if err = db.coll.Delete(context.Background(), index); errors.Is(err, nerrors.ErrNotFound) { 60 | return nil 61 | } 62 | return 63 | } 64 | 65 | func (db *Database) LoadOrCreateUid(id string) (*model.Dao, error) { 66 | if dm, err := model.NewAuthModel(id, db.coll, db.cache); err != nil { 67 | return nil, err 68 | } else if err = dm.Load(); errors.Is(err, nerrors.ErrNotFound) { 69 | if uid, err := db.generateId(); err != nil { 70 | return nil, err 71 | } else if dm, err = model.NewAuthModel(id, db.coll, db.cache); err != nil { 72 | return nil, err 73 | } else if err := dm.InitDefault(uid); err != nil { 74 | return nil, err 75 | } else if err = dm.Create(); err != nil { 76 | if err = dm.Load(); err != nil { 77 | return nil, err 78 | } else { 79 | return dm, nil 80 | } 81 | } else { 82 | return dm, nil 83 | } 84 | } else if err != nil { 85 | return nil, err 86 | } else { 87 | return dm, nil 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /services/auth/internal/db/model/dao.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gstones/moke-kit/orm/nosql" 7 | "github.com/gstones/moke-kit/orm/nosql/diface" 8 | ) 9 | 10 | type Dao struct { 11 | nosql.DocumentBase `bson:"-"` 12 | Data *Data `bson:"data"` 13 | } 14 | 15 | func (d *Dao) Init(id string, doc diface.ICollection, cache diface.ICache) error { 16 | key, e := NewAuthKey(id) 17 | if e != nil { 18 | return e 19 | } 20 | d.initData() 21 | d.DocumentBase.InitWithCache(context.Background(), &d.Data, d.clear, doc, key, cache) 22 | return nil 23 | } 24 | 25 | func NewAuthModel(id string, doc diface.ICollection, cache diface.ICache) (*Dao, error) { 26 | dm := &Dao{} 27 | if err := dm.Init(id, doc, cache); err != nil { 28 | return nil, err 29 | } 30 | return dm, nil 31 | } 32 | -------------------------------------------------------------------------------- /services/auth/internal/db/model/data.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Data struct { 4 | Uid string `json:"uid" bson:"uid"` 5 | } 6 | 7 | func createData() *Data { 8 | return &Data{} 9 | } 10 | 11 | func (d *Dao) initData() { 12 | data := createData() 13 | d.Data = data 14 | } 15 | 16 | func (d *Dao) InitDefault(uid string) error { 17 | data := createData() 18 | data.Uid = uid 19 | d.Data = data 20 | return nil 21 | } 22 | 23 | func (d *Dao) clear() { 24 | d.Data = nil 25 | } 26 | 27 | func (d *Dao) GetUid() string { 28 | return d.Data.Uid 29 | } 30 | -------------------------------------------------------------------------------- /services/auth/internal/db/model/keys.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/gstones/moke-kit/orm/nosql/key" 4 | 5 | func NewAuthKey(id string) (key.Key, error) { 6 | return key.NewKeyFromParts("auth", id) 7 | } 8 | 9 | func NewUidKey(appName string) (key.Key, error) { 10 | return key.NewKeyFromParts(appName, "uid") 11 | } 12 | -------------------------------------------------------------------------------- /services/auth/internal/db/redis/keys.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/nosql/key" 5 | ) 6 | 7 | func NewBlockListKey(uid string) (key.Key, error) { 8 | return key.NewKeyFromParts("auth", "blocked", uid) 9 | } 10 | 11 | func NewTokenKey(token string) (key.Key, error) { 12 | return key.NewKeyFromParts("auth", "token", token) 13 | } 14 | -------------------------------------------------------------------------------- /services/auth/internal/db/redis/status.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | func SaveAuthToken(redisCli *redis.Client, uid, token string, duration time.Duration) error { 11 | if key, err := NewTokenKey(uid); err != nil { 12 | return err 13 | } else if err := redisCli.Set(context.Background(), key.String(), token, duration).Err(); err != nil { 14 | return err 15 | } 16 | return nil 17 | } 18 | 19 | func IsAuthTokenSame(redisCli *redis.Client, uid, token string) (bool, error) { 20 | if key, err := NewTokenKey(uid); err != nil { 21 | return false, err 22 | } else if res := redisCli.Get(context.Background(), key.String()); res.Err() != nil { 23 | return false, res.Err() 24 | } else { 25 | return res.Val() == token, nil 26 | } 27 | } 28 | 29 | func IsAuthTokenExist(redisCli *redis.Client, uid string) (bool, error) { 30 | if key, err := NewTokenKey(uid); err != nil { 31 | return false, err 32 | } else if res := redisCli.Exists(context.Background(), key.String()); res.Err() != nil { 33 | return false, res.Err() 34 | } else { 35 | return res.Val() > 0, nil 36 | } 37 | } 38 | 39 | func BlockedProfile(redisCli *redis.Client, profileID string, duration time.Duration) error { 40 | if key, err := NewBlockListKey(profileID); err != nil { 41 | return err 42 | } else if err := redisCli.Set(context.Background(), key.String(), profileID, duration).Err(); err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | func UnBlockedProfile(redisCli *redis.Client, profileID string) error { 49 | if key, err := NewBlockListKey(profileID); err != nil { 50 | return err 51 | } else if err := redisCli.Del(context.Background(), key.String()).Err(); err != nil { 52 | return err 53 | } 54 | return nil 55 | } 56 | 57 | func IsBlocked(redisCli *redis.Client, profileID string) (bool, error) { 58 | if key, err := NewBlockListKey(profileID); err != nil { 59 | return false, err 60 | } else if res := redisCli.Exists(context.Background(), key.String()); res.Err() != nil { 61 | return false, res.Err() 62 | } else { 63 | return res.Val() > 0, nil 64 | } 65 | } 66 | 67 | func ClearAuthToken(redisCli *redis.Client, uid string) error { 68 | if key, err := NewTokenKey(uid); err != nil { 69 | return err 70 | } else if err := redisCli.Del(context.Background(), key.String()).Err(); err != nil { 71 | return err 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /services/auth/internal/errors.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 10 | ErrGenerateJwtFailure = status.Error(codes.Internal, "ErrGenerateJwtFailure") 11 | ErrClientParamFailure = status.Error(codes.Internal, "ErrClientParamFailure") 12 | ErrParseJwtTokenFailure = status.Error(codes.Internal, "ErrParseJwtTokenFailure") 13 | ErrPermissionDenied = status.Error(codes.PermissionDenied, "PermissionDenied") 14 | ) 15 | -------------------------------------------------------------------------------- /services/auth/internal/service.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gstones/moke-kit/fxmain/pkg/mfx" 7 | "github.com/gstones/moke-kit/orm/nosql/diface" 8 | "github.com/gstones/moke-kit/orm/pkg/ofx" 9 | "github.com/gstones/moke-kit/server/pkg/sfx" 10 | "github.com/gstones/moke-kit/server/siface" 11 | "github.com/gstones/moke-kit/utility" 12 | "github.com/redis/go-redis/v9" 13 | "go.opentelemetry.io/otel" 14 | "go.opentelemetry.io/otel/trace" 15 | "go.uber.org/fx" 16 | "go.uber.org/zap" 17 | 18 | pb "github.com/moke-game/platform/api/gen/auth/api" 19 | "github.com/moke-game/platform/services/auth/internal/db" 20 | "github.com/moke-game/platform/services/auth/pkg/afx" 21 | ) 22 | 23 | type Service struct { 24 | utility.WithoutAuth 25 | logger *zap.Logger 26 | jwtSecret string 27 | url string 28 | db *db.Database 29 | tracer trace.Tracer 30 | redisCli *redis.Client 31 | jwtExpire time.Duration 32 | } 33 | 34 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 35 | pb.RegisterAuthServiceServer(server.GrpcServer(), s) 36 | return nil 37 | } 38 | 39 | func NewService( 40 | l *zap.Logger, 41 | jwtSecret string, 42 | url string, 43 | coll diface.ICollection, 44 | redisCache diface.ICache, 45 | appName string, 46 | tracer trace.Tracer, 47 | redisCli *redis.Client, 48 | jwtExpire int32, 49 | ) (result *Service, err error) { 50 | result = &Service{ 51 | logger: l, 52 | jwtSecret: jwtSecret, 53 | url: url, 54 | db: db.OpenDatabase(l, appName, coll, redisCache), 55 | tracer: tracer, 56 | redisCli: redisCli, 57 | jwtExpire: time.Duration(jwtExpire) * time.Hour, 58 | } 59 | return 60 | } 61 | 62 | var ServiceModule = fx.Provide( 63 | func( 64 | l *zap.Logger, 65 | sSetting afx.AuthSettingParams, 66 | aParams mfx.AppParams, 67 | dbProvider ofx.DocumentStoreParams, 68 | rcParams ofx.RedisCacheParams, 69 | redisParams ofx.RedisParams, 70 | ) (rpc sfx.GrpcServiceResult, err error) { 71 | if coll, e := dbProvider.DriverProvider.OpenDbDriver(sSetting.AuthStoreName); e != nil { 72 | err = e 73 | } else { 74 | if svc, e := NewService( 75 | l, 76 | sSetting.JwtTokenSecret, 77 | sSetting.AuthUrl, 78 | coll, 79 | rcParams.RedisCache, 80 | aParams.AppName, 81 | otel.GetTracerProvider().Tracer(sSetting.AuthStoreName), 82 | redisParams.Redis, 83 | sSetting.JwtTokenExpire, 84 | ); e != nil { 85 | err = e 86 | } else { 87 | rpc.GrpcService = svc 88 | } 89 | } 90 | return 91 | }, 92 | ) 93 | -------------------------------------------------------------------------------- /services/auth/internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "time" 7 | 8 | "github.com/golang-jwt/jwt" 9 | ) 10 | 11 | type TokenType int32 12 | 13 | const ( 14 | TokenTypeAccess TokenType = iota 15 | TokenTypeRefresh 16 | ) 17 | 18 | var ( 19 | ErrTokenSigningMethod = errors.New("ErrTokenSigningMethod") 20 | ErrTokenExpired = errors.New("ErrTokenExpired") 21 | ErrTokenMalformed = errors.New("ErrTokenMalformed") 22 | ErrTokenHandle = errors.New("ErrTokenHandle") 23 | ErrSignedString = errors.New("ErrSignedString") 24 | ) 25 | 26 | // CreatJwt 生成一个JwtToken,包含uid 27 | func CreatJwt(uid string, tp TokenType, key string, data []byte, duration time.Duration) (string, error) { 28 | exp := int64(0) 29 | if duration > 0 { 30 | exp = time.Now().Add(duration).Unix() 31 | } 32 | 33 | at := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 34 | "uid": uid, 35 | "type": tp, 36 | "exp": exp, 37 | "data": data, 38 | }) 39 | token, err := at.SignedString([]byte(key)) 40 | if err != nil { 41 | return "", ErrSignedString 42 | } 43 | return token, nil 44 | } 45 | 46 | // ParseToken 从Jwt中解析Token 47 | func ParseToken(token string, tokenType TokenType, key string) (string, []byte, error) { 48 | claim, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { 49 | // Don't forget to validate the alg is what you expect: 50 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 51 | return nil, ErrTokenSigningMethod 52 | } 53 | return []byte(key), nil 54 | }) 55 | var ve *jwt.ValidationError 56 | if errors.As(err, &ve) { 57 | if ve.Errors&jwt.ValidationErrorMalformed != 0 { 58 | return "", nil, ErrTokenMalformed 59 | } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { 60 | // Token is either expired or not active yet 61 | return "", nil, ErrTokenExpired 62 | } else { 63 | return "", nil, ErrTokenHandle 64 | } 65 | } 66 | 67 | if claims, ok := claim.Claims.(jwt.MapClaims); ok && claim.Valid { 68 | if tp, ok := claims["type"]; ok && tp.(float64) == float64(tokenType) { 69 | resUid := "" 70 | var resData []byte 71 | if uid, ok := claims["uid"].(string); ok { 72 | resUid = uid 73 | } 74 | if data, ok := claims["data"].(string); ok { 75 | // base64 decode 76 | if raw, err := base64.StdEncoding.DecodeString(data); err != nil { 77 | return "", nil, ErrTokenHandle 78 | } else { 79 | resData = raw 80 | } 81 | } 82 | return resUid, resData, nil 83 | } 84 | } 85 | return "", nil, ErrTokenHandle 86 | } 87 | -------------------------------------------------------------------------------- /services/auth/pkg/afx/auth_client.go: -------------------------------------------------------------------------------- 1 | package afx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/server/pkg/sfx" 5 | "github.com/gstones/moke-kit/server/tools" 6 | "go.uber.org/fx" 7 | 8 | pb "github.com/moke-game/platform/api/gen/auth/api" 9 | ) 10 | 11 | type AuthClientParams struct { 12 | fx.In 13 | 14 | AuthClient pb.AuthServiceClient `name:"AuthClient"` 15 | } 16 | 17 | type AuthClientResult struct { 18 | fx.Out 19 | 20 | AuthClient pb.AuthServiceClient `name:"AuthClient"` 21 | } 22 | 23 | func NewAuthClient(host string, sSetting sfx.SecuritySettingsParams) (pb.AuthServiceClient, error) { 24 | if sSetting.MTLSEnable { 25 | if conn, err := tools.DialWithSecurity( 26 | host, 27 | sSetting.ClientCert, 28 | sSetting.ClientKey, 29 | sSetting.ServerName, 30 | sSetting.ServerCaCert, 31 | ); err != nil { 32 | return nil, err 33 | } else { 34 | return pb.NewAuthServiceClient(conn), nil 35 | } 36 | } else { 37 | if conn, err := tools.DialInsecure(host); err != nil { 38 | return nil, err 39 | } else { 40 | return pb.NewAuthServiceClient(conn), nil 41 | } 42 | } 43 | } 44 | 45 | var AuthClientModule = fx.Provide( 46 | func( 47 | setting AuthSettingParams, 48 | sSetting sfx.SecuritySettingsParams, 49 | ) (out AuthClientResult, err error) { 50 | if cli, e := NewAuthClient(setting.AuthUrl, sSetting); e != nil { 51 | err = e 52 | } else { 53 | out.AuthClient = cli 54 | } 55 | return 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /services/auth/pkg/afx/auth_middleware.go: -------------------------------------------------------------------------------- 1 | package afx 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" 7 | "github.com/gstones/moke-kit/server/pkg/sfx" 8 | "github.com/gstones/moke-kit/utility" 9 | "go.uber.org/fx" 10 | "go.uber.org/zap" 11 | "google.golang.org/grpc" 12 | 13 | pb "github.com/moke-game/platform/api/gen/auth/api" 14 | ) 15 | 16 | // Author is auth for grpc middleware 17 | type Author struct { 18 | client pb.AuthServiceClient 19 | unAuthMethods map[string]struct{} 20 | } 21 | 22 | // Auth will auth every grpc request 23 | func (d *Author) Auth(ctx context.Context) (context.Context, error) { 24 | method, _ := grpc.Method(ctx) 25 | if _, ok := d.unAuthMethods[method]; ok { 26 | return context.WithValue(ctx, utility.WithOutTag, true), nil 27 | } else if token, err := auth.AuthFromMD(ctx, string(utility.TokenContextKey)); err != nil { 28 | return ctx, err 29 | } else if resp, err := d.client.ValidateToken(ctx, &pb.ValidateTokenRequest{ 30 | AccessToken: token, 31 | }); err != nil { 32 | return ctx, err 33 | } else { 34 | ctx = context.WithValue(ctx, utility.UIDContextKey, resp.GetUid()) 35 | return ctx, nil 36 | } 37 | } 38 | 39 | func (d *Author) AddUnAuthMethod(method string) { 40 | if d.unAuthMethods == nil { 41 | d.unAuthMethods = make(map[string]struct{}) 42 | } 43 | d.unAuthMethods[method] = struct{}{} 44 | } 45 | 46 | // AuthCheckModule is the module for grpc middleware 47 | var AuthCheckModule = fx.Provide( 48 | func( 49 | l *zap.Logger, 50 | sSetting sfx.SecuritySettingsParams, 51 | params AuthClientParams, 52 | ) (out sfx.AuthMiddlewareResult, err error) { 53 | out.AuthMiddleware = &Author{ 54 | client: params.AuthClient, 55 | unAuthMethods: map[string]struct{}{}, 56 | } 57 | return 58 | }, 59 | ) 60 | -------------------------------------------------------------------------------- /services/auth/pkg/afx/auth_settings.go: -------------------------------------------------------------------------------- 1 | package afx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type AuthSettingParams struct { 9 | fx.In 10 | 11 | AuthUrl string `name:"AuthUrl"` 12 | AuthStoreName string `name:"AuthStoreName"` 13 | JwtTokenSecret string `name:"JwtTokenSecret"` 14 | JwtTokenExpire int32 `name:"JwtTokenExpire"` 15 | } 16 | 17 | type AuthSettingsResult struct { 18 | fx.Out 19 | 20 | AuthStoreName string `name:"AuthStoreName" envconfig:"AUTH_STORE_NAME" default:"auth"` 21 | AuthUrl string `name:"AuthUrl" envconfig:"AUTH_URL" default:"localhost:8081"` 22 | JwtTokenSecret string `name:"JwtTokenSecret" default:"" envconfig:"JWT_TOKEN_SECRET"` 23 | // JwtTokenExpire (hours) 24 | JwtTokenExpire int32 `name:"JwtTokenExpire" default:"12" envconfig:"JWT_TOKEN_EXPIRE"` 25 | } 26 | 27 | func (g *AuthSettingsResult) LoadFromEnv() (err error) { 28 | err = utility.Load(g) 29 | return 30 | } 31 | 32 | var SettingsModule = fx.Provide( 33 | func() (out AuthSettingsResult, err error) { 34 | err = out.LoadFromEnv() 35 | return 36 | }, 37 | ) 38 | -------------------------------------------------------------------------------- /services/auth/pkg/afx/auth_supabase_middleware.go: -------------------------------------------------------------------------------- 1 | package afx 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" 7 | "github.com/gstones/moke-kit/server/pkg/sfx" 8 | "github.com/gstones/moke-kit/utility" 9 | "github.com/supabase-community/supabase-go" 10 | "go.uber.org/fx" 11 | "go.uber.org/zap" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | // supabase auth middleware 16 | // https://supabase.com/docs/guides/auth 17 | 18 | // SupabaseAuthor is auth for grpc middleware 19 | type SupabaseAuthor struct { 20 | client *supabase.Client 21 | unAuthMethods map[string]struct{} 22 | } 23 | 24 | // Auth will auth every grpc request with supabase 25 | func (d *SupabaseAuthor) Auth(ctx context.Context) (context.Context, error) { 26 | method, _ := grpc.Method(ctx) 27 | if _, ok := d.unAuthMethods[method]; ok { 28 | return context.WithValue(ctx, utility.WithOutTag, true), nil 29 | } else if token, err := auth.AuthFromMD(ctx, string(utility.TokenContextKey)); err != nil { 30 | return ctx, err 31 | } else if resp, err := d.client.Auth.WithToken(token).GetUser(); err != nil { 32 | return ctx, err 33 | } else { 34 | ctx = context.WithValue(ctx, utility.UIDContextKey, resp.ID.String()) 35 | return ctx, nil 36 | } 37 | } 38 | 39 | // AddUnAuthMethod add unauth method 40 | func (d *SupabaseAuthor) AddUnAuthMethod(method string) { 41 | if d.unAuthMethods == nil { 42 | d.unAuthMethods = make(map[string]struct{}) 43 | } 44 | d.unAuthMethods[method] = struct{}{} 45 | } 46 | 47 | // SupabaseCheckModule is the supabase Auth module for grpc middleware 48 | var SupabaseCheckModule = fx.Provide( 49 | func( 50 | l *zap.Logger, 51 | sSetting SupabaseSettingParams, 52 | ) (out sfx.AuthMiddlewareResult, err error) { 53 | c, err := supabase.NewClient(sSetting.URL, sSetting.Key, nil) 54 | if err != nil { 55 | return 56 | } 57 | out.AuthMiddleware = &SupabaseAuthor{ 58 | client: c, 59 | unAuthMethods: map[string]struct{}{}, 60 | } 61 | return 62 | }, 63 | ) 64 | -------------------------------------------------------------------------------- /services/auth/pkg/afx/supabase_settings.go: -------------------------------------------------------------------------------- 1 | package afx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | // SupabaseSettingParams module params for injecting SupabaseSettings 9 | type SupabaseSettingParams struct { 10 | fx.In 11 | 12 | URL string `name:"supabaseUrl"` 13 | Key string `name:"supabaseKey"` 14 | } 15 | 16 | // SupabaseSettingsResult module result for exporting SupabaseSettings 17 | type SupabaseSettingsResult struct { 18 | fx.Out 19 | 20 | URL string `name:"supabaseUrl" envconfig:"SUPABASE_URL" default:""` 21 | Key string `name:"supabaseKey" envconfig:"SUPABASE_KEY" default:""` 22 | } 23 | 24 | // LoadFromEnv load from env 25 | func (g *SupabaseSettingsResult) LoadFromEnv() (err error) { 26 | err = utility.Load(g) 27 | return 28 | } 29 | 30 | // SupabaseSettingsModule is the supabase settings module 31 | // you can find them in https://app.supabase.io/project/setting/api 32 | var SupabaseSettingsModule = fx.Provide( 33 | func() (out SupabaseSettingsResult, err error) { 34 | err = out.LoadFromEnv() 35 | return 36 | }, 37 | ) 38 | -------------------------------------------------------------------------------- /services/auth/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/auth/internal" 7 | "github.com/moke-game/platform/services/auth/pkg/afx" 8 | ) 9 | 10 | // AuthModule Provides auth service 11 | var AuthModule = fx.Module("auth", 12 | afx.SettingsModule, 13 | internal.ServiceModule, 14 | ) 15 | 16 | // AuthClientModule Provides auth client for grpc 17 | var AuthClientModule = fx.Module("auth_client", 18 | afx.SettingsModule, 19 | afx.AuthClientModule, 20 | ) 21 | 22 | // AuthMiddlewareModule Provides auth middleware for grpc 23 | // if import this module, every grpc unary/stream will auth by {mfx.AuthCheckModule} 24 | var AuthMiddlewareModule = fx.Module("auth_middleware", 25 | afx.SettingsModule, 26 | afx.AuthClientModule, 27 | afx.AuthCheckModule, 28 | ) 29 | 30 | // SupabaseMiddlewareModule Provides supabase middleware for grpc 31 | // if import this module, every grpc unary/stream will auth by supabase auth 32 | // https://supabase.com/docs/guides/auth 33 | var SupabaseMiddlewareModule = fx.Module("supabase_middleware", 34 | afx.SupabaseSettingsModule, 35 | afx.SupabaseCheckModule, 36 | ) 37 | 38 | // AuthAllModule Provides client, service and middleware for auth 39 | var AuthAllModule = fx.Module("auth_all", 40 | internal.ServiceModule, 41 | afx.AuthClientModule, 42 | //afx.AuthCheckModule, 43 | afx.SettingsModule, 44 | ) 45 | -------------------------------------------------------------------------------- /services/buddy/README.md: -------------------------------------------------------------------------------- 1 | # Buddy Service 2 | 3 | 好友服务,提供好友关系的管理服务。 4 | -------------------------------------------------------------------------------- /services/buddy/client/buddy.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/abiosoft/ishell" 8 | mm "github.com/grpc-ecosystem/go-grpc-middleware/v2/metadata" 9 | "github.com/gstones/moke-kit/logging/slogger" 10 | "github.com/gstones/moke-kit/server/pkg/sfx" 11 | "google.golang.org/grpc/metadata" 12 | 13 | buddy "github.com/moke-game/platform/api/gen/buddy/api" 14 | "github.com/moke-game/platform/services/buddy/pkg/bfx" 15 | ) 16 | 17 | // BuddyClient is the client for buddy service 18 | type BuddyClient struct { 19 | client buddy.BuddyServiceClient 20 | cmd *ishell.Cmd 21 | } 22 | 23 | func CreateBuddyClient(host string) (*ishell.Cmd, error) { 24 | client, err := bfx.NewBuddyClient(host, sfx.SecuritySettingsParams{}) 25 | if err != nil { 26 | return nil, err 27 | } 28 | p := &BuddyClient{ 29 | client: client, 30 | } 31 | p.initShells() 32 | return p.cmd, nil 33 | 34 | } 35 | 36 | func (p *BuddyClient) initShells() { 37 | p.cmd = &ishell.Cmd{ 38 | Name: "buddy", 39 | Help: "buddy service interactive", 40 | Aliases: []string{"B"}, 41 | } 42 | p.initSubShells() 43 | } 44 | 45 | func (p *BuddyClient) initSubShells() { 46 | p.cmd.AddCmd(&ishell.Cmd{ 47 | Name: "add", 48 | Help: "add buddy", 49 | Aliases: []string{"A"}, 50 | Func: p.add, 51 | }) 52 | 53 | } 54 | 55 | func (p *BuddyClient) add(c *ishell.Context) { 56 | c.ShowPrompt(false) 57 | defer c.ShowPrompt(true) 58 | 59 | msg := slogger.ReadLine(c, "buddy uid: ") 60 | req := &buddy.AddBuddyRequest{ 61 | Uid: []string{msg}, 62 | ReqInfo: "test", 63 | } 64 | 65 | md := metadata.Pairs("authorization", fmt.Sprintf("%s %v", "bearer", "test")) 66 | ctx := mm.MD(md).ToOutgoing(context.Background()) 67 | 68 | if response, err := p.client.AddBuddy(ctx, req); err != nil { 69 | slogger.Warn(c, err) 70 | } else { 71 | slogger.Infof(c, "Response: %s", response) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /services/buddy/internal/db/factory.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gstones/moke-kit/orm/nerrors" 7 | "github.com/gstones/moke-kit/orm/nosql/diface" 8 | "go.uber.org/zap" 9 | 10 | "github.com/moke-game/platform/services/buddy/internal/db/model" 11 | ) 12 | 13 | type Database struct { 14 | logger *zap.Logger 15 | coll diface.ICollection 16 | cache diface.ICache 17 | } 18 | 19 | func OpenDatabase(l *zap.Logger, coll diface.ICollection, cache diface.ICache) *Database { 20 | return &Database{ 21 | logger: l, 22 | coll: coll, 23 | cache: cache, 24 | } 25 | } 26 | 27 | func (d *Database) NewBuddyQueue(id string) (*model.Dao, error) { 28 | bq := new(model.Dao) 29 | err := bq.Init(id, d.coll, d.cache) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return bq, nil 34 | } 35 | 36 | func (d *Database) CreateBuddyQueue(id string) error { 37 | if bq, err := d.NewBuddyQueue(id); err != nil { 38 | return err 39 | } else if err = bq.Create(); err != nil { 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | func (d *Database) LoadOrCreateBuddyQueue(id string) (*model.Dao, error) { 46 | if bq, err := d.NewBuddyQueue(id); err != nil { 47 | return nil, err 48 | } else if err := bq.Load(); errors.Is(err, nerrors.ErrKeyNotFound) { 49 | if bq, err := d.NewBuddyQueue(id); err != nil { 50 | return nil, err 51 | } else if err := bq.InitDefault(); err != nil { 52 | return nil, err 53 | } else if err := bq.Create(); err != nil { 54 | if err = bq.Load(); err != nil { 55 | return nil, err 56 | } 57 | } else { 58 | return bq, err 59 | } 60 | } else { 61 | return bq, nil 62 | } 63 | return nil, nil 64 | 65 | } 66 | -------------------------------------------------------------------------------- /services/buddy/internal/db/model/dao.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gstones/moke-kit/orm/nerrors" 7 | "github.com/gstones/moke-kit/orm/nosql" 8 | "github.com/gstones/moke-kit/orm/nosql/diface" 9 | 10 | "github.com/moke-game/platform/services/buddy/internal/db/model/data" 11 | ) 12 | 13 | type Dao struct { 14 | nosql.DocumentBase `bson:"-"` 15 | Data *data.BuddyQueue `bson:"data"` 16 | } 17 | 18 | func (b *Dao) Init(id string, ros diface.ICollection, cache diface.ICache) error { 19 | if ros == nil { 20 | return nerrors.ErrDocumentStoreIsNil 21 | } 22 | k, e := NewBuddyQueueKey(id) 23 | if e != nil { 24 | return e 25 | } 26 | b.Data = data.NewBuddyQueue(id) 27 | b.DocumentBase.InitWithCache(context.Background(), &b.Data, b.clear, ros, k, cache) 28 | return nil 29 | } 30 | 31 | func (b *Dao) clear() { 32 | b.Data.Clear() 33 | } 34 | 35 | func (b *Dao) InitDefault() error { 36 | return nil 37 | } 38 | 39 | func (b *Dao) GetBuddyDataByUID(uid string) { 40 | 41 | } 42 | -------------------------------------------------------------------------------- /services/buddy/internal/db/model/data/blocked_list.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "time" 5 | 6 | pb "github.com/moke-game/platform/api/gen/buddy/api" 7 | ) 8 | 9 | type BlockedProfile struct { 10 | ID string 11 | AddTime int64 12 | } 13 | 14 | func (p *BlockedProfile) ToProto() *pb.Blocked { 15 | return &pb.Blocked{ 16 | Uid: p.ID, 17 | AddTime: p.AddTime, 18 | } 19 | } 20 | 21 | func (bq *BuddyQueue) AddBlocked(id string) { 22 | bq.BlockedProfiles[id] = &BlockedProfile{ 23 | ID: id, 24 | AddTime: time.Now().Unix(), 25 | } 26 | } 27 | 28 | func (bq *BuddyQueue) AddBlockedProfiles(ids ...string) { 29 | for _, v := range ids { 30 | if v == "" { 31 | continue 32 | } 33 | bq.BlockedProfiles[v] = &BlockedProfile{ 34 | ID: v, 35 | AddTime: time.Now().Unix(), 36 | } 37 | } 38 | } 39 | 40 | func (bq *BuddyQueue) DeleteBlocked(id string) { 41 | delete(bq.BlockedProfiles, id) 42 | } 43 | 44 | func (bq *BuddyQueue) DeleteBlockedProfiles(ids ...string) { 45 | if len(ids) <= 0 { 46 | bq.BlockedProfiles = make(map[string]*BlockedProfile) 47 | return 48 | } 49 | for _, v := range ids { 50 | if v == "" { 51 | continue 52 | } 53 | delete(bq.BlockedProfiles, v) 54 | } 55 | } 56 | 57 | func (bq *BuddyQueue) IsBlocked(id string) bool { 58 | if _, ok := bq.BlockedProfiles[id]; ok { 59 | return true 60 | } 61 | return false 62 | } 63 | 64 | func (bq *BuddyQueue) GetBlockedNum() int32 { 65 | return int32(len(bq.BlockedProfiles)) 66 | } 67 | 68 | func (bq *BuddyQueue) FilterBlocked(ids ...string) []string { 69 | if len(bq.BlockedProfiles) <= 0 || bq.BlockedProfiles == nil { 70 | return ids 71 | } 72 | var blockedProfiles []string 73 | 74 | for _, v := range ids { 75 | if bq.IsBlocked(v) { 76 | continue 77 | } 78 | blockedProfiles = append(blockedProfiles, v) 79 | } 80 | return blockedProfiles 81 | } 82 | -------------------------------------------------------------------------------- /services/buddy/internal/db/model/data/recent_met.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | type RecentProfile struct { 4 | ID string 5 | AddTime int64 6 | } 7 | 8 | func (bq *BuddyQueue) AddRecentProfiles() { 9 | 10 | } 11 | 12 | func (bq *BuddyQueue) DeleteRecentProfiles(ids ...string) { 13 | for _, v := range ids { 14 | for i := 0; i < len(bq.RecentMet); i++ { 15 | if bq.RecentMet[i].ID == v { 16 | bq.RecentMet = append(bq.RecentMet[:i], bq.RecentMet[i+1:]...) 17 | i-- 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /services/buddy/internal/db/model/keys.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/gstones/moke-kit/orm/nosql/key" 4 | 5 | func NewBuddyQueueKey(id string) (key.Key, error) { 6 | return key.NewKeyFromParts("buddy", id) 7 | } 8 | -------------------------------------------------------------------------------- /services/buddy/internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 10 | ErrBuddyAlreadyAdded = status.Error(codes.AlreadyExists, "ErrBuddyAlreadyAdded") 11 | ErrBuddyAlreadyRequested = status.Error(codes.AlreadyExists, "ErrBuddyAlreadyRequested") 12 | ErrNoMetaData = status.Error(codes.PermissionDenied, "ErrNoMetaData") 13 | ErrBuddiesNotFound = status.Error(codes.NotFound, "ErrBuddiesNotFound") 14 | ErrInviterNotFound = status.Error(codes.NotFound, "ErrInviterNotFound") 15 | ErrSelfBuddiesTopLimit = status.Error(codes.Internal, "ErrSelfBuddiesTopLimit") 16 | ErrTargetInviterTopLimit = status.Error(codes.Internal, "ErrTargetInviterTopLimit") 17 | ErrTargetBuddiesTopLimit = status.Error(codes.Internal, "ErrTargetBuddiesTopLimit") 18 | ErrCanNotAddSelf = status.Error(codes.Internal, "ErrCanNotAddSelf") 19 | ErrInTargetBlockedList = status.Error(codes.Internal, "ErrInTargetBlockedList") 20 | ErrInSelfBlockedList = status.Error(codes.Internal, "ErrInSelfBlockedList") 21 | ErrDBErr = status.Error(codes.Internal, "ErrDBErr") 22 | ) 23 | -------------------------------------------------------------------------------- /services/buddy/internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/mq/miface" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/nosql/diface" 7 | "github.com/gstones/moke-kit/orm/pkg/ofx" 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | "github.com/gstones/moke-kit/server/siface" 10 | "go.uber.org/fx" 11 | "go.uber.org/zap" 12 | 13 | pb "github.com/moke-game/platform/api/gen/buddy/api" 14 | "github.com/moke-game/platform/services/buddy/internal/db" 15 | "github.com/moke-game/platform/services/buddy/pkg/bfx" 16 | ) 17 | 18 | type Service struct { 19 | logger *zap.Logger 20 | db *db.Database 21 | mq miface.MessageQueue 22 | maxInviter int32 23 | maxBuddies int32 24 | maxBlocked int32 25 | } 26 | 27 | func NewService( 28 | l *zap.Logger, 29 | coll diface.ICollection, 30 | cache diface.ICache, 31 | mq miface.MessageQueue, 32 | setting bfx.BuddySettingsParams, 33 | ) (result *Service, err error) { 34 | result = &Service{ 35 | logger: l, 36 | db: db.OpenDatabase(l, coll, cache), 37 | mq: mq, 38 | maxBuddies: setting.BuddyMaxCount, 39 | maxBlocked: setting.BlockedMaxCount, 40 | maxInviter: setting.InviterMaxCount, 41 | } 42 | return 43 | } 44 | 45 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 46 | pb.RegisterBuddyServiceServer(server.GrpcServer(), s) 47 | return nil 48 | } 49 | 50 | var Module = fx.Provide( 51 | func( 52 | l *zap.Logger, 53 | dProvider ofx.DocumentStoreParams, 54 | mqParams mfx.MessageQueueParams, 55 | setting bfx.BuddySettingsParams, 56 | rcParams ofx.RedisCacheParams, 57 | ) (out sfx.GrpcServiceResult, err error) { 58 | if coll, e := dProvider.DriverProvider.OpenDbDriver(setting.Name); e != nil { 59 | err = e 60 | } else if s, e := NewService(l, coll, rcParams.RedisCache, mqParams.MessageQueue, setting); e != nil { 61 | err = e 62 | } else { 63 | out.GrpcService = s 64 | } 65 | return 66 | }, 67 | ) 68 | -------------------------------------------------------------------------------- /services/buddy/internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gstones/moke-kit/mq/common" 7 | ) 8 | 9 | func MakeBuddyTopic(uid string) string { 10 | return common.NatsHeader.CreateTopic(fmt.Sprintf("buddy.changes.%s", uid)) 11 | } 12 | -------------------------------------------------------------------------------- /services/buddy/pkg/bfx/buddy_client.go: -------------------------------------------------------------------------------- 1 | package bfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/server/pkg/sfx" 5 | "github.com/gstones/moke-kit/server/tools" 6 | "go.uber.org/fx" 7 | 8 | pb "github.com/moke-game/platform/api/gen/buddy/api" 9 | ) 10 | 11 | type BuddyClientParams struct { 12 | fx.In 13 | 14 | BuddyClient pb.BuddyServiceClient `name:"BuddyClient"` 15 | } 16 | 17 | type BuddyClientResult struct { 18 | fx.Out 19 | 20 | BuddyClient pb.BuddyServiceClient `name:"BuddyClient"` 21 | } 22 | 23 | func NewBuddyClient(host string, sSetting sfx.SecuritySettingsParams) (pb.BuddyServiceClient, error) { 24 | if sSetting.MTLSEnable { 25 | if conn, err := tools.DialWithSecurity( 26 | host, 27 | sSetting.ClientCert, 28 | sSetting.ClientKey, 29 | sSetting.ServerName, 30 | sSetting.ServerCaCert, 31 | ); err != nil { 32 | return nil, err 33 | } else { 34 | return pb.NewBuddyServiceClient(conn), nil 35 | } 36 | } else { 37 | if conn, err := tools.DialInsecure(host); err != nil { 38 | return nil, err 39 | } else { 40 | return pb.NewBuddyServiceClient(conn), nil 41 | } 42 | } 43 | } 44 | 45 | var BuddyClientModule = fx.Provide( 46 | func( 47 | setting BuddySettingsParams, 48 | sSetting sfx.SecuritySettingsParams, 49 | ) (out BuddyClientResult, err error) { 50 | if cli, e := NewBuddyClient(setting.BuddyUrl, sSetting); e != nil { 51 | err = e 52 | } else { 53 | out.BuddyClient = cli 54 | } 55 | return 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /services/buddy/pkg/bfx/buddy_settings.go: -------------------------------------------------------------------------------- 1 | package bfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type BuddySettingsParams struct { 9 | fx.In 10 | 11 | BuddyUrl string `name:"BuddyUrl"` 12 | InviterMaxCount int32 `name:"InviterMaxCount"` 13 | BuddyMaxCount int32 `name:"BuddyMaxCount"` 14 | BlockedMaxCount int32 `name:"BlockedMaxCount"` 15 | Name string `name:"Name"` 16 | } 17 | 18 | type BuddySettingsResult struct { 19 | fx.Out 20 | 21 | BuddyUrl string `name:"BuddyUrl" envconfig:"BUDDY_URL" default:"localhost:8081"` 22 | BuddyMaxCount int32 `name:"BuddyMaxCount" envconfig:"BUDDY_MAX_COUNT" default:"1000"` 23 | BlockedMaxCount int32 `name:"BlockedMaxCount" envconfig:"BLOCKED_MAX_COUNT" default:"100"` 24 | InviterMaxCount int32 `name:"InviterMaxCount" envconfig:"INVITER_MAX_COUNT" default:"100"` 25 | Name string `name:"Name" envconfig:"NAME" default:"buddy"` 26 | } 27 | 28 | func (g *BuddySettingsResult) LoadFromEnv() (err error) { 29 | err = utility.Load(g) 30 | return 31 | } 32 | 33 | var BuddySettingsModule = fx.Provide( 34 | func() (out BuddySettingsResult, err error) { 35 | err = out.LoadFromEnv() 36 | return 37 | }, 38 | ) 39 | -------------------------------------------------------------------------------- /services/buddy/pkg/module/service.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/buddy/internal/service" 7 | "github.com/moke-game/platform/services/buddy/pkg/bfx" 8 | ) 9 | 10 | // BuddyModule Provides buddy service 11 | var BuddyModule = fx.Module("buddy", 12 | service.Module, 13 | bfx.BuddySettingsModule, 14 | ) 15 | 16 | // BuddyClientModule Provides buddy client for grpc 17 | var BuddyClientModule = fx.Module("buddy_client", 18 | bfx.BuddyClientModule, 19 | bfx.BuddySettingsModule, 20 | ) 21 | 22 | // BuddyAllModule Provides client, service for buddy 23 | var BuddyAllModule = fx.Module("buddy_all", 24 | service.Module, 25 | bfx.BuddyClientModule, 26 | bfx.BuddySettingsModule, 27 | ) 28 | -------------------------------------------------------------------------------- /services/chat/README.md: -------------------------------------------------------------------------------- 1 | # Chat Service 2 | 3 | 聊天服务,提供聊天功能。 -------------------------------------------------------------------------------- /services/chat/internal/service/db/database.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/redis/go-redis/v9" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | type Database struct { 12 | *redis.Client 13 | logger *zap.Logger 14 | } 15 | 16 | func OpenDatabase(l *zap.Logger, client *redis.Client) *Database { 17 | return &Database{ 18 | client, 19 | l, 20 | } 21 | } 22 | 23 | func (db *Database) IsBlocked(uid string) (bool, error) { 24 | if key, err := makeBlockedListKey(uid); err != nil { 25 | return false, err 26 | } else if val := db.Exists(context.Background(), key.String()).Val(); val > 0 { 27 | return true, nil 28 | } 29 | return false, nil 30 | } 31 | 32 | func (db *Database) AddBlockedList(blockedUid string, duration int64) error { 33 | if key, err := makeBlockedListKey(blockedUid); err != nil { 34 | return err 35 | } else if err := db.Set( 36 | context.Background(), 37 | key.String(), 38 | duration, 39 | time.Duration(duration)*time.Second, 40 | ).Err(); err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (db *Database) RemoveBlockedList(blockedUid string) error { 47 | if key, err := makeBlockedListKey(blockedUid); err != nil { 48 | return err 49 | } else if err := db.Del(context.Background(), key.String()).Err(); err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /services/chat/internal/service/db/keys.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "github.com/gstones/moke-kit/orm/nosql/key" 4 | 5 | func makeBlockedListKey(uid string) (key.Key, error) { 6 | return key.NewKeyFromParts("chat", "blocked", uid) 7 | } 8 | -------------------------------------------------------------------------------- /services/chat/internal/service/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNoMetaData = status.Error(codes.PermissionDenied, "ErrNoMetaData") 10 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 11 | ) 12 | -------------------------------------------------------------------------------- /services/chat/internal/service/private/handlers.go: -------------------------------------------------------------------------------- 1 | package private 2 | 3 | import ( 4 | "context" 5 | 6 | "go.uber.org/zap" 7 | 8 | pb "github.com/moke-game/platform/api/gen/chat/api" 9 | "github.com/moke-game/platform/services/chat/internal/service/errors" 10 | ) 11 | 12 | func (s *Service) AddBlocked(_ context.Context, request *pb.AddBlockedRequest) (*pb.AddBlockedResponse, error) { 13 | if request.IsBlocked { 14 | if err := s.db.AddBlockedList(request.ProfileId, request.Duration); err != nil { 15 | s.logger.Error("add blocked failed", zap.Error(err)) 16 | return nil, errors.ErrGeneralFailure 17 | } 18 | return &pb.AddBlockedResponse{}, nil 19 | } else { 20 | if err := s.db.RemoveBlockedList(request.ProfileId); err != nil { 21 | s.logger.Error("remove blocked failed", zap.Error(err)) 22 | return nil, errors.ErrGeneralFailure 23 | } 24 | return &pb.AddBlockedResponse{}, nil 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /services/chat/internal/service/private/service.go: -------------------------------------------------------------------------------- 1 | package private 2 | 3 | import ( 4 | mfx2 "github.com/gstones/moke-kit/fxmain/pkg/mfx" 5 | "github.com/gstones/moke-kit/orm/pkg/ofx" 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/siface" 8 | "github.com/gstones/moke-kit/utility" 9 | "go.uber.org/fx" 10 | "go.uber.org/zap" 11 | 12 | pb "github.com/moke-game/platform/api/gen/chat/api" 13 | "github.com/moke-game/platform/services/chat/internal/service/db" 14 | "github.com/moke-game/platform/services/chat/pkg/cfx" 15 | ) 16 | 17 | type Service struct { 18 | utility.WithoutAuth 19 | logger *zap.Logger 20 | appId string 21 | deployment string 22 | db *db.Database 23 | } 24 | 25 | func NewService( 26 | l *zap.Logger, 27 | db *db.Database, 28 | deployment string, 29 | appId string, 30 | ) (result *Service, err error) { 31 | result = &Service{ 32 | logger: l, 33 | appId: appId, 34 | deployment: deployment, 35 | db: db, 36 | } 37 | return 38 | } 39 | 40 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 41 | pb.RegisterChatPrivateServiceServer(server.GrpcServer(), s) 42 | return nil 43 | } 44 | 45 | var ChatService = fx.Provide( 46 | func( 47 | l *zap.Logger, 48 | setting cfx.ChatSettingParams, 49 | aParams mfx2.AppParams, 50 | redisParams ofx.RedisParams, 51 | ) (out sfx.GrpcServiceResult, err error) { 52 | if s, err := NewService( 53 | l, 54 | db.OpenDatabase(l, redisParams.Redis), 55 | aParams.Deployment, 56 | aParams.AppId, 57 | ); err != nil { 58 | return out, err 59 | } else { 60 | out.GrpcService = s 61 | } 62 | return 63 | }, 64 | ) 65 | -------------------------------------------------------------------------------- /services/chat/internal/service/public/service.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "time" 5 | 6 | mfx2 "github.com/gstones/moke-kit/fxmain/pkg/mfx" 7 | "github.com/gstones/moke-kit/mq/miface" 8 | "github.com/gstones/moke-kit/mq/pkg/mfx" 9 | "github.com/gstones/moke-kit/orm/pkg/ofx" 10 | "github.com/gstones/moke-kit/server/pkg/sfx" 11 | "github.com/gstones/moke-kit/server/siface" 12 | "github.com/gstones/moke-kit/utility" 13 | "go.uber.org/fx" 14 | "go.uber.org/zap" 15 | 16 | pb "github.com/moke-game/platform/api/gen/chat/api" 17 | "github.com/moke-game/platform/services/chat/internal/service/db" 18 | "github.com/moke-game/platform/services/chat/internal/service/errors" 19 | "github.com/moke-game/platform/services/chat/pkg/cfx" 20 | ) 21 | 22 | type Service struct { 23 | logger *zap.Logger 24 | mq miface.MessageQueue 25 | chatInterval time.Duration 26 | 27 | appId string 28 | deployment string 29 | db *db.Database 30 | } 31 | 32 | func NewService( 33 | l *zap.Logger, 34 | mq miface.MessageQueue, 35 | chatInterval int, 36 | deployment string, 37 | appId string, 38 | db *db.Database, 39 | ) (result *Service, err error) { 40 | result = &Service{ 41 | logger: l, 42 | mq: mq, 43 | appId: appId, 44 | deployment: deployment, 45 | chatInterval: time.Duration(chatInterval) * time.Second, 46 | db: db, 47 | } 48 | return 49 | } 50 | 51 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 52 | pb.RegisterChatServiceServer(server.GrpcServer(), s) 53 | return nil 54 | } 55 | 56 | var ChatService = fx.Provide( 57 | func( 58 | l *zap.Logger, 59 | setting cfx.ChatSettingParams, 60 | mqParams mfx.MessageQueueParams, 61 | aParams mfx2.AppParams, 62 | redisParams ofx.RedisParams, 63 | ) (out sfx.GrpcServiceResult, err error) { 64 | if s, err := NewService( 65 | l, 66 | mqParams.MessageQueue, 67 | setting.ChatInterval, 68 | aParams.Deployment, 69 | aParams.AppId, 70 | db.OpenDatabase(l, redisParams.Redis), 71 | ); err != nil { 72 | return out, err 73 | } else { 74 | out.GrpcService = s 75 | } 76 | return 77 | }, 78 | ) 79 | 80 | func (s *Service) Chat(server pb.ChatService_ChatServer) error { 81 | uid, ok := utility.FromContext(server.Context(), utility.UIDContextKey) 82 | if !ok { 83 | return errors.ErrNoMetaData 84 | } 85 | chatter := CreateChatter( 86 | uid, 87 | s.deployment, 88 | s.appId, 89 | server, 90 | s.logger, 91 | s.mq, 92 | s.chatInterval, 93 | s.db, 94 | ) 95 | chatter.Init() 96 | go chatter.Update() 97 | <-server.Context().Done() 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /services/chat/pkg/cfx/chat_client.go: -------------------------------------------------------------------------------- 1 | package cfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/tools" 8 | 9 | pb "github.com/moke-game/platform/api/gen/chat/api" 10 | ) 11 | 12 | type ChatClientParams struct { 13 | fx.In 14 | 15 | ChatClient pb.ChatServiceClient `name:"ChatClient"` 16 | } 17 | 18 | type ChatClientResult struct { 19 | fx.Out 20 | 21 | ChatClient pb.ChatServiceClient `name:"ChatClient"` 22 | } 23 | 24 | func NewChatClient(host string, sSetting sfx.SecuritySettingsParams) (pb.ChatServiceClient, error) { 25 | if sSetting.MTLSEnable { 26 | if conn, err := tools.DialWithSecurity( 27 | host, 28 | sSetting.ClientCert, 29 | sSetting.ClientKey, 30 | sSetting.ServerName, 31 | sSetting.ServerCaCert, 32 | ); err != nil { 33 | return nil, err 34 | } else { 35 | return pb.NewChatServiceClient(conn), nil 36 | } 37 | } else { 38 | if conn, err := tools.DialInsecure(host); err != nil { 39 | return nil, err 40 | } else { 41 | return pb.NewChatServiceClient(conn), nil 42 | } 43 | } 44 | } 45 | 46 | var ChatClientModule = fx.Provide( 47 | func( 48 | setting ChatSettingParams, 49 | sSetting sfx.SecuritySettingsParams, 50 | ) (out ChatClientResult, err error) { 51 | if cli, e := NewChatClient(setting.ChatUrl, sSetting); e != nil { 52 | err = e 53 | } else { 54 | out.ChatClient = cli 55 | } 56 | return 57 | }, 58 | ) 59 | -------------------------------------------------------------------------------- /services/chat/pkg/cfx/chat_private_client.go: -------------------------------------------------------------------------------- 1 | package cfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/tools" 8 | 9 | pb "github.com/moke-game/platform/api/gen/chat/api" 10 | ) 11 | 12 | type ChatPrivateClientParams struct { 13 | fx.In 14 | 15 | ChatClient pb.ChatPrivateServiceClient `name:"ChatPrivateClient"` 16 | } 17 | 18 | type ChatPrivateClientResult struct { 19 | fx.Out 20 | 21 | ChatClient pb.ChatPrivateServiceClient `name:"ChatPrivateClient"` 22 | } 23 | 24 | func NewChatPrivateClient(host string, sSetting sfx.SecuritySettingsParams) (pb.ChatPrivateServiceClient, error) { 25 | if sSetting.MTLSEnable { 26 | if conn, err := tools.DialWithSecurity( 27 | host, 28 | sSetting.ClientCert, 29 | sSetting.ClientKey, 30 | sSetting.ServerName, 31 | sSetting.ServerCaCert, 32 | ); err != nil { 33 | return nil, err 34 | } else { 35 | return pb.NewChatPrivateServiceClient(conn), nil 36 | } 37 | } else { 38 | if conn, err := tools.DialInsecure(host); err != nil { 39 | return nil, err 40 | } else { 41 | return pb.NewChatPrivateServiceClient(conn), nil 42 | } 43 | } 44 | } 45 | 46 | var ChatPrivateClientModule = fx.Provide( 47 | func( 48 | setting ChatSettingParams, 49 | sSetting sfx.SecuritySettingsParams, 50 | ) (out ChatPrivateClientResult, err error) { 51 | if cli, e := NewChatPrivateClient(setting.ChatUrl, sSetting); e != nil { 52 | err = e 53 | } else { 54 | out.ChatClient = cli 55 | } 56 | return 57 | }, 58 | ) 59 | -------------------------------------------------------------------------------- /services/chat/pkg/cfx/chat_settings.go: -------------------------------------------------------------------------------- 1 | package cfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type ChatSettingParams struct { 9 | fx.In 10 | Name string `name:"ChatName"` 11 | ChatUrl string `name:"ChatUrl"` 12 | ChatInterval int `name:"ChatInterval"` 13 | } 14 | 15 | type ChatSettingResult struct { 16 | fx.Out 17 | Name string `name:"ChatName" envconfig:"CHAT_NAME" default:"chat"` 18 | ChatUrl string `name:"ChatUrl" envconfig:"CHAT_URL" default:"localhost:8081"` 19 | ChatInterval int `name:"ChatInterval" envconfig:"WORLD_CHAT_INTERVAL" default:"2"` 20 | } 21 | 22 | func (l *ChatSettingResult) LoadFromEnv() (err error) { 23 | err = utility.Load(l) 24 | return 25 | } 26 | 27 | var ChatSettingsModule = fx.Provide( 28 | func() (out ChatSettingResult, err error) { 29 | err = out.LoadFromEnv() 30 | return 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /services/chat/pkg/module/service.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/chat/internal/service/private" 7 | "github.com/moke-game/platform/services/chat/internal/service/public" 8 | "github.com/moke-game/platform/services/chat/pkg/cfx" 9 | ) 10 | 11 | // ChatModule Provides chat service 12 | var ChatModule = fx.Module("chat", 13 | public.ChatService, 14 | private.ChatService, 15 | cfx.ChatSettingsModule, 16 | ) 17 | 18 | // ChatClientModule Provides chat client for grpc 19 | var ChatClientModule = fx.Module("chat_client", 20 | cfx.ChatClientModule, 21 | cfx.ChatSettingsModule, 22 | ) 23 | 24 | // ChatPrivateClientModule Provides chat private client for grpc 25 | var ChatPrivateClientModule = fx.Module("chat_private_client", 26 | cfx.ChatPrivateClientModule, 27 | cfx.ChatSettingsModule, 28 | ) 29 | 30 | // ChatAllModule Provides client, service for chat 31 | var ChatAllModule = fx.Module("chat_all", 32 | public.ChatService, 33 | private.ChatService, 34 | cfx.ChatClientModule, 35 | cfx.ChatSettingsModule, 36 | cfx.ChatPrivateClientModule, 37 | ) 38 | -------------------------------------------------------------------------------- /services/knapsack/README.md: -------------------------------------------------------------------------------- 1 | # Knapsack Service 2 | 背包服务器 3 | 4 | 5 | ## 流程图 6 | 7 | ![flow](../../draws/knapsack.drawio.png) -------------------------------------------------------------------------------- /services/knapsack/changes/topics.go: -------------------------------------------------------------------------------- 1 | package changes 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gstones/moke-kit/mq/common" 7 | "google.golang.org/protobuf/proto" 8 | 9 | pb "github.com/moke-game/platform/api/gen/knapsack/api" 10 | ) 11 | 12 | func CreateTopic(uid string) string { 13 | return common.NatsHeader.CreateTopic(fmt.Sprintf("knapsack.changes.%s", uid)) 14 | } 15 | 16 | func Pack(msg *pb.KnapsackModify) ([]byte, error) { 17 | return proto.Marshal(msg) 18 | } 19 | 20 | func UnPack(data []byte) (*pb.KnapsackModify, error) { 21 | msg := &pb.KnapsackModify{} 22 | if err := proto.Unmarshal(data, msg); err != nil { 23 | return nil, err 24 | } 25 | return msg, nil 26 | } 27 | -------------------------------------------------------------------------------- /services/knapsack/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNoMetaData = status.Error(codes.Internal, "ErrNoMetaData") 10 | ErrNotFound = status.Error(codes.NotFound, "ErrNotFound") 11 | ErrNotEnough = status.Error(codes.Internal, "ErrNotEnough") 12 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 13 | ) 14 | -------------------------------------------------------------------------------- /services/knapsack/internal/db/factory.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gstones/moke-kit/orm/nerrors" 7 | "github.com/gstones/moke-kit/orm/nosql/diface" 8 | "go.uber.org/zap" 9 | 10 | "github.com/moke-game/platform/services/knapsack/internal/db/model" 11 | ) 12 | 13 | type Database struct { 14 | logger *zap.Logger 15 | coll diface.ICollection 16 | cache diface.ICache 17 | } 18 | 19 | func OpenDatabase(l *zap.Logger, coll diface.ICollection, cache diface.ICache) *Database { 20 | return &Database{ 21 | logger: l, 22 | coll: coll, 23 | cache: cache, 24 | } 25 | } 26 | 27 | func NewKnapsackModel(id string, doc diface.ICollection, cache diface.ICache) (*model.Dao, error) { 28 | dm := &model.Dao{} 29 | if err := dm.Init(id, doc, cache); err != nil { 30 | return nil, err 31 | } 32 | return dm, nil 33 | } 34 | 35 | func (db *Database) LoadKnapsack(uid string) (*model.Dao, error) { 36 | if dm, err := NewKnapsackModel(uid, db.coll, db.cache); err != nil { 37 | return nil, err 38 | } else if err = dm.Load(); err != nil { 39 | return nil, err 40 | } else { 41 | return dm, nil 42 | } 43 | } 44 | 45 | func (db *Database) CreateKnapsack(uid string) (*model.Dao, error) { 46 | if dm, err := NewKnapsackModel(uid, db.coll, db.cache); err != nil { 47 | return nil, err 48 | } else if err = dm.InitDefault(uid); err != nil { 49 | return nil, err 50 | } else if err = dm.Create(); err != nil { 51 | return nil, err 52 | } else { 53 | return dm, nil 54 | } 55 | } 56 | 57 | func (db *Database) LoadOrCreateKnapsack(uid string) (*model.Dao, error) { 58 | if dao, err := db.LoadKnapsack(uid); err != nil { 59 | if errors.Is(err, nerrors.ErrNotFound) { 60 | return db.CreateKnapsack(uid) 61 | } 62 | return nil, err 63 | } else { 64 | return dao, nil 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /services/knapsack/internal/db/model/dao.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "google.golang.org/protobuf/proto" 9 | 10 | "github.com/gstones/moke-kit/orm/nosql" 11 | "github.com/gstones/moke-kit/orm/nosql/diface" 12 | 13 | pb "github.com/moke-game/platform/api/gen/knapsack/api" 14 | ) 15 | 16 | type Dao struct { 17 | nosql.DocumentBase `bson:"-"` 18 | Data *pb.Knapsack `bson:"data"` 19 | changesCache *pb.Knapsack `bson:"-"` 20 | } 21 | 22 | func (d *Dao) Init(id string, doc diface.ICollection, cache diface.ICache) error { 23 | key, e := NewKnapsackKey(id) 24 | if e != nil { 25 | return e 26 | } 27 | d.initData() 28 | d.DocumentBase.InitWithCache(context.Background(), &d.Data, d.clear, doc, key, cache) 29 | return nil 30 | } 31 | 32 | func (d *Dao) ToProto() *pb.Knapsack { 33 | return proto.Clone(d.Data).(*pb.Knapsack) 34 | } 35 | 36 | func (d *Dao) GetAndDeleteChanges(incrItems, decrItems map[int64]*pb.Item, source string) *pb.KnapsackModify { 37 | if d.changesCache == nil { 38 | return nil 39 | } 40 | changes := proto.Clone(d.changesCache).(*pb.Knapsack) 41 | d.changesCache.Reset() 42 | d.changesCache.Items = make(map[int64]*pb.Item) 43 | d.changesCache.Features = make(map[int32]bool) 44 | return &pb.KnapsackModify{ 45 | IncrItems: incrItems, 46 | DecrItems: decrItems, 47 | Knapsack: changes, 48 | Source: source, 49 | } 50 | } 51 | 52 | func (d *Dao) AddFeatures(features map[int32]bool) { 53 | if d.Data.Features == nil { 54 | d.Data.Features = make(map[int32]bool) 55 | } 56 | if d.changesCache.Features == nil { 57 | d.changesCache.Features = make(map[int32]bool) 58 | } 59 | for k, v := range features { 60 | d.Data.Features[k] = v 61 | } 62 | d.changesCache.Features = d.Data.Features 63 | } 64 | 65 | func (d *Dao) AddItems(items map[int64]*pb.Item) { 66 | if d.Data.Items == nil { 67 | d.Data.Items = map[int64]*pb.Item{} 68 | } 69 | for k, v := range items { 70 | if _, ok := d.Data.Items[k]; ok { 71 | d.Data.Items[k].Num += v.Num 72 | } else { 73 | d.Data.Items[k] = v 74 | } 75 | d.changesCache.Items[k] = d.Data.Items[k] 76 | } 77 | } 78 | 79 | func (d *Dao) RemoveItems(items map[int64]*pb.Item) error { 80 | nowTime := time.Now().UTC().Unix() 81 | for k, v := range items { 82 | if _, ok := d.Data.Items[k]; ok { 83 | if d.Data.Items[k].Expire > 0 && d.Data.Items[k].Expire < nowTime { 84 | return fmt.Errorf("item %d expire is %d", k, d.Data.Items[k].Expire) 85 | } 86 | if d.Data.Items[k].Num < v.Num { 87 | return fmt.Errorf("item %d not enough, need:%d has:%d", k, v.Num, d.Data.Items[k].Num) 88 | } 89 | d.Data.Items[k].Num -= v.Num 90 | d.changesCache.Items[k] = d.Data.Items[k] 91 | } else { 92 | return fmt.Errorf("item %d not found", k) 93 | } 94 | } 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /services/knapsack/internal/db/model/data.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import pb "github.com/moke-game/platform/api/gen/knapsack/api" 4 | 5 | func (d *Dao) initData() { 6 | d.Data = &pb.Knapsack{} 7 | d.Data.Items = make(map[int64]*pb.Item) 8 | d.Data.Features = make(map[int32]bool) 9 | d.changesCache = &pb.Knapsack{} 10 | d.changesCache.Items = make(map[int64]*pb.Item) 11 | d.changesCache.Features = make(map[int32]bool) 12 | } 13 | 14 | func (d *Dao) InitDefault(uid string) error { 15 | d.Data = &pb.Knapsack{} 16 | d.Data.Uid = uid 17 | d.Data.Items = make(map[int64]*pb.Item) 18 | d.Data.Features = make(map[int32]bool) 19 | d.changesCache = &pb.Knapsack{} 20 | d.changesCache.Items = make(map[int64]*pb.Item) 21 | d.changesCache.Features = make(map[int32]bool) 22 | return nil 23 | } 24 | 25 | func (d *Dao) clear() { 26 | d.Data.Reset() 27 | } 28 | 29 | func (d *Dao) GetUid() string { 30 | return d.Data.GetUid() 31 | } 32 | -------------------------------------------------------------------------------- /services/knapsack/internal/db/model/keys.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/nosql/key" 5 | ) 6 | 7 | func NewKnapsackKey(id string) (key.Key, error) { 8 | return key.NewKeyFromParts("knapsack", id) 9 | } 10 | -------------------------------------------------------------------------------- /services/knapsack/internal/service/private/service.go: -------------------------------------------------------------------------------- 1 | package private 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | "go.uber.org/zap" 7 | 8 | pb "github.com/moke-game/platform/api/gen/knapsack/api" 9 | "github.com/moke-game/platform/services/knapsack/internal/db" 10 | "github.com/moke-game/platform/services/knapsack/pkg/kfx" 11 | 12 | "github.com/gstones/moke-kit/mq/miface" 13 | "github.com/gstones/moke-kit/mq/pkg/mfx" 14 | "github.com/gstones/moke-kit/orm/nosql/diface" 15 | "github.com/gstones/moke-kit/orm/pkg/ofx" 16 | "github.com/gstones/moke-kit/server/pkg/sfx" 17 | "github.com/gstones/moke-kit/server/siface" 18 | ) 19 | 20 | type Service struct { 21 | utility.WithoutAuth 22 | 23 | logger *zap.Logger 24 | db *db.Database 25 | mq miface.MessageQueue 26 | } 27 | 28 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 29 | pb.RegisterKnapsackPrivateServiceServer(server.GrpcServer(), s) 30 | return nil 31 | } 32 | func NewService( 33 | l *zap.Logger, 34 | coll diface.ICollection, 35 | mq miface.MessageQueue, 36 | redisCache diface.ICache, 37 | ) (result *Service, err error) { 38 | result = &Service{ 39 | logger: l, 40 | db: db.OpenDatabase(l, coll, redisCache), 41 | mq: mq, 42 | } 43 | return 44 | } 45 | 46 | var Module = fx.Provide( 47 | func( 48 | logger *zap.Logger, 49 | dbProvider ofx.DocumentStoreParams, 50 | setting kfx.KnapsackSettingParams, 51 | mParams mfx.MessageQueueParams, 52 | rcParams ofx.RedisCacheParams, 53 | ) (out sfx.GrpcServiceResult, err error) { 54 | if coll, e := dbProvider.DriverProvider.OpenDbDriver(setting.KnapsackStoreName); e != nil { 55 | err = e 56 | } else { 57 | if svc, e := NewService( 58 | logger, 59 | coll, 60 | mParams.MessageQueue, 61 | rcParams.RedisCache, 62 | ); e != nil { 63 | err = e 64 | } else { 65 | out.GrpcService = svc 66 | } 67 | } 68 | return 69 | }, 70 | ) 71 | -------------------------------------------------------------------------------- /services/knapsack/internal/service/public/service.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/mq/miface" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/nosql/diface" 7 | "github.com/gstones/moke-kit/orm/pkg/ofx" 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | "github.com/gstones/moke-kit/server/siface" 10 | "go.uber.org/fx" 11 | "go.uber.org/zap" 12 | 13 | pb "github.com/moke-game/platform/api/gen/knapsack/api" 14 | "github.com/moke-game/platform/services/knapsack/internal/db" 15 | "github.com/moke-game/platform/services/knapsack/pkg/kfx" 16 | ) 17 | 18 | type Service struct { 19 | logger *zap.Logger 20 | db *db.Database 21 | mq miface.MessageQueue 22 | } 23 | 24 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 25 | pb.RegisterKnapsackServiceServer(server.GrpcServer(), s) 26 | return nil 27 | } 28 | func NewService( 29 | l *zap.Logger, 30 | coll diface.ICollection, 31 | mq miface.MessageQueue, 32 | redisCache diface.ICache, 33 | ) (result *Service, err error) { 34 | result = &Service{ 35 | logger: l, 36 | db: db.OpenDatabase(l, coll, redisCache), 37 | mq: mq, 38 | } 39 | return 40 | } 41 | 42 | var Module = fx.Provide( 43 | func( 44 | logger *zap.Logger, 45 | dbProvider ofx.DocumentStoreParams, 46 | setting kfx.KnapsackSettingParams, 47 | mParams mfx.MessageQueueParams, 48 | rcParams ofx.RedisCacheParams, 49 | ) (out sfx.GrpcServiceResult, err error) { 50 | if coll, e := dbProvider.DriverProvider.OpenDbDriver(setting.KnapsackStoreName); e != nil { 51 | err = e 52 | } else { 53 | if svc, e := NewService( 54 | logger, 55 | coll, 56 | mParams.MessageQueue, 57 | rcParams.RedisCache, 58 | ); e != nil { 59 | err = e 60 | } else { 61 | out.GrpcService = svc 62 | } 63 | } 64 | return 65 | }, 66 | ) 67 | -------------------------------------------------------------------------------- /services/knapsack/pkg/kfx/knapsack_client.go: -------------------------------------------------------------------------------- 1 | package kfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/server/pkg/sfx" 5 | "github.com/gstones/moke-kit/server/tools" 6 | "go.uber.org/fx" 7 | 8 | pb "github.com/moke-game/platform/api/gen/knapsack/api" 9 | ) 10 | 11 | type KnapsackClientParams struct { 12 | fx.In 13 | 14 | KnapsackClient pb.KnapsackServiceClient `name:"KnapsackClient"` 15 | KnapsackPrivateClient pb.KnapsackPrivateServiceClient `name:"KnapsackPrivateClient"` 16 | } 17 | 18 | type KnapsackClientResult struct { 19 | fx.Out 20 | 21 | KnapsackClient pb.KnapsackServiceClient `name:"KnapsackClient"` 22 | KnapsackPrivateClient pb.KnapsackPrivateServiceClient `name:"KnapsackPrivateClient"` 23 | } 24 | 25 | func NewKnapsackClient(host string, sSetting sfx.SecuritySettingsParams) (pb.KnapsackServiceClient, error) { 26 | if sSetting.MTLSEnable { 27 | if conn, err := tools.DialWithSecurity( 28 | host, 29 | sSetting.ClientCert, 30 | sSetting.ClientKey, 31 | sSetting.ServerName, 32 | sSetting.ServerCaCert, 33 | ); err != nil { 34 | return nil, err 35 | } else { 36 | return pb.NewKnapsackServiceClient(conn), nil 37 | } 38 | } else { 39 | if conn, err := tools.DialInsecure(host); err != nil { 40 | return nil, err 41 | } else { 42 | return pb.NewKnapsackServiceClient(conn), nil 43 | } 44 | } 45 | } 46 | 47 | func NewKnapsackPrivateClient(host string, sSetting sfx.SecuritySettingsParams) (pb.KnapsackPrivateServiceClient, error) { 48 | if sSetting.MTLSEnable { 49 | if conn, err := tools.DialWithSecurity( 50 | host, 51 | sSetting.ClientCert, 52 | sSetting.ClientKey, 53 | sSetting.ServerName, 54 | sSetting.ServerCaCert, 55 | ); err != nil { 56 | return nil, err 57 | } else { 58 | return pb.NewKnapsackPrivateServiceClient(conn), nil 59 | } 60 | } else { 61 | if conn, err := tools.DialInsecure(host); err != nil { 62 | return nil, err 63 | } else { 64 | return pb.NewKnapsackPrivateServiceClient(conn), nil 65 | } 66 | } 67 | } 68 | 69 | var KnapsackClientModule = fx.Provide( 70 | func( 71 | setting KnapsackSettingParams, 72 | sSetting sfx.SecuritySettingsParams, 73 | ) (out KnapsackClientResult, err error) { 74 | if cli, e := NewKnapsackClient(setting.KnapsackUrl, sSetting); e != nil { 75 | err = e 76 | } else { 77 | out.KnapsackClient = cli 78 | } 79 | if cli, e := NewKnapsackPrivateClient(setting.KnapsackUrl, sSetting); e != nil { 80 | err = e 81 | } else { 82 | out.KnapsackPrivateClient = cli 83 | } 84 | return 85 | }, 86 | ) 87 | -------------------------------------------------------------------------------- /services/knapsack/pkg/kfx/knapsack_settings.go: -------------------------------------------------------------------------------- 1 | package kfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type KnapsackSettingParams struct { 9 | fx.In 10 | 11 | KnapsackUrl string `name:"KnapsackUrl"` 12 | KnapsackStoreName string `name:"KnapsackStoreName"` 13 | } 14 | 15 | type KnapsackSettingsResult struct { 16 | fx.Out 17 | 18 | KnapsackStoreName string `name:"KnapsackStoreName" envconfig:"KNAPSACK_STORE_NAME" default:"knapsack"` 19 | KnapsackUrl string `name:"KnapsackUrl" envconfig:"KNAPSACK_URL" default:"localhost:8081"` 20 | } 21 | 22 | func (g *KnapsackSettingsResult) LoadFromEnv() (err error) { 23 | err = utility.Load(g) 24 | return 25 | } 26 | 27 | var SettingsModule = fx.Provide( 28 | func() (out KnapsackSettingsResult, err error) { 29 | err = out.LoadFromEnv() 30 | return 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /services/knapsack/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/knapsack/internal/service/private" 7 | "github.com/moke-game/platform/services/knapsack/internal/service/public" 8 | "github.com/moke-game/platform/services/knapsack/pkg/kfx" 9 | ) 10 | 11 | // KnapsackModule Provides knapsack service 12 | var KnapsackModule = fx.Module("knapsack", 13 | kfx.SettingsModule, 14 | public.Module, 15 | private.Module, 16 | ) 17 | 18 | // KnapsackPrivateModule Provides knapsack private service 19 | var KnapsackPrivateModule = fx.Module("knapsack_private", 20 | kfx.SettingsModule, 21 | private.Module, 22 | ) 23 | 24 | // KnapsackClientModule Provides knapsack client for grpc 25 | var KnapsackClientModule = fx.Module("knapsack_client", 26 | kfx.SettingsModule, 27 | kfx.KnapsackClientModule, 28 | ) 29 | 30 | // KnapsackAllModule Provides client, service for knapsack 31 | var KnapsackAllModule = fx.Module("knapsack_all", 32 | kfx.SettingsModule, 33 | public.Module, 34 | private.Module, 35 | kfx.KnapsackClientModule, 36 | ) 37 | -------------------------------------------------------------------------------- /services/leaderboard/README.md: -------------------------------------------------------------------------------- 1 | # Leaderboard Service 2 | 排行榜服务, 提供排行榜功能。 3 | -------------------------------------------------------------------------------- /services/leaderboard/internal/db/keys.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "github.com/gstones/moke-kit/orm/nosql/key" 4 | 5 | func MakeLeaderboardKey(id string) (key.Key, error) { 6 | return key.NewKeyFromParts("leaderboard", id) 7 | } 8 | 9 | func MakeLeaderboardStarKey(id string) (key.Key, error) { 10 | return key.NewKeyFromParts("leaderboard", "star", id) 11 | } 12 | 13 | func MakeLeaderboardStarSelfKey(id string) (key.Key, error) { 14 | return key.NewKeyFromParts("leaderboard", "star", "self", id) 15 | } 16 | -------------------------------------------------------------------------------- /services/leaderboard/internal/db/model/playerInfo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type PlayerInfo struct { 4 | Uid string 5 | Nickname string 6 | Avatar string 7 | } 8 | -------------------------------------------------------------------------------- /services/leaderboard/internal/service/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNoMetaData = status.Error(codes.Internal, "ErrNoMetaData") 10 | ErrExpireFailed = status.Error(codes.Internal, "ErrExpireFailed") 11 | ErrMaxNum = status.Error(codes.Internal, "ErrMaxNum") 12 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 13 | ) 14 | -------------------------------------------------------------------------------- /services/leaderboard/internal/service/private/service.go: -------------------------------------------------------------------------------- 1 | package private 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | "github.com/gstones/moke-kit/server/pkg/sfx" 8 | "github.com/gstones/moke-kit/server/siface" 9 | "github.com/gstones/moke-kit/utility" 10 | "github.com/redis/go-redis/v9" 11 | "go.uber.org/fx" 12 | "go.uber.org/zap" 13 | 14 | leaderboard "github.com/moke-game/platform/api/gen/leaderboard/api" 15 | "github.com/moke-game/platform/services/leaderboard/internal/db" 16 | "github.com/moke-game/platform/services/leaderboard/pkg/lbfx" 17 | ) 18 | 19 | type Service struct { 20 | utility.WithoutAuth 21 | 22 | logger *zap.Logger 23 | db *db.Database 24 | expireTime time.Duration 25 | } 26 | 27 | func NewService(l *zap.Logger, cli *redis.Client, setting lbfx.LeaderboardSettingParams) (result *Service, err error) { 28 | return &Service{ 29 | logger: l, 30 | expireTime: time.Hour * 24 * time.Duration(setting.Expire), 31 | db: db.OpenDatabase(cli, setting.MaxNum, setting.StarRank), 32 | }, nil 33 | } 34 | 35 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 36 | leaderboard.RegisterLeaderboardPrivateServiceServer( 37 | server.GrpcServer(), 38 | s, 39 | ) 40 | return nil 41 | } 42 | 43 | var Module = fx.Provide( 44 | func( 45 | logger *zap.Logger, 46 | cliParams ofx.RedisParams, 47 | setting lbfx.LeaderboardSettingParams, 48 | ) (sfx.GrpcServiceResult, error) { 49 | if s, err := NewService(logger, cliParams.Redis, setting); err != nil { 50 | return sfx.GrpcServiceResult{}, err 51 | } else { 52 | return sfx.GrpcServiceResult{ 53 | GrpcService: s, 54 | }, nil 55 | } 56 | }) 57 | -------------------------------------------------------------------------------- /services/leaderboard/internal/service/public/service.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/pkg/ofx" 5 | "github.com/gstones/moke-kit/server/pkg/sfx" 6 | "github.com/gstones/moke-kit/server/siface" 7 | "github.com/redis/go-redis/v9" 8 | "go.uber.org/fx" 9 | "go.uber.org/zap" 10 | 11 | leaderboard "github.com/moke-game/platform/api/gen/leaderboard/api" 12 | "github.com/moke-game/platform/services/leaderboard/internal/db" 13 | "github.com/moke-game/platform/services/leaderboard/pkg/lbfx" 14 | ) 15 | 16 | type Service struct { 17 | logger *zap.Logger 18 | db *db.Database 19 | maxNum int32 20 | } 21 | 22 | func NewService(l *zap.Logger, cli *redis.Client, setting lbfx.LeaderboardSettingParams) (result *Service, err error) { 23 | return &Service{ 24 | logger: l, 25 | db: db.OpenDatabase(cli, setting.MaxNum, setting.StarRank), 26 | maxNum: setting.MaxNum, 27 | }, nil 28 | } 29 | 30 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 31 | leaderboard.RegisterLeaderboardServiceServer( 32 | server.GrpcServer(), 33 | s, 34 | ) 35 | return nil 36 | } 37 | 38 | var Module = fx.Provide( 39 | func( 40 | logger *zap.Logger, 41 | cliParams ofx.RedisParams, 42 | setting lbfx.LeaderboardSettingParams, 43 | ) (sfx.GrpcServiceResult, error) { 44 | if s, err := NewService(logger, cliParams.Redis, setting); err != nil { 45 | return sfx.GrpcServiceResult{}, err 46 | } else { 47 | return sfx.GrpcServiceResult{ 48 | GrpcService: s, 49 | }, nil 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /services/leaderboard/pkg/lbfx/leaderboard_client_private.go: -------------------------------------------------------------------------------- 1 | package lbfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/server/pkg/sfx" 5 | "github.com/gstones/moke-kit/server/tools" 6 | "go.uber.org/fx" 7 | 8 | leaderboard "github.com/moke-game/platform/api/gen/leaderboard/api" 9 | ) 10 | 11 | type LeaderboardClientPrivateParams struct { 12 | fx.In 13 | 14 | Client leaderboard.LeaderboardPrivateServiceClient `name:"LeaderboardClientPrivate"` 15 | } 16 | 17 | type LeaderboardClientPrivateResult struct { 18 | fx.Out 19 | 20 | Client leaderboard.LeaderboardPrivateServiceClient `name:"LeaderboardClientPrivate"` 21 | } 22 | 23 | func CreateLeaderboardPrivateClient(host string, sSetting sfx.SecuritySettingsParams) (leaderboard.LeaderboardPrivateServiceClient, error) { 24 | if sSetting.MTLSEnable { 25 | if conn, err := tools.DialWithSecurity( 26 | host, 27 | sSetting.ClientCert, 28 | sSetting.ClientKey, 29 | sSetting.ServerName, 30 | sSetting.ServerCaCert, 31 | ); err != nil { 32 | return nil, err 33 | } else { 34 | return leaderboard.NewLeaderboardPrivateServiceClient(conn), nil 35 | } 36 | } else { 37 | if conn, err := tools.DialInsecure(host); err != nil { 38 | return nil, err 39 | } else { 40 | return leaderboard.NewLeaderboardPrivateServiceClient(conn), nil 41 | } 42 | } 43 | } 44 | 45 | var LeaderboardClientPrivateModule = fx.Provide( 46 | func( 47 | setting LeaderboardSettingParams, 48 | sSetting sfx.SecuritySettingsParams, 49 | ) (out LeaderboardClientPrivateResult, err error) { 50 | if cli, e := CreateLeaderboardPrivateClient(setting.Url, sSetting); e != nil { 51 | err = e 52 | } else { 53 | out.Client = cli 54 | } 55 | return 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /services/leaderboard/pkg/lbfx/leaderboard_client_public.go: -------------------------------------------------------------------------------- 1 | package lbfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/server/pkg/sfx" 5 | "github.com/gstones/moke-kit/server/tools" 6 | "go.uber.org/fx" 7 | 8 | leaderboard "github.com/moke-game/platform/api/gen/leaderboard/api" 9 | ) 10 | 11 | type LeaderboardClientParams struct { 12 | fx.In 13 | 14 | Client leaderboard.LeaderboardServiceClient `name:"LeaderboardClient"` 15 | } 16 | 17 | type LeaderboardClientResult struct { 18 | fx.Out 19 | 20 | Client leaderboard.LeaderboardServiceClient `name:"LeaderboardClient"` 21 | } 22 | 23 | func CreateLeaderboardClient(host string, sSetting sfx.SecuritySettingsParams) (leaderboard.LeaderboardServiceClient, error) { 24 | if sSetting.MTLSEnable { 25 | if conn, err := tools.DialWithSecurity( 26 | host, 27 | sSetting.ClientCert, 28 | sSetting.ClientKey, 29 | sSetting.ServerName, 30 | sSetting.ServerCaCert, 31 | ); err != nil { 32 | return nil, err 33 | } else { 34 | return leaderboard.NewLeaderboardServiceClient(conn), nil 35 | } 36 | } else { 37 | if conn, err := tools.DialInsecure(host); err != nil { 38 | return nil, err 39 | } else { 40 | return leaderboard.NewLeaderboardServiceClient(conn), nil 41 | } 42 | } 43 | } 44 | 45 | var LeaderboardClientModule = fx.Provide( 46 | func( 47 | setting LeaderboardSettingParams, 48 | sSetting sfx.SecuritySettingsParams, 49 | ) (out LeaderboardClientResult, err error) { 50 | if cli, e := CreateLeaderboardClient(setting.Url, sSetting); e != nil { 51 | err = e 52 | } else { 53 | out.Client = cli 54 | } 55 | return 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /services/leaderboard/pkg/lbfx/leaderboard_setting.go: -------------------------------------------------------------------------------- 1 | package lbfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type LeaderboardSettingParams struct { 9 | fx.In 10 | Url string `name:"leaderboardUrl"` 11 | // leaderboard expired time in days 12 | Expire int64 `name:"leaderboardExpire"` 13 | // leaderboard max number 14 | MaxNum int32 `name:"leaderboardMaxNum"` 15 | // can star the leaderboard rank 16 | StarRank int32 `name:"leaderboardStarRank"` 17 | } 18 | 19 | type LeaderboardSettingResult struct { 20 | fx.Out 21 | 22 | Url string `name:"leaderboardUrl" envconfig:"LEADERBOARD_URL" default:"localhost:8081"` 23 | // Expire is the expired time of the leaderboard in days 24 | Expire int64 `name:"leaderboardExpire" envconfig:"LEADERBOARD_EXPIRE" default:"30"` 25 | // MaxNum is the max number of the leaderboard 26 | MaxNum int32 `name:"leaderboardMaxNum" envconfig:"LEADERBOARD_MAX_NUM" default:"5000"` 27 | // StarRank is the rank that can be starred 28 | StarRank int32 `name:"leaderboardStarRank" envconfig:"LEADERBOARD_STAR_RANK" default:"3"` 29 | } 30 | 31 | func (g *LeaderboardSettingResult) LoadFromEnv() (err error) { 32 | err = utility.Load(g) 33 | return 34 | } 35 | 36 | var LeaderboardSettingModule = fx.Provide( 37 | func() (out LeaderboardSettingResult, err error) { 38 | err = out.LoadFromEnv() 39 | return 40 | }) 41 | -------------------------------------------------------------------------------- /services/leaderboard/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/leaderboard/internal/service/private" 7 | "github.com/moke-game/platform/services/leaderboard/internal/service/public" 8 | "github.com/moke-game/platform/services/leaderboard/pkg/lbfx" 9 | ) 10 | 11 | var LeaderboardModule = fx.Module("leaderboard", 12 | lbfx.LeaderboardSettingModule, 13 | public.Module, 14 | private.Module, 15 | ) 16 | 17 | var LeaderboardClientPublic = fx.Module("leaderboardClientPublic", 18 | lbfx.LeaderboardClientModule, 19 | lbfx.LeaderboardSettingModule, 20 | ) 21 | 22 | var LeaderboardClientPrivate = fx.Module("leaderboardClientPrivate", 23 | lbfx.LeaderboardClientPrivateModule, 24 | lbfx.LeaderboardSettingModule, 25 | ) 26 | 27 | var LeaderboardClientAll = fx.Module("leaderboardClientAll", 28 | lbfx.LeaderboardClientPrivateModule, 29 | lbfx.LeaderboardClientModule, 30 | lbfx.LeaderboardSettingModule, 31 | ) 32 | 33 | var LeaderboardAll = fx.Module("leaderboardAll", 34 | public.Module, 35 | private.Module, 36 | lbfx.LeaderboardClientModule, 37 | lbfx.LeaderboardClientPrivateModule, 38 | lbfx.LeaderboardSettingModule, 39 | ) 40 | -------------------------------------------------------------------------------- /services/mail/README.md: -------------------------------------------------------------------------------- 1 | # Mail Service 2 | 邮件服务,提供邮件发送功能。 -------------------------------------------------------------------------------- /services/mail/internal/service/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | pb "github.com/moke-game/platform/api/gen/mail/api" 7 | ) 8 | 9 | func FilterMailsMapWithLanguage(mails map[int64]*pb.Mail, language string) (map[int64]*pb.Mail, error) { 10 | res := make(map[int64]*pb.Mail) 11 | for k, v := range mails { 12 | body, err := filterMailsWithLanguage(v.Body, language) 13 | if err != nil { 14 | return nil, err 15 | } 16 | v.Body = body 17 | title, err := filterMailsWithLanguage(v.Title, language) 18 | if err != nil { 19 | return nil, err 20 | } 21 | v.Title = title 22 | res[k] = v 23 | } 24 | return res, nil 25 | } 26 | 27 | func FilterMailsWithLanguage(mails []*pb.Mail, language string) ([]*pb.Mail, error) { 28 | res := make([]*pb.Mail, 0) 29 | for _, v := range mails { 30 | body, err := filterMailsWithLanguage(v.Body, language) 31 | if err != nil { 32 | return nil, err 33 | } 34 | v.Body = body 35 | title, err := filterMailsWithLanguage(v.Title, language) 36 | if err != nil { 37 | return nil, err 38 | } 39 | v.Title = title 40 | res = append(res, v) 41 | } 42 | return res, nil 43 | } 44 | 45 | func FilterMailsMapWithRegisterTime(mails map[int64]*pb.Mail, registerTime int64) map[int64]*pb.Mail { 46 | res := make(map[int64]*pb.Mail) 47 | for k, v := range mails { 48 | if v.Filters.RegisterTime >= registerTime { 49 | res[k] = v 50 | } 51 | } 52 | return res 53 | } 54 | 55 | func FilterMailsWithRegisterTime(mails []*pb.Mail, registerTime int64) []*pb.Mail { 56 | res := make([]*pb.Mail, 0) 57 | for _, v := range mails { 58 | if v.Filters.RegisterTime >= registerTime { 59 | res = append(res, v) 60 | } 61 | } 62 | return res 63 | } 64 | 65 | func filterMailsWithLanguage(contens map[string]string, language string) (map[string]string, error) { 66 | if len(contens) == 0 { 67 | return nil, fmt.Errorf("mail body is empty") 68 | } 69 | body := make(map[string]string) 70 | for k1, v1 := range contens { 71 | if k1 == language { 72 | body[k1] = v1 73 | break 74 | } 75 | } 76 | if len(body) == 0 { 77 | if contens["en"] != "" { 78 | body["en"] = contens["en"] 79 | } else { 80 | for k1, v1 := range contens { 81 | body[k1] = v1 82 | break 83 | } 84 | } 85 | } 86 | return body, nil 87 | } 88 | -------------------------------------------------------------------------------- /services/mail/internal/service/common/encrypt.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/md5" 8 | "encoding/base64" 9 | "encoding/hex" 10 | "fmt" 11 | "strings" 12 | ) 13 | 14 | // CBCDecrypt AES-CBC 解密 15 | func CBCDecrypt(key []byte, ciphertext string) (string, error) { 16 | block, err := aes.NewCipher(key) 17 | if err != nil { 18 | return "", err 19 | } 20 | 21 | ciphercode, err := base64.StdEncoding.DecodeString(ciphertext) 22 | if err != nil { 23 | return "", err 24 | } 25 | if len(ciphercode)%aes.BlockSize != 0 { 26 | return "", fmt.Errorf("ciphercode length is not a multiple of the block size") 27 | } 28 | 29 | iv := key[:aes.BlockSize] 30 | mode := cipher.NewCBCDecrypter(block, iv) 31 | mode.CryptBlocks(ciphercode, ciphercode) 32 | ciphercode = bytes.Trim(ciphercode, "\x00") 33 | return strings.TrimSpace(string(ciphercode)), nil 34 | } 35 | 36 | // EncryptMD5 encrypt data with md5. 37 | func EncryptMD5(data, key string) (string, error) { 38 | str := fmt.Sprintf("%s.%s", data, key) 39 | _, err := md5.New().Write([]byte(str)) 40 | if err != nil { 41 | return "", err 42 | } 43 | return hex.EncodeToString(md5.New().Sum(nil)), nil 44 | } 45 | -------------------------------------------------------------------------------- /services/mail/internal/service/common/encrypt_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_DecryptCBC(t *testing.T) { 8 | str := "E468f0U/vS/+LX8Cuk0QOMImPbmoM0pUCRNKjMaRU1BNfEQY3BXioRV5BO29sXlkdFjQ9iK47Zw4zCMhFcBhdnkzmWLLVJH9R/eyW7iCIUrftSZyNglgjk4jOyKZGDs6Zk48F5gcqPSI4RgUmJRE/aAeBr6kTgI4gknmyf4D7mNl5fyqqeQQmL7ofkkQwcCYfOd+jaX74KH/uFdGb3c5GSW7zAuFSDTwzqZiopsuT+Z0RCz59ikjCMKFzQ0PPVe1b5XKSkbRsF2bWw2decdDpc2Knm92TP20o0TYqDXS3pBGJVe7Dxx1aywoK1IgKMW1gFmi6D81myaAgksf19xZbFqNWfq8xLYHLGmY/sbEv7c1RsI7/bC0XiwMHcBDzScg9l3pDrIDFFKfdUiX6bfGqA==" 9 | data, err := CBCDecrypt([]byte("CTeGahnbQWfAr5hW"), str) 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | t.Log(data) 14 | } 15 | 16 | func Test_Md5(t *testing.T) { 17 | data := "E468f0U/vS/+LX8Cuk0QOMImPbmoM0pUCRNKjMaRU1BNfEQY3BXioRV5BO29sXlkdFjQ9iK47Zw4zCMhFcBhdnkzmWLLVJH9R/eyW7iCIUrftSZyNglgjk4jOyKZGDs6Zk48F5gcqPSI4RgUmJRE/aAeBr6kTgI4gknmyf4D7mNl5fyqqeQQmL7ofkkQwcCYfOd+jaX74KH/uFdGb3c5GSW7zAuFSDTwzqZiopsuT+Z0RCz59ikjCMKFzQ0PPVe1b5XKSkbRsF2bWw2decdDpc2Knm92TP20o0TYqDXS3pBGJVe7Dxx1aywoK1IgKMW1gFmi6D81myaAgksf19xZbFqNWfq8xLYHLGmY/sbEv7c1RsI7/bC0XiwMHcBDzScg9l3pDrIDFFKfdUiX6bfGqA\\u003d\\u003d" 18 | key := "CTeGahnbQWfAr5hW" 19 | res, err := EncryptMD5(data, key) 20 | if err != nil { 21 | panic(err) 22 | } 23 | t.Log(res) 24 | } 25 | -------------------------------------------------------------------------------- /services/mail/internal/service/common/topics.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gstones/moke-kit/mq/common" 7 | ) 8 | 9 | func MakePrivateTopic(uid string) string { 10 | return common.NatsHeader.CreateTopic(fmt.Sprintf("mail.private.%s", uid)) 11 | } 12 | 13 | func MakePublicTopic(channel string) string { 14 | if channel == "" { 15 | channel = "0" 16 | } 17 | return common.NatsHeader.CreateTopic(fmt.Sprintf("mail.public.%s", channel)) 18 | } 19 | -------------------------------------------------------------------------------- /services/mail/internal/service/db/keys.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/gstones/moke-kit/orm/nosql/key" 7 | ) 8 | 9 | const ( 10 | MailTheme = "mail" 11 | MailPublicIndex = "index" 12 | MailPublicKeyPrefix = "public" 13 | ) 14 | 15 | func makeMailKey(profileId string) (key.Key, error) { 16 | return key.NewKeyFromParts(MailTheme, profileId) 17 | } 18 | 19 | func makeMailPublicListKey(channel string) (key.Key, error) { 20 | if channel == "" { 21 | channel = "0" 22 | } 23 | return key.NewKeyFromParts(MailPublicKeyPrefix, channel) 24 | } 25 | 26 | func makeMailPublicIndexKey(profileId string, channel string) (key.Key, error) { 27 | if channel == "" { 28 | channel = "0" 29 | } 30 | return key.NewKeyFromParts(MailTheme, channel, MailPublicIndex, profileId) 31 | } 32 | 33 | func makeFieldMailKey(uid int64) (key.Key, error) { 34 | idStr := strconv.FormatInt(uid, 10) 35 | return key.NewKeyFromParts(MailTheme, idStr) 36 | } 37 | -------------------------------------------------------------------------------- /services/mail/internal/service/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNoMetaData = status.Error(codes.InvalidArgument, "ErrNoMetaData") 10 | ErrParamsInvalid = status.Error(codes.InvalidArgument, "ErrParamsInvalid") 11 | ErrSaveMailFailed = status.Error(codes.Internal, "ErrSaveMailFailed") 12 | ErrPublishMailFailed = status.Error(codes.Internal, "ErrPublishMailFailed") 13 | ) 14 | -------------------------------------------------------------------------------- /services/mail/internal/service/private/service.go: -------------------------------------------------------------------------------- 1 | package private 2 | 3 | import ( 4 | "time" 5 | 6 | mfx2 "github.com/gstones/moke-kit/fxmain/pkg/mfx" 7 | "github.com/gstones/moke-kit/mq/miface" 8 | "github.com/gstones/moke-kit/mq/pkg/mfx" 9 | "github.com/gstones/moke-kit/orm/pkg/ofx" 10 | "github.com/gstones/moke-kit/server/pkg/sfx" 11 | "github.com/gstones/moke-kit/server/siface" 12 | "github.com/gstones/moke-kit/utility" 13 | "github.com/redis/go-redis/v9" 14 | "go.uber.org/fx" 15 | "go.uber.org/zap" 16 | 17 | pb "github.com/moke-game/platform/api/gen/mail/api" 18 | "github.com/moke-game/platform/services/mail/internal/service/db" 19 | "github.com/moke-game/platform/services/mail/pkg/mailfx" 20 | ) 21 | 22 | type Service struct { 23 | utility.WithoutAuth 24 | 25 | appId string 26 | logger *zap.Logger 27 | deployment string 28 | db *db.Database 29 | url string 30 | mq miface.MessageQueue 31 | defaultExpire time.Duration // default expire time of mail, unit is day 32 | aesKey string 33 | } 34 | 35 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 36 | pb.RegisterMailPrivateServiceServer( 37 | server.GrpcServer(), 38 | s, 39 | ) 40 | return nil 41 | } 42 | 43 | func NewService( 44 | l *zap.Logger, 45 | deployment string, 46 | redis *redis.Client, 47 | url string, 48 | defaultExpire int32, 49 | aesKey string, 50 | mq miface.MessageQueue, 51 | aParams mfx2.AppParams, 52 | ) (result *Service, err error) { 53 | result = &Service{ 54 | appId: aParams.AppId, 55 | logger: l, 56 | deployment: deployment, 57 | db: db.OpenDatabase(l, redis), 58 | url: url, 59 | mq: mq, 60 | defaultExpire: time.Hour * 24 * time.Duration(defaultExpire), 61 | aesKey: aesKey, 62 | } 63 | return 64 | } 65 | 66 | var Module = fx.Provide( 67 | func( 68 | l *zap.Logger, 69 | s mfx2.AppParams, 70 | ms mailfx.MailSettingParams, 71 | mqParams mfx.MessageQueueParams, 72 | redisParams ofx.RedisParams, 73 | aParams mfx2.AppParams, 74 | ) (gOut sfx.GrpcServiceResult, err error) { 75 | if svc, e := NewService( 76 | l, 77 | s.Deployment, 78 | redisParams.Redis, 79 | ms.MailUrl, 80 | ms.MailDefaultExpire, 81 | ms.MailEncryptionKey, 82 | mqParams.MessageQueue, 83 | aParams, 84 | ); e != nil { 85 | err = e 86 | return 87 | } else { 88 | gOut.GrpcService = svc 89 | return 90 | } 91 | }, 92 | ) 93 | -------------------------------------------------------------------------------- /services/mail/internal/service/public/service.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | mfx2 "github.com/gstones/moke-kit/fxmain/pkg/mfx" 5 | "github.com/gstones/moke-kit/mq/miface" 6 | "github.com/gstones/moke-kit/mq/pkg/mfx" 7 | "github.com/gstones/moke-kit/orm/pkg/ofx" 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | "github.com/gstones/moke-kit/server/siface" 10 | "github.com/redis/go-redis/v9" 11 | "go.uber.org/fx" 12 | "go.uber.org/zap" 13 | 14 | pb "github.com/moke-game/platform/api/gen/mail/api" 15 | "github.com/moke-game/platform/services/mail/internal/service/db" 16 | "github.com/moke-game/platform/services/mail/pkg/mailfx" 17 | ) 18 | 19 | type Service struct { 20 | appId string 21 | logger *zap.Logger 22 | deployment string 23 | db *db.Database 24 | url string 25 | mq miface.MessageQueue 26 | maxNum int 27 | } 28 | 29 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 30 | pb.RegisterMailServiceServer( 31 | server.GrpcServer(), 32 | s, 33 | ) 34 | return nil 35 | } 36 | 37 | func NewService( 38 | l *zap.Logger, 39 | deployment string, 40 | redis *redis.Client, 41 | url string, 42 | maxNum int, 43 | mq miface.MessageQueue, 44 | aParams mfx2.AppParams, 45 | ) (result *Service, err error) { 46 | result = &Service{ 47 | appId: aParams.AppId, 48 | logger: l, 49 | deployment: deployment, 50 | db: db.OpenDatabase(l, redis), 51 | url: url, 52 | mq: mq, 53 | maxNum: maxNum, 54 | } 55 | return 56 | } 57 | 58 | var ServiceModule = fx.Provide( 59 | func( 60 | l *zap.Logger, 61 | s mfx2.AppParams, 62 | ms mailfx.MailSettingParams, 63 | mqParams mfx.MessageQueueParams, 64 | redisParams ofx.RedisParams, 65 | aParams mfx2.AppParams, 66 | ) (out sfx.GrpcServiceResult, err error) { 67 | if svc, e := NewService( 68 | l, 69 | s.Deployment, 70 | redisParams.Redis, 71 | ms.MailUrl, 72 | ms.MailNumMax, 73 | mqParams.MessageQueue, 74 | aParams, 75 | ); e != nil { 76 | err = e 77 | return 78 | } else { 79 | out.GrpcService = svc 80 | return 81 | } 82 | }, 83 | ) 84 | -------------------------------------------------------------------------------- /services/mail/pkg/mailfx/mail_client.go: -------------------------------------------------------------------------------- 1 | package mailfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/tools" 8 | 9 | pb "github.com/moke-game/platform/api/gen/mail/api" 10 | ) 11 | 12 | type MailClientParams struct { 13 | fx.In 14 | MailClient pb.MailServiceClient `name:"MailClient"` 15 | } 16 | 17 | type MailClientResult struct { 18 | fx.Out 19 | MailClient pb.MailServiceClient `name:"MailClient"` 20 | } 21 | 22 | func NewMailClient(target string, sSetting sfx.SecuritySettingsParams) (pb.MailServiceClient, error) { 23 | if sSetting.MTLSEnable { 24 | if c, e := tools.DialWithSecurity( 25 | target, 26 | sSetting.ClientCert, 27 | sSetting.ClientKey, 28 | sSetting.ServerName, 29 | sSetting.ServerCaCert, 30 | ); e != nil { 31 | return nil, e 32 | } else { 33 | return pb.NewMailServiceClient(c), nil 34 | } 35 | } else { 36 | if c, e := tools.DialInsecure(target); e != nil { 37 | return nil, e 38 | } else { 39 | return pb.NewMailServiceClient(c), nil 40 | } 41 | } 42 | } 43 | 44 | func (g *MailClientResult) Execute( 45 | a MailSettingParams, 46 | sSetting sfx.SecuritySettingsParams, 47 | ) (err error) { 48 | g.MailClient, err = NewMailClient(a.MailUrl, sSetting) 49 | return 50 | } 51 | 52 | var MailClientModule = fx.Provide( 53 | func( 54 | a MailSettingParams, 55 | sSetting sfx.SecuritySettingsParams, 56 | ) (out MailClientResult, err error) { 57 | err = out.Execute(a, sSetting) 58 | return 59 | }, 60 | ) 61 | -------------------------------------------------------------------------------- /services/mail/pkg/mailfx/mail_client_private.go: -------------------------------------------------------------------------------- 1 | package mailfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/tools" 8 | 9 | pb "github.com/moke-game/platform/api/gen/mail/api" 10 | ) 11 | 12 | type MailClientPrivateParams struct { 13 | fx.In 14 | MailClient pb.MailPrivateServiceClient `name:"MailPrivateClient"` 15 | } 16 | 17 | type MailClientPrivateResult struct { 18 | fx.Out 19 | MailClient pb.MailPrivateServiceClient `name:"MailPrivateClient"` 20 | } 21 | 22 | func NewMailPrivateClient(target string, sSetting sfx.SecuritySettingsParams) (pb.MailPrivateServiceClient, error) { 23 | if sSetting.MTLSEnable { 24 | if c, e := tools.DialWithSecurity( 25 | target, 26 | sSetting.ClientCert, 27 | sSetting.ClientKey, 28 | sSetting.ServerName, 29 | sSetting.ServerCaCert, 30 | ); e != nil { 31 | return nil, e 32 | } else { 33 | return pb.NewMailPrivateServiceClient(c), nil 34 | } 35 | } else { 36 | if c, e := tools.DialInsecure(target); e != nil { 37 | return nil, e 38 | } else { 39 | return pb.NewMailPrivateServiceClient(c), nil 40 | } 41 | } 42 | } 43 | 44 | func (g *MailClientPrivateResult) Execute( 45 | a MailSettingParams, 46 | sSetting sfx.SecuritySettingsParams, 47 | ) (err error) { 48 | g.MailClient, err = NewMailPrivateClient(a.MailUrl, sSetting) 49 | return 50 | } 51 | 52 | var MailClientPrivateModule = fx.Provide( 53 | func( 54 | a MailSettingParams, 55 | sSetting sfx.SecuritySettingsParams, 56 | ) (out MailClientPrivateResult, err error) { 57 | err = out.Execute(a, sSetting) 58 | return 59 | }, 60 | ) 61 | -------------------------------------------------------------------------------- /services/mail/pkg/mailfx/mail_settings.go: -------------------------------------------------------------------------------- 1 | package mailfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type MailSettingParams struct { 9 | fx.In 10 | MailStoreName string `name:"MailStoreName"` 11 | MailUrl string `name:"MailUrl"` 12 | MailNumMax int `name:"MailNumMax"` 13 | MailDefaultExpire int32 `name:"MailDefaultExpire"` 14 | MailEncryptionKey string `name:"MailEncryptionKey"` 15 | } 16 | 17 | type MailSettingsResult struct { 18 | fx.Out 19 | MailStoreName string `name:"MailStoreName" envconfig:"MAIL_STORE_NAME" default:"mail"` 20 | MailUrl string `name:"MailUrl" envconfig:"MAIL_URL" default:"localhost:8081"` 21 | // MailNumMax is the max number of mail that can be stored in the mail store 22 | MailNumMax int `name:"MailNumMax" envconfig:"MAIL_NUM_MAX" default:"99"` 23 | // MailDefaultExpire is the default expire time of mail (day) 24 | MailDefaultExpire int32 `name:"MailDefaultExpire" envconfig:"MAIL_DEFAULT_EXPIRE" default:"90"` 25 | // MailEncryptionKey is the key used to encrypt mail data 26 | MailEncryptionKey string `name:"MailEncryptionKey" envconfig:"MAIL_ENCRYPTION_KEY" default:"CTeGahnbQWfAr5hW"` 27 | } 28 | 29 | func (msl *MailSettingsResult) LoadFromEnv() (err error) { 30 | err = utility.Load(msl) 31 | return 32 | } 33 | 34 | var MailSettingsModule = fx.Provide( 35 | func() (out MailSettingsResult, err error) { 36 | err = out.LoadFromEnv() 37 | return 38 | }, 39 | ) 40 | -------------------------------------------------------------------------------- /services/mail/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/mail/internal/service/private" 7 | "github.com/moke-game/platform/services/mail/internal/service/public" 8 | "github.com/moke-game/platform/services/mail/pkg/mailfx" 9 | ) 10 | 11 | var MailModule = fx.Module("mail", 12 | public.ServiceModule, 13 | private.Module, 14 | mailfx.MailSettingsModule, 15 | ) 16 | 17 | var MailPrivateModule = fx.Module("mail_private", 18 | private.Module, 19 | mailfx.MailSettingsModule, 20 | ) 21 | 22 | var MailClientModule = fx.Module("mail_client", 23 | mailfx.MailSettingsModule, 24 | mailfx.MailClientModule, 25 | ) 26 | 27 | var MailAllClientModule = fx.Module("mail_all_client", 28 | mailfx.MailSettingsModule, 29 | mailfx.MailClientModule, 30 | mailfx.MailClientPrivateModule, 31 | ) 32 | 33 | var MailAllModule = fx.Module("mail_all", 34 | public.ServiceModule, 35 | private.Module, 36 | mailfx.MailSettingsModule, 37 | mailfx.MailClientModule, 38 | mailfx.MailClientPrivateModule, 39 | ) 40 | 41 | var MailClientPrivateModule = fx.Module("mail_client_private", 42 | mailfx.MailSettingsModule, 43 | mailfx.MailClientPrivateModule, 44 | ) 45 | -------------------------------------------------------------------------------- /services/matchmaking/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/abiosoft/ishell" 8 | mm "github.com/grpc-ecosystem/go-grpc-middleware/v2/metadata" 9 | "github.com/gstones/moke-kit/logging/slogger" 10 | "github.com/gstones/moke-kit/server/pkg/sfx" 11 | "google.golang.org/grpc/metadata" 12 | 13 | matchmaking "github.com/moke-game/platform/api/gen/matchmaking/api" 14 | "github.com/moke-game/platform/services/matchmaking/pkg/mmfx" 15 | ) 16 | 17 | type Client struct { 18 | client matchmaking.MatchServiceClient 19 | cmd *ishell.Cmd 20 | } 21 | 22 | func CreateClient(host string) (*ishell.Cmd, error) { 23 | if client, err := mmfx.NewClient(host, sfx.SecuritySettingsParams{}); err != nil { 24 | return nil, err 25 | } else { 26 | p := &Client{ 27 | client: client, 28 | } 29 | p.initShells() 30 | return p.cmd, nil 31 | } 32 | } 33 | 34 | func (p *Client) initShells() { 35 | p.cmd = &ishell.Cmd{ 36 | Name: "matchmaking", 37 | Help: "matchmaking interactive", 38 | Aliases: []string{"MM"}, 39 | } 40 | p.initSubShells() 41 | } 42 | 43 | func (p *Client) initSubShells() { 44 | p.cmd.AddCmd(&ishell.Cmd{ 45 | Name: "match", 46 | Help: "match interactive", 47 | Aliases: []string{"M"}, 48 | Func: p.match, 49 | }) 50 | } 51 | 52 | func (p *Client) match(c *ishell.Context) { 53 | md := metadata.Pairs("authorization", fmt.Sprintf("%s %v", "bearer", "test")) 54 | ctx := mm.MD(md).ToOutgoing(context.Background()) 55 | if stream, err := p.client.Match(ctx, &matchmaking.MatchRequest{}); err != nil { 56 | slogger.Warn(c, err) 57 | } else { 58 | for { 59 | if res, err := stream.Recv(); err != nil { 60 | slogger.Warn(c, err) 61 | break 62 | } else { 63 | slogger.Infof(c, "game id: %s", res.GameId) 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /services/matchmaking/pkg/mmfx/client.go: -------------------------------------------------------------------------------- 1 | package mmfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/server/pkg/sfx" 5 | "github.com/gstones/moke-kit/server/tools" 6 | "go.uber.org/fx" 7 | 8 | matchmaking "github.com/moke-game/platform/api/gen/matchmaking/api" 9 | ) 10 | 11 | type ClientParams struct { 12 | fx.In 13 | 14 | Client matchmaking.MatchServiceClient `name:"MatchServiceClient"` 15 | } 16 | 17 | type ClientResult struct { 18 | fx.Out 19 | 20 | Client matchmaking.MatchServiceClient `name:"MatchServiceClient"` 21 | } 22 | 23 | func NewClient(host string, setting sfx.SecuritySettingsParams) (matchmaking.MatchServiceClient, error) { 24 | if setting.MTLSEnable { 25 | if conn, err := tools.DialWithSecurity( 26 | host, 27 | setting.ClientCert, 28 | setting.ClientKey, 29 | setting.ServerName, 30 | setting.ServerCaCert, 31 | ); err != nil { 32 | return nil, err 33 | } else { 34 | return matchmaking.NewMatchServiceClient(conn), nil 35 | } 36 | } else { 37 | if conn, err := tools.DialInsecure(host); err != nil { 38 | return nil, err 39 | } else { 40 | return matchmaking.NewMatchServiceClient(conn), nil 41 | } 42 | } 43 | } 44 | 45 | var ClientModule = fx.Provide( 46 | func( 47 | setting MatchmakingSettingParams, 48 | security sfx.SecuritySettingsParams, 49 | ) (out ClientResult, err error) { 50 | if client, e := NewClient(setting.URL, security); e != nil { 51 | err = e 52 | } else { 53 | out.Client = client 54 | } 55 | return 56 | }) 57 | -------------------------------------------------------------------------------- /services/matchmaking/pkg/mmfx/setting.go: -------------------------------------------------------------------------------- 1 | package mmfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type MatchmakingSettingParams struct { 9 | fx.In 10 | 11 | // Matchmaking service URL 12 | URL string `name:"matchmakingUrl"` 13 | 14 | // AWS subnets 15 | AWSVPCSubnets string `name:"awsVPCSubnets"` 16 | 17 | // Open Match Frontend URL 18 | OMFrontendUrl string `name:"frontendUrl"` 19 | // Open Match Backend URL 20 | OMBackendUrl string `name:"backendUrl"` 21 | // Open Match Function URL 22 | OMFuncUrl string `name:"funcUrl"` 23 | // Open Match Function Port 24 | OMFuncPort int32 `name:"funcPort"` 25 | } 26 | 27 | type MatchmakingSettingResult struct { 28 | fx.Out 29 | 30 | // AWS subnets 31 | AWSVPCSubnets string `name:"awsVPCSubnets" envconfig:"AWS_VPC_SUBNETS" default:"subnet-0b1b2c3d4e5f6g7h8"` 32 | 33 | // Matchmaking service URL 34 | URL string `name:"matchmakingUrl" envconfig:"MATCHMAKING_URL" default:"localhost:8081"` 35 | // Open Match Frontend URL 36 | OMFrontendUrl string `name:"frontendUrl" envconfig:"OM_FRONTEND_URL" default:"localhost:50504"` 37 | // Open Match Backend URL 38 | OMBackendUrl string `name:"backendUrl" envconfig:"OM_BACKEND_URL" default:"localhost:50505"` 39 | 40 | // Open Match Function URL 41 | OMFuncUrl string `name:"funcUrl" envconfig:"OM_FUNC_URL" default:"192.168.50.11"` 42 | // Open Match Function Port 43 | OMFuncPort int32 `name:"funcPort" envconfig:"OM_FUNC_PORT" default:"8081"` 44 | } 45 | 46 | // LoadFromEnv load from env 47 | func (g *MatchmakingSettingResult) LoadFromEnv() (err error) { 48 | err = utility.Load(g) 49 | return 50 | } 51 | 52 | var MatchmakingSettingModule = fx.Provide( 53 | func() (out MatchmakingSettingResult, err error) { 54 | err = out.LoadFromEnv() 55 | return 56 | }) 57 | -------------------------------------------------------------------------------- /services/matchmaking/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | agones "github.com/gstones/moke-kit/3rd/agones/pkg/module" 5 | awsConfig "github.com/gstones/moke-kit/3rd/cloud/pkg/module" 6 | "go.uber.org/fx" 7 | 8 | "github.com/moke-game/platform/services/matchmaking/internal" 9 | "github.com/moke-game/platform/services/matchmaking/pkg/mmfx" 10 | ) 11 | 12 | var MatchmakingModule = fx.Module("matchmaking", 13 | agones.AgonesAllocateClientModule, 14 | awsConfig.AWSConfigModule, 15 | 16 | mmfx.MatchmakingSettingModule, 17 | internal.Module, 18 | ) 19 | 20 | var MatchmakingClientModule = fx.Module("matchmaking_client", 21 | mmfx.ClientModule, 22 | mmfx.MatchmakingSettingModule, 23 | ) 24 | -------------------------------------------------------------------------------- /services/party/README.md: -------------------------------------------------------------------------------- 1 | # party Service 2 | 组队服务,提供组队功能。 -------------------------------------------------------------------------------- /services/party/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNoMetaData = status.Error(codes.PermissionDenied, "ErrNoMetaData") 10 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 11 | ErrPartyNotFound = status.Error(codes.NotFound, "ErrPartyNotFound") 12 | ErrNotOwner = status.Error(codes.PermissionDenied, "ErrNotOwner") 13 | ErrIllegal = status.Error(codes.PermissionDenied, "ErrIllegal") 14 | ErrPartyFull = status.Error(codes.PermissionDenied, "ErrPartyFull") 15 | ErrHasParty = status.Error(codes.Internal, "ErrHasParty") 16 | ) 17 | -------------------------------------------------------------------------------- /services/party/internal/db/keys.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "github.com/gstones/moke-kit/orm/nosql/key" 4 | 5 | func makePartyKey(id string) (key.Key, error) { 6 | return key.NewKeyFromParts("party", "manager", id) 7 | } 8 | 9 | func makePartyMemberKey(partyId string) (key.Key, error) { 10 | return key.NewKeyFromParts("party", "members", partyId) 11 | } 12 | 13 | func makeUid2PidKey() (key.Key, error) { 14 | return key.NewKeyFromParts("party", "uid2pid") 15 | } 16 | 17 | func makeInviteKey(id string) (key.Key, error) { 18 | return key.NewKeyFromParts("party", "invite", id) 19 | } 20 | 21 | func makePartyIdKey() (key.Key, error) { 22 | return key.NewKeyFromParts("party", "generate", "id") 23 | } 24 | -------------------------------------------------------------------------------- /services/party/internal/db/model.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | pb "github.com/moke-game/platform/api/gen/party/api" 7 | ) 8 | 9 | type Party struct { 10 | Id string `redis:"id"` 11 | Type int32 `redis:"type"` 12 | Owner string `redis:"owner"` 13 | Name string `redis:"name"` 14 | MaxMember int32 `redis:"max_member"` 15 | Refuse map[string]int64 `redis:"refuse"` //拒绝列表 key:申请的玩家ID value:拒绝时间戳 秒 16 | } 17 | 18 | type PartyInvite struct { 19 | Id string `redis:"id"` 20 | Inviter string `redis:"Inviter"` //邀请玩家ID 21 | InviteTime int64 `redis:"invite_time"` //邀请时间戳 秒 22 | Refuse map[string]int64 `redis:"refuse"` //拒绝列表 key:邀请玩家ID value:拒绝时间戳 秒 23 | } 24 | 25 | func (p *Party) ToProto() *pb.PartySetting { 26 | return &pb.PartySetting{ 27 | Id: p.Id, 28 | Owner: p.Owner, 29 | Name: p.Name, 30 | Type: p.Type, 31 | MaxMember: p.MaxMember, 32 | Refuse: p.Refuse, 33 | } 34 | } 35 | 36 | func (p *Party) MarshalBinary() ([]byte, error) { 37 | return json.Marshal(p) 38 | } 39 | 40 | func (pi *PartyInvite) MarshalBinary() ([]byte, error) { 41 | return json.Marshal(pi) 42 | } 43 | -------------------------------------------------------------------------------- /services/party/internal/service/common.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | const ( 4 | RefuseTime = 5 * 60 //拒绝邀请时间 5分钟 5 | InviteEffectiveTime = 15 //邀请有效时间 15秒 6 | ) 7 | -------------------------------------------------------------------------------- /services/party/internal/service/public/service.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | mfx2 "github.com/gstones/moke-kit/fxmain/pkg/mfx" 5 | "github.com/gstones/moke-kit/mq/miface" 6 | "github.com/gstones/moke-kit/mq/pkg/mfx" 7 | "github.com/gstones/moke-kit/orm/pkg/ofx" 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | "github.com/gstones/moke-kit/server/siface" 10 | "github.com/redis/go-redis/v9" 11 | "go.uber.org/fx" 12 | "go.uber.org/zap" 13 | 14 | pb "github.com/moke-game/platform/api/gen/party/api" 15 | "github.com/moke-game/platform/services/party/internal/db" 16 | "github.com/moke-game/platform/services/party/pkg/ptfx" 17 | ) 18 | 19 | type Service struct { 20 | logger *zap.Logger 21 | mq miface.MessageQueue 22 | redis *redis.Client 23 | appId string 24 | deployment string 25 | db *db.Database 26 | } 27 | 28 | func NewService( 29 | l *zap.Logger, 30 | rClient *redis.Client, 31 | mq miface.MessageQueue, 32 | deployment string, 33 | appId string, 34 | ) (result *Service, err error) { 35 | result = &Service{ 36 | logger: l, 37 | redis: rClient, 38 | mq: mq, 39 | appId: appId, 40 | deployment: deployment, 41 | db: db.OpenDatabase(l, rClient), 42 | } 43 | return 44 | } 45 | 46 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 47 | pb.RegisterPartyServiceServer(server.GrpcServer(), s) 48 | return nil 49 | } 50 | 51 | var PartyService = fx.Provide( 52 | func( 53 | l *zap.Logger, 54 | setting ptfx.PartySettingParams, 55 | mqParams mfx.MessageQueueParams, 56 | redisParams ofx.RedisParams, 57 | aParams mfx2.AppParams, 58 | ) (out sfx.GrpcServiceResult, err error) { 59 | if s, err := NewService( 60 | l, 61 | redisParams.Redis, 62 | mqParams.MessageQueue, 63 | aParams.Deployment, 64 | aParams.AppId, 65 | ); err != nil { 66 | return out, err 67 | } else { 68 | out.GrpcService = s 69 | } 70 | return 71 | }, 72 | ) 73 | -------------------------------------------------------------------------------- /services/party/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/party/internal/service/public" 7 | "github.com/moke-game/platform/services/party/pkg/ptfx" 8 | ) 9 | 10 | // PartyModule Provides party service 11 | var PartyModule = fx.Module("party", 12 | public.PartyService, 13 | ptfx.PartySettingsModule, 14 | ) 15 | 16 | // PartyClientModule Provides party client for grpc 17 | var PartyClientModule = fx.Module("party_client", 18 | ptfx.PartyClientModule, 19 | ptfx.PartySettingsModule, 20 | ) 21 | 22 | // PartyAllModule Provides client, service for party 23 | var PartyAllModule = fx.Module("party_all", 24 | public.PartyService, 25 | ptfx.PartyClientModule, 26 | ptfx.PartySettingsModule, 27 | ) 28 | -------------------------------------------------------------------------------- /services/party/pkg/ptfx/party_client.go: -------------------------------------------------------------------------------- 1 | package ptfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/server/pkg/sfx" 7 | "github.com/gstones/moke-kit/server/tools" 8 | 9 | pb "github.com/moke-game/platform/api/gen/party/api" 10 | ) 11 | 12 | type PartyClientParams struct { 13 | fx.In 14 | 15 | PartyClient pb.PartyServiceClient `name:"PartyClient"` 16 | } 17 | 18 | type PartyClientResult struct { 19 | fx.Out 20 | 21 | PartyClient pb.PartyServiceClient `name:"PartyClient"` 22 | } 23 | 24 | func NewPartyClient(host string, sSetting sfx.SecuritySettingsParams) (pb.PartyServiceClient, error) { 25 | if sSetting.MTLSEnable { 26 | if conn, err := tools.DialWithSecurity( 27 | host, 28 | sSetting.ClientCert, 29 | sSetting.ClientKey, 30 | sSetting.ServerName, 31 | sSetting.ServerCaCert, 32 | ); err != nil { 33 | return nil, err 34 | } else { 35 | return pb.NewPartyServiceClient(conn), nil 36 | } 37 | } else { 38 | if conn, err := tools.DialInsecure(host); err != nil { 39 | return nil, err 40 | } else { 41 | return pb.NewPartyServiceClient(conn), nil 42 | } 43 | } 44 | } 45 | 46 | var PartyClientModule = fx.Provide( 47 | func( 48 | setting PartySettingParams, 49 | sSetting sfx.SecuritySettingsParams, 50 | ) (out PartyClientResult, err error) { 51 | if cli, e := NewPartyClient(setting.PartyUrl, sSetting); e != nil { 52 | err = e 53 | } else { 54 | out.PartyClient = cli 55 | } 56 | return 57 | }, 58 | ) 59 | -------------------------------------------------------------------------------- /services/party/pkg/ptfx/party_settings.go: -------------------------------------------------------------------------------- 1 | package ptfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type PartySettingParams struct { 9 | fx.In 10 | Name string `name:"PartyName"` 11 | PartyUrl string `name:"PartyUrl"` 12 | } 13 | 14 | type PartySettingResult struct { 15 | fx.Out 16 | Name string `name:"PartyName" envconfig:"PARTY_NAME" default:"party"` 17 | PartyUrl string `name:"PartyUrl" envconfig:"PARTY_URL" default:"localhost:8081"` 18 | } 19 | 20 | func (l *PartySettingResult) LoadFromEnv() (err error) { 21 | err = utility.Load(l) 22 | return 23 | } 24 | 25 | var PartySettingsModule = fx.Provide( 26 | func() (out PartySettingResult, err error) { 27 | err = out.LoadFromEnv() 28 | return 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /services/profile/README.md: -------------------------------------------------------------------------------- 1 | # Profile Service 2 | 3 | 玩家信息服务,提供玩家信息的管理服务。 4 | 5 | ## Overview 6 | 7 | The profile service is responsible for managing user profiles. 8 | 9 | -------------------------------------------------------------------------------- /services/profile/changes/topics.go: -------------------------------------------------------------------------------- 1 | package changes 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gstones/moke-kit/mq/common" 7 | ) 8 | 9 | func MakeProfileTopic(uid string) string { 10 | return common.NatsHeader.CreateTopic(fmt.Sprintf("profile.changes.%s", uid)) 11 | } 12 | -------------------------------------------------------------------------------- /services/profile/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | ) 7 | 8 | var ( 9 | ErrNoMetaData = status.Error(codes.Internal, "ErrNoMetaData") 10 | ErrNotFound = status.Error(codes.NotFound, "ErrNotFound") 11 | ErrGeneralFailure = status.Error(codes.Internal, "ErrGeneralFailure") 12 | ErrUpdateFailure = status.Error(codes.Internal, "ErrUpdateFailure") 13 | ErrLoadFailure = status.Error(codes.Internal, "ErrLoadFailure") 14 | ErrAlreadyExists = status.Error(codes.AlreadyExists, "ErrAlreadyExists") 15 | ErrInvalidArgument = status.Error(codes.InvalidArgument, "ErrInvalidArgument") 16 | ) 17 | -------------------------------------------------------------------------------- /services/profile/internal/db/factory.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/nosql/diface" 5 | "go.mongodb.org/mongo-driver/mongo" 6 | "go.uber.org/zap" 7 | 8 | pb "github.com/moke-game/platform/api/gen/profile/api" 9 | "github.com/moke-game/platform/services/profile/internal/db/model" 10 | ) 11 | 12 | type Database struct { 13 | logger *zap.Logger 14 | coll diface.ICollection 15 | cache diface.ICache 16 | } 17 | 18 | func OpenDatabase(l *zap.Logger, coll diface.ICollection, cache diface.ICache) *Database { 19 | return &Database{ 20 | logger: l, 21 | coll: coll, 22 | cache: cache, 23 | } 24 | } 25 | 26 | func NewProfileModel(id string, doc diface.ICollection, cache diface.ICache) (*model.Dao, error) { 27 | dm := &model.Dao{} 28 | if err := dm.Init(id, doc, cache); err != nil { 29 | return nil, err 30 | } 31 | return dm, nil 32 | } 33 | 34 | func (db *Database) LoadProfile(uid string) (*model.Dao, error) { 35 | if dm, err := NewProfileModel(uid, db.coll, db.cache); err != nil { 36 | return nil, err 37 | } else if err = dm.Load(); err != nil { 38 | return nil, err 39 | } else { 40 | return dm, nil 41 | } 42 | } 43 | 44 | func (db *Database) CreateProfile(uid string, profile *pb.Profile) (*model.Dao, error) { 45 | if dm, err := NewProfileModel(uid, db.coll, db.cache); err != nil { 46 | return nil, err 47 | } else if err = dm.InitDefault(uid, profile); err != nil { 48 | return nil, err 49 | } else if err = dm.Create(); err != nil { 50 | return nil, err 51 | } else { 52 | return dm, nil 53 | } 54 | } 55 | 56 | func NewProfilePrivateDao(db *mongo.Database) (*model.PrivateDao, error) { 57 | pd := &model.PrivateDao{} 58 | if err := pd.Init(db); err != nil { 59 | return nil, err 60 | } 61 | return pd, nil 62 | } 63 | -------------------------------------------------------------------------------- /services/profile/internal/db/model/dao.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/nosql" 5 | "github.com/gstones/moke-kit/orm/nosql/diface" 6 | 7 | "context" 8 | 9 | "google.golang.org/protobuf/proto" 10 | 11 | pb "github.com/moke-game/platform/api/gen/profile/api" 12 | ) 13 | 14 | type Dao struct { 15 | nosql.DocumentBase `bson:"-"` 16 | Data *pb.Profile `bson:"data"` 17 | } 18 | 19 | func (d *Dao) Init(id string, doc diface.ICollection, cache diface.ICache) error { 20 | key, e := NewProfileKey(id) 21 | if e != nil { 22 | return e 23 | } 24 | d.initData() 25 | d.DocumentBase.InitWithCache(context.Background(), &d.Data, d.clear, doc, key, cache) 26 | return nil 27 | } 28 | 29 | func (d *Dao) ToProto() *pb.Profile { 30 | return proto.Clone(d.Data).(*pb.Profile) 31 | } 32 | 33 | func (d *Dao) UpdateData(profile *pb.Profile) bool { 34 | if profile.Nickname != "" { 35 | d.Data.Nickname = profile.Nickname 36 | } 37 | 38 | if profile.Avatar != "" { 39 | d.Data.Avatar = profile.Avatar 40 | } 41 | if profile.Phone != "" { 42 | d.Data.Phone = profile.Phone 43 | } 44 | if profile.Email != "" { 45 | d.Data.Email = profile.Email 46 | } 47 | 48 | if profile.RechargeAmount > 0 { 49 | d.Data.RechargeAmount += profile.RechargeAmount 50 | } 51 | return true 52 | } 53 | -------------------------------------------------------------------------------- /services/profile/internal/db/model/data.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import pb "github.com/moke-game/platform/api/gen/profile/api" 4 | 5 | func (d *Dao) initData() { 6 | d.Data = &pb.Profile{} 7 | } 8 | 9 | func (d *Dao) InitDefault(uid string, profile *pb.Profile) error { 10 | d.Data = profile 11 | d.Data.Uid = uid 12 | return nil 13 | } 14 | 15 | func (d *Dao) clear() { 16 | d.Data.Reset() 17 | } 18 | 19 | func (d *Dao) GetUid() string { 20 | return d.Data.GetUid() 21 | } 22 | -------------------------------------------------------------------------------- /services/profile/internal/db/model/keys.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/nosql/key" 5 | ) 6 | 7 | func NewProfileKey(id string) (key.Key, error) { 8 | return key.NewKeyFromParts("profile", id) 9 | } 10 | 11 | func NewProfileCollectionName() (key.Key, error) { 12 | return key.NewKeyFromParts("profile") 13 | } 14 | -------------------------------------------------------------------------------- /services/profile/internal/db/redis/basic.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/redis/go-redis/v9" 8 | 9 | pb "github.com/moke-game/platform/api/gen/profile/api" 10 | ) 11 | 12 | type ProfileBasic struct { 13 | Uid string `json:"uid" redis:"uid"` 14 | Nickname string `json:"nickname" redis:"nickname"` 15 | Avatar string `json:"avatar" redis:"avatar"` 16 | HeroId int32 `json:"hero_id" redis:"hero_id" ` 17 | HallUrl string `json:"hall_url" redis:"hall_url"` 18 | BattleUrl string `json:"battle_url" redis:"battle_url"` 19 | RoomId string `json:"room_id" redis:"room_id"` 20 | } 21 | 22 | func (p *ProfileBasic) MarshalBinary() ([]byte, error) { 23 | return json.Marshal(p) 24 | } 25 | 26 | func (p *ProfileBasic) UnmarshalBinary(data []byte) error { 27 | return json.Unmarshal(data, p) 28 | } 29 | 30 | func (p *ProfileBasic) toProto() *pb.ProfileBasic { 31 | return &pb.ProfileBasic{ 32 | Uid: p.Uid, 33 | Nickname: p.Nickname, 34 | Avatar: p.Avatar, 35 | } 36 | } 37 | 38 | func GetBasicInfo(redisCli *redis.Client, uids ...string) (map[string]*pb.ProfileBasic, error) { 39 | pCli := redisCli.Pipeline() 40 | for _, uid := range uids { 41 | if key, err := NewProfileBasicKey(uid); err != nil { 42 | return nil, err 43 | } else { 44 | if err := pCli.HGetAll(context.Background(), key.String()).Err(); err != nil { 45 | return nil, err 46 | } 47 | } 48 | } 49 | cmds, err := pCli.Exec(context.Background()) 50 | if err != nil { 51 | return nil, err 52 | } 53 | res := make(map[string]*pb.ProfileBasic) 54 | for _, v := range cmds { 55 | info := &ProfileBasic{} 56 | cmd := v.(*redis.MapStringStringCmd) 57 | if err := cmd.Scan(info); err != nil { 58 | return nil, err 59 | } 60 | res[info.Uid] = info.toProto() 61 | } 62 | return res, nil 63 | } 64 | 65 | func UpdateBasicWithProfile(redisCli *redis.Client, uid string, profile *pb.Profile) error { 66 | basic := &pb.ProfileBasic{ 67 | Uid: uid, 68 | Nickname: profile.Nickname, 69 | Avatar: profile.Avatar, 70 | } 71 | return SetBasicInfo(redisCli, uid, basic) 72 | } 73 | 74 | func SetBasicInfo(redisCli *redis.Client, uid string, basic *pb.ProfileBasic) error { 75 | if basic == nil { 76 | return nil 77 | } 78 | if key, err := NewProfileBasicKey(uid); err != nil { 79 | return err 80 | } else { 81 | dataMap := make(map[string]interface{}) 82 | if basic.Uid != "" { 83 | dataMap["uid"] = basic.Uid 84 | } 85 | if basic.Nickname != "" { 86 | dataMap["nickname"] = basic.Nickname 87 | } 88 | 89 | if basic.Avatar != "" { 90 | dataMap["avatar"] = basic.Avatar 91 | } 92 | return redisCli.HSet(context.Background(), key.String(), dataMap).Err() 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /services/profile/internal/db/redis/keys.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/orm/nosql/key" 5 | ) 6 | 7 | func NewNameKey() (key.Key, error) { 8 | return key.NewKeyFromParts("profile", "nickname") 9 | } 10 | 11 | func NewProfileStatusKey(uid string) (key.Key, error) { 12 | return key.NewKeyFromParts("profile", "status", uid) 13 | } 14 | 15 | func NewProfileBasicKey(uid string) (key.Key, error) { 16 | return key.NewKeyFromParts("profile", "basic", uid) 17 | } 18 | -------------------------------------------------------------------------------- /services/profile/internal/db/redis/name.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/random" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | func IsNameExist(redisCli *redis.Client, name string) (bool, error) { 12 | if key, err := NewNameKey(); err != nil { 13 | return false, err 14 | } else if res, err := redisCli.HExists( 15 | context.Background(), 16 | key.String(), 17 | name, 18 | ).Result(); err != nil { 19 | return false, err 20 | } else { 21 | return res, nil 22 | } 23 | } 24 | 25 | // SaveName TODO fix Redis String to Hash 26 | func SaveName(redisCli *redis.Client, name string) error { 27 | if key, err := NewNameKey(); err != nil { 28 | return err 29 | } else if err := redisCli.HSet(context.Background(), key.String(), name, 0).Err(); err != nil { 30 | return err 31 | } else { 32 | return nil 33 | } 34 | } 35 | 36 | func ChangeName(redisCli *redis.Client, oldName, newName string) error { 37 | key, err := NewNameKey() 38 | if err != nil { 39 | return err 40 | } 41 | ctx := context.Background() 42 | cmd := redisCli.HSet(ctx, key.String(), newName, 0) 43 | ret, err := cmd.Result() 44 | if err != nil { 45 | return err 46 | } 47 | if ret != 1 { 48 | return fmt.Errorf("name already exists") 49 | } 50 | redisCli.HDel(ctx, key.String(), oldName) 51 | return nil 52 | } 53 | 54 | const ( 55 | NameTryCount = 5 56 | ) 57 | 58 | func RandomName(redisCli *redis.Client, name string) (string, error) { 59 | index := 0 60 | for index <= NameTryCount { 61 | rs := random.RandString(6) 62 | name := fmt.Sprintf("%s%s", name, rs) 63 | if ok, err := IsNameExist(redisCli, name); err != nil { 64 | return "", err 65 | } else if !ok { 66 | return name, nil 67 | } 68 | index++ 69 | } 70 | return "", fmt.Errorf("failed to generate random name") 71 | } 72 | -------------------------------------------------------------------------------- /services/profile/internal/db/redis/status.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | const statusExpire = time.Second * 10 12 | 13 | func SetProfileStatus(redisCli *redis.Client, profileID string, status int) error { 14 | if key, err := NewProfileStatusKey(profileID); err != nil { 15 | return err 16 | } else if err := redisCli.Set(context.Background(), key.String(), status, statusExpire).Err(); err != nil { 17 | return err 18 | } else { 19 | return nil 20 | } 21 | } 22 | 23 | func GetProfileStatus(redisCli *redis.Client, profileID ...string) map[string]int32 { 24 | result := make(map[string]int32) 25 | keys := make([]string, len(profileID)) 26 | for i, id := range profileID { 27 | if key, err := NewProfileStatusKey(id); err != nil { 28 | continue 29 | } else { 30 | keys[i] = key.String() 31 | } 32 | } 33 | if res, err := redisCli.MGet(context.Background(), keys...).Result(); err != nil { 34 | return result 35 | } else { 36 | for i, id := range profileID { 37 | if res[i] == nil { 38 | continue 39 | } 40 | 41 | if status, err := strconv.ParseInt(res[i].(string), 10, 32); err != nil { 42 | continue 43 | } else { 44 | result[id] = int32(status) 45 | } 46 | } 47 | } 48 | return result 49 | } 50 | -------------------------------------------------------------------------------- /services/profile/internal/private/service.go: -------------------------------------------------------------------------------- 1 | package private 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/mq/miface" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/pkg/ofx" 7 | "github.com/gstones/moke-kit/server/pkg/sfx" 8 | "github.com/gstones/moke-kit/server/siface" 9 | "github.com/gstones/moke-kit/utility" 10 | "github.com/redis/go-redis/v9" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "go.uber.org/fx" 13 | "go.uber.org/zap" 14 | 15 | pb "github.com/moke-game/platform/api/gen/profile/api" 16 | "github.com/moke-game/platform/services/profile/internal/db" 17 | "github.com/moke-game/platform/services/profile/internal/db/model" 18 | "github.com/moke-game/platform/services/profile/pkg/pfx" 19 | ) 20 | 21 | type Service struct { 22 | utility.WithoutAuth 23 | url string 24 | 25 | logger *zap.Logger 26 | redisCli *redis.Client 27 | mq miface.MessageQueue 28 | privateDao *model.PrivateDao 29 | } 30 | 31 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 32 | pb.RegisterProfilePrivateServiceServer(server.GrpcServer(), s) 33 | return nil 34 | } 35 | 36 | func NewService( 37 | l *zap.Logger, 38 | url string, 39 | client *redis.Client, 40 | mq miface.MessageQueue, 41 | mongoDB *mongo.Database, 42 | ) (result *Service, err error) { 43 | pd, err := db.NewProfilePrivateDao(mongoDB) 44 | if err != nil { 45 | return nil, err 46 | } 47 | result = &Service{ 48 | logger: l, 49 | redisCli: client, 50 | url: url, 51 | mq: mq, 52 | privateDao: pd, 53 | } 54 | return 55 | } 56 | 57 | var Module = fx.Provide( 58 | func( 59 | l *zap.Logger, 60 | pSetting pfx.ProfileSettingParams, 61 | dbProvider ofx.DocumentStoreParams, 62 | redisParams ofx.RedisParams, 63 | rcParams ofx.RedisCacheParams, 64 | mongoParams ofx.MongoParams, 65 | mqParams mfx.MessageQueueParams, 66 | ) (out sfx.GrpcServiceResult, err error) { 67 | if svc, e := NewService( 68 | l, 69 | pSetting.ProfileUrl, 70 | redisParams.Redis, 71 | mqParams.MessageQueue, 72 | mongoParams.MongoClient.Database(pSetting.ProfileStoreName), 73 | ); e != nil { 74 | err = e 75 | } else { 76 | out.GrpcService = svc 77 | } 78 | return 79 | }, 80 | ) 81 | -------------------------------------------------------------------------------- /services/profile/internal/public/service.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/mq/miface" 5 | "github.com/gstones/moke-kit/mq/pkg/mfx" 6 | "github.com/gstones/moke-kit/orm/nosql/diface" 7 | "github.com/gstones/moke-kit/orm/pkg/ofx" 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | "github.com/gstones/moke-kit/server/siface" 10 | "github.com/redis/go-redis/v9" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "go.uber.org/fx" 13 | "go.uber.org/zap" 14 | 15 | pb "github.com/moke-game/platform/api/gen/profile/api" 16 | "github.com/moke-game/platform/services/profile/internal/db" 17 | "github.com/moke-game/platform/services/profile/pkg/pfx" 18 | ) 19 | 20 | type Service struct { 21 | logger *zap.Logger 22 | db *db.Database 23 | redisCli *redis.Client 24 | mongoCli *mongo.Client 25 | mq miface.MessageQueue 26 | authMiddleware siface.IAuthMiddleware 27 | } 28 | 29 | func (s *Service) RegisterWithGrpcServer(server siface.IGrpcServer) error { 30 | pb.RegisterProfileServiceServer(server.GrpcServer(), s) 31 | return nil 32 | } 33 | 34 | func NewService( 35 | l *zap.Logger, 36 | coll diface.ICollection, 37 | client *redis.Client, 38 | redisCache diface.ICache, 39 | mongoCli *mongo.Client, 40 | mq miface.MessageQueue, 41 | authMiddleware siface.IAuthMiddleware, 42 | ) (result *Service, err error) { 43 | result = &Service{ 44 | logger: l, 45 | db: db.OpenDatabase(l, coll, redisCache), 46 | redisCli: client, 47 | mongoCli: mongoCli, 48 | mq: mq, 49 | authMiddleware: authMiddleware, 50 | } 51 | return 52 | } 53 | 54 | var Module = fx.Provide( 55 | func( 56 | l *zap.Logger, 57 | pSetting pfx.ProfileSettingParams, 58 | dbProvider ofx.DocumentStoreParams, 59 | redisParams ofx.RedisParams, 60 | rcParams ofx.RedisCacheParams, 61 | dbParams ofx.MongoParams, 62 | mqParams mfx.MessageQueueParams, 63 | authMiddlewareParams sfx.AuthMiddlewareParams, 64 | ) (out sfx.GrpcServiceResult, err error) { 65 | if coll, e := dbProvider.DriverProvider.OpenDbDriver(pSetting.ProfileStoreName); e != nil { 66 | err = e 67 | } else { 68 | if svc, e := NewService( 69 | l, 70 | coll, 71 | redisParams.Redis, 72 | rcParams.RedisCache, 73 | dbParams.MongoClient, 74 | mqParams.MessageQueue, 75 | authMiddlewareParams.AuthMiddleware, 76 | ); e != nil { 77 | err = e 78 | } else { 79 | out.GrpcService = svc 80 | } 81 | } 82 | return 83 | }, 84 | ) 85 | -------------------------------------------------------------------------------- /services/profile/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/moke-game/platform/services/profile/internal/private" 7 | "github.com/moke-game/platform/services/profile/internal/public" 8 | "github.com/moke-game/platform/services/profile/pkg/pfx" 9 | ) 10 | 11 | // ProfileModule Provides profile service 12 | var ProfileModule = fx.Module("profile", 13 | pfx.SettingsModule, 14 | public.Module, 15 | private.Module, 16 | ) 17 | 18 | // ProfilePrivateModule Provides profile private service 19 | var ProfilePrivateModule = fx.Module("profile_private", 20 | pfx.SettingsModule, 21 | private.Module, 22 | ) 23 | 24 | // ProfileClientModule Provides profile client for grpc 25 | var ProfileClientModule = fx.Module("profile_client", 26 | pfx.SettingsModule, 27 | pfx.ProfileClientModule, 28 | ) 29 | 30 | // ProfileAllModule Provides client, service for profile 31 | var ProfileAllModule = fx.Module("profile_all", 32 | pfx.SettingsModule, 33 | public.Module, 34 | private.Module, 35 | pfx.ProfileClientModule, 36 | ) 37 | -------------------------------------------------------------------------------- /services/profile/pkg/pfx/profile_client.go: -------------------------------------------------------------------------------- 1 | package pfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | pb "github.com/moke-game/platform/api/gen/profile/api" 7 | 8 | "github.com/gstones/moke-kit/server/pkg/sfx" 9 | "github.com/gstones/moke-kit/server/tools" 10 | ) 11 | 12 | type ProfileClientParams struct { 13 | fx.In 14 | 15 | ProfileClient pb.ProfileServiceClient `name:"ProfileClient"` 16 | ProfilePrivateClient pb.ProfilePrivateServiceClient `name:"ProfilePrivateClient"` 17 | } 18 | 19 | type ProfileClientResult struct { 20 | fx.Out 21 | 22 | ProfileClient pb.ProfileServiceClient `name:"ProfileClient"` 23 | ProfilePrivateClient pb.ProfilePrivateServiceClient `name:"ProfilePrivateClient"` 24 | } 25 | 26 | func NewProfileClient( 27 | host string, 28 | sSetting sfx.SecuritySettingsParams, 29 | ) (pb.ProfileServiceClient, pb.ProfilePrivateServiceClient, error) { 30 | if sSetting.MTLSEnable { 31 | if conn, err := tools.DialWithSecurity( 32 | host, 33 | sSetting.ClientCert, 34 | sSetting.ClientKey, 35 | sSetting.ServerName, 36 | sSetting.ServerCaCert, 37 | ); err != nil { 38 | return nil, nil, err 39 | } else { 40 | return pb.NewProfileServiceClient(conn), pb.NewProfilePrivateServiceClient(conn), nil 41 | } 42 | } else { 43 | if conn, err := tools.DialInsecure(host); err != nil { 44 | return nil, nil, err 45 | } else { 46 | return pb.NewProfileServiceClient(conn), pb.NewProfilePrivateServiceClient(conn), nil 47 | } 48 | } 49 | } 50 | 51 | var ProfileClientModule = fx.Provide( 52 | func( 53 | setting ProfileSettingParams, 54 | sSetting sfx.SecuritySettingsParams, 55 | ) (out ProfileClientResult, err error) { 56 | if cli, pCli, e := NewProfileClient(setting.ProfileUrl, sSetting); e != nil { 57 | err = e 58 | } else { 59 | out.ProfileClient = cli 60 | out.ProfilePrivateClient = pCli 61 | } 62 | return 63 | }, 64 | ) 65 | -------------------------------------------------------------------------------- /services/profile/pkg/pfx/profile_settings.go: -------------------------------------------------------------------------------- 1 | package pfx 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/utility" 5 | "go.uber.org/fx" 6 | ) 7 | 8 | type ProfileSettingParams struct { 9 | fx.In 10 | 11 | ProfileUrl string `name:"ProfileUrl"` 12 | ProfileStoreName string `name:"ProfileStoreName"` 13 | } 14 | 15 | type ProfileSettingsResult struct { 16 | fx.Out 17 | 18 | ProfileStoreName string `name:"ProfileStoreName" envconfig:"PROFILE_STORE_NAME" default:"profile"` 19 | ProfileUrl string `name:"ProfileUrl" envconfig:"PROFILE_URL" default:"localhost:8081"` 20 | } 21 | 22 | func (g *ProfileSettingsResult) LoadFromEnv() (err error) { 23 | err = utility.Load(g) 24 | return 25 | } 26 | 27 | var SettingsModule = fx.Provide( 28 | func() (out ProfileSettingsResult, err error) { 29 | err = out.LoadFromEnv() 30 | return 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /services/room/README.md: -------------------------------------------------------------------------------- 1 | # Room Service 2 | 3 | ## 简介: 4 | 5 | 房间服务器是对战斗跟大世界的抽象,大世界作为一种特殊类型的战斗 6 | 7 | ## 同步方式参考: 8 | 9 | [状态同步](https://www.gabrielgambetta.com/client-side-prediction-live-demo.html) 10 | [CSGO](https://developer.valvesoftware.com/w/index.php?title=Source_Multiplayer_Networking&uselang=zh) 11 | [Synchronization](https://engineering.monstar-lab.com/en/post/2021/02/09/Game-server-Synchronization/) 12 | 13 | 14 | -------------------------------------------------------------------------------- /services/room/client/room.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/abiosoft/ishell" 5 | "github.com/duke-git/lancet/v2/random" 6 | "github.com/gstones/moke-kit/logging/slogger" 7 | "github.com/gstones/zinx/ziface" 8 | "github.com/gstones/zinx/znet" 9 | "google.golang.org/protobuf/proto" 10 | 11 | room "github.com/moke-game/platform/api/gen/room/api" 12 | ) 13 | 14 | type RoomWSClient struct { 15 | cmd *ishell.Cmd 16 | 17 | username string 18 | client ziface.IClient 19 | } 20 | 21 | func (ac *RoomWSClient) initShell() { 22 | ac.cmd = &ishell.Cmd{ 23 | Name: "room", 24 | Help: "room interactive shell", 25 | Aliases: []string{"R"}, 26 | } 27 | ac.initSubCmd() 28 | } 29 | 30 | func (ac *RoomWSClient) initSubCmd() { 31 | ac.cmd.AddCmd(&ishell.Cmd{ 32 | Name: "join", 33 | Help: "join room", 34 | Aliases: []string{"J"}, 35 | Func: ac.joinRoom, 36 | }) 37 | 38 | ac.cmd.AddCmd(&ishell.Cmd{ 39 | Name: "sync", 40 | Help: "sync message", 41 | Aliases: []string{"S"}, 42 | Func: ac.sync, 43 | }) 44 | 45 | ac.cmd.AddCmd(&ishell.Cmd{ 46 | Name: "exit", 47 | Help: "exit room", 48 | Aliases: []string{"E"}, 49 | Func: ac.exit, 50 | }) 51 | } 52 | 53 | func (ac *RoomWSClient) joinRoom(c *ishell.Context) { 54 | token := slogger.ReadLine(c, "input room token:") 55 | 56 | req := &room.ReqJoin{ 57 | Token: token, 58 | } 59 | 60 | data, err := proto.Marshal(req) 61 | if err != nil { 62 | return 63 | } 64 | if err := ac.client.Conn().SendMsg(uint32(room.MsgID_MSG_ID_ROOM_JOIN), data); err != nil { 65 | return 66 | } 67 | } 68 | 69 | func (ac *RoomWSClient) sync(c *ishell.Context) { 70 | req := &room.ReqSync{ 71 | Cmd: &room.CmdData{ 72 | Uid: ac.username, 73 | }, 74 | } 75 | 76 | data, err := proto.Marshal(req) 77 | if err != nil { 78 | return 79 | } 80 | if err := ac.client.Conn().SendMsg(uint32(room.MsgID_MSG_ID_ROOM_SYNC), data); err != nil { 81 | return 82 | } 83 | } 84 | 85 | func (ac *RoomWSClient) exit(c *ishell.Context) { 86 | req := &room.ReqExit{} 87 | 88 | data, err := proto.Marshal(req) 89 | if err != nil { 90 | return 91 | } 92 | if err := ac.client.Conn().SendMsg(uint32(room.MsgID_MSG_ID_ROOM_EXIT), data); err != nil { 93 | return 94 | } 95 | } 96 | 97 | func CreateRoomWSClient(url string, port int) (*ishell.Cmd, error) { 98 | client := znet.NewWsClient(url, port) 99 | rc := &RoomWSClient{ 100 | client: client, 101 | username: random.RandString(8), 102 | } 103 | rc.initShell() 104 | client.Start() 105 | return rc.cmd, nil 106 | } 107 | -------------------------------------------------------------------------------- /services/room/internal/common/consts.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/gstones/moke-kit/utility" 4 | 5 | const ( 6 | UID string = "uid" 7 | Room string = "room" 8 | ) 9 | 10 | var DeploymentGlobal utility.Deployments 11 | -------------------------------------------------------------------------------- /services/room/internal/common/response.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/gstones/zinx/ziface" 5 | "google.golang.org/protobuf/proto" 6 | 7 | room "github.com/moke-game/platform/api/gen/room/api" 8 | ) 9 | 10 | func Response(connect ziface.IConnection, msgId room.MsgID, code room.RoomErrorCode, msg proto.Message) error { 11 | if connect == nil { 12 | return nil 13 | } else if msg, err := proto.Marshal(msg); err != nil { 14 | return err 15 | } else if resp, err := proto.Marshal(&room.Response{ 16 | ErrorCode: code, 17 | Message: msg, 18 | }); err != nil { 19 | return err 20 | } else if err := connect.SendBuffMsg(uint32(msgId), resp); err != nil { 21 | return err 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /services/room/internal/room/factory.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | "github.com/moke-game/platform/services/room/internal/room/riface" 7 | "github.com/moke-game/platform/services/room/pkg/rfx" 8 | ) 9 | 10 | func CreateRoom( 11 | roomId string, 12 | logger *zap.Logger, 13 | setting rfx.RoomSettingParams, 14 | ) (riface.IRoom, error) { 15 | if room, err := NewRoom( 16 | roomId, 17 | logger, 18 | setting, 19 | ); err != nil { 20 | return nil, err 21 | } else if err := room.Init(1); err != nil { 22 | return nil, err 23 | } else { 24 | return room, nil 25 | } 26 | } 27 | 28 | func CreateMsgHub(logger *zap.Logger) (*MsgSender, error) { 29 | msg := &MsgSender{ 30 | logger: logger, 31 | } 32 | if err := msg.Init(); err != nil { 33 | return nil, err 34 | } 35 | return msg, nil 36 | } 37 | -------------------------------------------------------------------------------- /services/room/internal/room/frames.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import roompb "github.com/moke-game/platform/api/gen/room/api" 4 | 5 | type Frames struct { 6 | frameIndex uint32 7 | frames map[uint32]*roompb.FrameData 8 | } 9 | 10 | func NewFrames() *Frames { 11 | return &Frames{ 12 | frames: make(map[uint32]*roompb.FrameData), 13 | } 14 | } 15 | 16 | func (f *Frames) tick() { 17 | f.frameIndex++ 18 | } 19 | 20 | func (f *Frames) getFrameIndex() uint32 { 21 | return f.frameIndex 22 | } 23 | 24 | func (f *Frames) checkCmdIsExist(cmd *roompb.CmdData) bool { 25 | if frame, ok := f.frames[f.frameIndex]; ok { 26 | for _, c := range frame.Cmds { 27 | if c.GetUid() == cmd.GetUid() { 28 | return true 29 | } 30 | } 31 | } 32 | return false 33 | } 34 | 35 | func (f *Frames) PushCmd(cmd *roompb.CmdData) bool { 36 | if frame, ok := f.frames[f.frameIndex]; ok { 37 | if f.checkCmdIsExist(cmd) { 38 | return false 39 | } 40 | frame.Cmds = append(frame.Cmds, cmd) 41 | } else { 42 | f.frames[f.frameIndex] = &roompb.FrameData{ 43 | FrameIndex: f.frameIndex, 44 | Cmds: []*roompb.CmdData{cmd}, 45 | } 46 | } 47 | return true 48 | } 49 | 50 | func (f *Frames) getRangeFrames(start, end uint32) []*roompb.FrameData { 51 | var frames []*roompb.FrameData 52 | for i := start; i <= end; i++ { 53 | if frame, ok := f.frames[i]; ok { 54 | frames = append(frames, frame) 55 | } 56 | } 57 | return frames 58 | } 59 | 60 | func (f *Frames) getCurrentFrame() *roompb.FrameData { 61 | return f.frames[f.frameIndex] 62 | } 63 | -------------------------------------------------------------------------------- /services/room/internal/room/handlers.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import ( 4 | "github.com/duke-git/lancet/v2/random" 5 | "github.com/gstones/zinx/ziface" 6 | "go.uber.org/zap" 7 | "google.golang.org/protobuf/proto" 8 | 9 | roompb "github.com/moke-game/platform/api/gen/room/api" 10 | ) 11 | 12 | func (r *Room) joinRoom(uid string, request ziface.IRequest) (proto.Message, roompb.RoomErrorCode) { 13 | req := &roompb.ReqJoin{} 14 | if err := proto.Unmarshal(request.GetData(), req); err != nil { 15 | return nil, roompb.RoomErrorCode_ROOM_ERROR_CODE_INVALID 16 | } 17 | p := &roompb.Player{ 18 | Uid: uid, 19 | Nickname: random.RandString(8), 20 | } 21 | 22 | r.msgSender.AddSession(uid, request.GetConnection()) 23 | if err := r.players.AddPlayer(p); err != nil { 24 | return nil, roompb.RoomErrorCode_ROOM_ERROR_CODE_FULL 25 | } 26 | // notice current player to add other players 27 | r.broadcastInclude(roompb.NoticeID_NOTICE_ID_ROOM_JOINED, &roompb.NtfRoomJoined{ 28 | Players: r.players.GetAllPlayers(), 29 | }, uid) 30 | // notice other players to add current player 31 | r.broadcastExclude(roompb.NoticeID_NOTICE_ID_ROOM_JOINED, &roompb.NtfRoomJoined{ 32 | Players: []*roompb.Player{p}, 33 | }, uid) 34 | 35 | fs := r.frames.getRangeFrames(req.LastFrameIndex, r.frames.getFrameIndex()) 36 | if len(fs) > 0 { 37 | r.broadcastInclude(roompb.NoticeID_NOTICE_ID_ROOM_SYNC, &roompb.NtfFrame{ 38 | Frames: fs, 39 | }, uid) 40 | } 41 | return &roompb.RspJoin{ 42 | RandomSeed: r.randomSeed, 43 | }, 0 44 | } 45 | func (r *Room) exitRoom(uid string, _ ziface.IRequest) (proto.Message, roompb.RoomErrorCode) { 46 | r.players.RemovePlayer(uid) 47 | r.broadcastExclude(roompb.NoticeID_NOTICE_ID_ROOM_EXIT, &roompb.NtfRoomExit{ 48 | Uids: []string{uid}, 49 | }) 50 | r.msgSender.RemoveSession(uid) 51 | return &roompb.RspExit{}, 0 52 | } 53 | 54 | func (r *Room) sync(uid string, request ziface.IRequest) (proto.Message, roompb.RoomErrorCode) { 55 | req := &roompb.ReqSync{} 56 | if err := proto.Unmarshal(request.GetData(), req); err != nil { 57 | return nil, roompb.RoomErrorCode_ROOM_ERROR_CODE_INVALID 58 | } 59 | if ok := r.frames.PushCmd(req.GetCmd()); !ok { 60 | r.logger.Warn("sync cmd is exist", zap.String("uid", uid)) 61 | } 62 | 63 | return &roompb.RspSync{}, 0 64 | } 65 | -------------------------------------------------------------------------------- /services/room/internal/room/msgsender.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/gstones/zinx/ziface" 7 | "github.com/gstones/zinx/zpack" 8 | "go.uber.org/zap" 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | type MsgSender struct { 13 | logger *zap.Logger 14 | sessions map[string]ziface.IConnection 15 | writeBuff map[string][]ziface.IMessage 16 | } 17 | 18 | func (ms *MsgSender) Init() error { 19 | ms.sessions = make(map[string]ziface.IConnection) 20 | ms.writeBuff = make(map[string][]ziface.IMessage) 21 | return nil 22 | } 23 | 24 | func (ms *MsgSender) AddSession(uid string, s ziface.IConnection) { 25 | ms.sessions[uid] = s 26 | ms.writeBuff[uid] = make([]ziface.IMessage, 0) 27 | } 28 | 29 | func (ms *MsgSender) RemoveSession(uid string) { 30 | delete(ms.sessions, uid) 31 | delete(ms.writeBuff, uid) 32 | } 33 | 34 | func (ms *MsgSender) HandleSendBuff() { 35 | for k, v := range ms.writeBuff { 36 | s, ok := ms.sessions[k] 37 | if !ok { 38 | continue 39 | } 40 | for _, v1 := range v { 41 | if err := s.SendBuffMsg(v1.GetMsgID(), v1.GetData()); err != nil { 42 | ms.logger.Error("send buff msg failed", zap.Error(err)) 43 | continue 44 | } 45 | } 46 | } 47 | ms.writeBuff = make(map[string][]ziface.IMessage) 48 | } 49 | 50 | func (ms *MsgSender) CacheMsg(uid string, msg ziface.IMessage) { 51 | if ms.sessions[uid] == nil { 52 | return 53 | } 54 | ms.writeBuff[uid] = append(ms.writeBuff[uid], msg) 55 | } 56 | 57 | func (ms *MsgSender) SendResponse(uid string, msg ziface.IMessage) { 58 | if sess, ok := ms.sessions[uid]; ok { 59 | if err := sess.SendBuffMsg(msg.GetMsgID(), msg.GetData()); err != nil { 60 | ms.logger.Error("send buff msg failed", zap.Error(err)) 61 | } 62 | } 63 | } 64 | 65 | func (ms *MsgSender) BroadcastInclude(msg ziface.IMessage, uids ...string) { 66 | for _, uid := range uids { 67 | ms.CacheMsg(uid, msg) 68 | } 69 | } 70 | 71 | func (ms *MsgSender) BroadcastExclude(msg ziface.IMessage, uids ...string) { 72 | for uid := range ms.sessions { 73 | if !ms.contains(uids, uid) { 74 | ms.CacheMsg(uid, msg) 75 | } 76 | } 77 | } 78 | 79 | func (ms *MsgSender) contains(uids []string, uid string) bool { 80 | for _, v := range uids { 81 | if v == uid { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | func (ms *MsgSender) SendTo(uid int64, msgId uint32, msg interface{}) { 89 | if msgId == 0 { 90 | return 91 | } 92 | if _msg, ok := msg.(proto.Message); ok { 93 | if data, err := proto.Marshal(_msg); err != nil { 94 | ms.logger.Error("room ntf marshal failed", zap.Error(err)) 95 | } else { 96 | pack := zpack.NewMsgPackage(msgId, data) 97 | ms.SendResponse(strconv.FormatInt(uid, 10), pack) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /services/room/internal/room/player/error.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrPlayersFull = errors.New("players full") 7 | ) 8 | -------------------------------------------------------------------------------- /services/room/internal/room/player/players.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import roompb "github.com/moke-game/platform/api/gen/room/api" 4 | 5 | type Players struct { 6 | max int32 7 | players map[string]*roompb.Player 8 | } 9 | 10 | func NewPlayers(max int32) *Players { 11 | return &Players{ 12 | max: max, 13 | players: make(map[string]*roompb.Player), 14 | } 15 | } 16 | 17 | func (p *Players) AddPlayer(player *roompb.Player) error { 18 | if int32(len(p.players)) >= p.max { 19 | return ErrPlayersFull 20 | } 21 | p.players[player.Uid] = player 22 | return nil 23 | } 24 | 25 | func (p *Players) RemovePlayer(uid string) bool { 26 | delete(p.players, uid) 27 | return len(p.players) == 0 28 | } 29 | 30 | func (p *Players) GetAllPlayers() []*roompb.Player { 31 | players := make([]*roompb.Player, 0, len(p.players)) 32 | for _, player := range p.players { 33 | players = append(players, player) 34 | } 35 | return players 36 | } 37 | -------------------------------------------------------------------------------- /services/room/internal/room/riface/ihandler.go: -------------------------------------------------------------------------------- 1 | package riface 2 | 3 | import ( 4 | "github.com/gstones/zinx/ziface" 5 | "google.golang.org/protobuf/proto" 6 | 7 | room "github.com/moke-game/platform/api/gen/room/api" 8 | ) 9 | 10 | type IHandler func(uid string, request ziface.IRequest) (proto.Message, room.RoomErrorCode) 11 | -------------------------------------------------------------------------------- /services/room/internal/room/riface/iroom.go: -------------------------------------------------------------------------------- 1 | package riface 2 | 3 | import ( 4 | "github.com/gstones/zinx/ziface" 5 | ) 6 | 7 | type RoomCreator func() (IRoom, error) 8 | 9 | type IMessage interface { 10 | GetMsgId() uint32 // Gets the ID of the message(获取消息ID) 11 | GetMsgData() []byte 12 | } 13 | 14 | type IRoom interface { 15 | Init(playId int32) error 16 | Run() error 17 | RoomId() string 18 | Receive(uid string, message ziface.IRequest) 19 | Exit(uid string) 20 | } 21 | -------------------------------------------------------------------------------- /services/room/internal/roommgr.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/gstones/moke-kit/3rd/agones/aiface" 8 | "go.uber.org/atomic" 9 | "go.uber.org/zap" 10 | 11 | "github.com/moke-game/platform/services/room/internal/room/riface" 12 | ) 13 | 14 | type RoomMgr struct { 15 | logger *zap.Logger 16 | rooms *sync.Map 17 | count *atomic.Int32 18 | 19 | // agones 相关 20 | canShutdown *atomic.Bool // 当前服务是否可以关闭(agones),当没有房间时,关闭当前进程 21 | agones aiface.IAgones 22 | } 23 | 24 | func NewRoomMgr(logger *zap.Logger, agonesSdk aiface.IAgones) *RoomMgr { 25 | return &RoomMgr{ 26 | logger: logger, 27 | count: atomic.NewInt32(0), 28 | rooms: &sync.Map{}, 29 | canShutdown: atomic.NewBool(false), 30 | agones: agonesSdk, 31 | } 32 | } 33 | 34 | func (rm *RoomMgr) SetCanShutdown() { 35 | rm.canShutdown.Store(true) 36 | } 37 | 38 | func (rm *RoomMgr) LoadOrCreateRoom(roomId string, creator riface.RoomCreator) (riface.IRoom, error) { 39 | if r, ok := rm.rooms.Load(roomId); !ok { 40 | cr, err := creator() 41 | if err != nil { 42 | return nil, err 43 | } 44 | r, ok = rm.rooms.LoadOrStore(roomId, cr) 45 | room := r.(riface.IRoom) 46 | if !ok { 47 | rm.runRoom(room) 48 | } else { 49 | rm.logger.Warn("room already exists", zap.String("roomId", roomId)) 50 | } 51 | return room, nil 52 | } else { 53 | return r.(riface.IRoom), nil 54 | } 55 | } 56 | 57 | func (rm *RoomMgr) LoadRoom(roomId string) (riface.IRoom, error) { 58 | if r, ok := rm.rooms.Load(roomId); !ok { 59 | return nil, fmt.Errorf("room %s not found", roomId) 60 | } else { 61 | return r.(riface.IRoom), nil 62 | } 63 | } 64 | 65 | func (rm *RoomMgr) runRoom(room riface.IRoom) { 66 | go func() { 67 | defer func() { 68 | rm.rooms.Delete(room.RoomId()) 69 | rm.count.Dec() 70 | if rm.canShutdown.Load() && rm.count.Load() == 0 { 71 | if err := rm.agones.Shutdown(); err != nil { 72 | rm.logger.Error("agonesSDK shutdown failed", zap.Error(err)) 73 | } 74 | } 75 | rm.logger.Info( 76 | "room destroy", 77 | zap.String("roomId", room.RoomId()), 78 | zap.String("roomCount", rm.count.String()), 79 | ) 80 | }() 81 | rm.count.Inc() 82 | rm.logger.Info( 83 | "room run", 84 | zap.String("roomId", room.RoomId()), 85 | zap.String("roomCount", rm.count.String()), 86 | ) 87 | if err := room.Run(); err != nil { 88 | rm.logger.Error("room run error", zap.Error(err)) 89 | } 90 | }() 91 | } 92 | -------------------------------------------------------------------------------- /services/room/pkg/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "github.com/gstones/moke-kit/fxmain/pkg/mfx" 5 | "github.com/gstones/moke-kit/utility" 6 | "go.uber.org/fx" 7 | "go.uber.org/zap" 8 | 9 | "github.com/moke-game/platform/services/room/internal" 10 | "github.com/moke-game/platform/services/room/internal/common" 11 | "github.com/moke-game/platform/services/room/pkg/rfx" 12 | ) 13 | 14 | // RoomModule backend for frontend service module 15 | var RoomModule = fx.Module("room", fx.Options( 16 | rfx.SettingsModule, 17 | internal.Module, 18 | globalModule, 19 | fx.Decorate(func(log *zap.Logger) *zap.Logger { 20 | return log.Named("room") 21 | }), 22 | )) 23 | 24 | var globalModule = fx.Invoke( 25 | func( 26 | //l *zap.Logger, 27 | //p cfx.ConfigsParams, 28 | aParams mfx.AppParams, 29 | ) { 30 | common.DeploymentGlobal = utility.ParseDeployments(aParams.Deployment) 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /services/room/pkg/rfx/room_settings.go: -------------------------------------------------------------------------------- 1 | package rfx 2 | 3 | import ( 4 | "go.uber.org/fx" 5 | 6 | "github.com/gstones/moke-kit/utility" 7 | ) 8 | 9 | type RoomSettingParams struct { 10 | fx.In 11 | 12 | RoomUrl string `name:"RoomUrl"` 13 | RoomCountMax int32 `name:"RoomCountMax"` 14 | RoomPlayerMax int32 `name:"RoomPlayerMax"` 15 | } 16 | 17 | type RoomSettingsResult struct { 18 | fx.Out 19 | 20 | RoomUrl string `name:"RoomUrl" envconfig:"ROOM_URL" default:"localhost:8888"` 21 | RoomCountMax int32 `name:"RoomCountMax" envconfig:"ROOM_COUNT_MAX" default:"100"` 22 | RoomPlayerMax int32 `name:"RoomPlayerMax" envconfig:"ROOM_PLAYER_MAX" default:"100"` 23 | } 24 | 25 | func (g *RoomSettingsResult) LoadFromEnv() (err error) { 26 | err = utility.Load(g) 27 | return 28 | } 29 | 30 | var SettingsModule = fx.Provide( 31 | func() (out RoomSettingsResult, err error) { 32 | err = out.LoadFromEnv() 33 | return 34 | }, 35 | ) 36 | -------------------------------------------------------------------------------- /tests/auth/auth.js: -------------------------------------------------------------------------------- 1 | import {Client, StatusOK} from 'k6/net/grpc'; 2 | import {check, sleep} from 'k6'; 3 | 4 | 5 | const client = new Client(); 6 | client.load(['../../api/'], './auth/auth.proto'); 7 | 8 | const GRPC_ADDR = __ENV.SERVER_HOST || '127.0.0.1:8081'; 9 | export default function () { 10 | client.connect(GRPC_ADDR, { 11 | plaintext: true 12 | }); 13 | const data = { 14 | app_id: 'test', 15 | id: 'test', 16 | }; 17 | 18 | let response = client.invoke('auth.v1.AuthService/Authenticate', data); 19 | check(response, { 20 | 'status is OK': (r) => r && r.status === StatusOK, 21 | }); 22 | response = client.invoke('auth.v1.AuthService/RefreshToken', {"refresh_token": response["message"]["refreshToken"]}); 23 | check(response, { 24 | 'status is OK': (r) => r && r.status === StatusOK, 25 | }); 26 | 27 | response = client.invoke('auth.v1.AuthService/ValidateToken', {"access_token": response["message"]["accessToken"]}); 28 | check(response, { 29 | 'status is OK': (r) => r && r.status === StatusOK, 30 | }); 31 | 32 | response = client.invoke('auth.v1.AuthService/AddBlocked', { 33 | "uid": "test", 34 | "is_block": true, 35 | "duration": 100 36 | }); 37 | check(response, { 38 | 'status is OK': (r) => r && r.status === StatusOK, 39 | }); 40 | 41 | 42 | client.close(); 43 | sleep(1); 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/common/common.js: -------------------------------------------------------------------------------- 1 | export const makeParams = (token) => { 2 | return { 3 | metadata: { 4 | 'x-my-header': 'k6test', 5 | 'x-my-header-bin': new Uint8Array([1, 2, 3]), 6 | 'authorization': "bearer " + token, 7 | }, 8 | tags: {k6test: 'yes'}, 9 | }; 10 | } -------------------------------------------------------------------------------- /tests/readme.md: -------------------------------------------------------------------------------- 1 | # Load Test 2 | Load test with [k6](https://grafana.com/docs/k6/latest/) --------------------------------------------------------------------------------