Ver Fonte

新增小七上报角色创建信息接口

lt há 2 meses atrás
pai
commit
42335d782b

+ 202 - 0
RO_Server_Trunk-branch_0.1.39/roserver/game/api/xiaoqiReport.go

@@ -0,0 +1,202 @@
+package api
+
+import (
+	"bytes"
+	"context"
+	"crypto/md5"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+const xiaoQiRoleReportMethod = "common.roleReport"
+
+// XiaoQiReporter 封裝小七上報能力。
+type XiaoQiReporter struct {
+	Endpoint   string
+	AppID      string
+	AppSecret  string
+	HTTPClient *http.Client
+}
+
+// XiaoQiRole 角色信息(對應文檔 Role 欄位)。
+type XiaoQiRole struct {
+	RoleID             string  `json:"roleId"`                // 必填
+	GUID               string  `json:"guid"`                  // 必填
+	RoleName           string  `json:"roleName"`              // 必填
+	ServerID           string  `json:"serverId"`              // 必填
+	ServerName         string  `json:"serverName"`            // 必填
+	RoleLevel          string  `json:"roleLevel"`             // 必填
+	RoleCE             string  `json:"roleCE"`                // 必填
+	RoleStage          string  `json:"roleStage"`             // 必填(可放 JSON 字串)
+	RoleRechargeAmount float64 `json:"roleRechargeAmount"`    // 必填(兩位精度)
+	RoleGuild          string  `json:"roleGuild,omitempty"`   // 選填
+	RoleGuildID        string  `json:"roleGuildId,omitempty"` // 選填
+}
+
+// XiaoQiRoleReportBizParams 業務參數。
+type XiaoQiRoleReportBizParams struct {
+	Role XiaoQiRole `json:"role"`
+}
+
+// xiaoQiCommonRequest 小七公共請求包。
+type xiaoQiCommonRequest struct {
+	APIMethod string      `json:"apiMethod"`
+	AppID     string      `json:"appId"`
+	Timestamp int64       `json:"timestamp"`
+	BizParams interface{} `json:"bizParams"`
+	Sign      string      `json:"sign"`
+}
+
+// XiaoQiBizResp 業務回應(文檔 bizResp)。
+type XiaoQiBizResp struct {
+	RespCode string `json:"respCode"`
+	RespMsg  string `json:"respMsg"`
+}
+
+// XiaoQiCommonResponse 通用回包容器。
+type XiaoQiCommonResponse struct {
+	BizResp XiaoQiBizResp `json:"bizResp"`
+}
+
+// ReportRole 創建/變更角色時,上報角色信息到小七。
+// 成功條件:HTTP 2xx 且 bizResp.respCode == SUCCESS。
+func (r *XiaoQiReporter) ReportRole(ctx context.Context, role XiaoQiRole) (*XiaoQiBizResp, error) {
+	if err := r.validate(); err != nil {
+		return nil, err
+	}
+	if err := validateRole(role); err != nil {
+		return nil, err
+	}
+
+	// 保留兩位小數,避免浮點顯示差異導致簽名/平台解析問題。
+	role.RoleRechargeAmount, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", role.RoleRechargeAmount), 64)
+
+	biz := XiaoQiRoleReportBizParams{Role: role}
+	reqBody := xiaoQiCommonRequest{
+		APIMethod: xiaoQiRoleReportMethod,
+		AppID:     r.AppID,
+		Timestamp: time.Now().Unix(),
+		BizParams: biz,
+	}
+	//reqBody.Sign = buildSign(reqBody.APIMethod, reqBody.AppID, reqBody.Timestamp, biz, r.AppSecret)
+
+	var resp XiaoQiCommonResponse
+	if err := r.postJSON(ctx, reqBody, &resp); err != nil {
+		return nil, err
+	}
+
+	if !strings.EqualFold(resp.BizResp.RespCode, "SUCCESS") {
+		return &resp.BizResp, fmt.Errorf("xiaoqi role report failed, respCode=%s respMsg=%s", resp.BizResp.RespCode, resp.BizResp.RespMsg)
+	}
+	return &resp.BizResp, nil
+}
+
+func (r *XiaoQiReporter) validate() error {
+	if strings.TrimSpace(r.Endpoint) == "" {
+		return errors.New("xiaoqi endpoint is empty")
+	}
+	return nil
+}
+
+func validateRole(role XiaoQiRole) error {
+	if strings.TrimSpace(role.RoleID) == "" {
+		return errors.New("roleId is required")
+	}
+	if strings.TrimSpace(role.GUID) == "" {
+		return errors.New("guid is required")
+	}
+	if strings.TrimSpace(role.RoleName) == "" {
+		return errors.New("roleName is required")
+	}
+	if strings.TrimSpace(role.ServerID) == "" {
+		return errors.New("serverId is required")
+	}
+	if strings.TrimSpace(role.ServerName) == "" {
+		return errors.New("serverName is required")
+	}
+	if strings.TrimSpace(role.RoleLevel) == "" {
+		return errors.New("roleLevel is required")
+	}
+	if strings.TrimSpace(role.RoleCE) == "" {
+		return errors.New("roleCE is required")
+	}
+	if strings.TrimSpace(role.RoleStage) == "" {
+		return errors.New("roleStage is required")
+	}
+	if role.RoleRechargeAmount < 0 {
+		return errors.New("roleRechargeAmount must be >= 0")
+	}
+	return nil
+}
+
+// buildSign 生成簽名:將公共參數與 bizParams(JSON字串)按 key 排序拼接,再拼 appSecret 後做 MD5。
+// 具體拼接規則若你們文檔有固定格式,可只替換此函數,不影響上層業務調用。
+func buildSign(apiMethod, appID string, timestamp int64, bizParams interface{}, secret string) string {
+	bizBytes, _ := json.Marshal(bizParams)
+	params := map[string]string{
+		"apiMethod": apiMethod,
+		"appId":     appID,
+		"bizParams": string(bizBytes),
+		"timestamp": strconv.FormatInt(timestamp, 10),
+	}
+
+	keys := make([]string, 0, len(params))
+	for k := range params {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	var sb strings.Builder
+	for _, k := range keys {
+		sb.WriteString(k)
+		sb.WriteString("=")
+		sb.WriteString(params[k])
+		sb.WriteString("&")
+	}
+	sb.WriteString("appSecret=")
+	sb.WriteString(secret)
+
+	sum := md5.Sum([]byte(sb.String()))
+	return hex.EncodeToString(sum[:])
+}
+
+func (r *XiaoQiReporter) postJSON(ctx context.Context, reqBody interface{}, out interface{}) error {
+	client := r.HTTPClient
+	if client == nil {
+		client = &http.Client{Timeout: 5 * time.Second}
+	}
+
+	payload, err := json.Marshal(reqBody)
+	if err != nil {
+		return fmt.Errorf("marshal request failed: %w", err)
+	}
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.Endpoint, bytes.NewReader(payload))
+	if err != nil {
+		return fmt.Errorf("build request failed: %w", err)
+	}
+	req.Header.Set("Content-Type", "application/json")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return fmt.Errorf("http post failed: %w", err)
+	}
+	defer resp.Body.Close()
+
+	body, _ := io.ReadAll(resp.Body)
+	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+		return fmt.Errorf("http status=%d body=%s", resp.StatusCode, string(body))
+	}
+	if err := json.Unmarshal(body, out); err != nil {
+		return fmt.Errorf("unmarshal response failed: %w body=%s", err, string(body))
+	}
+	return nil
+}

+ 64 - 0
RO_Server_Trunk-branch_0.1.39/roserver/game/model/role_state.go

@@ -1,14 +1,18 @@
 package model
 
 import (
+	"context"
+	"fmt"
 	"math/rand"
 	"rocommon"
 	"rocommon/service"
 	"rocommon/util"
 	"roserver/baseserver"
 	"roserver/baseserver/model"
+	"roserver/game/api"
 	"roserver/serverproto"
 	"runtime/debug"
+	"strings"
 	"time"
 	"unsafe"
 )
@@ -233,9 +237,69 @@ func (this *Role) createDbRoleSuccess() int32 {
 	}
 	this.ReplayGate(createMsg, true)
 	this.roleTask.AddTypeCnt(serverproto.TaskType_Eve_Login_Day, 1)
+
+	// 创角成功后异步上报小七,失败仅记录日志,不影响主流程。
+	go this.reportCreateRoleToXiaoQi(accRole)
 	return ROLE_STATE_SELECT_ROLE
 }
 
+func (this *Role) reportCreateRoleToXiaoQi(accRole *serverproto.AccountRole) {
+	defer func() {
+		if err := recover(); err != nil {
+			util.InfoF("uid=%v reportCreateRoleToXiaoQi panic=%v", this.GetUUid(), err)
+		}
+	}()
+
+	const endpoint = "https://api.x7sy.com/vendorApi/sample"
+
+	guid := strings.TrimSpace(this.GetOpenId())
+	if guid == "" {
+		return
+	}
+
+	cfg := service.GetServiceConfig()
+	zoneID := 0
+	if cfg != nil {
+		zoneID = cfg.Node.Zone
+	}
+	if this.GetSelectZone() > 0 {
+		zoneID = int(this.GetSelectZone())
+	}
+	serverID := fmt.Sprintf("%d", zoneID)
+	serverName := serverID
+
+	reporter := &api.XiaoQiReporter{
+		Endpoint: endpoint,
+	}
+
+	role := api.XiaoQiRole{
+		RoleID:             fmt.Sprintf("%d", this.GetUUid()),
+		GUID:               guid,
+		RoleName:           this.GetNickName(),
+		ServerID:           serverID,
+		ServerName:         serverName,
+		RoleLevel:          fmt.Sprintf("%d", this.GetRoleLevel()),
+		RoleCE:             "0",
+		RoleStage:          "{}",
+		RoleRechargeAmount: 0,
+		RoleGuild:          "",
+		RoleGuildID:        "",
+	}
+
+	if role.RoleName == "" {
+		role.RoleName = fmt.Sprintf("role_%d", this.GetUUid())
+	}
+
+	_, err := reporter.ReportRole(context.Background(), role)
+	if err != nil {
+		uid := this.GetUUid()
+		if accRole != nil && accRole.Uid > 0 {
+			uid = accRole.Uid
+		}
+		util.InfoF("uid=%v xiaoqi role report failed: %v", uid, err)
+	}
+}
+
 func (this *Role) createDbRoleFailure(data interface{}) int32 {
 	msg := &serverproto.SCCreateRoleAck{}
 	//msg.Error = data.(int32)