271 lines
11 KiB
Go
271 lines
11 KiB
Go
package main
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/awa/go-iap/appstore/api"
|
||
_ "github.com/go-sql-driver/mysql"
|
||
"github.com/labstack/echo/v4"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
type UserResponse struct {
|
||
ID int `json:"id"`
|
||
UserID string `json:"userid"`
|
||
UserName string `json:"username"`
|
||
VIP int `json:"vip"`
|
||
}
|
||
|
||
// TODO: 以后引入 GORM mysql driver ,简化数据库操作。
|
||
|
||
func queryUserHandler(c echo.Context) error {
|
||
// 从 context 中获取变量
|
||
deviceID := c.Get(KEY_DEVICEID).(string)
|
||
GID, _ := c.Get(KEY_GID).(int)
|
||
|
||
db, _ := GetDBManager()
|
||
var response UserResponse
|
||
|
||
// 查询 user 表
|
||
err := db.MySQL.QueryRow("SELECT ID, UserID, UserName FROM user WHERE DeviceID = ?", deviceID).Scan(&response.ID, &response.UserID, &response.UserName)
|
||
if err == sql.ErrNoRows {
|
||
// 用户不存在,创建新用户
|
||
// TODO: 这里要不要自动分配userid,这个userid在内部基本不会用到
|
||
res, err := db.MySQL.Exec("INSERT INTO user (DeviceID) VALUES (?)", deviceID)
|
||
if err != nil {
|
||
logger.Error("insert db error", zap.Error(err))
|
||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user")
|
||
}
|
||
lastID, err := res.LastInsertId()
|
||
if err != nil {
|
||
logger.Error("insert db error", zap.Error(err))
|
||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to retrieve last insert ID")
|
||
}
|
||
response.ID = int(lastID)
|
||
response.UserName = ""
|
||
logger.Debug("insert user", zap.Int("ID", response.ID), zap.String("DeviceID", deviceID))
|
||
|
||
} else if err != nil {
|
||
logger.Error("query db error", zap.Error(err))
|
||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||
} else {
|
||
// 看上传的 userID 跟数据库的是否一致 userID 转int ..
|
||
if response.ID != GID {
|
||
logger.Warn("userid not match", zap.Int("ID", response.ID), zap.Int("userGID", GID))
|
||
//log.Printf("userid not match: %v != %v", numid, response.ID)
|
||
}
|
||
}
|
||
|
||
// 查询 vip 表,因为可能VIP过期,所以要加上时间戳的判断。这里的服务器是东八区时间。
|
||
err = db.MySQL.QueryRow("SELECT IsVIP FROM vip WHERE ID = ? and ExpDate >= ?", response.ID, time.Now()).Scan(&response.VIP)
|
||
if err == sql.ErrNoRows {
|
||
response.VIP = 0 // 默认非VIP
|
||
} else if err != nil {
|
||
logger.Error("query db error", zap.Error(err))
|
||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||
}
|
||
|
||
setResponse(c, response)
|
||
return nil
|
||
//return c.JSON(http.StatusOK, response)
|
||
}
|
||
|
||
// 查询用户的所有redis key,内部接口
|
||
func UserRightsHandler(c echo.Context) error {
|
||
// 获取 c 的 GET方法的参数
|
||
ID, _ := strconv.Atoi(c.QueryParam("ID"))
|
||
|
||
db, _ := GetDBManager()
|
||
ub := NewUserBenefits(db.Redis)
|
||
|
||
// 查询redis
|
||
userData, err := ub.QueryUserBenefits(ID)
|
||
if err != nil {
|
||
logger.Error("QueryUserBenefits", zap.Error(err), zap.Int("ID", ID))
|
||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||
} else {
|
||
logger.Debug("QueryUserBenefits", zap.Any("userData", userData), zap.Int("ID", ID))
|
||
}
|
||
|
||
setResponse(c, userData)
|
||
return nil
|
||
}
|
||
|
||
// 查询用户的所有redis key,内部接口
|
||
func ResetUserRightsHandler(c echo.Context) error {
|
||
// 获取 c 的 GET方法的参数
|
||
ID, _ := strconv.Atoi(c.QueryParam("ID"))
|
||
datastr := c.QueryParam("datestr")
|
||
|
||
db, _ := GetDBManager()
|
||
ub := NewUserBenefits(db.Redis)
|
||
|
||
// 查询redis
|
||
err := ub.ResetUserBenefits(ID, datastr)
|
||
if err != nil {
|
||
logger.Error("ResetUserRightsHandler", zap.Error(err), zap.Int("ID", ID))
|
||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||
} else {
|
||
logger.Debug("ResetUserRightsHandler", zap.Int("ID", ID))
|
||
}
|
||
|
||
setResponse(c, nil)
|
||
return nil
|
||
}
|
||
|
||
// 编写查询用户是否VIP的函数,输入是GID,输出是vip,并且给出是否有error ,不需要使用 echo context
|
||
func queryUserVIP(ID int) (int, error) {
|
||
db, _ := GetDBManager()
|
||
var vip int
|
||
err := db.MySQL.QueryRow("SELECT IsVIP FROM vip WHERE ID = ?", ID).Scan(&vip)
|
||
if err == sql.ErrNoRows {
|
||
return 0, nil // 默认非VIP
|
||
} else if err != nil {
|
||
logger.Error("query db error", zap.Error(err))
|
||
return 0, err
|
||
}
|
||
return vip, nil
|
||
}
|
||
|
||
// 编写查询用户是否有权限使用某功能,输入是用户ID,如果用户是VIP,则有权限;否则查询 QueryUserBenefits 看是否超过免费限制,输出 是否有权限,以及是否出错。
|
||
func queryUserBenefits(c echo.Context) (bool, error) {
|
||
// 获取参数
|
||
ID, _ := c.Get(KEY_GID).(int)
|
||
timeZone := c.Request().Header.Get(KEY_HEADER_TIMEZONE)
|
||
secondsFromGMT, _ := strconv.Atoi(c.Request().Header.Get(KEY_HEADER_SECONDSFROMGMT))
|
||
|
||
db, _ := GetDBManager()
|
||
var vip int
|
||
err := db.MySQL.QueryRow("SELECT IsVIP FROM vip WHERE ID = ?", ID).Scan(&vip)
|
||
if err == sql.ErrNoRows {
|
||
// 非VIP,查询redis的免费次数
|
||
db, _ := GetDBManager()
|
||
ub := NewUserBenefits(db.Redis)
|
||
status, err := ub.CheckAndDecrement(ID, timeZone, secondsFromGMT)
|
||
if err != nil {
|
||
logger.Error("CheckAndDecrement", zap.Error(err), zap.Int("ID", ID), zap.String("timeZone", timeZone), zap.Int("secondsFromGMT", secondsFromGMT))
|
||
return false, err
|
||
} else {
|
||
logger.Debug("CheckAndDecrement", zap.Int("ID", ID), zap.String("timeZone", timeZone), zap.Int("secondsFromGMT", secondsFromGMT), zap.Int("status", status))
|
||
return status == 0, nil
|
||
}
|
||
} else if err != nil {
|
||
logger.Error("query db error", zap.Error(err))
|
||
return false, err
|
||
}
|
||
|
||
if vip == 1 {
|
||
return true, nil
|
||
}
|
||
return false, nil
|
||
}
|
||
|
||
// 从苹果校验订单后,插入vip表中
|
||
func UpdateOrderByVerify(ID int, AppAcountToken string, OriginTransID string, transantion *api.JWSTransaction) error {
|
||
// 写入vip表,如果ID对应记录不存在,则插入,否则更新
|
||
db, _ := GetDBManager()
|
||
|
||
var ProductType, Currency string
|
||
var Price, Duration int
|
||
// 先从 product 表中,根据 transantion.ProductID 获取到对应的 Duration,ProductName, Price, Currency
|
||
err := db.MySQL.QueryRow("SELECT ProductType, Price, Currency, Duration from product where ProductID = ?", transantion.ProductID).Scan(&ProductType, &Price, &Currency, &Duration)
|
||
if err == sql.ErrNoRows {
|
||
logger.Error("query productID empty.", zap.Error(err), zap.Int("ID", ID), zap.String("AppAcountToken", AppAcountToken), zap.String("OriginTransID", OriginTransID))
|
||
return err
|
||
} else if err != nil {
|
||
logger.Error("query productID error", zap.Error(err), zap.Int("ID", ID), zap.String("AppAcountToken", AppAcountToken), zap.String("OriginTransID", OriginTransID))
|
||
return err
|
||
}
|
||
|
||
// 取当前的时间戳
|
||
//purchase_time := time.Now().Unix()
|
||
//exp_time := purchase_time + int64(Duration)*3600*24
|
||
|
||
currentTime := time.Now()
|
||
nextDay := time.Now().AddDate(0, 0, Duration)
|
||
|
||
// 如果是Sandbox交易,那么直接使用Transaction中的过期时间,注意匹配时区
|
||
if strings.EqualFold(string(transantion.Environment), "Sandbox") {
|
||
nextDay = time.Unix(transantion.ExpiresDate/1000, 0).In(time.Local)
|
||
logger.Debug("Sandbox ExpireDate", zap.Any("ExpireDate", nextDay), zap.Any("NowDate", currentTime))
|
||
}
|
||
|
||
// TODO: transaction.TransactionReason 有新购和续费,需要区分;同一个购买或者续费事件,可能有通知多次,需要排重
|
||
var tmpID int
|
||
errDup := db.MySQL.QueryRow("SELECT ID from vip where TransactionID = ? and OriginalTransactionID = ? and IsVip = 1 and ExpDate > ?", transantion.TransactionID, transantion.OriginalTransactionId, currentTime).Scan(&tmpID)
|
||
if errDup != sql.ErrNoRows {
|
||
// 表示重复了,可以直接返回
|
||
logger.Info("duplicate request", zap.Int("ID", ID), zap.String("AppAcountToken", AppAcountToken), zap.String("OriginTransID", OriginTransID), zap.String("TransactionID", transantion.TransactionID))
|
||
return nil
|
||
} else if errDup != nil {
|
||
logger.Info("prepare to insert record", zap.Int("ID", ID), zap.String("AppAcountToken", AppAcountToken), zap.String("OriginTransID", OriginTransID))
|
||
// 这里不返回,继续尝试更新。
|
||
}
|
||
|
||
// 更新到DB
|
||
sql := `INSERT INTO vip (ID, IsVip, AppStore, ProductID, ProductType, Environment, Price, Currency, Storefront, PurchaseDate, ExpDate, AutoRenew, OriginalTransactionID, TransactionID, AppAccountToken, TransactionReason)
|
||
VALUES (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
IsVip = 1, AppStore = ?, ProductID = ?, ProductType = ?, Environment = ?, Price = ?, Currency = ?, Storefront = ?, PurchaseDate = ?, ExpDate = ?, AutoRenew = ?, OriginalTransactionID = ? , TransactionID = ?, AppAccountToken = ?, TransactionReason = ? `
|
||
|
||
_, err2 := db.MySQL.Exec(sql,
|
||
ID, APPSTORE, transantion.ProductID, ProductType, transantion.Environment, Price, Currency, transantion.Storefront, currentTime, nextDay, 1, OriginTransID, transantion.TransactionID, transantion.AppAccountToken, transantion.TransactionReason,
|
||
APPSTORE, transantion.ProductID, ProductType, transantion.Environment, Price, Currency, transantion.Storefront, currentTime, nextDay, 1, OriginTransID, transantion.TransactionID, transantion.AppAccountToken, transantion.TransactionReason)
|
||
|
||
if err2 != nil {
|
||
logger.Error("UpdateOrderByVerify", zap.Error(err), zap.Int("ID", ID), zap.String("AppAcountToken", AppAcountToken), zap.String("OriginTransID", OriginTransID))
|
||
return err2
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// 接收到appstore的回调,写入数据。因为不知道对应的用户账号,所以只能记录。
|
||
func UpdateOrderByNotify(Notification *AppStoreServerNotification) error {
|
||
db, _ := GetDBManager()
|
||
|
||
// 先写order_log表
|
||
sql := `INSERT INTO order_log (AppStore, NotificationType, Subtype, Environment, AppAccountToken, TransactionInfo, RenewalInfo, Payload)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?) `
|
||
|
||
// 需要把 Notification.TransactionInfo 转成 字符串
|
||
TransactionInfo, _ := json.Marshal(Notification.TransactionInfo)
|
||
RenewalInfo, _ := json.Marshal(Notification.RenewalInfo)
|
||
Payload, _ := json.Marshal(Notification.Payload)
|
||
|
||
_, err := db.MySQL.Exec(sql,
|
||
APPSTORE, Notification.Payload.NotificationType, Notification.Payload.Subtype, Notification.Payload.Data.Environment, Notification.TransactionInfo.AppAccountToken, TransactionInfo, RenewalInfo, Payload)
|
||
|
||
if err != nil {
|
||
logger.Error("UpdateOrderByNotify", zap.Error(err))
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// 给定 appaccounttoken,在order_log中查询是否已经存在了,如果存在,则无需向苹果发起验证请求
|
||
func CheckOrderByAppAcountToken(AppAccountToken string) (bool, error) {
|
||
db, _ := GetDBManager()
|
||
|
||
// 根据AppAccountToken查询 order_log表,查看记录是否存在,如果存在,返回true,否则false
|
||
var LogID int
|
||
err := db.MySQL.QueryRow("SELECT LogID from order_log where AppAccountToken = ? and AppStore = ? ", AppAccountToken, APPSTORE).Scan(&LogID)
|
||
if err == sql.ErrNoRows {
|
||
logger.Info("query empty", zap.String("AppAccountToken", AppAccountToken))
|
||
return false, nil
|
||
} else if err != nil {
|
||
logger.Error("query error.", zap.Error(err), zap.String("AppAccountToken", AppAccountToken))
|
||
return false, err
|
||
}
|
||
|
||
// TODO: 可以在这里更新 vip 表,这样 Verify 的过程中,如果查询到记录,就不需要再去appstore校验了。
|
||
|
||
return true, nil
|
||
}
|