本文基於專案開發實踐
介面需求
提供使用者金幣新增介面
- 客戶端上傳獎勵型別和獎勵金幣數給服務端
- 服務端將使用者獎勵金幣新增到使用者金幣總數
- 服務端需要新增使用者金幣變化記錄,需要記錄 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 協議》,轉載必須註明作者和本文連結