Go XORM 实践--用户金币奖励接口开发

本文基于项目开发实践

接口需求

提供用户金币添加接口

  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
讨论数量: 1

初次编写代码实践,希望大家多多指教

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!