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