Go XORM 實踐--使用者金幣獎勵介面開發

codawu發表於2020-04-08

本文基於專案開發實踐

介面需求

提供使用者金幣新增介面

  1. 客戶端上傳獎勵型別和獎勵金幣數給服務端
  2. 服務端將使用者獎勵金幣新增到使用者金幣總數
  3. 服務端需要新增使用者金幣變化記錄,需要記錄 PreValue、ChangeValue、AfterValue, EventType 主要欄位

介面API

採用openapi 3.0 協議,可以使用swagger檢視

openapi: 3.0.0
info:
  title: reward
  version: '1.0'
  contact:
    name: mt@wangdawu.com
    email: mt@wangdawu.com
servers:
  - url: 'http://localhost:5000'
paths:
  /users/rewards:
    post:
      summary: User Reward Coin
      operationId: post-users-reward
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                  message:
                    type: string
              examples:
                example:
                  value:
                    code: 0
                    message: OK
      description: 獎勵使用者
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reward_type:
                  type: integer
                  description: 獎勵型別
                reward_value:
                  type: integer
                  description: 獎勵金幣數
              required:
                - reward_type
                - reward_value
        description: ''
      parameters:
        - schema:
            type: string
          in: header
          name: X-USER-ID
          description: 簡化介面使用者ID
    parameters: []
components:
  schemas: {}

XORM Model 定義

/**
    使用者金幣表
*/
type UserCoin struct {
    UserId  string    `json:"user_id" xorm:"user_id not null unique"`
    Total   int       `json:"coin_total" xorm:"total"`
    Created time.Time `json:"created" xorm:"created"`
    Updated time.Time `json:"updated" xorm:"updated"`
}
/**
    金幣變更記錄
*/
type CoinHistory struct {
    HistoryId   string    `json:"history_id" xorm:"history_id not null unique"`
    UserId      string    `json:"user_id" xorm:"user_id not null index"`
    EventType   int       `json:"event_type" xorm:"event_type not null"`
    PreValue    int       `json:"event_type" xorm:"event_type not null"`
    AfterValue  int       `json:"event_type" xorm:"event_type not null"`
    ChangeValue int       `json:"event_type" xorm:"event_type not null"`
    Created     time.Time `json:"created" xorm:"created"`
    Updated     time.Time `json:"updated" xorm:"updated"`
}

介面業務實現 不支援單使用者併發

使用iris框架開發

type Response struct {
   Code int `json:"code"`
  Message string `json:"message"`
}
func PostRewardsHandler(ctx iris.Context){
    // 從請求頭獲取使用者ID
    userId := c.GetHeader("X-User-Id")
    // 定義請求引數結構
    type Params struct {
       RewardType int `json:"reward_type"`
       RewardValue int `json:"reward_value"`
    }
    params := Params{}
    // 獲取請求引數
    err := c.ReadJSON(&params)
    if err != nil {
           msg:=fmt.Sprintf("get params %s errror: %s",userId, err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 400,
            Message:msg
        }
           return
    }
    if params.RewardValue == 0{
        msg:=fmt.Sprintf("get params %s reward value must > 0",userId, err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 400,
            Message:msg
        }
           return
    }
    // 獲取資料庫Session
    session:= db.NewSession()
    defer session.Close()
    userCoin:=new(model.UserCoin)
    if err:=session.Begin();err!=nil{
        msg:=fmt.Sprintf("db errror: %s", err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }
    // 查詢使用者UserCoin記錄,需要對使用者UserCoin for update加鎖
    has,err:=session.ForUpdate().Where("user_id = ?", userId).Get(userCoin)
    if err!=nil {
        msg:=fmt.Sprintf("get user coin user_id:%s  errror: %s",userId, err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }else if !has{
        msg:=fmt.Sprintf("get user coin user_id: %s not found",userId)
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }
    history:=model.CoinHistory{
        HistoryId: uuid.Rand(10),
        UserId: userId,
        EventType: params.RewardType,
        PreValue: userCoin.Total,
        ChangeValue: params.RewardValue,
        AfterValue: userCoin.Total + params.RewardValue
    }
    count,err:=session.InsertOne(&hitosry)
    if err!=nil {
        defer session.Rollback()
        msg:=fmt.Sprintf("insert coin history user_id:%s  errror: %s",userId, err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }else if count!=1{
       defer session.Rollback()
        msg:=fmt.Sprintf("insert coin history user_id:%s  count!=1",userId)
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }
    userCoin.Total += params.RewardValue
    count,err=session.Where("user_id = ?",userCoin.UserId).Update(userCoin)
    if err!=nil {
        defer session.Rollback()
        msg:=fmt.Sprintf("update user coin  user_id:%s  errror: %s",userId, err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }else if count!=1{
        defer session.Rollback()
        msg:=fmt.Sprintf("update user coin user_id:%s  count!=1",userId)
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }
    if err:=session.Commit();err!=nil{
        defer session.Rollback()
        msg:=fmt.Sprintf("db errror: %s", err.Error())
        logger.info(msg)
        reponse:=Response{
            Code: 500,
            Message:msg
        }
        ctx.JSON(response)
        return
    }
    response:=Response{
            Code: 0,
            Message:"OK"
    }
    ctx.JSON(response)
        return
}

總結

  1. 針對改需求最重要的是要保證介面在併發情況下,使用者金幣數正常;
  2. 資料庫在使用for update 查詢資料時,若當前已有 for update 鎖,會等待鎖釋放
本作品採用《CC 協議》,轉載必須註明作者和本文連結

codawu

相關文章