Files
aigrammar/user.go
2024-08-12 04:10:48 +00:00

264 lines
10 KiB
Go
Raw 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"
"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 表
err = db.MySQL.QueryRow("SELECT IsVIP FROM vip WHERE ID = ?", response.ID).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)
// 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.Error("query error", zap.Error(errDup), 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
}