Files
aigrammar/user.go
2024-08-17 04:29:48 +00:00

271 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 获取到对应的 DurationProductName 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
}