Go XORM 实践--用户金币奖励接口开发
本文基于项目开发实践
接口需求
提供用户金币添加接口
- 客户端上传奖励类型和奖励金币数给服务端
- 服务端将用户奖励金币添加到用户金币总数
- 服务端需要添加用户金币变化记录,需要记录 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(¶ms)
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
}
总结
- 针对改需求最重要的是要保证接口在并发情况下,用户金币数正常;
- 数据库在使用for update 查询数据时,若当前已有 for update 锁,会等待锁释放
本作品采用《CC 协议》,转载必须注明作者和本文链接
初次编写代码实践,希望大家多多指教