This is an automated email from the ASF dual-hosted git repository.
xuetaoli pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go-samples.git
The following commit(s) were added to refs/heads/main by this push:
new 863a0881 feat: migrate old game sample (#970)
863a0881 is described below
commit 863a0881aa890e6dcc3ebf3400a6d8dafeb7c7f5
Author: zbchi <[email protected]>
AuthorDate: Wed Nov 26 17:01:18 2025 +0800
feat: migrate old game sample (#970)
* feat: migrate old game sample
* migrate integrate test
* fix docs mistake
---
README.md | 2 +-
README_CN.md | 2 +-
compatibility/game/go-server-game/cmd/server.go | 79 -----
compatibility/game/go-server-game/conf/dubbogo.yml | 54 ---
compatibility/game/go-server-game/pkg/consumer.go | 24 --
compatibility/game/go-server-game/pkg/provider.go | 127 -------
compatibility/game/go-server-gate/conf/dubbogo.yml | 56 ---
compatibility/game/go-server-gate/pkg/consumer.go | 43 ---
compatibility/game/pkg/consumer/game/basketball.go | 36 --
compatibility/game/pkg/consumer/gate/basketball.go | 34 --
compatibility/game/pkg/pojo/info.go | 27 --
compatibility/game/website/css/style.css | 54 ---
compatibility/game/website/index.html | 56 ---
compatibility/game/website/js/api.js | 64 ----
compatibility/game/website/js/index.js | 130 -------
game/README.md | 72 ++++
game/README_zh.md | 75 ++++
.../game/go-server/cmd/main.go | 45 ++-
.../main_test.go => game/game/pkg/consumer.go | 38 +-
game/game/pkg/provider.go | 173 +++++++++
.../server.go => game/gate/go-server/cmd/main.go | 117 +++++-
game/gate/pkg/consumer.go | 98 +++++
.../go-server-gate => game/gate}/pkg/provider.go | 20 +-
game/proto/game/game.pb.go | 395 +++++++++++++++++++++
.../main_test.go => game/proto/game/game.proto | 47 +--
game/proto/game/game.triple.go | 176 +++++++++
game/proto/gate/gate.pb.go | 262 ++++++++++++++
.../pojo/result.go => game/proto/gate/gate.proto | 26 +-
game/proto/gate/gate.triple.go | 122 +++++++
game/website/css/style.css | 238 +++++++++++++
{compatibility/game => game}/website/img/bac.png | Bin
game/website/img/ball.svg | 11 +
game/website/img/gate.svg | 5 +
game/website/img/people.svg | 7 +
game/website/index.html | 76 ++++
game/website/js/api.js | 77 ++++
game/website/js/index.js | 228 ++++++++++++
.../game/game/tests/integration/main_test.go | 152 ++++++++
.../game}/tests/integration/provider_test.go | 43 ++-
.../gate}/tests/integration/main_test.go | 27 +-
.../gate}/tests/integration/provider_test.go | 13 +-
start_integrate_test.sh | 4 +-
42 files changed, 2432 insertions(+), 903 deletions(-)
diff --git a/README.md b/README.md
index 6c297ec2..8e8bf766 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ A collection of runnable Dubbo-go examples covering
configuration, registries, o
* `healthcheck`: Service health check example.
* `helloworld`: Basic “Hello World” example for Dubbo-go, also includes
Go–Java interoperability.
* `direct`: Triple point-to-point invocation sample without a registry.
+* `game`: Game service example.
* `integrate_test`: Integration test cases for Dubbo-go samples.
* `java_interop`: Demonstrates interoperability between Java and Go Dubbo
implementations.
* `llm`: Example of integrating Large Language Models (LLMs) with Dubbo-go.
@@ -49,7 +50,6 @@ A collection of runnable Dubbo-go examples covering
configuration, registries, o
* `compatibility/config-api`: Shows how to use Dubbo-go via APIs without
configuration files.
* `compatibility/configcenter`: Usage of different config centers, including
Zookeeper, Nacos, and Apollo.
* `compatibility/filter`: Examples of different filters, including
`custom_filter` and `tpslimit`.
-* `compatibility/game`: Game service example.
* `compatibility/generic`: Generic invocation example.
* `compatibility/mesh`: Proxy-based service mesh example showing how to deploy
Dubbo-go services with Envoy on Kubernetes.
* `compatibility/proxyless`: Proxyless service mesh example for deploying
Dubbo-go services on Kubernetes.
diff --git a/README_CN.md b/README_CN.md
index ac91dc38..2638fcfc 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -21,6 +21,7 @@
* `healthcheck`:服务健康检查示例。
* `helloworld`:Dubbo-go 最基础的 “Hello World” 示例,同时包含 Go 与 Java 的互操作示例。
* `direct`:不依赖注册中心的 Triple 点对点调用示例。
+* `game`:游戏服务示例。
* `integrate_test`:Dubbo-go 示例的集成测试用例。
* `java_interop`:展示 Java 与 Go Dubbo 实现之间的互操作能力。
* `llm`:将大模型(LLM)集成到 Dubbo-go 中的示例。
@@ -49,7 +50,6 @@
* `compatibility/config-api`:演示如何在不使用配置文件的情况下,通过 API 使用 Dubbo-go。
* `compatibility/configcenter`:不同配置中心的使用示例,包括 Zookeeper、Nacos 和 Apollo。
* `compatibility/filter`:多种 Filter 示例,包括 `custom_filter` 和 `tpslimit`。
-* `compatibility/game`:游戏服务示例。
* `compatibility/generic`:泛化调用示例。
* `compatibility/mesh`:基于代理的服务网格示例,展示如何在 Kubernetes 上结合 Envoy 部署 Dubbo-go 服务。
* `compatibility/proxyless`:无 Sidecar 的服务网格示例,展示在 Kubernetes 上的部署方式。
diff --git a/compatibility/game/go-server-game/cmd/server.go
b/compatibility/game/go-server-game/cmd/server.go
deleted file mode 100644
index 63f56776..00000000
--- a/compatibility/game/go-server-game/cmd/server.go
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package main
-
-import (
- "os"
- "os/signal"
- "syscall"
- "time"
-)
-
-import (
- "dubbo.apache.org/dubbo-go/v3/config"
- _ "dubbo.apache.org/dubbo-go/v3/imports"
-
- hessian "github.com/apache/dubbo-go-hessian2"
-
- "github.com/dubbogo/gost/log/logger"
-)
-
-import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/go-server-game/pkg"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
-)
-
-func init() {
- config.SetProviderService(new(pkg.BasketballService))
-
- config.SetConsumerService(pkg.GateBasketball)
-
- hessian.RegisterPOJO(&pojo.Result{})
-}
-
-func main() {
- err := config.Load()
- if err != nil {
- panic(err)
- }
-
- initSignal()
-}
-
-func initSignal() {
- signals := make(chan os.Signal, 1)
-
- signal.Notify(signals, os.Interrupt, syscall.SIGHUP, syscall.SIGQUIT,
syscall.SIGTERM)
- for {
- sig := <-signals
- logger.Infof("get signal %#s", sig.String())
- switch sig {
- case syscall.SIGHUP:
- logger.Infof("app need reload")
- default:
- time.AfterFunc(time.Duration(time.Second*5), func() {
- logger.Warnf("app exit now by force...")
- os.Exit(1)
- })
-
- // The program exits normally or timeout forcibly exits.
- logger.Warnf("app exit now...")
- return
- }
- }
-}
diff --git a/compatibility/game/go-server-game/conf/dubbogo.yml
b/compatibility/game/go-server-game/conf/dubbogo.yml
deleted file mode 100644
index 6441204b..00000000
--- a/compatibility/game/go-server-game/conf/dubbogo.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-dubbo:
- registries:
- demoZK:
- protocol: zookeeper
- timeout: 3s
- address: 127.0.0.1:2181
- consumer:
- registry-ids:
- - demoZK
- references:
- gateConsumer.basketballService:
- protocol: dubbo
- interface: org.apache.dubbo.gate.basketballService
- cluster: "failover"
- methods:
- - name: "Send"
- retries: 0
- provider:
- registry-ids:
- - demoZK
- services:
- gameProvider.basketballService:
- protocol: dubbo
- interface: org.apache.dubbo.game.basketballService # must be
compatible with grpc or dubbo-java
- loadbalance: "random"
- warmup: "100"
- cluster: "failover"
- methods:
- - name: "Online"
- retries: 0
- - name: "Offline"
- retries: 0
- - name: "Message"
- retries: 0
- protocols:
- dubbo:
- name: dubbo
- port: 20000
- # 在 params 中定义当前使用的协议特有的网络配置
- # 如该 sample 配置的是 dubbo 协议(底层使用 getty 通信库)
- params:
- session-number: 700
- session-timeout: 180s
- compress-encoding: false
- tcp-no-delay: true
- tcp-keep-alive: true
- keep-alive-period: 120s
- tcp-r-buf-size: 262144
- tcp-w-buf-size: 65536
- tcp-read-timeout: 10s
- tcp-write-timeout: 5s
- wait-timeout: 1s
- max-msg-len: 1024000
- session-name: server
\ No newline at end of file
diff --git a/compatibility/game/go-server-game/pkg/consumer.go
b/compatibility/game/go-server-game/pkg/consumer.go
deleted file mode 100644
index 00888d07..00000000
--- a/compatibility/game/go-server-game/pkg/consumer.go
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package pkg
-
-import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/pkg/consumer/gate"
-)
-
-var GateBasketball = gate.BasketballService{}
diff --git a/compatibility/game/go-server-game/pkg/provider.go
b/compatibility/game/go-server-game/pkg/provider.go
deleted file mode 100644
index 64ba17dd..00000000
--- a/compatibility/game/go-server-game/pkg/provider.go
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package pkg
-
-import (
- "context"
- "fmt"
- "strconv"
-)
-
-import (
- "github.com/dubbogo/gost/log/logger"
-)
-
-import (
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
-)
-
-type BasketballService struct{}
-
-var userMap = make(map[string]*pojo.Info)
-
-func (p *BasketballService) Login(ctx context.Context, uid string)
(*pojo.Result, error) {
- logger.Infof("message: %#v", uid)
- var (
- info *pojo.Info
- ok bool
- )
-
- // auto reply the same message
- rsp, err := GateBasketball.Send(context.TODO(), uid, "")
- if err != nil {
- logger.Errorf("send fail: %#s", err.Error())
- return &pojo.Result{Code: 1, Msg: err.Error()}, err
- }
-
- fmt.Println("receive data from gate:", rsp)
-
- if info, ok = userMap[uid]; !ok {
- info = &pojo.Info{}
- info.Name = uid
- userMap[uid] = info
- }
- return &pojo.Result{Code: 0, Msg: info.Name + ", your score is " +
strconv.Itoa(info.Score), Data: map[string]interface{}{"to": uid, "score":
info.Score}}, nil
-}
-
-func (p *BasketballService) Score(ctx context.Context, uid, score string)
(*pojo.Result, error) {
- logger.Infof("message: %#v, %#v", uid, score)
- var (
- info = &pojo.Info{}
- ok bool
- )
-
- // auto reply the same message
- rsp, err := GateBasketball.Send(context.TODO(), uid, score)
- if err != nil {
- logger.Errorf("send fail: %#s", err.Error())
- return &pojo.Result{Code: 1, Msg: err.Error()}, err
- }
-
- fmt.Println("receive data from gate:", rsp)
-
- if info, ok = userMap[uid]; !ok {
- info = &pojo.Info{
- Name: uid,
- }
- userMap[uid] = info
- logger.Error("user data not found")
- return &pojo.Result{Code: 1, Msg: "user data not found", Data:
map[string]interface{}{}}, nil
- }
- intSource, err := strconv.Atoi(score)
- if err != nil {
- logger.Error(err.Error())
- }
- info.Score += intSource
-
- return &pojo.Result{Code: 0, Msg: "进球成功", Data:
map[string]interface{}{"to": uid, "score": info.Score}}, nil
-}
-
-func (p *BasketballService) Rank(ctx context.Context, uid string)
(*pojo.Result, error) {
- var (
- rank = 1
- info *pojo.Info
- ok bool
- )
-
- // auto reply the same message
- rsp, err := GateBasketball.Send(context.TODO(), uid, "")
- if err != nil {
- logger.Errorf("send fail: %#s", err.Error())
- return &pojo.Result{Code: 1, Msg: err.Error()}, err
- }
-
- fmt.Println("receive data from gate:", rsp)
-
- if info, ok = userMap[uid]; !ok {
- logger.Error("no user found")
- return &pojo.Result{Code: 1, Msg: "no user found", Data:
map[string]interface{}{"to": uid, "rank": rank}}, nil
- }
-
- for _, v := range userMap {
- if v.Score > info.Score {
- rank++
- }
- }
-
- return &pojo.Result{Code: 0, Msg: "success", Data:
map[string]interface{}{"to": uid, "rank": rank}}, nil
-}
-
-func (p *BasketballService) Reference() string {
- return "gameProvider.basketballService"
-}
diff --git a/compatibility/game/go-server-gate/conf/dubbogo.yml
b/compatibility/game/go-server-gate/conf/dubbogo.yml
deleted file mode 100644
index d2bb08fd..00000000
--- a/compatibility/game/go-server-gate/conf/dubbogo.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-dubbo:
- registries:
- demoZK:
- protocol: zookeeper
- timeout: 3s
- address: 127.0.0.1:2181
- consumer:
- registry-ids:
- - demoZK
- references:
- gameConsumer.basketballService:
- protocol: dubbo
- interface: org.apache.dubbo.game.basketballService
- cluster: "failover"
- registry-ids:
- - demoZK
- methods:
- - name: "Online"
- retries: 0
- - name: "Offline"
- retries: 0
- - name: "Message"
- retries: 0
- provider:
- registry-ids:
- - demoZK
- services:
- gateProvider.basketballService:
- protocol: dubbo
- interface: org.apache.dubbo.gate.basketballService # must be
compatible with grpc or dubbo-java
- loadbalance: "random"
- warmup: "100"
- cluster: "failover"
- methods:
- - name: "Send"
- retries: 0
- protocols:
- dubbo:
- name: dubbo
- port: 20001
- # 在 params 中定义当前使用的协议特有的网络配置
- # 如该 sample 配置的是 dubbo 协议(底层使用 getty 通信库)
- params:
- session-number: 700
- session-timeout: 180s
- compress-encoding: false
- tcp-no-delay: true
- tcp-keep-alive: true
- keep-alive-period: 120s
- tcp-r-buf-size: 262144
- tcp-w-buf-size: 65536
- tcp-read-timeout: 10s
- tcp-write-timeout: 5s
- wait-timeout: 1s
- max-msg-len: 1024000
- session-name: server
\ No newline at end of file
diff --git a/compatibility/game/go-server-gate/pkg/consumer.go
b/compatibility/game/go-server-gate/pkg/consumer.go
deleted file mode 100644
index 74c635bd..00000000
--- a/compatibility/game/go-server-gate/pkg/consumer.go
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package pkg
-
-import (
- "context"
-)
-
-import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/pkg/consumer/game"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
-)
-
-var GameBasketball = game.BasketballService{}
-
-// just easy for demo test
-
-func Login(ctx context.Context, data string) (*pojo.Result, error) {
- return GameBasketball.Login(ctx, data)
-}
-
-func Score(ctx context.Context, uid, score string) (*pojo.Result, error) {
- return GameBasketball.Score(ctx, uid, score)
-}
-
-func Rank(ctx context.Context, uid string) (*pojo.Result, error) {
- return GameBasketball.Rank(ctx, uid)
-}
diff --git a/compatibility/game/pkg/consumer/game/basketball.go
b/compatibility/game/pkg/consumer/game/basketball.go
deleted file mode 100644
index 4e733e4b..00000000
--- a/compatibility/game/pkg/consumer/game/basketball.go
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package game
-
-import (
- "context"
-)
-
-import (
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
-)
-
-type BasketballService struct {
- Login func(ctx context.Context, uid string) (*pojo.Result, error)
- Score func(ctx context.Context, uid, score string) (*pojo.Result, error)
- Rank func(ctx context.Context, uid string) (*pojo.Result, error)
-}
-
-func (p *BasketballService) Reference() string {
- return "gameConsumer.basketballService"
-}
diff --git a/compatibility/game/pkg/consumer/gate/basketball.go
b/compatibility/game/pkg/consumer/gate/basketball.go
deleted file mode 100644
index d342cf63..00000000
--- a/compatibility/game/pkg/consumer/gate/basketball.go
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package gate
-
-import (
- "context"
-)
-
-import (
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
-)
-
-type BasketballService struct {
- Send func(ctx context.Context, uid string, data string) (*pojo.Result,
error)
-}
-
-func (p *BasketballService) Reference() string {
- return "gateConsumer.basketballService"
-}
diff --git a/compatibility/game/pkg/pojo/info.go
b/compatibility/game/pkg/pojo/info.go
deleted file mode 100644
index e3a03160..00000000
--- a/compatibility/game/pkg/pojo/info.go
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package pojo
-
-type Info struct {
- Name string `json:"name"`
- Score int `json:"score"`
-}
-
-func (m *Info) JavaClassName() string {
- return "org.apache.dubbo.pojo.Info"
-}
diff --git a/compatibility/game/website/css/style.css
b/compatibility/game/website/css/style.css
deleted file mode 100644
index 66dffa0d..00000000
--- a/compatibility/game/website/css/style.css
+++ /dev/null
@@ -1,54 +0,0 @@
-*{
- margin: 0;
- padding: 0;
-}
-#myProgress {
- width: 100%;
- background-color: #ddd;
-}
-#myBar {
- width: 0%;
- height: 30px;
- background-color: #4CAF50;
- text-align: center;
- line-height: 30px;
- color: white;
-}
-.background{
- background-image: url('bac.png');
- position: absolute;
- background-size: 100% 100%;
- background-repeat: no-repeat;
- width: 100%;
- height:100%;
-}
-.gate{
- width:8rem;
- height: 4rem;
- top: 5%;
- left: 50%;
- margin-left: -4rem;
- position: absolute;
-}
-.ball{
- width: 3rem;
- height: 3rem;
- bottom: 0;
- left: 50%;
- margin-left: -2.5rem;
- position: absolute;
-}
-.people{
- width: 5rem;
- height: 10rem;
- bottom: 0;
- left: 50%;
- margin-left: -2.5rem;
- position: absolute;
-}
-.info{
- position: absolute;
- top: 10px;
- left: 10px;
- color: white;
-}
\ No newline at end of file
diff --git a/compatibility/game/website/index.html
b/compatibility/game/website/index.html
deleted file mode 100644
index a3554dbf..00000000
--- a/compatibility/game/website/index.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>FootBall Game</title>
- <script
src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
- <!-- import Vue before Element -->
- <script src="https://unpkg.com/vue/dist/vue.js"></script>
- <!-- 引入样式 -->
- <link rel="stylesheet"
href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
- <link rel="stylesheet" href="css/style.css">
- <!-- 引入组件库 -->
- <script src="https://unpkg.com/element-ui/lib/index.js"></script>
-
-</head>
-
-<body>
- <div id="app" class="background">
- <div id="myProgress">
- <div id="myBar">0%</div>
- </div>
- <div class="info" v-if="!dialogVisible">
- 用户:{{info.name}}
- <br>
- 分数:{{info.score}}
- <br>
- 排名:{{info.rank}}
- </div>
- <svg id="gate" class="gate" t="1620527545136" class="icon" viewBox="0
0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2966"
width="400" height="400"><path d="M861.414041 192.806237 179.968861
192.806237c-51.828412 0-93.985558 42.164309-93.985558 93.985558l0 514.915399
61.975517 0 0-0.276293 0.796132 0.650823 74.704422-89.905636 597.571934 0
72.39482 87.839581 0 1.691525 62.007239 0L955.433368 286.791795C955.432345
234.970546 913.228127 192.806237 861.414041 19 [...]
- <svg id="ball" class="ball" t="1620527208144" class="icon" viewBox="0
0 1365 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1801"
width="400" height="400"><path d="M647.937 1023.573c-40.96
0-82.133-5.12-122.24-14.933-66.773-16.427-128.427-45.44-183.04-86.4-52.693-39.467-96.853-88.107-131.2-144.853-34.347-56.747-56.96-118.4-67.2-183.467-10.88-67.413-7.893-135.467
8.533-202.24 14.08-56.747 37.334-110.293 69.12-158.72 30.934-46.72
69.12-88.107 113.707-122.453 44.16-34. [...]
- <svg class="people" @click="clickPeople" t="1620527325090"
class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2618" width="400" height="400"><path
d="M563.240423 102.320551m-102.320551 0a102.320551 102.320551 0 1 0 204.641102
0 102.320551 102.320551 0 1 0-204.641102 0Z" p-id="2619"
fill="#e6e6e6"></path><path d="M322.895467 827.809372c-8.907907
20.054828-155.767992 195.805422-155.767992
195.805421h122.447605s106.292996-119.58263 113.924906 [...]
- <el-dialog
- title="输入姓名"
- :visible="dialogVisible"
- width="30%"
- :show-close="false"
- >
- <el-form v-model="info">
- <el-form-item label="姓名">
- <el-input v-model="info.name"></el-input>
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button type="primary" @click="submitInfo">确 定</el-button>
- </span>
- </el-dialog>
- </div>
- <script src="js/api.js"></script>
- <script src="js/index.js"></script>
-
-</body>
-
-</html>
\ No newline at end of file
diff --git a/compatibility/game/website/js/api.js
b/compatibility/game/website/js/api.js
deleted file mode 100644
index 32a23588..00000000
--- a/compatibility/game/website/js/api.js
+++ /dev/null
@@ -1,64 +0,0 @@
-var baseURL = 'http://127.0.0.1:8089/'
-
-function login(data) {
- return new Promise(function(resolve, reject) {
- $.ajax({
- type: "get",
- url: baseURL + 'login',
- data:data,
- dataType: "json", //指定服务器返回的数据类型
- success: function (response) {
- if (response.code != 0) {
- reject(response.msg)
- } else {
- resolve(response)
- }
- },
- error:function(err){
- reject(err)
- }
- });
- })
-}
-
-function score(data) {
- return new Promise(function(resolve, reject) {
- $.ajax({
- type: "post",
- url: baseURL + 'score',
- data:data,
- dataType: "json", //指定服务器返回的数据类型
- success: function (response) {
- if (response.code != 0) {
- reject(response.msg)
- } else {
- resolve(response)
- }
- },
- error:function(err){
- reject(err)
- }
- })
- })
-}
-
-function rank(data) {
- return new Promise(function(resolve, reject) {
- $.ajax({
- type: "get",
- url: baseURL + 'rank',
- data:data,
- dataType: "json", //指定服务器返回的数据类型
- success: function (response) {
- if (response.code != 0) {
- reject(response.msg)
- } else {
- resolve(response)
- }
- },
- error:function(err){
- reject(err)
- }
- })
- })
-}
\ No newline at end of file
diff --git a/compatibility/game/website/js/index.js
b/compatibility/game/website/js/index.js
deleted file mode 100644
index 7e9be357..00000000
--- a/compatibility/game/website/js/index.js
+++ /dev/null
@@ -1,130 +0,0 @@
-new Vue({
- el: '#app',
- data: function () {
- return {
- visible: false,
- dialogVisible:true,
- info:{
- name:'',
- score:0,
- rank:0
- },
- isMeet:false
- }
- },
- computed:{
- clientHeight:function(){
- return document.documentElement.clientWidth
- },
- gateHeight:function(){
- var self = document.getElementById("gate");
- return self.getBoundingClientRect().top +
document.documentElement.scrollTop
- },
- geteLeft:function(){
- var self = document.getElementById("gate");
- return self.getBoundingClientRect().left
- }
- },
- methods: {
- getRank:function(){
- rank({name: this.info.name}).then(Response => {
- this.info.rank = Response.data.rank
- }).catch(e => {
- this.$message({
- message: "获取排名失败" + e,
- type:"danger"
- })
- })
- },
- submitInfo:function(){
- this.dialogVisible = false
- login({name: this.info.name}).then(Response => {
- this.info.name = Response.data.to
- this.$message({
- message:"欢迎" + Response.msg
- })
- this.getRank()
- }).catch(e => {
- this.$message({
- message: "登陆失败" + e,
- type:"danger"
- })
- })
- },
- moveBall:function(){
- var elem = document.getElementsByClassName("ball")
- var gate = document.getElementById("gate")
- var id = setInterval(frame, 20)
- var tempHeight = 0
- var that = this
- function frame() {
- var space = elem[0].getBoundingClientRect().left -
gate.getBoundingClientRect().left
- var hight = elem[0].getBoundingClientRect().top -
gate.getBoundingClientRect().top
- if (!that.isMeet && (space <60 && space > -20 && hight < 20 &&
hight > -20)) {
- that.isMeet = true
- score(JSON.stringify({name:that.info.name,
score:1})).then(Response => {
- that.info.score = Response.data.score
- that.$message({
- message:"进球成功, 总分数为:" + Response.data.score,
- type:"success"
- })
- that.getRank()
- }).catch( e => {
- that.$message({
- message: "分数统计失败" + e,
- type:"danger"
- })
- })
- }
- if (tempHeight >= that.clientHeight) {
- elem[0].style.bottom = 0 + ''
- clearInterval(id)
- } else{
- tempHeight += 20
- elem[0].style.bottom = tempHeight + ''
- }
- }
- },
- move: function () {
- var elem = document.getElementById("myBar")
- var width = 0
- var id = setInterval(frame, 20)
- function frame() {
- if (width >= 100) {
- clearInterval(id)
- elem.remove()
- } else {
- width++
- elem.style.width = width + '%'
- elem.innerHTML = width * 1 + '%'
- }
- }
-
- },
- moveGate:function(){
- var elem = document.getElementById("gate")
- var id = setInterval(frame, 20)
- var model = false
- var tempValue = 0
- var increase = 1
- function frame() {
- if (tempValue > 100 || tempValue < -100) {
- increase = -increase
- }
- tempValue += increase
- elem.style.marginLeft = tempValue + 'px'
- }
- },
- clickPeople(){
- this.isMeet = false
- this.moveBall()
- }
- },
- mounted(){
- this.move()
- this.moveGate()
- },
- created() {
-
- }
-})
\ No newline at end of file
diff --git a/game/README.md b/game/README.md
new file mode 100644
index 00000000..e066050d
--- /dev/null
+++ b/game/README.md
@@ -0,0 +1,72 @@
+# Game Example for dubbo-go
+
+English | [中文](README_zh.md)
+
+This example demonstrates a football game application using dubbo-go as an RPC
framework. It consists of two services: a game service that handles game logic
(login, scoring, ranking) and a gate service that provides HTTP endpoints for
the web frontend and acts as a gateway between the frontend and game service.
+
+## Architecture
+
+- **game**: Game service that handles game logic (Login, Score, Rank)
+- **gate**: Gate service that provides HTTP API for web frontend and RPC
service for game service
+- **website**: Web frontend for the football game
+- **proto**: Protocol buffer definitions for game and gate services
+
+## Contents
+
+- `game/go-server/cmd/main.go` - Game service server implementation
+- `game/pkg/provider.go` - Game service handler implementation
+- `gate/go-server/cmd/main.go` - Gate service server with HTTP and RPC
+- `gate/pkg/provider.go` - Gate service handler implementation
+- `gate/pkg/consumer.go` - Game service client for gate service
+- `proto/` - Protocol buffer definitions and generated code
+- `website/` - Web frontend files
+
+## How to run
+
+### Run Game Server
+
+Start the game service server (listens on port 20000):
+
+```shell
+go run ./game/go-server/cmd/main.go
+```
+
+### Run Gate Server
+
+Start the gate service server (listens on port 20001 for RPC and 8089 for
HTTP):
+
+```shell
+go run ./gate/go-server/cmd/main.go
+```
+
+### Access Web Frontend
+
+Open `http://127.0.0.1:8089/` in your browser to access the game frontend. The
frontend will communicate with the gate service HTTP API.
+
+## Service Communication
+
+1. **Frontend → Gate Service**: HTTP requests to `/login`, `/score`, `/rank`
+2. **Gate Service → Game Service**: RPC calls using Triple protocol
+3. **Game Service → Gate Service**: RPC calls for gate operations
+
+## Testing
+
+You can test the services using curl:
+
+```shell
+# Test login
+curl "http://127.0.0.1:8089/login?name=player1"
+
+# Test score
+curl -X POST http://127.0.0.1:8089/score \
+ -H "Content-Type: application/json" \
+ -d '{"name":"player1","score":1}'
+
+# Test rank
+curl "http://127.0.0.1:8089/rank?name=player1"
+```
+
+## Notes
+
+- Make sure both game server and gate server are running before accessing the
web frontend
+- The game server must be started before the gate server, as the gate server
needs to connect to the game service
diff --git a/game/README_zh.md b/game/README_zh.md
new file mode 100644
index 00000000..13a55406
--- /dev/null
+++ b/game/README_zh.md
@@ -0,0 +1,75 @@
+# dubbo-go 游戏示例
+
+[English](README.md) | 中文
+
+本示例演示了一个使用 dubbo-go 作为 RPC 框架的足球游戏应用。它包含两个服务:处理游戏逻辑的游戏服务(登录、得分、排名)和提供 Web 前端
HTTP 接口的网关服务,作为前端和游戏服务之间的网关。
+
+## 架构
+
+- **game**: 处理游戏逻辑的游戏服务(Login、Score、Rank)
+- **gate**: 为 Web 前端提供 HTTP API 并为游戏服务提供 RPC 服务的网关服务
+- **website**: 足球游戏的 Web 前端
+- **proto**: 游戏和网关服务的 Protocol Buffer 定义
+
+## 目录结构
+
+- `game/go-server/cmd/main.go` - 游戏服务服务器实现
+- `game/pkg/provider.go` - 游戏服务处理器实现
+- `gate/go-server/cmd/main.go` - 带 HTTP 和 RPC 的网关服务服务器
+- `gate/pkg/provider.go` - 网关服务处理器实现
+- `gate/pkg/consumer.go` - 网关服务调用游戏服务的客户端
+- `proto/` - Protocol Buffer 定义和生成的代码
+- `website/` - Web 前端文件
+
+## 运行方法
+
+### 启动游戏服务
+
+启动游戏服务服务器(监听端口 20000):
+
+```shell
+go run ./game/go-server/cmd/main.go
+```
+
+### 启动网关服务
+
+启动网关服务服务器(监听端口 20001 用于 RPC,8089 用于 HTTP):
+
+```shell
+go run ./gate/go-server/cmd/main.go
+```
+
+### 访问 Web 前端
+
+在浏览器中打开 `http://127.0.0.1:8089/` 访问游戏前端页面。前端将与网关服务的 HTTP API 通信。
+
+## 服务通信
+
+1. **前端 → 网关服务**: 对 `/login`、`/score`、`/rank` 的 HTTP 请求
+2. **网关服务 → 游戏服务**: 使用 Triple 协议的 RPC 调用
+3. **游戏服务 → 网关服务**: 用于网关操作的 RPC 调用
+
+## 测试
+
+您可以使用 curl 测试服务:
+
+```shell
+# 测试登录
+curl "http://127.0.0.1:8089/login?name=player1"
+
+# 测试得分
+curl -X POST http://127.0.0.1:8089/score \
+ -H "Content-Type: application/json" \
+ -d '{"name":"player1","score":1}'
+
+# 测试排名
+curl "http://127.0.0.1:8089/rank?name=player1"
+```
+
+## 注意事项
+
+- 在访问 Web 前端之前,请确保游戏服务器和网关服务器都在运行
+- 游戏服务器必须在网关服务器之前启动,因为网关服务器需要连接到游戏服务
+
+[version3]: https://protobuf.dev/programming-guides/proto3/
+[Protocol Buffer Compiler Installation]:
https://dubbo-next.staged.apache.org/zh-cn/overview/reference/protoc-installation/
diff --git
a/integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
b/game/game/go-server/cmd/main.go
similarity index 51%
copy from
integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
copy to game/game/go-server/cmd/main.go
index 14c98300..db5e0a95 100644
---
a/integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
+++ b/game/game/go-server/cmd/main.go
@@ -15,35 +15,44 @@
* limitations under the License.
*/
-package integration
+package main
import (
- "os"
- "testing"
-)
-
-import (
- "dubbo.apache.org/dubbo-go/v3/config"
_ "dubbo.apache.org/dubbo-go/v3/imports"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
- hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/dubbogo/gost/log/logger"
)
import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/go-server-game/pkg"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
+ "github.com/apache/dubbo-go-samples/game/game/pkg"
+ game "github.com/apache/dubbo-go-samples/game/proto/game"
)
-var gameProvider = pkg.BasketballService{}
-
-func TestMain(m *testing.M) {
- config.SetConsumerService(gameProvider)
+func main() {
+ srv, err := server.NewServer(
+ server.WithServerProtocol(
+ protocol.WithPort(20000),
+ protocol.WithTriple(),
+ ),
+ )
+ if err != nil {
+ logger.Fatalf("failed to create server: %v", err)
+ }
- hessian.RegisterPOJO(&pojo.Result{})
- err := config.Load()
+ // Initialize gate client
+ cli, err := pkg.InitGateClient()
if err != nil {
- panic(err)
+ logger.Fatalf("failed to create gate client: %v", err)
+ }
+ pkg.SetGateClient(cli)
+
+ if err := game.RegisterGameServiceHandler(srv,
&pkg.GameServiceHandler{}); err != nil {
+ logger.Fatalf("failed to register game service handler: %v",
err)
}
- os.Exit(m.Run())
+ if err := srv.Serve(); err != nil {
+ logger.Fatalf("failed to serve: %v", err)
+ }
}
diff --git
a/integrate_test/compatibility/game/go-server-gate/tests/integration/main_test.go
b/game/game/pkg/consumer.go
similarity index 60%
rename from
integrate_test/compatibility/game/go-server-gate/tests/integration/main_test.go
rename to game/game/pkg/consumer.go
index 6241ab08..0968ace2 100644
---
a/integrate_test/compatibility/game/go-server-gate/tests/integration/main_test.go
+++ b/game/game/pkg/consumer.go
@@ -15,34 +15,38 @@
* limitations under the License.
*/
-package integration
+package pkg
import (
- "os"
- "testing"
-)
-
-import (
- "dubbo.apache.org/dubbo-go/v3/config"
+ "dubbo.apache.org/dubbo-go/v3/client"
_ "dubbo.apache.org/dubbo-go/v3/imports"
- hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/dubbogo/gost/log/logger"
)
import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/go-server-gate/pkg"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
+ gateProto "github.com/apache/dubbo-go-samples/game/proto/gate"
)
-var gateProvider = pkg.BasketballService{}
+var GateFootball gateProto.GateService
-func TestMain(m *testing.M) {
- config.SetConsumerService(gateProvider)
+func InitGateClient() (gateProto.GateService, error) {
+ cli, err := client.NewClient(
+ client.WithClientURL("127.0.0.1:20001"),
+ )
+ if err != nil {
+ return nil, err
+ }
- hessian.RegisterPOJO(&pojo.Result{})
- if err := config.Load(); err != nil {
- panic(err)
+ svc, err := gateProto.NewGateService(cli)
+ if err != nil {
+ return nil, err
}
- os.Exit(m.Run())
+ return svc, nil
+}
+
+func SetGateClient(cli gateProto.GateService) {
+ GateFootball = cli
+ logger.Info("gate client initialized")
}
diff --git a/game/game/pkg/provider.go b/game/game/pkg/provider.go
new file mode 100644
index 00000000..517e026b
--- /dev/null
+++ b/game/game/pkg/provider.go
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package pkg
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+
+ "google.golang.org/protobuf/types/known/structpb"
+)
+
+import (
+ game "github.com/apache/dubbo-go-samples/game/proto/game"
+ gateProto "github.com/apache/dubbo-go-samples/game/proto/gate"
+)
+
+type Info struct {
+ Name string
+ Score int
+}
+
+type GameServiceHandler struct{}
+
+var userMap = make(map[string]*Info)
+
+func (p *GameServiceHandler) Login(ctx context.Context, req
*game.LoginRequest) (*game.Result, error) {
+ logger.Infof("message: %#v", req.Uid)
+ var (
+ info *Info
+ ok bool
+ )
+
+ // call gate service
+ rsp, err := GateFootball.Send(context.TODO(), &gateProto.SendRequest{
+ Uid: req.Uid,
+ Data: "",
+ })
+ if err != nil {
+ logger.Errorf("send fail: %#s", err.Error())
+ return &game.Result{Code: 1, Msg: err.Error()}, err
+ }
+
+ fmt.Println("receive data from gate:", rsp)
+
+ if info, ok = userMap[req.Uid]; !ok {
+ info = &Info{}
+ info.Name = req.Uid
+ userMap[req.Uid] = info
+ }
+
+ data, _ := structpb.NewStruct(map[string]interface{}{
+ "to": req.Uid,
+ "score": info.Score,
+ })
+
+ return &game.Result{
+ Code: 0,
+ Msg: info.Name + ", your score is " + strconv.Itoa(info.Score),
+ Data: data,
+ }, nil
+}
+
+func (p *GameServiceHandler) Score(ctx context.Context, req
*game.ScoreRequest) (*game.Result, error) {
+ logger.Infof("message: %#v, %#v", req.Uid, req.Score)
+ var (
+ info *Info
+ ok bool
+ )
+
+ // call gate service
+ rsp, err := GateFootball.Send(context.TODO(), &gateProto.SendRequest{
+ Uid: req.Uid,
+ Data: req.Score,
+ })
+ if err != nil {
+ logger.Errorf("send fail: %#s", err.Error())
+ return &game.Result{Code: 1, Msg: err.Error()}, err
+ }
+
+ fmt.Println("receive data from gate:", rsp)
+
+ if info, ok = userMap[req.Uid]; !ok {
+ info = &Info{
+ Name: req.Uid,
+ }
+ userMap[req.Uid] = info
+ logger.Error("user data not found")
+ data, _ := structpb.NewStruct(map[string]interface{}{})
+ return &game.Result{Code: 1, Msg: "user data not found", Data:
data}, nil
+ }
+ intSource, err := strconv.Atoi(req.Score)
+ if err != nil {
+ logger.Error(err.Error())
+ }
+ info.Score += intSource
+
+ data, _ := structpb.NewStruct(map[string]interface{}{
+ "to": req.Uid,
+ "score": info.Score,
+ })
+
+ return &game.Result{
+ Code: 0,
+ Msg: "进球成功",
+ Data: data,
+ }, nil
+}
+
+func (p *GameServiceHandler) Rank(ctx context.Context, req *game.RankRequest)
(*game.Result, error) {
+ var (
+ rank = 1
+ info *Info
+ ok bool
+ )
+
+ // call gate service
+ rsp, err := GateFootball.Send(context.TODO(), &gateProto.SendRequest{
+ Uid: req.Uid,
+ Data: "",
+ })
+ if err != nil {
+ logger.Errorf("send fail: %#s", err.Error())
+ return &game.Result{Code: 1, Msg: err.Error()}, err
+ }
+
+ fmt.Println("receive data from gate:", rsp)
+
+ if info, ok = userMap[req.Uid]; !ok {
+ logger.Error("no user found")
+ data, _ := structpb.NewStruct(map[string]interface{}{
+ "to": req.Uid,
+ "rank": rank,
+ })
+ return &game.Result{Code: 1, Msg: "no user found", Data: data},
nil
+ }
+
+ for _, v := range userMap {
+ if v.Score > info.Score {
+ rank++
+ }
+ }
+
+ data, _ := structpb.NewStruct(map[string]interface{}{
+ "to": req.Uid,
+ "rank": rank,
+ })
+
+ return &game.Result{
+ Code: 0,
+ Msg: "success",
+ Data: data,
+ }, nil
+}
diff --git a/compatibility/game/go-server-gate/cmd/server.go
b/game/gate/go-server/cmd/main.go
similarity index 59%
rename from compatibility/game/go-server-gate/cmd/server.go
rename to game/gate/go-server/cmd/main.go
index a824bc6e..85d08719 100644
--- a/compatibility/game/go-server-gate/cmd/server.go
+++ b/game/gate/go-server/cmd/main.go
@@ -20,46 +20,67 @@ package main
import (
"context"
"encoding/json"
- "io/ioutil"
+ "io"
"net/http"
"os"
"os/signal"
+ "path/filepath"
"strconv"
"syscall"
"time"
)
import (
- "dubbo.apache.org/dubbo-go/v3/config"
_ "dubbo.apache.org/dubbo-go/v3/imports"
-
- hessian "github.com/apache/dubbo-go-hessian2"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
"github.com/dubbogo/gost/log/logger"
)
import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/go-server-gate/pkg"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
+ "github.com/apache/dubbo-go-samples/game/gate/pkg"
+ gateProto "github.com/apache/dubbo-go-samples/game/proto/gate"
)
-func init() {
- config.SetProviderService(new(pkg.BasketballService))
-
- config.SetConsumerService(pkg.GameBasketball)
-
- hessian.RegisterPOJO(&pojo.Result{})
+type Info struct {
+ Name string `json:"name"`
+ Score int `json:"score"`
}
func main() {
- err := config.Load()
+ // Initialize game client
+ cli, err := pkg.InitGameClient()
if err != nil {
- panic(err)
+ logger.Fatalf("failed to create game client: %v", err)
+ }
+ pkg.SetGameClient(cli)
+
+ // Start RPC server
+ srv, err := server.NewServer(
+ server.WithServerProtocol(
+ protocol.WithPort(20001),
+ protocol.WithTriple(),
+ ),
+ )
+ if err != nil {
+ logger.Fatalf("failed to create server: %v", err)
}
+ if err := gateProto.RegisterGateServiceHandler(srv,
&pkg.GateServiceHandler{}); err != nil {
+ logger.Fatalf("failed to register gate service handler: %v",
err)
+ }
+
+ // Start HTTP server in goroutine
go startHttp()
- initSignal()
+ // Start signal handler in goroutine
+ go initSignal()
+
+ // Start RPC server
+ if err := srv.Serve(); err != nil {
+ logger.Fatalf("failed to serve: %v", err)
+ }
}
func initSignal() {
@@ -86,7 +107,7 @@ func initSignal() {
}
func startHttp() {
-
+ // Register API endpoints first (before static file server)
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
res, err := pkg.Login(context.TODO(), r.URL.Query().Get("name"))
if err != nil {
@@ -103,11 +124,11 @@ func startHttp() {
})
http.HandleFunc("/score", func(w http.ResponseWriter, r *http.Request) {
- reqBody, err := ioutil.ReadAll(r.Body)
+ reqBody, err := io.ReadAll(r.Body)
if err != nil {
logger.Error(err.Error())
}
- var info pojo.Info
+ var info Info
err = json.Unmarshal(reqBody, &info)
if err != nil {
responseWithOrigin(w, r, 500, []byte(err.Error()))
@@ -141,6 +162,28 @@ func startHttp() {
responseWithOrigin(w, r, 200, b)
})
+ // Serve static files from website directory (after API endpoints)
+ websiteDir := resolveWebsiteDir()
+ var fs http.Handler
+ if websiteDir != "" {
+ logger.Infof("serving static files from %s", websiteDir)
+ fs = http.FileServer(http.Dir(websiteDir))
+ } else {
+ logger.Warnf("website directory not found, static files
disabled")
+ fs = http.HandlerFunc(func(w http.ResponseWriter, r
*http.Request) {
+ http.NotFound(w, r)
+ })
+ }
+ // Only serve static files for non-API paths
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ // Check if it's an API endpoint
+ if r.URL.Path == "/login" || r.URL.Path == "/score" ||
r.URL.Path == "/rank" {
+ http.NotFound(w, r)
+ return
+ }
+ fs.ServeHTTP(w, r)
+ })
+
_ = http.ListenAndServe("127.0.0.1:8089", nil)
}
@@ -158,3 +201,41 @@ func responseWithOrigin(w http.ResponseWriter, r
*http.Request, code int, json [
panic(err)
}
}
+
+func resolveWebsiteDir() string {
+ if dir := os.Getenv("GAME_WEBSITE_DIR"); dir != "" {
+ return dir
+ }
+
+ var roots []string
+ if wd, err := os.Getwd(); err == nil {
+ roots = append(roots, wd)
+ }
+ if exe, err := os.Executable(); err == nil {
+ roots = append(roots, filepath.Dir(exe))
+ }
+
+ seen := make(map[string]struct{})
+ for _, root := range roots {
+ cur := root
+ for i := 0; i < 6; i++ {
+ if _, ok := seen[cur]; ok {
+ break
+ }
+ seen[cur] = struct{}{}
+
+ candidate := filepath.Join(cur, "website")
+ if info, err := os.Stat(candidate); err == nil &&
info.IsDir() {
+ return candidate
+ }
+
+ parent := filepath.Dir(cur)
+ if parent == cur {
+ break
+ }
+ cur = parent
+ }
+ }
+
+ return ""
+}
diff --git a/game/gate/pkg/consumer.go b/game/gate/pkg/consumer.go
new file mode 100644
index 00000000..4a3c4a3b
--- /dev/null
+++ b/game/gate/pkg/consumer.go
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package pkg
+
+import (
+ "context"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/client"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+ gameProto "github.com/apache/dubbo-go-samples/game/proto/game"
+)
+
+var GameFootball gameProto.GameService
+
+func InitGameClient() (gameProto.GameService, error) {
+ cli, err := client.NewClient(
+ client.WithClientURL("127.0.0.1:20000"),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ svc, err := gameProto.NewGameService(cli)
+ if err != nil {
+ return nil, err
+ }
+
+ return svc, nil
+}
+
+func SetGameClient(cli gameProto.GameService) {
+ GameFootball = cli
+ logger.Info("game client initialized")
+}
+
+// Helper functions for HTTP handlers
+type Result struct {
+ Code int32 `json:"code"`
+ Msg string `json:"msg,omitempty"`
+ Data map[string]interface{} `json:"data,omitempty"`
+}
+
+func Login(ctx context.Context, data string) (*Result, error) {
+ resp, err := GameFootball.Login(ctx, &gameProto.LoginRequest{Uid: data})
+ if err != nil {
+ return nil, err
+ }
+ return convertResult(resp), nil
+}
+
+func Score(ctx context.Context, uid, score string) (*Result, error) {
+ resp, err := GameFootball.Score(ctx, &gameProto.ScoreRequest{Uid: uid,
Score: score})
+ if err != nil {
+ return nil, err
+ }
+ return convertResult(resp), nil
+}
+
+func Rank(ctx context.Context, uid string) (*Result, error) {
+ resp, err := GameFootball.Rank(ctx, &gameProto.RankRequest{Uid: uid})
+ if err != nil {
+ return nil, err
+ }
+ return convertResult(resp), nil
+}
+
+func convertResult(resp *gameProto.Result) *Result {
+ result := &Result{
+ Code: resp.Code,
+ Msg: resp.Msg,
+ }
+ if resp.Data != nil {
+ result.Data = resp.Data.AsMap()
+ }
+ return result
+}
diff --git a/compatibility/game/go-server-gate/pkg/provider.go
b/game/gate/pkg/provider.go
similarity index 65%
rename from compatibility/game/go-server-gate/pkg/provider.go
rename to game/gate/pkg/provider.go
index 60857bf6..19612bb7 100644
--- a/compatibility/game/go-server-gate/pkg/provider.go
+++ b/game/gate/pkg/provider.go
@@ -23,19 +23,21 @@ import (
import (
"github.com/dubbogo/gost/log/logger"
+
+ "google.golang.org/protobuf/types/known/structpb"
)
import (
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
+ gateProto "github.com/apache/dubbo-go-samples/game/proto/gate"
)
-type BasketballService struct{}
-
-func (p *BasketballService) Send(ctx context.Context, uid, data string)
(*pojo.Result, error) {
- logger.Infof("basketball: to=%s, message=%s", uid, data)
- return &pojo.Result{Code: 0, Data: map[string]interface{}{"to": uid,
"message": data}}, nil
-}
+type GateServiceHandler struct{}
-func (p *BasketballService) Reference() string {
- return "gateProvider.basketballService"
+func (p *GateServiceHandler) Send(ctx context.Context, req
*gateProto.SendRequest) (*gateProto.Result, error) {
+ logger.Infof("football: to=%s, message=%s", req.Uid, req.Data)
+ data, _ := structpb.NewStruct(map[string]interface{}{
+ "to": req.Uid,
+ "message": req.Data,
+ })
+ return &gateProto.Result{Code: 0, Data: data}, nil
}
diff --git a/game/proto/game/game.pb.go b/game/proto/game/game.pb.go
new file mode 100644
index 00000000..0b35f088
--- /dev/null
+++ b/game/proto/game/game.pb.go
@@ -0,0 +1,395 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.31.0
+// protoc v3.21.12
+// source: game.proto
+
+package game
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ structpb "google.golang.org/protobuf/types/known/structpb"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Result struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Code int32 `protobuf:"varint,1,opt,name=code,proto3"
json:"code,omitempty"` // 0: success, [1, *): error
+ Msg string `protobuf:"bytes,2,opt,name=msg,proto3"
json:"msg,omitempty"`
+ Data *structpb.Struct `protobuf:"bytes,3,opt,name=data,proto3"
json:"data,omitempty"`
+}
+
+func (x *Result) Reset() {
+ *x = Result{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_game_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Result) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Result) ProtoMessage() {}
+
+func (x *Result) ProtoReflect() protoreflect.Message {
+ mi := &file_game_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Result.ProtoReflect.Descriptor instead.
+func (*Result) Descriptor() ([]byte, []int) {
+ return file_game_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Result) GetCode() int32 {
+ if x != nil {
+ return x.Code
+ }
+ return 0
+}
+
+func (x *Result) GetMsg() string {
+ if x != nil {
+ return x.Msg
+ }
+ return ""
+}
+
+func (x *Result) GetData() *structpb.Struct {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+type LoginRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Uid string `protobuf:"bytes,1,opt,name=uid,proto3" json:"uid,omitempty"`
+}
+
+func (x *LoginRequest) Reset() {
+ *x = LoginRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_game_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *LoginRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LoginRequest) ProtoMessage() {}
+
+func (x *LoginRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_game_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.
+func (*LoginRequest) Descriptor() ([]byte, []int) {
+ return file_game_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *LoginRequest) GetUid() string {
+ if x != nil {
+ return x.Uid
+ }
+ return ""
+}
+
+type ScoreRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Uid string `protobuf:"bytes,1,opt,name=uid,proto3"
json:"uid,omitempty"`
+ Score string `protobuf:"bytes,2,opt,name=score,proto3"
json:"score,omitempty"`
+}
+
+func (x *ScoreRequest) Reset() {
+ *x = ScoreRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_game_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ScoreRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ScoreRequest) ProtoMessage() {}
+
+func (x *ScoreRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_game_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ScoreRequest.ProtoReflect.Descriptor instead.
+func (*ScoreRequest) Descriptor() ([]byte, []int) {
+ return file_game_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ScoreRequest) GetUid() string {
+ if x != nil {
+ return x.Uid
+ }
+ return ""
+}
+
+func (x *ScoreRequest) GetScore() string {
+ if x != nil {
+ return x.Score
+ }
+ return ""
+}
+
+type RankRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Uid string `protobuf:"bytes,1,opt,name=uid,proto3" json:"uid,omitempty"`
+}
+
+func (x *RankRequest) Reset() {
+ *x = RankRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_game_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RankRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RankRequest) ProtoMessage() {}
+
+func (x *RankRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_game_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RankRequest.ProtoReflect.Descriptor instead.
+func (*RankRequest) Descriptor() ([]byte, []int) {
+ return file_game_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *RankRequest) GetUid() string {
+ if x != nil {
+ return x.Uid
+ }
+ return ""
+}
+
+var File_game_proto protoreflect.FileDescriptor
+
+var file_game_proto_rawDesc = []byte{
+ 0x0a, 0x0a, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x04, 0x67, 0x61,
+ 0x6d, 0x65, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f,
+ 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f,
+ 0x22, 0x5b, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x63, 0x6f,
+ 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f,
0x64, 0x65, 0x12, 0x10,
+ 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6d, 0x73, 0x67,
+ 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x17,
+ 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66,
+ 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74,
0x61, 0x22, 0x20, 0x0a,
+ 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x10, 0x0a,
+ 0x03, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x75, 0x69, 0x64, 0x22,
+ 0x36, 0x0a, 0x0c, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12,
+ 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x75, 0x69,
+ 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x1f, 0x0a, 0x0b, 0x52,
0x61, 0x6e, 0x6b, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69,
0x64, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x32, 0x92, 0x01, 0x0a,
0x0b, 0x47, 0x61, 0x6d,
+ 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x05,
0x4c, 0x6f, 0x67, 0x69,
+ 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x4c, 0x6f, 0x67,
0x69, 0x6e, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x67, 0x61, 0x6d, 0x65,
0x2e, 0x52, 0x65, 0x73,
+ 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x05, 0x53, 0x63, 0x6f,
0x72, 0x65, 0x12, 0x12,
+ 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x52, 0x65,
0x73, 0x75, 0x6c, 0x74,
+ 0x22, 0x00, 0x12, 0x29, 0x0a, 0x04, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x11,
0x2e, 0x67, 0x61, 0x6d,
+ 0x65, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x0c, 0x2e,
+ 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,
0x00, 0x42, 0x39, 0x5a,
+ 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x61, 0x70, 0x61, 0x63,
+ 0x68, 0x65, 0x2f, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x2d, 0x67, 0x6f, 0x2d,
0x73, 0x61, 0x6d, 0x70,
+ 0x6c, 0x65, 0x73, 0x2f, 0x67, 0x61, 0x6d, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2f, 0x67,
+ 0x61, 0x6d, 0x65, 0x3b, 0x67, 0x61, 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_game_proto_rawDescOnce sync.Once
+ file_game_proto_rawDescData = file_game_proto_rawDesc
+)
+
+func file_game_proto_rawDescGZIP() []byte {
+ file_game_proto_rawDescOnce.Do(func() {
+ file_game_proto_rawDescData =
protoimpl.X.CompressGZIP(file_game_proto_rawDescData)
+ })
+ return file_game_proto_rawDescData
+}
+
+var file_game_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_game_proto_goTypes = []interface{}{
+ (*Result)(nil), // 0: game.Result
+ (*LoginRequest)(nil), // 1: game.LoginRequest
+ (*ScoreRequest)(nil), // 2: game.ScoreRequest
+ (*RankRequest)(nil), // 3: game.RankRequest
+ (*structpb.Struct)(nil), // 4: google.protobuf.Struct
+}
+var file_game_proto_depIdxs = []int32{
+ 4, // 0: game.Result.data:type_name -> google.protobuf.Struct
+ 1, // 1: game.GameService.Login:input_type -> game.LoginRequest
+ 2, // 2: game.GameService.Score:input_type -> game.ScoreRequest
+ 3, // 3: game.GameService.Rank:input_type -> game.RankRequest
+ 0, // 4: game.GameService.Login:output_type -> game.Result
+ 0, // 5: game.GameService.Score:output_type -> game.Result
+ 0, // 6: game.GameService.Rank:output_type -> game.Result
+ 4, // [4:7] is the sub-list for method output_type
+ 1, // [1:4] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_game_proto_init() }
+func file_game_proto_init() {
+ if File_game_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_game_proto_msgTypes[0].Exporter = func(v interface{}, i
int) interface{} {
+ switch v := v.(*Result); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_game_proto_msgTypes[1].Exporter = func(v interface{}, i
int) interface{} {
+ switch v := v.(*LoginRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_game_proto_msgTypes[2].Exporter = func(v interface{}, i
int) interface{} {
+ switch v := v.(*ScoreRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_game_proto_msgTypes[3].Exporter = func(v interface{}, i
int) interface{} {
+ switch v := v.(*RankRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_game_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 4,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_game_proto_goTypes,
+ DependencyIndexes: file_game_proto_depIdxs,
+ MessageInfos: file_game_proto_msgTypes,
+ }.Build()
+ File_game_proto = out.File
+ file_game_proto_rawDesc = nil
+ file_game_proto_goTypes = nil
+ file_game_proto_depIdxs = nil
+}
diff --git
a/integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
b/game/proto/game/game.proto
similarity index 58%
copy from
integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
copy to game/proto/game/game.proto
index 14c98300..e3ad76fc 100644
---
a/integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
+++ b/game/proto/game/game.proto
@@ -15,35 +15,36 @@
* limitations under the License.
*/
-package integration
+syntax = "proto3";
-import (
- "os"
- "testing"
-)
+package game;
-import (
- "dubbo.apache.org/dubbo-go/v3/config"
- _ "dubbo.apache.org/dubbo-go/v3/imports"
+option go_package = "github.com/apache/dubbo-go-samples/game/proto/game;game";
- hessian "github.com/apache/dubbo-go-hessian2"
-)
+import "google/protobuf/struct.proto";
-import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/go-server-game/pkg"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
-)
+message Result {
+ int32 code = 1; // 0: success, [1, *): error
+ string msg = 2;
+ google.protobuf.Struct data = 3;
+}
-var gameProvider = pkg.BasketballService{}
+message LoginRequest {
+ string uid = 1;
+}
-func TestMain(m *testing.M) {
- config.SetConsumerService(gameProvider)
+message ScoreRequest {
+ string uid = 1;
+ string score = 2;
+}
- hessian.RegisterPOJO(&pojo.Result{})
- err := config.Load()
- if err != nil {
- panic(err)
- }
+message RankRequest {
+ string uid = 1;
+}
- os.Exit(m.Run())
+service GameService {
+ rpc Login(LoginRequest) returns (Result) {}
+ rpc Score(ScoreRequest) returns (Result) {}
+ rpc Rank(RankRequest) returns (Result) {}
}
+
diff --git a/game/proto/game/game.triple.go b/game/proto/game/game.triple.go
new file mode 100644
index 00000000..2bdebe9d
--- /dev/null
+++ b/game/proto/game/game.triple.go
@@ -0,0 +1,176 @@
+// Code generated by protoc-gen-triple. DO NOT EDIT.
+//
+// Source: game.proto
+package game
+
+import (
+ "context"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3"
+ "dubbo.apache.org/dubbo-go/v3/client"
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the
Triple package
+// are compatible. If you get a compiler error that this constant is not
defined, this code was
+// generated with a version of Triple newer than the one compiled into your
binary. You can fix the
+// problem by either regenerating this code with an older version of Triple or
updating the Triple
+// version compiled into your binary.
+const _ = triple_protocol.IsAtLeastVersion0_1_0
+
+const (
+ // GameServiceName is the fully-qualified name of the GameService
service.
+ GameServiceName = "game.GameService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this
package. They're
+// exposed at runtime as procedure and as the final two segments of the HTTP
route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these
constants to
+// reflection-formatted method names, remove the leading slash and convert the
remaining slash to a
+// period.
+const (
+ // GameServiceLoginProcedure is the fully-qualified name of the
GameService's Login RPC.
+ GameServiceLoginProcedure = "/game.GameService/Login"
+ // GameServiceScoreProcedure is the fully-qualified name of the
GameService's Score RPC.
+ GameServiceScoreProcedure = "/game.GameService/Score"
+ // GameServiceRankProcedure is the fully-qualified name of the
GameService's Rank RPC.
+ GameServiceRankProcedure = "/game.GameService/Rank"
+)
+
+var (
+ _ GameService = (*GameServiceImpl)(nil)
+)
+
+// GameService is a client for the game.GameService service.
+type GameService interface {
+ Login(ctx context.Context, req *LoginRequest, opts
...client.CallOption) (*Result, error)
+ Score(ctx context.Context, req *ScoreRequest, opts
...client.CallOption) (*Result, error)
+ Rank(ctx context.Context, req *RankRequest, opts ...client.CallOption)
(*Result, error)
+}
+
+// NewGameService constructs a client for the game.GameService service.
+func NewGameService(cli *client.Client, opts ...client.ReferenceOption)
(GameService, error) {
+ conn, err := cli.DialWithInfo("game.GameService",
&GameService_ClientInfo, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return &GameServiceImpl{
+ conn: conn,
+ }, nil
+}
+
+func SetConsumerGameService(srv common.RPCService) {
+ dubbo.SetConsumerServiceWithInfo(srv, &GameService_ClientInfo)
+}
+
+// GameServiceImpl implements GameService.
+type GameServiceImpl struct {
+ conn *client.Connection
+}
+
+func (c *GameServiceImpl) Login(ctx context.Context, req *LoginRequest, opts
...client.CallOption) (*Result, error) {
+ resp := new(Result)
+ if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "Login",
opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
+func (c *GameServiceImpl) Score(ctx context.Context, req *ScoreRequest, opts
...client.CallOption) (*Result, error) {
+ resp := new(Result)
+ if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "Score",
opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
+func (c *GameServiceImpl) Rank(ctx context.Context, req *RankRequest, opts
...client.CallOption) (*Result, error) {
+ resp := new(Result)
+ if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "Rank",
opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
+var GameService_ClientInfo = client.ClientInfo{
+ InterfaceName: "game.GameService",
+ MethodNames: []string{"Login", "Score", "Rank"},
+ ConnectionInjectFunc: func(dubboCliRaw interface{}, conn
*client.Connection) {
+ dubboCli := dubboCliRaw.(*GameServiceImpl)
+ dubboCli.conn = conn
+ },
+}
+
+// GameServiceHandler is an implementation of the game.GameService service.
+type GameServiceHandler interface {
+ Login(context.Context, *LoginRequest) (*Result, error)
+ Score(context.Context, *ScoreRequest) (*Result, error)
+ Rank(context.Context, *RankRequest) (*Result, error)
+}
+
+func RegisterGameServiceHandler(srv *server.Server, hdlr GameServiceHandler,
opts ...server.ServiceOption) error {
+ return srv.Register(hdlr, &GameService_ServiceInfo, opts...)
+}
+
+func SetProviderGameService(srv common.RPCService) {
+ dubbo.SetProviderServiceWithInfo(srv, &GameService_ServiceInfo)
+}
+
+var GameService_ServiceInfo = server.ServiceInfo{
+ InterfaceName: "game.GameService",
+ ServiceType: (*GameServiceHandler)(nil),
+ Methods: []server.MethodInfo{
+ {
+ Name: "Login",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() interface{} {
+ return new(LoginRequest)
+ },
+ MethodFunc: func(ctx context.Context, args
[]interface{}, handler interface{}) (interface{}, error) {
+ req := args[0].(*LoginRequest)
+ res, err :=
handler.(GameServiceHandler).Login(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
+ {
+ Name: "Score",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() interface{} {
+ return new(ScoreRequest)
+ },
+ MethodFunc: func(ctx context.Context, args
[]interface{}, handler interface{}) (interface{}, error) {
+ req := args[0].(*ScoreRequest)
+ res, err :=
handler.(GameServiceHandler).Score(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
+ {
+ Name: "Rank",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() interface{} {
+ return new(RankRequest)
+ },
+ MethodFunc: func(ctx context.Context, args
[]interface{}, handler interface{}) (interface{}, error) {
+ req := args[0].(*RankRequest)
+ res, err :=
handler.(GameServiceHandler).Rank(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
+ },
+}
diff --git a/game/proto/gate/gate.pb.go b/game/proto/gate/gate.pb.go
new file mode 100644
index 00000000..980ff10a
--- /dev/null
+++ b/game/proto/gate/gate.pb.go
@@ -0,0 +1,262 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.31.0
+// protoc v3.21.12
+// source: gate/gate.proto
+
+package gate
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ structpb "google.golang.org/protobuf/types/known/structpb"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Result struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Code int32 `protobuf:"varint,1,opt,name=code,proto3"
json:"code,omitempty"` // 0: success, [1, *): error
+ Msg string `protobuf:"bytes,2,opt,name=msg,proto3"
json:"msg,omitempty"`
+ Data *structpb.Struct `protobuf:"bytes,3,opt,name=data,proto3"
json:"data,omitempty"`
+}
+
+func (x *Result) Reset() {
+ *x = Result{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gate_gate_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Result) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Result) ProtoMessage() {}
+
+func (x *Result) ProtoReflect() protoreflect.Message {
+ mi := &file_gate_gate_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Result.ProtoReflect.Descriptor instead.
+func (*Result) Descriptor() ([]byte, []int) {
+ return file_gate_gate_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Result) GetCode() int32 {
+ if x != nil {
+ return x.Code
+ }
+ return 0
+}
+
+func (x *Result) GetMsg() string {
+ if x != nil {
+ return x.Msg
+ }
+ return ""
+}
+
+func (x *Result) GetData() *structpb.Struct {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+type SendRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Uid string `protobuf:"bytes,1,opt,name=uid,proto3"
json:"uid,omitempty"`
+ Data string `protobuf:"bytes,2,opt,name=data,proto3"
json:"data,omitempty"`
+}
+
+func (x *SendRequest) Reset() {
+ *x = SendRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gate_gate_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SendRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SendRequest) ProtoMessage() {}
+
+func (x *SendRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_gate_gate_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SendRequest.ProtoReflect.Descriptor instead.
+func (*SendRequest) Descriptor() ([]byte, []int) {
+ return file_gate_gate_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *SendRequest) GetUid() string {
+ if x != nil {
+ return x.Uid
+ }
+ return ""
+}
+
+func (x *SendRequest) GetData() string {
+ if x != nil {
+ return x.Data
+ }
+ return ""
+}
+
+var File_gate_gate_proto protoreflect.FileDescriptor
+
+var file_gate_gate_proto_rawDesc = []byte{
+ 0x0a, 0x0f, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x12, 0x04, 0x67, 0x61, 0x74, 0x65, 0x1a, 0x1c, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72,
0x75, 0x63, 0x74, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5b, 0x0a, 0x06, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x12,
+ 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x04, 0x63,
+ 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74,
0x61, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74,
0x52, 0x04, 0x64, 0x61,
+ 0x74, 0x61, 0x22, 0x33, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03,
+ 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18,
0x02, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x38, 0x0a, 0x0b, 0x47,
0x61, 0x74, 0x65, 0x53,
+ 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x04, 0x53, 0x65,
0x6e, 0x64, 0x12, 0x11,
+ 0x2e, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x1a, 0x0c, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x22,
+ 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f,
+ 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x64, 0x75, 0x62, 0x62, 0x6f,
0x2d, 0x67, 0x6f, 0x2d,
+ 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x67, 0x61, 0x6d, 0x65,
0x2f, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x3b, 0x67, 0x61, 0x74, 0x65,
0x62, 0x06, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_gate_gate_proto_rawDescOnce sync.Once
+ file_gate_gate_proto_rawDescData = file_gate_gate_proto_rawDesc
+)
+
+func file_gate_gate_proto_rawDescGZIP() []byte {
+ file_gate_gate_proto_rawDescOnce.Do(func() {
+ file_gate_gate_proto_rawDescData =
protoimpl.X.CompressGZIP(file_gate_gate_proto_rawDescData)
+ })
+ return file_gate_gate_proto_rawDescData
+}
+
+var file_gate_gate_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_gate_gate_proto_goTypes = []interface{}{
+ (*Result)(nil), // 0: gate.Result
+ (*SendRequest)(nil), // 1: gate.SendRequest
+ (*structpb.Struct)(nil), // 2: google.protobuf.Struct
+}
+var file_gate_gate_proto_depIdxs = []int32{
+ 2, // 0: gate.Result.data:type_name -> google.protobuf.Struct
+ 1, // 1: gate.GateService.Send:input_type -> gate.SendRequest
+ 0, // 2: gate.GateService.Send:output_type -> gate.Result
+ 2, // [2:3] is the sub-list for method output_type
+ 1, // [1:2] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_gate_gate_proto_init() }
+func file_gate_gate_proto_init() {
+ if File_gate_gate_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_gate_gate_proto_msgTypes[0].Exporter = func(v interface{},
i int) interface{} {
+ switch v := v.(*Result); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gate_gate_proto_msgTypes[1].Exporter = func(v interface{},
i int) interface{} {
+ switch v := v.(*SendRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_gate_gate_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_gate_gate_proto_goTypes,
+ DependencyIndexes: file_gate_gate_proto_depIdxs,
+ MessageInfos: file_gate_gate_proto_msgTypes,
+ }.Build()
+ File_gate_gate_proto = out.File
+ file_gate_gate_proto_rawDesc = nil
+ file_gate_gate_proto_goTypes = nil
+ file_gate_gate_proto_depIdxs = nil
+}
diff --git a/compatibility/game/pkg/pojo/result.go b/game/proto/gate/gate.proto
similarity index 67%
rename from compatibility/game/pkg/pojo/result.go
rename to game/proto/gate/gate.proto
index 7b4e2b4b..9b93297f 100644
--- a/compatibility/game/pkg/pojo/result.go
+++ b/game/proto/gate/gate.proto
@@ -15,18 +15,26 @@
* limitations under the License.
*/
-package pojo
+syntax = "proto3";
-type Result struct {
- Code int32 `json:"code"` // 0: success, [1, *): error
- Msg string `json:"msg,omitempty"`
- Data map[string]interface{} `json:"data,omitempty"`
+package gate;
+
+option go_package = "github.com/apache/dubbo-go-samples/game/proto/gate;gate";
+
+import "google/protobuf/struct.proto";
+
+message Result {
+ int32 code = 1; // 0: success, [1, *): error
+ string msg = 2;
+ google.protobuf.Struct data = 3;
}
-func (m *Result) JavaClassName() string {
- return "org.apache.dubbo.pojo.Result"
+message SendRequest {
+ string uid = 1;
+ string data = 2;
}
-func (m *Result) Success() bool {
- return m.Code == 0
+service GateService {
+ rpc Send(SendRequest) returns (Result) {}
}
+
diff --git a/game/proto/gate/gate.triple.go b/game/proto/gate/gate.triple.go
new file mode 100644
index 00000000..78309a15
--- /dev/null
+++ b/game/proto/gate/gate.triple.go
@@ -0,0 +1,122 @@
+// Code generated by protoc-gen-triple. DO NOT EDIT.
+//
+// Source: gate/gate.proto
+package gate
+
+import (
+ "context"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3"
+ "dubbo.apache.org/dubbo-go/v3/client"
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the
Triple package
+// are compatible. If you get a compiler error that this constant is not
defined, this code was
+// generated with a version of Triple newer than the one compiled into your
binary. You can fix the
+// problem by either regenerating this code with an older version of Triple or
updating the Triple
+// version compiled into your binary.
+const _ = triple_protocol.IsAtLeastVersion0_1_0
+
+const (
+ // GateServiceName is the fully-qualified name of the GateService
service.
+ GateServiceName = "gate.GateService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this
package. They're
+// exposed at runtime as procedure and as the final two segments of the HTTP
route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these
constants to
+// reflection-formatted method names, remove the leading slash and convert the
remaining slash to a
+// period.
+const (
+ // GateServiceSendProcedure is the fully-qualified name of the
GateService's Send RPC.
+ GateServiceSendProcedure = "/gate.GateService/Send"
+)
+
+var (
+ _ GateService = (*GateServiceImpl)(nil)
+)
+
+// GateService is a client for the gate.GateService service.
+type GateService interface {
+ Send(ctx context.Context, req *SendRequest, opts ...client.CallOption)
(*Result, error)
+}
+
+// NewGateService constructs a client for the gate.GateService service.
+func NewGateService(cli *client.Client, opts ...client.ReferenceOption)
(GateService, error) {
+ conn, err := cli.DialWithInfo("gate.GateService",
&GateService_ClientInfo, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return &GateServiceImpl{
+ conn: conn,
+ }, nil
+}
+
+func SetConsumerGateService(srv common.RPCService) {
+ dubbo.SetConsumerServiceWithInfo(srv, &GateService_ClientInfo)
+}
+
+// GateServiceImpl implements GateService.
+type GateServiceImpl struct {
+ conn *client.Connection
+}
+
+func (c *GateServiceImpl) Send(ctx context.Context, req *SendRequest, opts
...client.CallOption) (*Result, error) {
+ resp := new(Result)
+ if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "Send",
opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
+var GateService_ClientInfo = client.ClientInfo{
+ InterfaceName: "gate.GateService",
+ MethodNames: []string{"Send"},
+ ConnectionInjectFunc: func(dubboCliRaw interface{}, conn
*client.Connection) {
+ dubboCli := dubboCliRaw.(*GateServiceImpl)
+ dubboCli.conn = conn
+ },
+}
+
+// GateServiceHandler is an implementation of the gate.GateService service.
+type GateServiceHandler interface {
+ Send(context.Context, *SendRequest) (*Result, error)
+}
+
+func RegisterGateServiceHandler(srv *server.Server, hdlr GateServiceHandler,
opts ...server.ServiceOption) error {
+ return srv.Register(hdlr, &GateService_ServiceInfo, opts...)
+}
+
+func SetProviderGateService(srv common.RPCService) {
+ dubbo.SetProviderServiceWithInfo(srv, &GateService_ServiceInfo)
+}
+
+var GateService_ServiceInfo = server.ServiceInfo{
+ InterfaceName: "gate.GateService",
+ ServiceType: (*GateServiceHandler)(nil),
+ Methods: []server.MethodInfo{
+ {
+ Name: "Send",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() interface{} {
+ return new(SendRequest)
+ },
+ MethodFunc: func(ctx context.Context, args
[]interface{}, handler interface{}) (interface{}, error) {
+ req := args[0].(*SendRequest)
+ res, err :=
handler.(GateServiceHandler).Send(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
+ },
+}
diff --git a/game/website/css/style.css b/game/website/css/style.css
new file mode 100644
index 00000000..a7fb09b6
--- /dev/null
+++ b/game/website/css/style.css
@@ -0,0 +1,238 @@
+/**
+ * Game stylesheet
+ * Includes responsive design and modern styles
+ */
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+}
+
+/* Loading progress bar */
+#myProgress {
+ width: 100%;
+ height: 30px;
+ background-color: rgba(221, 221, 221, 0.8);
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1000;
+ border-bottom: 2px solid #4CAF50;
+}
+
+#myBar {
+ width: 0%;
+ height: 100%;
+ background: linear-gradient(90deg, #4CAF50, #45a049);
+ text-align: center;
+ line-height: 30px;
+ color: white;
+ font-weight: bold;
+ transition: width 0.1s ease;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+/* Background */
+.background {
+ background-image: url('../img/bac.png');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ min-height: 100vh;
+}
+
+/* Gate */
+.gate {
+ width: 8rem;
+ height: 4rem;
+ top: 5%;
+ left: 50%;
+ margin-left: -4rem;
+ position: absolute;
+ z-index: 10;
+ transition: margin-left 0.02s linear;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+}
+
+/* Ball */
+.ball {
+ width: 3rem;
+ height: 3rem;
+ bottom: 0;
+ left: 50%;
+ margin-left: -1.5rem;
+ position: absolute;
+ z-index: 20;
+ transition: bottom 0.02s linear;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+ cursor: pointer;
+}
+
+/* Person */
+.people {
+ width: 5rem;
+ height: 10rem;
+ bottom: 0;
+ left: 50%;
+ margin-left: -2.5rem;
+ position: absolute;
+ z-index: 15;
+ cursor: pointer;
+ transition: transform 0.1s ease;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+}
+
+.people:hover {
+ transform: scale(1.05);
+}
+
+.people:active {
+ transform: scale(0.95);
+}
+
+/* User info display */
+.info {
+ position: absolute;
+ top: 50px;
+ left: 20px;
+ background: rgba(0, 0, 0, 0.6);
+ padding: 15px 20px;
+ border-radius: 8px;
+ color: white;
+ font-size: 16px;
+ line-height: 1.8;
+ z-index: 100;
+ backdrop-filter: blur(10px);
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
+ min-width: 180px;
+}
+
+.info-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.info-item:last-child {
+ margin-bottom: 0;
+}
+
+.info-label {
+ font-weight: 500;
+ margin-right: 8px;
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.info-value {
+ font-weight: bold;
+ color: #4CAF50;
+ font-size: 18px;
+}
+
+/* Dialog footer style */
+.dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .gate {
+ width: 6rem;
+ height: 3rem;
+ margin-left: -3rem;
+ }
+
+ .ball {
+ width: 2.5rem;
+ height: 2.5rem;
+ margin-left: -1.25rem;
+ }
+
+ .people {
+ width: 4rem;
+ height: 8rem;
+ margin-left: -2rem;
+ }
+
+ .info {
+ top: 40px;
+ left: 10px;
+ padding: 12px 15px;
+ font-size: 14px;
+ min-width: 150px;
+ }
+
+ .info-value {
+ font-size: 16px;
+ }
+
+ .el-dialog {
+ width: 85% !important;
+ }
+}
+
+@media (max-width: 480px) {
+ .gate {
+ width: 5rem;
+ height: 2.5rem;
+ margin-left: -2.5rem;
+ }
+
+ .ball {
+ width: 2rem;
+ height: 2rem;
+ margin-left: -1rem;
+ }
+
+ .people {
+ width: 3.5rem;
+ height: 7rem;
+ margin-left: -1.75rem;
+ }
+
+ .info {
+ top: 35px;
+ left: 8px;
+ padding: 10px 12px;
+ font-size: 12px;
+ min-width: 120px;
+ }
+
+ .info-value {
+ font-size: 14px;
+ }
+}
+
+/* Animation optimization */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.background {
+ animation: fadeIn 0.5s ease-in;
+}
+
+/* Ensure SVG images display correctly */
+img[src$=".svg"] {
+ display: block;
+ object-fit: contain;
+}
diff --git a/compatibility/game/website/img/bac.png b/game/website/img/bac.png
similarity index 100%
rename from compatibility/game/website/img/bac.png
rename to game/website/img/bac.png
diff --git a/game/website/img/ball.svg b/game/website/img/ball.svg
new file mode 100644
index 00000000..ab005139
--- /dev/null
+++ b/game/website/img/ball.svg
@@ -0,0 +1,11 @@
+<svg id="ball" class="ball" viewBox="0 0 1365 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" width="400" height="400">
+ <path d="M647.937 1023.573c-40.96
0-82.133-5.12-122.24-14.933-66.773-16.427-128.427-45.44-183.04-86.4-52.693-39.467-96.853-88.107-131.2-144.853-34.347-56.747-56.96-118.4-67.2-183.467-10.88-67.413-7.893-135.467
8.533-202.24 14.08-56.747 37.334-110.293 69.12-158.72 30.934-46.72
69.12-88.107 113.707-122.453 44.16-34.134 93.227-60.587 145.92-78.934
53.547-18.56 109.44-27.946 165.973-27.946 40.96 0 82.134 5.12 122.24 14.933
66.774 16.427 128.427 45.44 183.04 86.4 52.694 39.467 96.854 88.107 [...]
+ <path d="M749.27 37.547l21.974 5.333L615.51 679.04l-21.973-5.333L749.27
37.547zM628.95 522.24L281.43 845.867" fill="#333333" p-id="1803"></path>
+ <path d="M621.27 513.92l15.36 16.64-347.306 323.627-15.36-16.64L621.27
513.92z m521.814 18.133l-662.187-35.626" fill="#333333" p-id="1804"></path>
+ <path d="M481.537 485.12l662.187 35.627-1.28 22.613-662.187-35.627
1.28-22.613z m314.667 496.64L603.99 374.613" fill="#333333" p-id="1805"></path>
+ <path d="M614.87 371.2l192.214 607.147-21.547 6.826-192.213-607.146
21.546-6.827z m-397.44-63.147l581.334 268.16" fill="#333333" p-id="1806"></path>
+ <path d="M222.124 297.813l581.546 268.16-9.386 20.48-581.76-267.946
9.6-20.694z m0 0" fill="#333333" p-id="1807"></path>
+ <path d="M364.844 376.747L336.47 135.253l-11.306-2.346C247.51 198.4 189.057
288.213 162.817 394.453c-6.613 26.667-10.88 53.76-13.013 81.28l215.04-98.986z
m47.573 351.573L178.39 684.16c45.44 124.16 139.52 228.693 264.96
284.587L412.417 728.32z m177.707 273.92l4.693 7.04c136.32 14.507 268.587-28.373
369.493-111.573l-221.013-84.694-153.173 189.227z m376.96-472.107l125.866
205.654c16-32.214 28.587-66.56 37.547-102.827 23.68-96.213
17.707-192.64-12.16-279.893L967.084 530.133zM562.39 22.4l15 [...]
+</svg>
+
+
diff --git a/game/website/img/gate.svg b/game/website/img/gate.svg
new file mode 100644
index 00000000..a5fc854f
--- /dev/null
+++ b/game/website/img/gate.svg
@@ -0,0 +1,5 @@
+<svg id="gate" class="gate" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" width="400" height="400">
+ <path d="M861.414041 192.806237 179.968861 192.806237c-51.828412 0-93.985558
42.164309-93.985558 93.985558l0 514.915399 61.975517 0 0-0.276293 0.796132
0.650823 74.704422-89.905636 597.571934 0 72.39482 87.839581 0 1.691525
62.007239 0L955.433368 286.791795C955.432345 234.970546 913.228127 192.806237
861.414041 192.806237zM199.153806 689.236634l-51.196009 61.657268L147.957797
286.791795c0-2.625804 0.422625-5.13802 0.992607-7.584746l50.203402
55.610552L199.153806 689.236634zM232.481865 [...]
+</svg>
+
+
diff --git a/game/website/img/people.svg b/game/website/img/people.svg
new file mode 100644
index 00000000..36c8f802
--- /dev/null
+++ b/game/website/img/people.svg
@@ -0,0 +1,7 @@
+<svg class="people" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" width="400" height="400">
+ <path d="M563.240423 102.320551m-102.320551 0a102.320551 102.320551 0 1 0
204.641102 0 102.320551 102.320551 0 1 0-204.641102 0Z" fill="#e6e6e6"></path>
+ <path d="M322.895467 827.809372c-8.907907 20.054828-155.767992
195.805422-155.767992 195.805421h122.447605s106.292996-119.58263
113.924906-134.027884 38.954036-122.784661
38.954035-122.784661l-74.633813-83.469495s-36.113136 124.397715-44.924741
144.476619z" fill="#e6e6e6"></path>
+ <path d="M837.50765
515.815955c0-0.409282-90.571744-32.718501-102.609456-37.533586s-29.684998-14.445254-38.520678-32.911104-29.083112-75.957962-44.130252-99.503727c-9.10051-14.228575-57.781017-92.425552-157.284743-88.284579-49.860203
2.094562-175.750594 47.019303-207.506078 65.003644-47.693415
27.301531-124.542167 159.427456-124.542167 159.427457A50.895446 50.895446 0 1 0
247.033807 539.289493s68.927938-109.90431
85.877036-125.192204c19.934451-17.815814 81.302706-35.824231 92.955211-37 [...]
+</svg>
+
+
diff --git a/game/website/index.html b/game/website/index.html
new file mode 100644
index 00000000..d16aaae1
--- /dev/null
+++ b/game/website/index.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>FootBall Game</title>
+
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
+
+ <link rel="stylesheet"
href="https://unpkg.com/element-plus/dist/index.css">
+
+ <link rel="stylesheet" href="css/style.css">
+
+ <script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
+</head>
+
+<body>
+ <div id="app" class="background">
+ <div id="myProgress" v-if="loadingProgress < 100">
+ <div id="myBar">{{ loadingProgress }}%</div>
+ </div>
+
+ <div class="info" v-if="!dialogVisible">
+ <div class="info-item">
+ <span class="info-label">用户:</span>
+ <span class="info-value">{{ info.name }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">分数:</span>
+ <span class="info-value">{{ info.score }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">排名:</span>
+ <span class="info-value">{{ info.rank }}</span>
+ </div>
+ </div>
+
+ <img id="gate" src="img/gate.svg" alt="球门" class="gate" />
+
+ <img id="ball" src="img/ball.svg" alt="足球" class="ball" />
+
+ <img class="people" src="img/people.svg" alt="球员" @click="clickPeople"
/>
+
+ <el-dialog
+ v-model="dialogVisible"
+ title="输入姓名"
+ width="30%"
+ :show-close="false"
+ :close-on-click-modal="false"
+ :close-on-press-escape="false"
+ >
+ <el-form :model="info">
+ <el-form-item label="姓名">
+ <el-input
+ v-model="info.name"
+ placeholder="请输入您的姓名"
+ @keyup.enter="submitInfo"
+ autofocus
+ ></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" @click="submitInfo">确
定</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+
+ <script src="js/api.js"></script>
+ <script src="js/index.js"></script>
+</body>
+
+</html>
diff --git a/game/website/js/api.js b/game/website/js/api.js
new file mode 100644
index 00000000..21ce7d35
--- /dev/null
+++ b/game/website/js/api.js
@@ -0,0 +1,77 @@
+/**
+ * API wrapper
+ * Using fetch API instead of jQuery
+ */
+
+const baseURL = 'http://127.0.0.1:8089/';
+
+/**
+ * Unified API request handler
+ * @param {string} url - Request URL
+ * @param {object} options - Fetch options
+ * @returns {Promise} Returns Promise
+ */
+async function request(url, options = {}) {
+ try {
+ const response = await fetch(url, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ if (data.code !== 0) {
+ throw new Error(data.msg || 'Request failed');
+ }
+
+ return data;
+ } catch (error) {
+ throw error;
+ }
+}
+
+/**
+ * User login
+ * @param {object} data - Login data {name: string}
+ * @returns {Promise} Returns Promise
+ */
+function login(data) {
+ const params = new URLSearchParams({ name: data.name });
+ return request(`${baseURL}login?${params.toString()}`, {
+ method: 'GET',
+ });
+}
+
+/**
+ * Submit score
+ * @param {object} data - Score data {name: string, score: number}
+ * @returns {Promise} Returns Promise
+ */
+function score(data) {
+ return request(`${baseURL}score`, {
+ method: 'POST',
+ body: JSON.stringify({
+ name: data.name,
+ score: data.score,
+ }),
+ });
+}
+
+/**
+ * Get rank
+ * @param {object} data - Rank query data {name: string}
+ * @returns {Promise} Returns Promise
+ */
+function rank(data) {
+ const params = new URLSearchParams({ name: data.name });
+ return request(`${baseURL}rank?${params.toString()}`, {
+ method: 'GET',
+ });
+}
diff --git a/game/website/js/index.js b/game/website/js/index.js
new file mode 100644
index 00000000..d597b19e
--- /dev/null
+++ b/game/website/js/index.js
@@ -0,0 +1,228 @@
+/**
+ * Game main logic
+ * Using Vue 3 Composition API
+ */
+
+const { createApp, ref, reactive, onMounted, onUnmounted } = Vue;
+const { ElMessage } = ElementPlus;
+
+createApp({
+ setup() {
+ // Reactive data
+ const dialogVisible = ref(true);
+ const info = reactive({
+ name: '',
+ score: 0,
+ rank: 0,
+ });
+ const isMeet = ref(false);
+ const loadingProgress = ref(0);
+
+ // Animation interval IDs storage for cleanup
+ const intervals = ref({
+ progress: null,
+ gate: null,
+ ball: null,
+ });
+
+ /**
+ * Get rank
+ */
+ const getRank = async () => {
+ try {
+ const response = await rank({ name: info.name });
+ if (response.data && response.data.rank !== undefined) {
+ info.rank = response.data.rank;
+ }
+ } catch (error) {
+ ElMessage({
+ message: `Failed to get rank: ${error.message || error}`,
+ type: 'error',
+ });
+ }
+ };
+
+ /**
+ * Submit user info
+ */
+ const submitInfo = async () => {
+ if (!info.name.trim()) {
+ ElMessage({
+ message: 'Please enter your name',
+ type: 'warning',
+ });
+ return;
+ }
+
+ dialogVisible.value = false;
+ try {
+ const response = await login({ name: info.name });
+ if (response.data && response.data.to) {
+ info.name = response.data.to;
+ }
+ if (response.data && response.data.score !== undefined) {
+ info.score = response.data.score;
+ }
+ ElMessage({
+ message: `Welcome ${response.msg || info.name}`,
+ type: 'success',
+ });
+ await getRank();
+ } catch (error) {
+ ElMessage({
+ message: `Login failed: ${error.message || error}`,
+ type: 'error',
+ });
+ dialogVisible.value = true;
+ }
+ };
+
+ /**
+ * Move ball (kick animation)
+ */
+ const moveBall = () => {
+ const ballElement = document.querySelector('.ball');
+ const gateElement = document.getElementById('gate');
+
+ if (!ballElement || !gateElement) {
+ return;
+ }
+
+ // Clear previous animation
+ if (intervals.value.ball) {
+ clearInterval(intervals.value.ball);
+ }
+
+ let tempHeight = 0;
+ const clientHeight = document.documentElement.clientHeight;
+
+ intervals.value.ball = setInterval(() => {
+ const ballRect = ballElement.getBoundingClientRect();
+ const gateRect = gateElement.getBoundingClientRect();
+
+ const space = ballRect.left - gateRect.left;
+ const height = ballRect.top - gateRect.top;
+
+ // Detect goal (fixed collision detection logic)
+ if (!isMeet.value && space < 60 && space > -20 && height < 20
&& height > -20) {
+ isMeet.value = true;
+ clearInterval(intervals.value.ball);
+ intervals.value.ball = null;
+
+ // Submit score
+ score({ name: info.name, score: 1 })
+ .then((response) => {
+ if (response.data && response.data.score !==
undefined) {
+ info.score = response.data.score;
+ }
+ ElMessage({
+ message: `Goal! Total score:
${response.data.score || info.score}`,
+ type: 'success',
+ });
+ getRank();
+ })
+ .catch((error) => {
+ ElMessage({
+ message: `Failed to record score:
${error.message || error}`,
+ type: 'error',
+ });
+ });
+ }
+
+ // Ball moves to bottom
+ if (tempHeight >= clientHeight) {
+ ballElement.style.bottom = '0px';
+ clearInterval(intervals.value.ball);
+ intervals.value.ball = null;
+ } else {
+ tempHeight += 20;
+ ballElement.style.bottom = `${tempHeight}px`;
+ }
+ }, 20);
+ };
+
+ /**
+ * Loading progress bar animation
+ */
+ const moveProgress = () => {
+ const progressBar = document.getElementById('myBar');
+ if (!progressBar) {
+ return;
+ }
+
+ let width = 0;
+ intervals.value.progress = setInterval(() => {
+ if (width >= 100) {
+ clearInterval(intervals.value.progress);
+ intervals.value.progress = null;
+ progressBar.remove();
+ } else {
+ width++;
+ loadingProgress.value = width;
+ progressBar.style.width = `${width}%`;
+ progressBar.textContent = `${width}%`;
+ }
+ }, 20);
+ };
+
+ /**
+ * Move gate (left-right animation)
+ */
+ const moveGate = () => {
+ const gateElement = document.getElementById('gate');
+ if (!gateElement) {
+ return;
+ }
+
+ // Get initial marginLeft (set to -4rem in CSS, which is -64px)
+ const initialMargin = -64; // 4rem = 64px
+ let tempValue = 0;
+ let increase = 1;
+
+ intervals.value.gate = setInterval(() => {
+ if (tempValue > 100 || tempValue < -100) {
+ increase = -increase;
+ }
+ tempValue += increase;
+ // Move based on initial position
+ gateElement.style.marginLeft = `${initialMargin +
tempValue}px`;
+ }, 20);
+ };
+
+ /**
+ * Click person to kick ball
+ */
+ const clickPeople = () => {
+ isMeet.value = false;
+ moveBall();
+ };
+
+ // Start animations when component is mounted
+ onMounted(() => {
+ moveProgress();
+ moveGate();
+ });
+
+ // Clean up all animations when component is unmounted
+ onUnmounted(() => {
+ if (intervals.value.progress) {
+ clearInterval(intervals.value.progress);
+ }
+ if (intervals.value.gate) {
+ clearInterval(intervals.value.gate);
+ }
+ if (intervals.value.ball) {
+ clearInterval(intervals.value.ball);
+ }
+ });
+
+ return {
+ dialogVisible,
+ info,
+ isMeet,
+ loadingProgress,
+ submitInfo,
+ clickPeople,
+ };
+ },
+}).use(ElementPlus).mount('#app');
diff --git a/integrate_test/game/game/tests/integration/main_test.go
b/integrate_test/game/game/tests/integration/main_test.go
new file mode 100644
index 00000000..3588bcba
--- /dev/null
+++ b/integrate_test/game/game/tests/integration/main_test.go
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package integration
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+ "time"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/client"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+)
+
+import (
+ game "github.com/apache/dubbo-go-samples/game/proto/game"
+)
+
+var gameService game.GameService
+
+func TestMain(m *testing.M) {
+ stopGate, err := ensureGateServer()
+ if err != nil {
+ panic(err)
+ }
+
+ cli, err := client.NewClient(
+ client.WithClientURL(envOrDefault("GAME_SERVER_ADDR",
"127.0.0.1:20000")),
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ gameService, err = game.NewGameService(cli)
+ if err != nil {
+ panic(err)
+ }
+
+ code := m.Run()
+
+ if stopGate != nil {
+ stopGate()
+ }
+
+ os.Exit(code)
+}
+
+func envOrDefault(key, fallback string) string {
+ if val := os.Getenv(key); val != "" {
+ return val
+ }
+ return fallback
+}
+
+func ensureGateServer() (func(), error) {
+ addr := envOrDefault("GATE_SERVER_ADDR", "127.0.0.1:20001")
+
+ if portReachable(addr, 500*time.Millisecond) {
+ return nil, nil
+ }
+
+ root, err := repoRoot()
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := exec.Command("go", "run", "./game/gate/go-server/cmd")
+ cmd.Dir = root
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("failed to start gate server: %w", err)
+ }
+
+ if err := waitForPort(addr, 15*time.Second); err != nil {
+ _ = cmd.Process.Kill()
+ return nil, fmt.Errorf("gate server failed to start: %w", err)
+ }
+
+ return func() {
+ _ = cmd.Process.Signal(os.Interrupt)
+ done := make(chan struct{})
+ go func() {
+ _ = cmd.Wait()
+ close(done)
+ }()
+
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ _ = cmd.Process.Kill()
+ }
+ }, nil
+}
+
+func waitForPort(addr string, timeout time.Duration) error {
+ deadline := time.Now().Add(timeout)
+ for time.Now().Before(deadline) {
+ if portReachable(addr, 500*time.Millisecond) {
+ return nil
+ }
+ time.Sleep(200 * time.Millisecond)
+ }
+ return fmt.Errorf("timed out waiting for %s", addr)
+}
+
+func portReachable(addr string, timeout time.Duration) bool {
+ conn, err := net.DialTimeout("tcp", addr, timeout)
+ if err != nil {
+ return false
+ }
+ _ = conn.Close()
+ return true
+}
+
+func repoRoot() (string, error) {
+ dir, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+ for {
+ if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
+ return dir, nil
+ }
+ parent := filepath.Dir(dir)
+ if parent == dir {
+ return "", fmt.Errorf("go.mod not found from %s", dir)
+ }
+ dir = parent
+ }
+}
diff --git
a/integrate_test/compatibility/game/go-server-game/tests/integration/provider_test.go
b/integrate_test/game/game/tests/integration/provider_test.go
similarity index 55%
rename from
integrate_test/compatibility/game/go-server-game/tests/integration/provider_test.go
rename to integrate_test/game/game/tests/integration/provider_test.go
index 2d1774a7..9cb664e2 100644
---
a/integrate_test/compatibility/game/go-server-game/tests/integration/provider_test.go
+++ b/integrate_test/game/game/tests/integration/provider_test.go
@@ -26,26 +26,53 @@ import (
"github.com/stretchr/testify/assert"
)
+import (
+ game "github.com/apache/dubbo-go-samples/game/proto/game"
+)
+
func TestLogin(t *testing.T) {
- res, err := gameProvider.Login(context.TODO(), "A001")
+ uid := t.Name()
+ res := login(t, uid)
+ data := res.Data.AsMap()
+ assert.Equal(t, uid, data["to"])
+ assert.EqualValues(t, 0, data["score"])
+}
+
+func TestScore(t *testing.T) {
+ uid := t.Name()
+ _ = login(t, uid)
+
+ res, err := gameService.Score(context.TODO(), &game.ScoreRequest{Uid:
uid, Score: "1"})
assert.Nil(t, err)
assert.NotNil(t, res)
assert.Equal(t, int32(0), res.Code)
- assert.NotNil(t, res.Data)
- assert.Equal(t, "A001", res.Data["to"])
- assert.Equal(t, 0, res.Data["score"])
+ data := res.Data.AsMap()
+ assert.Equal(t, uid, data["to"])
+ assert.EqualValues(t, 1, data["score"])
}
-func TestScore(t *testing.T) {
- res, err := gameProvider.Score(context.TODO(), "A001", "1")
+func TestRank(t *testing.T) {
+ uid := t.Name()
+ _ = login(t, uid)
+
+ // boost score so this user ranks first regardless of previous tests
+ _, err := gameService.Score(context.TODO(), &game.ScoreRequest{Uid:
uid, Score: "100"})
+ assert.Nil(t, err)
+
+ res, err := gameService.Rank(context.TODO(), &game.RankRequest{Uid:
uid})
assert.Nil(t, err)
assert.NotNil(t, res)
assert.Equal(t, int32(0), res.Code)
+ data := res.Data.AsMap()
+ assert.Equal(t, uid, data["to"])
+ assert.EqualValues(t, 1, data["rank"])
}
-func TestRank(t *testing.T) {
- res, err := gameProvider.Rank(context.TODO(), "A001")
+func login(t *testing.T, uid string) *game.Result {
+ t.Helper()
+ res, err := gameService.Login(context.TODO(), &game.LoginRequest{Uid:
uid})
assert.Nil(t, err)
assert.NotNil(t, res)
assert.Equal(t, int32(0), res.Code)
+ return res
}
diff --git
a/integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
b/integrate_test/game/gate/tests/integration/main_test.go
similarity index 68%
rename from
integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
rename to integrate_test/game/gate/tests/integration/main_test.go
index 14c98300..cd935c9c 100644
---
a/integrate_test/compatibility/game/go-server-game/tests/integration/main_test.go
+++ b/integrate_test/game/gate/tests/integration/main_test.go
@@ -23,27 +23,36 @@ import (
)
import (
- "dubbo.apache.org/dubbo-go/v3/config"
+ "dubbo.apache.org/dubbo-go/v3/client"
_ "dubbo.apache.org/dubbo-go/v3/imports"
-
- hessian "github.com/apache/dubbo-go-hessian2"
)
import (
-
"github.com/apache/dubbo-go-samples/compatibility/game/go-server-game/pkg"
- "github.com/apache/dubbo-go-samples/compatibility/game/pkg/pojo"
+ gate "github.com/apache/dubbo-go-samples/game/proto/gate"
)
-var gameProvider = pkg.BasketballService{}
+var gateService gate.GateService
func TestMain(m *testing.M) {
- config.SetConsumerService(gameProvider)
+ var err error
+ cli, err := client.NewClient(
+ client.WithClientURL(envOrDefault("GATE_SERVER_ADDR",
"127.0.0.1:20001")),
+ )
+ if err != nil {
+ panic(err)
+ }
- hessian.RegisterPOJO(&pojo.Result{})
- err := config.Load()
+ gateService, err = gate.NewGateService(cli)
if err != nil {
panic(err)
}
os.Exit(m.Run())
}
+
+func envOrDefault(key, fallback string) string {
+ if val := os.Getenv(key); val != "" {
+ return val
+ }
+ return fallback
+}
diff --git
a/integrate_test/compatibility/game/go-server-gate/tests/integration/provider_test.go
b/integrate_test/game/gate/tests/integration/provider_test.go
similarity index 78%
rename from
integrate_test/compatibility/game/go-server-gate/tests/integration/provider_test.go
rename to integrate_test/game/gate/tests/integration/provider_test.go
index 66c0f611..768f5573 100644
---
a/integrate_test/compatibility/game/go-server-gate/tests/integration/provider_test.go
+++ b/integrate_test/game/gate/tests/integration/provider_test.go
@@ -26,12 +26,17 @@ import (
"github.com/stretchr/testify/assert"
)
+import (
+ gate "github.com/apache/dubbo-go-samples/game/proto/gate"
+)
+
func TestSend(t *testing.T) {
- res, err := gateProvider.Send(context.TODO(), "A001", "hello")
+ uid := t.Name()
+ res, err := gateService.Send(context.TODO(), &gate.SendRequest{Uid:
uid, Data: "hello"})
assert.Nil(t, err)
assert.NotNil(t, res)
assert.Equal(t, int32(0), res.Code)
- assert.NotNil(t, res.Data)
- assert.Equal(t, "A001", res.Data["to"])
- assert.Equal(t, "hello", res.Data["message"])
+ data := res.Data.AsMap()
+ assert.Equal(t, uid, data["to"])
+ assert.Equal(t, "hello", data["message"])
}
diff --git a/start_integrate_test.sh b/start_integrate_test.sh
index 9ad129e3..12ba57d6 100755
--- a/start_integrate_test.sh
+++ b/start_integrate_test.sh
@@ -19,8 +19,8 @@
array+=("helloworld")
# game
-#array+=("game/go-server-game")
-#array+=("game/go-server-gate")
+#array+=("game/game")
+#array+=("game/gate")
# config-api
array+=("compatibility/config-api/rpc/triple")