533 lines
19 KiB
Go
533 lines
19 KiB
Go
package game
|
|
|
|
import (
|
|
"context"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
gamev1 "hyapp.local/api/proto/game/v1"
|
|
userv1 "hyapp.local/api/proto/user/v1"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
"hyapp/pkg/xerr"
|
|
gamedomain "hyapp/services/game-service/internal/domain/game"
|
|
)
|
|
|
|
const (
|
|
yomiCodeOK = 200
|
|
yomiCodeSystemError = 9998
|
|
yomiCodeInsufficientBalance = 10003
|
|
yomiCodeGameMaintenance = 10004
|
|
yomiCodeTokenInvalid = 10005
|
|
yomiCodeSignatureInvalid = 10007
|
|
yomiCodeGameNotFound = 10018
|
|
yomiCodeUserNotFound = 10019
|
|
yomiCodeIPForbidden = 10020
|
|
|
|
yomiDefaultTokenTTL = 24 * time.Hour
|
|
)
|
|
|
|
type yomiAdapterConfig struct {
|
|
UIDMode string `json:"uid_mode"`
|
|
DefaultLang string `json:"default_lang"`
|
|
TokenTTLSeconds int64 `json:"token_ttl_seconds"`
|
|
GameLevelUID int64 `json:"game_level_uid"`
|
|
}
|
|
|
|
func yomiConfigFromPlatform(value any) yomiAdapterConfig {
|
|
var raw string
|
|
switch typed := value.(type) {
|
|
case gamedomain.Platform:
|
|
raw = typed.AdapterConfigJSON
|
|
case gamedomain.LaunchableGame:
|
|
raw = typed.AdapterConfigJSON
|
|
}
|
|
var config yomiAdapterConfig
|
|
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
|
|
config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
|
|
config.DefaultLang = strings.TrimSpace(config.DefaultLang)
|
|
return config
|
|
}
|
|
|
|
func (c yomiAdapterConfig) TokenTTL() time.Duration {
|
|
if c.TokenTTLSeconds > 0 {
|
|
return time.Duration(c.TokenTTLSeconds) * time.Second
|
|
}
|
|
return yomiDefaultTokenTTL
|
|
}
|
|
|
|
func externalUserID(session gamedomain.LaunchSession, uidMode string) string {
|
|
if strings.EqualFold(uidMode, "display_user_id") && strings.TrimSpace(session.DisplayUserID) != "" {
|
|
return strings.TrimSpace(session.DisplayUserID)
|
|
}
|
|
return strconv.FormatInt(session.UserID, 10)
|
|
}
|
|
|
|
func (s *Service) handleYomiOperation(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest, operation string, requestHash string) ([]byte, string, string, bool, string, error) {
|
|
if !callbackIPAllowed(req, platform.CallbackIPWhitelist) {
|
|
raw, contentType := yomiJSON(yomiCodeIPForbidden, "ip forbidden", map[string]any{})
|
|
return raw, contentType, strconv.Itoa(yomiCodeIPForbidden), false, "", nil
|
|
}
|
|
body, err := decryptYomiBody(req.GetRawBody(), platform.CallbackSecretCiphertext)
|
|
if err != nil {
|
|
raw, contentType := yomiJSON(yomiCodeSignatureInvalid, "data signature invalid", map[string]any{})
|
|
return raw, contentType, strconv.Itoa(yomiCodeSignatureInvalid), false, "", nil
|
|
}
|
|
|
|
switch operation {
|
|
case "gettoken", "get_token", "api/gettoken":
|
|
return s.handleYomiGetToken(ctx, app, platform, req, body)
|
|
case "userinfo", "user_info", "api/userinfo":
|
|
return s.handleYomiUserInfo(ctx, app, platform, req, body)
|
|
case "change_balance", "changebalance", "api/change_balance":
|
|
return s.handleYomiChangeBalance(ctx, app, platform, req, body, requestHash)
|
|
case "repair_order", "repairorder", "api/repair_order":
|
|
return s.handleYomiRepairOrder(ctx, app, platform, body)
|
|
default:
|
|
raw, contentType := yomiJSON(yomiCodeOK, "success", map[string]any{})
|
|
return raw, contentType, strconv.Itoa(yomiCodeOK), true, "", nil
|
|
}
|
|
}
|
|
|
|
func (s *Service) handleYomiGetToken(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest, raw []byte) ([]byte, string, string, bool, string, error) {
|
|
var body struct {
|
|
GameUID json.Number `json:"gameUid"`
|
|
Lang string `json:"lang"`
|
|
PlatAuthCode string `json:"platAuthCode"`
|
|
PlatUserID string `json:"platUserId"`
|
|
PlatPayload string `json:"platPayload"`
|
|
PlatRoomID string `json:"platRoomId"`
|
|
}
|
|
if err := decodeYomiJSON(raw, &body); err != nil {
|
|
return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, "")
|
|
}
|
|
config := yomiConfigFromPlatform(platform)
|
|
session, err := s.validYomiSession(ctx, app, body.PlatAuthCode)
|
|
if err != nil || !yomiSessionMatches(session, config, body.PlatUserID, body.GameUID.String()) {
|
|
return yomiAdapterError(yomiCodeTokenInvalid, "token invalid", true, "")
|
|
}
|
|
snapshot, err := s.yomiUserSnapshot(ctx, app, req, session.UserID)
|
|
if err != nil {
|
|
return yomiAdapterError(yomiCodeFromError(err), yomiMessageFromError(err), true, "")
|
|
}
|
|
data := map[string]any{
|
|
"platUserId": body.PlatUserID,
|
|
"platUserOpenId": session.DisplayUserID,
|
|
"nickname": snapshot.Nickname,
|
|
"avatar": snapshot.Avatar,
|
|
"balance": snapshot.Balance,
|
|
"platToken": body.PlatAuthCode,
|
|
"expireAt": session.ExpiresAtMS / 1000,
|
|
}
|
|
rawResp, contentType := yomiJSON(yomiCodeOK, "success", data)
|
|
return rawResp, contentType, strconv.Itoa(yomiCodeOK), true, "", nil
|
|
}
|
|
|
|
func (s *Service) handleYomiUserInfo(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest, raw []byte) ([]byte, string, string, bool, string, error) {
|
|
var body struct {
|
|
PlatUserID string `json:"platUserId"`
|
|
PlatToken string `json:"platToken"`
|
|
PlatPayload string `json:"platPayload"`
|
|
PlatRoomID string `json:"platRoomId"`
|
|
}
|
|
if err := decodeYomiJSON(raw, &body); err != nil {
|
|
return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, "")
|
|
}
|
|
config := yomiConfigFromPlatform(platform)
|
|
session, err := s.validYomiSession(ctx, app, body.PlatToken)
|
|
if err != nil || !yomiSessionMatches(session, config, body.PlatUserID, "") {
|
|
return yomiAdapterError(yomiCodeTokenInvalid, "token invalid", true, "")
|
|
}
|
|
snapshot, err := s.yomiUserSnapshot(ctx, app, req, session.UserID)
|
|
if err != nil {
|
|
return yomiAdapterError(yomiCodeFromError(err), yomiMessageFromError(err), true, "")
|
|
}
|
|
data := map[string]any{
|
|
"platUserId": body.PlatUserID,
|
|
"platUserOpenId": session.DisplayUserID,
|
|
"nickname": snapshot.Nickname,
|
|
"avatar": snapshot.Avatar,
|
|
"balance": snapshot.Balance,
|
|
}
|
|
rawResp, contentType := yomiJSON(yomiCodeOK, "success", data)
|
|
return rawResp, contentType, strconv.Itoa(yomiCodeOK), true, "", nil
|
|
}
|
|
|
|
func (s *Service) handleYomiChangeBalance(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest, raw []byte, requestHash string) ([]byte, string, string, bool, string, error) {
|
|
var body struct {
|
|
RequestID json.Number `json:"requestId"`
|
|
PlatUserID string `json:"platUserId"`
|
|
PlatToken string `json:"platToken"`
|
|
Gold int64 `json:"gold"`
|
|
GameUID json.Number `json:"gameUid"`
|
|
GameLevelUID json.Number `json:"gameLevelUid"`
|
|
ReasonType int32 `json:"reasonType"`
|
|
GameRoundID json.Number `json:"gameRoundId"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
PlatPayload string `json:"platPayload"`
|
|
PlatRoomID string `json:"platRoomId"`
|
|
GameInfos []any `json:"game_infos"`
|
|
}
|
|
if err := decodeYomiJSON(raw, &body); err != nil {
|
|
return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, "")
|
|
}
|
|
providerOrderID := strings.TrimSpace(body.RequestID.String())
|
|
config := yomiConfigFromPlatform(platform)
|
|
session, err := s.validYomiSession(ctx, app, body.PlatToken)
|
|
if err != nil || !yomiSessionMatches(session, config, body.PlatUserID, body.GameUID.String()) {
|
|
return yomiAdapterError(yomiCodeTokenInvalid, "token invalid", true, providerOrderID)
|
|
}
|
|
opType := yomiOpType(body.ReasonType)
|
|
if providerOrderID == "" || opType == "" || body.Gold < 0 {
|
|
return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, providerOrderID)
|
|
}
|
|
result, err := s.applyYomiCoinChange(ctx, app, req, session, providerOrderID, body.GameRoundID.String(), opType, body.Gold, body.PlatRoomID, requestHash)
|
|
if err != nil {
|
|
code := yomiCodeFromError(err)
|
|
message := yomiMessageFromError(err)
|
|
balance := s.bestEffortCoinBalance(ctx, app, req.GetMeta().GetRequestId(), session.UserID)
|
|
rawResp, contentType := yomiJSON(code, message, map[string]any{"balance": balance})
|
|
return rawResp, contentType, strconv.Itoa(code), true, providerOrderID, nil
|
|
}
|
|
rawResp, contentType := yomiJSON(yomiCodeOK, "success", map[string]any{"balance": result.BalanceAfter})
|
|
return rawResp, contentType, strconv.Itoa(yomiCodeOK), true, providerOrderID, nil
|
|
}
|
|
|
|
func (s *Service) handleYomiRepairOrder(ctx context.Context, app string, platform gamedomain.Platform, raw []byte) ([]byte, string, string, bool, string, error) {
|
|
var body struct {
|
|
RequestID json.Number `json:"requestId"`
|
|
OrderID string `json:"orderId"`
|
|
PlatUserID string `json:"platUserId"`
|
|
Gold int64 `json:"gold"`
|
|
GameUID json.Number `json:"gameUid"`
|
|
GameRoundID json.Number `json:"gameRoundId"`
|
|
RewardType int32 `json:"rewardType"`
|
|
WinID string `json:"winId"`
|
|
PlatRoomID string `json:"platRoomId"`
|
|
}
|
|
if err := decodeYomiJSON(raw, &body); err != nil {
|
|
return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, "")
|
|
}
|
|
providerOrderID := strings.TrimSpace(body.OrderID)
|
|
if providerOrderID == "" {
|
|
providerOrderID = strings.TrimSpace(body.RequestID.String())
|
|
}
|
|
if providerOrderID == "" || strings.TrimSpace(body.PlatUserID) == "" || strings.TrimSpace(body.GameUID.String()) == "" || body.Gold < 0 {
|
|
return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, providerOrderID)
|
|
}
|
|
_, _, err := s.repository.CreateRepairOrder(ctx, gamedomain.RepairOrder{
|
|
AppCode: app,
|
|
RepairID: "grep_" + stableHash(app+"|"+platform.PlatformCode+"|"+providerOrderID),
|
|
PlatformCode: platform.PlatformCode,
|
|
ProviderOrderID: providerOrderID,
|
|
Reason: "yomi_repair_order",
|
|
Status: gamedomain.RepairStatusLogged,
|
|
})
|
|
if err != nil {
|
|
return yomiAdapterError(yomiCodeFromError(err), yomiMessageFromError(err), true, providerOrderID)
|
|
}
|
|
rawResp, contentType := yomiJSON(yomiCodeOK, "success", map[string]any{})
|
|
return rawResp, contentType, strconv.Itoa(yomiCodeOK), true, providerOrderID, nil
|
|
}
|
|
|
|
type yomiCoinChangeResult struct {
|
|
BalanceAfter int64
|
|
}
|
|
|
|
func (s *Service) applyYomiCoinChange(ctx context.Context, app string, req *gamev1.CallbackRequest, session gamedomain.LaunchSession, providerOrderID string, providerRoundID string, opType string, amount int64, roomID string, requestHash string) (yomiCoinChangeResult, error) {
|
|
order := gamedomain.GameOrder{
|
|
AppCode: app,
|
|
OrderID: "gord_" + stableHash(app+"|"+req.GetPlatformCode()+"|"+providerOrderID),
|
|
PlatformCode: strings.TrimSpace(req.GetPlatformCode()),
|
|
ProviderOrderID: providerOrderID,
|
|
ProviderRoundID: providerRoundID,
|
|
GameID: session.GameID,
|
|
ProviderGameID: session.ProviderGameID,
|
|
UserID: session.UserID,
|
|
RoomID: strings.TrimSpace(firstNonEmpty(roomID, session.RoomID)),
|
|
OpType: opType,
|
|
CoinAmount: amount,
|
|
Status: gamedomain.OrderStatusWalletApplying,
|
|
RequestHash: requestHash,
|
|
}
|
|
order, exists, err := s.repository.CreateGameOrder(ctx, order)
|
|
if err != nil {
|
|
return yomiCoinChangeResult{}, err
|
|
}
|
|
if exists && order.Status == gamedomain.OrderStatusSucceeded {
|
|
return yomiCoinChangeResult{BalanceAfter: order.WalletBalanceAfter}, nil
|
|
}
|
|
if amount == 0 {
|
|
balance := s.bestEffortCoinBalance(ctx, app, req.GetMeta().GetRequestId(), session.UserID)
|
|
if err := s.repository.MarkOrderSucceeded(ctx, app, order.OrderID, "", balance, s.now().UnixMilli()); err != nil {
|
|
return yomiCoinChangeResult{}, err
|
|
}
|
|
return yomiCoinChangeResult{BalanceAfter: balance}, nil
|
|
}
|
|
if s.wallet == nil {
|
|
return yomiCoinChangeResult{}, xerr.New(xerr.Unavailable, "wallet client is not configured")
|
|
}
|
|
walletResp, err := s.wallet.ApplyGameCoinChange(ctx, &walletv1.ApplyGameCoinChangeRequest{
|
|
RequestId: req.GetMeta().GetRequestId(),
|
|
AppCode: app,
|
|
CommandId: "game:" + strings.TrimSpace(req.GetPlatformCode()) + ":" + providerOrderID,
|
|
UserId: session.UserID,
|
|
PlatformCode: strings.TrimSpace(req.GetPlatformCode()),
|
|
GameId: session.GameID,
|
|
ProviderOrderId: providerOrderID,
|
|
ProviderRoundId: providerRoundID,
|
|
OpType: opType,
|
|
CoinAmount: amount,
|
|
RoomId: firstNonEmpty(roomID, session.RoomID),
|
|
RequestHash: requestHash,
|
|
})
|
|
nowMs := s.now().UnixMilli()
|
|
if err != nil {
|
|
status := gamedomain.OrderStatusFailed
|
|
if xerr.IsCode(err, xerr.IdempotencyConflict) {
|
|
status = gamedomain.OrderStatusConflict
|
|
}
|
|
_ = s.repository.MarkOrderFailed(ctx, app, order.OrderID, status, string(xerr.ReasonFromGRPC(err)), err.Error(), nowMs)
|
|
return yomiCoinChangeResult{}, err
|
|
}
|
|
if err := s.repository.MarkOrderSucceeded(ctx, app, order.OrderID, walletResp.GetWalletTransactionId(), walletResp.GetBalanceAfter(), nowMs); err != nil {
|
|
return yomiCoinChangeResult{}, err
|
|
}
|
|
return yomiCoinChangeResult{BalanceAfter: walletResp.GetBalanceAfter()}, nil
|
|
}
|
|
|
|
type yomiUserSnapshot struct {
|
|
Nickname string
|
|
Avatar string
|
|
Balance int64
|
|
}
|
|
|
|
func (s *Service) yomiUserSnapshot(ctx context.Context, app string, req *gamev1.CallbackRequest, userID int64) (yomiUserSnapshot, error) {
|
|
if s.user == nil || s.wallet == nil {
|
|
return yomiUserSnapshot{}, xerr.New(xerr.Unavailable, "user or wallet client is not configured")
|
|
}
|
|
userResp, err := s.user.GetUser(ctx, &userv1.GetUserRequest{
|
|
Meta: &userv1.RequestMeta{
|
|
RequestId: req.GetMeta().GetRequestId(),
|
|
Caller: "game-service",
|
|
SentAtMs: s.now().UnixMilli(),
|
|
AppCode: app,
|
|
},
|
|
UserId: userID,
|
|
})
|
|
if err != nil {
|
|
return yomiUserSnapshot{}, err
|
|
}
|
|
return yomiUserSnapshot{
|
|
Nickname: userResp.GetUser().GetUsername(),
|
|
Avatar: userResp.GetUser().GetAvatar(),
|
|
Balance: s.bestEffortCoinBalance(ctx, app, req.GetMeta().GetRequestId(), userID),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) bestEffortCoinBalance(ctx context.Context, app string, requestID string, userID int64) int64 {
|
|
if s.wallet == nil || userID <= 0 {
|
|
return 0
|
|
}
|
|
resp, err := s.wallet.GetBalances(ctx, &walletv1.GetBalancesRequest{
|
|
RequestId: requestID,
|
|
AppCode: app,
|
|
UserId: userID,
|
|
AssetTypes: []string{"COIN"},
|
|
})
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
for _, item := range resp.GetBalances() {
|
|
if item.GetAssetType() == "COIN" {
|
|
return item.GetAvailableAmount()
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (s *Service) validYomiSession(ctx context.Context, app string, token string) (gamedomain.LaunchSession, error) {
|
|
if strings.TrimSpace(token) == "" {
|
|
return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid")
|
|
}
|
|
session, err := s.repository.GetLaunchSessionByToken(ctx, app, token)
|
|
if err != nil {
|
|
return gamedomain.LaunchSession{}, err
|
|
}
|
|
if session.Status != gamedomain.SessionActive || session.ExpiresAtMS <= s.now().UnixMilli() {
|
|
return gamedomain.LaunchSession{}, xerr.New(xerr.SessionExpired, "token expired")
|
|
}
|
|
return session, nil
|
|
}
|
|
|
|
func yomiSessionMatches(session gamedomain.LaunchSession, config yomiAdapterConfig, platUserID string, gameUID string) bool {
|
|
if strings.TrimSpace(gameUID) != "" && strings.TrimSpace(gameUID) != strings.TrimSpace(session.ProviderGameID) {
|
|
return false
|
|
}
|
|
expectedUserID := externalUserID(session, config.UIDMode)
|
|
return strings.TrimSpace(platUserID) == expectedUserID
|
|
}
|
|
|
|
func yomiOpType(reasonType int32) string {
|
|
switch reasonType {
|
|
case 1:
|
|
return "debit"
|
|
case 2:
|
|
return "credit"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func decodeYomiJSON(raw []byte, out any) error {
|
|
decoder := json.NewDecoder(strings.NewReader(string(raw)))
|
|
decoder.UseNumber()
|
|
return decoder.Decode(out)
|
|
}
|
|
|
|
func decryptYomiBody(raw []byte, secret string) ([]byte, error) {
|
|
secret = strings.TrimSpace(secret)
|
|
if secret == "" {
|
|
return nil, xerr.New(xerr.InvalidArgument, "yomi secret is empty")
|
|
}
|
|
decoded, err := decodeYomiBase64(strings.TrimSpace(string(raw)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := aes.NewCipher([]byte(secret))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(decoded) <= gcm.NonceSize() {
|
|
return nil, xerr.New(xerr.InvalidArgument, "yomi payload is too short")
|
|
}
|
|
nonce := decoded[:gcm.NonceSize()]
|
|
ciphertext := decoded[gcm.NonceSize():]
|
|
return gcm.Open(nil, nonce, ciphertext, nil)
|
|
}
|
|
|
|
func decodeYomiBase64(value string) ([]byte, error) {
|
|
encodings := []*base64.Encoding{
|
|
base64.URLEncoding,
|
|
base64.RawURLEncoding,
|
|
base64.StdEncoding,
|
|
base64.RawStdEncoding,
|
|
}
|
|
var lastErr error
|
|
for _, encoding := range encodings {
|
|
decoded, err := encoding.DecodeString(value)
|
|
if err == nil {
|
|
return decoded, nil
|
|
}
|
|
lastErr = err
|
|
}
|
|
return nil, lastErr
|
|
}
|
|
|
|
func callbackIPAllowed(req *gamev1.CallbackRequest, whitelist []string) bool {
|
|
if len(whitelist) == 0 {
|
|
return true
|
|
}
|
|
ip := net.ParseIP(callbackRequestIP(req))
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
for _, raw := range whitelist {
|
|
value := strings.TrimSpace(raw)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if _, network, err := net.ParseCIDR(value); err == nil && network.Contains(ip) {
|
|
return true
|
|
}
|
|
if allowed := net.ParseIP(value); allowed != nil && allowed.Equal(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func callbackRequestIP(req *gamev1.CallbackRequest) string {
|
|
for _, key := range []string{"X-Forwarded-For", "x-forwarded-for"} {
|
|
if raw := strings.TrimSpace(req.GetHeaders()[key]); raw != "" {
|
|
return strings.TrimSpace(strings.Split(raw, ",")[0])
|
|
}
|
|
}
|
|
for _, key := range []string{"X-Real-IP", "x-real-ip"} {
|
|
if raw := strings.TrimSpace(req.GetHeaders()[key]); raw != "" {
|
|
return raw
|
|
}
|
|
}
|
|
host, _, err := net.SplitHostPort(strings.TrimSpace(req.GetRemoteAddr()))
|
|
if err == nil {
|
|
return host
|
|
}
|
|
return strings.TrimSpace(req.GetRemoteAddr())
|
|
}
|
|
|
|
func yomiAdapterError(code int, message string, signatureValid bool, providerRequestID string) ([]byte, string, string, bool, string, error) {
|
|
raw, contentType := yomiJSON(code, message, map[string]any{})
|
|
return raw, contentType, strconv.Itoa(code), signatureValid, providerRequestID, nil
|
|
}
|
|
|
|
func yomiJSON(code int, message string, data any) ([]byte, string) {
|
|
if strings.TrimSpace(message) == "" {
|
|
message = "success"
|
|
}
|
|
if data == nil {
|
|
data = map[string]any{}
|
|
}
|
|
return jsonResponse(map[string]any{"code": code, "message": message, "data": data})
|
|
}
|
|
|
|
func yomiCodeFromError(err error) int {
|
|
switch {
|
|
case err == nil:
|
|
return yomiCodeOK
|
|
case xerr.IsCode(err, xerr.InsufficientBalance):
|
|
return yomiCodeInsufficientBalance
|
|
case xerr.IsCode(err, xerr.SessionExpired), xerr.IsCode(err, xerr.Unauthorized):
|
|
return yomiCodeTokenInvalid
|
|
case xerr.IsCode(err, xerr.NotFound):
|
|
return yomiCodeUserNotFound
|
|
case xerr.IsCode(err, xerr.Conflict), xerr.IsCode(err, xerr.IdempotencyConflict):
|
|
return yomiCodeSystemError
|
|
default:
|
|
return yomiCodeSystemError
|
|
}
|
|
}
|
|
|
|
func yomiMessageFromError(err error) string {
|
|
switch yomiCodeFromError(err) {
|
|
case yomiCodeInsufficientBalance:
|
|
return "gold not enough"
|
|
case yomiCodeTokenInvalid:
|
|
return "token invalid"
|
|
case yomiCodeUserNotFound:
|
|
return "user not found"
|
|
default:
|
|
if err != nil && strings.TrimSpace(xerr.MessageOf(err)) != "" {
|
|
return xerr.MessageOf(err)
|
|
}
|
|
return "system error"
|
|
}
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return strings.TrimSpace(value)
|
|
}
|
|
}
|
|
return ""
|
|
}
|