
# 游戏前端请求转发至Doctor平台协议规范

## 游戏前端 -> RP网关 
使用 rp_10010 spin请求作为示例
POST 调用我方网关

url:
https://rpgames-api.rpenenenhkdev.com/gameapi/rp_10010/spin

Header:
```
Content-Type: application/json
X-Rp-Token: eyJQIjoxMDQxOTYsIkUiOjE3ODAwNjM3NzIsIlMiOjEwMDAsIkQiOiJycF8xMDAxMCJ9.z1KOZNyQ00DRcLo4isBZg-3opdmRE_6U_y3snTsucVk
```

payload:
```json
{"Bet":10000}
```

返回数据:
Header:
content-type: application/json

Body:
```json
{"Balance":99980000,"Data":{"Rid":"686b3a3e5585252df1119e6a_1_1","AllScore":0,"RoundEndScore":0,"Score":0,"SpinScore":0,"Data":[5,1,4,4,7,5,4,6,4],"Remove":null,"HitLine":null,"HitBlock":null,"DataType":null,"Mode":0,"FreeSpin":null,"Bet":10000,"Balance":99980000,"IsFree":false,"WinMultiPlier":1,"CrossSymbols":null,"WaysNum":0,"SymbolNumOfReels":null,"IsEnd":false,"SymbolWinInfos":null,"WinPosition":null,"PanColor":null,"PanChanges":null,"RoundInfo":null,"ScNum":0,"GetGamesTimes":0,"ProfitScore":-10000,"ScoreInfos":null,"AllBet":0,"GmNum":0,"MultiPlierSymbolNum":0,"Nst":0,"TopTNum":null,"AnimationCtrl":{"Axis13EqualPlayAnim":false,"AxisTitleInfo":[0,1,0],"BgFlag":0},"HistoryDetail":{"Score":0,"Formula":"","FormulaDetail":"","IsDoubleSymbol":false,"ColorType":0,"PayOutValue":0},"XnInfo":null,"Addtional":null,"Wc":0,"ChangeMode":null,"MustWin":false},"Frb":{"Finished":null,"Ongoing":null}}
```

> 注意 上面的请求和响应只是示例, 具体的请求参数和响应数据格式由游戏开发者和Doctor平台协商确定, 只要满足 Doctor平台提供的接口规范即可.

## RP网关 -> Doctor平台
通过nats发起RPC调用
```js
nats.Msg {
Subject: "/gameapi/rp_10010/spin",
Data: `{Bet:10000}`,
Header: {
    ... /*http 请求本身的header参数, 在这里传递*/
    "Pid": 100001 // token解析出的玩家id, int
}
```

doctor 平台返回
```json
    {"xxx": "yyyy"}
```
如果有error
在返回的 header 里面添加 `error: 1#player is banned`
此时网关不在读取body数据, 直接返回错误给前端
一些常用的错误定义
```go
const (
	RpCode_Banned       = 1 // 账号封禁
	RpCode_NoCash       = 2 // 余额不足
	RpCode_BetGrade     = 3 // 非法投注额
	RpCode_Internal     = 4 // 内部错误
	RpCode_InvalidToken = 5 //
)

type ErrCode struct {
	err  error
	code int
}

func NewErrCode(err error, code int) ErrCode {
	return ErrCode{err, code}
}

func (ec ErrCode) Error() string {
	return fmt.Sprintf("%d#%s", ec.code, ec.err.Error())
}

var (
	CodeBanned        = NewErrCode(errors.New("player is banned. "), RpCode_Banned)
	CodeNotEnoughCash = NewErrCode(errors.New("not enough cash. "), RpCode_NoCash)
	CodeBetGrade      = NewErrCode(errors.New("not find bet. "), RpCode_BetGrade)
	CodeInvilidToken  = NewErrCode(errors.New("invalid token."), RpCode_InvalidToken)
)
```

对应的golang 转发逻辑代码
```go
httpmux.HandleFunc("POST /gameapi/", gameapi)
func gameapi(w http.ResponseWriter, r *http.Request) {
	var (
		payload []byte
		err     error
	)

	w.Header().Set("Access-Control-Allow-Origin", "*")

	payload, err = io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	token := r.Header.Get("X-Rp-Token")
	pid, err := jwtutil.ParseToken(token)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	header := nats.Header(r.Header)
	header.Set("Pid", strconv.Itoa(int(pid)))

	subj := r.URL.Path
	resp, err := mq.NC().RequestMsg(&nats.Msg{
		Subject: subj,
		Data:    payload,
		Header:  header,
	}, time.Second*60)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if errstr := resp.Header.Get("error"); errstr != "" {
		http.Error(w, errstr, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	w.Write(resp.Data)
}
```

> nc js client [文档](https://github.com/nats-io/nats.js#readme)

```js
// 一个简单的 nc js 示例
import { connect } from "@nats-io/transport-node";

const nc = await connect({ servers: "nats://rp-hk-dev-2:11002" });
console.log(`connected`);

nc.subscribe('/gameapi/rp_10010/spin', {
  queue: 'jsapi',
  callback: (err, msg) => {
    console.log(new Date().toISOString(), 'Received spin request');
    const ps = JSON.parse(msg.data);
    // ps: {bet: 100, pid: 100001}

    if (Math.random() < 0.5) {
    //   msg.respond(JSON.stringify({error: "some error occur", data: null}))
        msg.respond(null, {header: {"error": "999#some error occur"}})
      return
    }
    msg.respond(JSON.stringify({result: "win", amount: 200}))
  },
});
```


# 内部服务RPC调用协议规范
***接口描述***
请求和返回 都是JSON格式
返回数据 固定 {error: string, data: object} 样式,
    error字段不为"", 则表示有错误发生, 此时不应再使用data字段. error:"" 表示成功,

    1.  ```{"error":"","data":{"Balance":245.9}}```
    2.  ```{"error":"some error occur","data":null}```



## RP 提供的接口
### 获取玩家余额
/gamecenter/player/getBalance
> 参数:
```{"Pid": 100081}```

> 返回:
```
{
    "error": "",
    "data": {
        "Balance": 12345600  (int64)# 表示金币 12345600, 或者 货币1234.56 (float), 金币和货币的比例是 10000 : 1
    }
}
```

### 修改玩家余额
/gamecenter/player/modifyGold
```
	GameID  string  # 游戏id, pokdeng
	Pid     int64 # rp平台玩家id
	Change  int64 # 增加/扣除金额 (+ 增加, - 扣除), ***注: 单位是金币, 非货币***
	Comment string # 其它需要记载的描述, 没有可以忽略
	RoundID string # 对应的游戏回合id, 对pokdeng来讲, 就是从开始下注到结算是一个回合, 不同的回合 RoundID不应重复 (可以使用时间戳来表示)
	Reason  string # 修改余额的原因, bet 下注扣款; win 派奖; refund 服务器内部出错,退回下注 
	IsCompleted bool # 是否已完成 false:本轮没有结束  true:本轮已结束
```

> 返回:
```
{
"Balance": 12345600  (int64)# 修改后的最新余额
}
```

### 修改玩家游戏状态
/gamecenter/player/updateGameStatus
```
    Players    []int64   # [100001, 100002] 玩家id数组
    GameID     string    # "pokdeng" 游戏id
    Completed  bool      # false|true,  未结算是false
```

> 返回:
```
{
"UpdateCount": 2  #修改的数量
}
```

说明: 玩家下注后, 设置 Completed: false, 等完成结算后设置为 true


### 推送结算详情
/betlog/addPokerBetLog  
    注: ***非request请求, 请使用 nc.publish 方法推送，一局游戏只用上传一次***

```
    //一盘游戏数据
	{
        RoundID        string // 必须和 ModifyGoldPs.RoundID 一致, 并且不同对局唯一
        ServiceName    string // pokdeng
        PlayerBetInfos []*PokerPlayerBetLog // 一局内的所有玩家
    }
    
    // 个人数据
    type PokerPlayerBetLog {
        ID           string // 关联玩家下注历史id
        Pid          int64
        Bet          int64  // 下注
        Win          int64  // 输赢
        Balance      int64  // 余额
        TotalWinLoss int64  // 当前局结算总输赢
        AppID        string // optional
        Uid          string // optional
    }
```

### 查询 游戏&玩家 是否被封禁
/gamecenter/player/IsBanned
```
{"Game":"pokdeng","Pid":100001}
```
> 返回:
```
{"IsBanned":false}
```

说明: 每次玩家下注时需要检查, 当  IsBanned: true 的时候禁止下注

