package shanghu import ( "duoduo/apis/common" "duoduo/apis/pdd" "duoduo/apis/shanghu/models" "duoduo/models/shanghu" "duoduo/tools" "duoduo/tools/app" "encoding/base64" "encoding/json" "errors" "fmt" "github.com/gin-gonic/gin" "github.com/go-pay/gopay" "github.com/go-pay/gopay/wechat" "github.com/shopspring/decimal" "math/rand" "strconv" "time" ) // 一个活动对应一个抽奖,一个抽奖对应多个奖品 // 一个活动对应多个拼团,一个拼团对应一个张主图,一个拼团对应多个商品(多个商品是为了核销) func ActiveConfigCreate(c *gin.Context) { var inData models.CreateActiveConfigRequest var sqlData shanghu.MerchantActiveConfig var drawProduct shanghu.MerchantActiveDrawProduct err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } if inData.DrawMode != 0 { if len(inData.DrawId) <= 0 { app.Error(c, 500, errors.New("奖品未配置"), "奖品未配置") return } //校验总概率 总概率相加不能大于100 drawList, _, err := drawProduct.GetDrawProductListById(inData.DrawId) if err != nil { app.Error(c, 500, err, err.Error()) return } sumOdds := 0 for _, v := range drawList { sumOdds = sumOdds + v.DrawOdds } if sumOdds != 100 { app.Error(c, 500, errors.New("配置的总中奖率不满100%"), "配置的总中奖率不满100%") return } } if inData.GroupBuyMode == 0 && inData.DrawMode == 0 { app.Error(c, 500, errors.New("请选择抽奖与开团"), "请选择抽奖与开团") return } sqlData.ActivityEndTime, err = tools.TimeToInt64(inData.ActivityEndTime, "2006-01-02") if err != nil { app.Error(c, 400, err, err.Error()) return } sqlData.ActivityStartTime, err = tools.TimeToInt64(inData.ActivityStartTime, "2006-01-02") if err != nil { app.Error(c, 400, err, err.Error()) return } //必中校验 //if inData.DrawOneBiZhong > 0 { // var checkBZ shanghu.MerchantActiveDrawProduct // checkBZ.ID = inData.DrawOneBiZhong // bZInfo, err := checkBZ.GetDrawProductById() // if err != nil { // app.Error(c, 500, err, err.Error()) // return // } // // if !bZInfo.IsPrize { // // app.Error(c, 500, err, err.Error()) // return // } //} sqlData.ActiveName = inData.ActiveName sqlData.MerchantOpenID = inData.MerchantOpenID sqlData.CreatedAt = time.Now() sqlData.UpdatedAt = time.Now() sqlData.ConfigMode = 1 sqlData.DrawOneBiZhong = inData.DrawOneBiZhong sqlData.DrawMode = inData.DrawMode sqlData.GroupBuyMode = inData.GroupBuyMode sqlData.GroupBuyUrl = inData.GroupBuyUrl sqlData.GroupBuy = `{}` sqlData.DrawProduct = `{}` sqlData.BackgroundImage = inData.BackgroundImage configData, err := sqlData.Create() if err != nil { app.Error(c, 500, err, err.Error()) return } for _, v := range inData.DrawId { var drawData shanghu.MerchantActiveDrawProduct drawData.ActiveConfigID = configData.ID err = drawData.UpdateConfigId(v) if err != nil { app.Error(c, 500, err, err.Error()) return } } for _, v := range inData.GroupBuyId { var groupBuy shanghu.MerchantActiveGroupBuy groupBuy.ActiveConfigID = configData.ID err = groupBuy.UpdateConfigId(v) if err != nil { app.Error(c, 500, err, err.Error()) return } } app.OK(c, nil, app.Success) } // 活动配置信息 func ActiveConfigInfo(c *gin.Context) { var inData models.ActiveConfigRequest var sqlData shanghu.MerchantActiveConfig var outData models.ActiveConfigReply var drawInfoSql shanghu.MerchantActiveDrawProduct var groupBuy shanghu.MerchantActiveGroupBuy var merchant shanghu.Merchant err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } sqlData.MerchantOpenID = inData.MerchantOpenID sqlData.ID = inData.ActiveConfigId configInfo, err := sqlData.GetConfigInfoById() if err != nil { app.Error(c, 500, err, err.Error()) return } outData.GroupBuyMode = configInfo.GroupBuyMode outData.DrawMode = configInfo.DrawMode outData.GroupBuyUrl = configInfo.GroupBuyUrl outData.DrawOneBiZhong = configInfo.DrawOneBiZhong outData.ActivityEndTime = tools.TimeToStr(configInfo.ActivityEndTime) outData.ActivityStartTime = tools.TimeToStr(configInfo.ActivityStartTime) outData.ActiveName = configInfo.ActiveName outData.BackgroundImage = configInfo.BackgroundImage //中奖商品 drawInfoSql.ActiveConfigID = configInfo.ID drawInfo, _, err := drawInfoSql.GetDrawProductList() if err != nil { app.Error(c, 500, err, err.Error()) return } for _, v := range drawInfo { var drawProduct models.DrawProduct drawProduct.DrawProductName = v.DrawProductName drawProduct.DrawUrl = v.DrawUrl drawProduct.DrawOdds = v.DrawOdds drawProduct.IsPrize = v.IsPrize drawProduct.TotalStock = v.TotalStock drawProduct.Stock = v.Stock outData.DrawProduct = append(outData.DrawProduct, drawProduct) } // 查询拼团列表 groupBuy.ActiveConfigID = configInfo.ID groupBuyInfoList, _, err := groupBuy.GetGroupBuyList() if err != nil { app.Error(c, 500, err, err.Error()) return } for _, v := range groupBuyInfoList { var groupBuyInfo models.DrawGroupBuy var groupBuyProject shanghu.MerchantActiveGroupByProject groupBuyInfo.GroupBuyName = v.GroupBuyName groupBuyInfo.GroupBuyMode = v.GroupBuyMode groupBuyInfo.GroupBuyUrl = v.GroupBuyUrl groupBuyInfo.GroupBuyThreeNum = v.GroupBuyThreeNum groupBuyInfo.GroupBuyThreePrice = v.GroupBuyThreePrice groupBuyInfo.GroupBuyFourNum = v.GroupBuyFourNum groupBuyInfo.GroupBuyFourPrice = v.GroupBuyFourPrice groupBuyInfo.GroupBuyTwoNum = v.GroupBuyTwoNum groupBuyInfo.GroupBuyTwoPrice = v.GroupBuyTwoPrice groupBuyInfo.GroupBuyOneNum = v.GroupBuyOneNum groupBuyInfo.GroupBuyOnePrice = v.GroupBuyOnePrice groupBuyInfo.MerchantOpenID = v.MerchantOpenID groupBuyInfo.OriginalPrice = v.OriginalPrice groupBuyInfo.RebateRate = v.RebateRate groupBuyProject.GroupBuyID = v.ID projectList, _, err := groupBuyProject.GetGroupBuyProjectList() if err != nil { app.Error(c, 500, err, err.Error()) return } for _, v := range projectList { var groupBuyProjectInfo models.GroupBuyProject groupBuyProjectInfo.ProjectName = v.ProjectName groupBuyProjectInfo.CancelNum = v.CancelNum groupBuyInfo.GroupBuyProject = append(groupBuyInfo.GroupBuyProject, groupBuyProjectInfo) } outData.DrawGroupBuy = append(outData.DrawGroupBuy, groupBuyInfo) } merchant.OpenId = inData.MerchantOpenID merchantInfo, err := merchant.GetMerchant() if err != nil { app.Error(c, 500, err, err.Error()) return } outData.MerchantName = merchantInfo.MerchantName app.OK(c, outData, app.Success) } // whxy func UpdateActiveConfigWHXY(c *gin.Context) { var inData models.ActiveConfigWHXYRequest var sqlData shanghu.MerchantActiveConfig var whxy []models.WHXYStr err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } err = json.Unmarshal([]byte(inData.WHXY), &whxy) if err != nil { app.Error(c, 400, err, err.Error()) return } for i := 0; i < len(whxy); i++ { if !whxy[i].Y.IsZero() { sqlData.W = whxy[i].W.String() sqlData.X = whxy[i].X.String() sqlData.Y = whxy[i].Y.String() sqlData.H = whxy[i].H.String() } } sqlData.ID = inData.ActiveConfigId err = sqlData.UpdateMerchantWHXY() if err != nil { app.Error(c, 400, err, err.Error()) return } app.OK(c, nil, app.Success) } // 活动列表 func ActiveConfigList(c *gin.Context) { var inData models.ActiveConfigListRequest var sqlData shanghu.MerchantActiveConfig var outData []models.ActiveConfigListReply err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } sqlData.MerchantOpenID = inData.OpenId var pageSize = 10 var pageIndex = 1 if inData.PageSize != 0 { pageSize = inData.PageSize } if inData.PageIndex != 0 { pageIndex = inData.PageIndex } activeConfigList, count, err := sqlData.GetActiveConfigList(pageSize, pageIndex) if err != nil { app.Error(c, 500, err, err.Error()) return } for _, v := range activeConfigList { var activeConfig models.ActiveConfigListReply activeConfig.ID = v.ID activeConfig.ActivityStart = tools.TimeToStr(v.ActivityStartTime) activeConfig.ActivityEnd = tools.TimeToStr(v.ActivityEndTime) activeConfig.ActiveName = v.ActiveName activeConfig.CreatedAt = v.CreatedAt.Format(time.DateTime) outData = append(outData, activeConfig) } app.PageOK(c, outData, count, pageIndex, pageSize, app.Success) } // 抽奖 func Draw(c *gin.Context) { var inData models.DrawRequest var sqlData shanghu.MerchantActiveConfig var drawNumSql shanghu.ClientActiveDrawNum var draw shanghu.ClientActiveDrawLog var outData models.DrawReply err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } // 校验抽奖次数 drawNumSql.ActiveConfigID = inData.ActiveConfigID drawNumSql.ClientOpenID = inData.ClientOpenID drawNum, err := drawNumSql.GetDrawNum() if err != nil { app.Error(c, 500, err, err.Error()) return } //校验数量 draw.ActiveConfigID = inData.ActiveConfigID draw.ClientOpenID = inData.ClientOpenID clientDrawNum, err := draw.GetClientActiveDrawLogNum() if err != nil { app.Error(c, 500, err, err.Error()) return } if drawNum+1 < clientDrawNum { app.Error(c, 500, errors.New("抽奖次数用完"), "抽奖次数用完") return } // 先校验是否有必中抽奖 sqlData.ID = inData.ActiveConfigID config, err := sqlData.GetConfigInfoById() if err != nil { app.Error(c, 500, err, err.Error()) return } if config.DrawOneBiZhong > 0 { // 必中抽奖需要产看中奖记录是否中过,没有中过直接中,中过了走正常的中奖概率 var drawConfig shanghu.ClientActiveDrawLog drawConfig.ActiveConfigID = inData.ActiveConfigID drawConfig.ClientOpenID = inData.ClientOpenID drawConfig.DrawProductID = config.DrawOneBiZhong count, err := drawConfig.GetClientActiveDrawLogByBiZHong() if err != nil { app.Error(c, 500, err, err.Error()) return } // 为0直接中奖 写中奖记录并且返回 查询中奖 if count == 0 { //var draw shanghu.ClientActiveDrawLog var drawProduct shanghu.MerchantActiveDrawProduct drawProduct.ID = config.DrawOneBiZhong drawProductInfo, err := drawProduct.GetDrawProductById() if err != nil { app.Error(c, 500, err, err.Error()) return } if drawProductInfo.ActiveConfigID != inData.ActiveConfigID { app.Error(c, 500, errors.New("必中配置活动不一致"), "必中配置活动不一致") return } if drawProductInfo.Stock <= 0 { app.Error(c, 500, errors.New("库存不足"), "库存不足") return } fmt.Println(drawProductInfo) //新建log 减库存 err = subStockAddLog(drawProductInfo, inData.ClientOpenID) if err != nil { app.Error(c, 500, err, err.Error()) return } //draw.DrawProductID = config.DrawOneBiZhong //draw.ActiveConfigID = inData.ActiveConfigID //draw.ClientOpenID = inData.ClientOpenID // //if drawProductInfo.IsPrize { // draw.IsPrize = models.NotClaimed //未兑奖 必中一定是要能兑奖的。 //} else { // draw.IsPrize = models.NotWon //未中奖 //} // //draw.CreatedAt = time.Now() //draw.UpdatedAt = time.Now() //_, err = draw.Create() //创建完成 //if err != nil { // app.Error(c, 500, err, err.Error()) // return //} outData.DrawProductName = drawProductInfo.DrawProductName outData.DrawUrl = drawProductInfo.DrawUrl outData.ID = config.DrawOneBiZhong app.OK(c, outData, app.Success) return } } var drawProductList shanghu.MerchantActiveDrawProduct var prizeList []Prize drawProductList.ActiveConfigID = inData.ActiveConfigID drawProductInfo, _, err := drawProductList.GetDrawProductListByActiveId() if err != nil { app.Error(c, 500, err, err.Error()) return } for _, v := range drawProductInfo { var prize Prize if v.Stock <= 0 { continue } prize.Name = v.DrawProductName prize.ID = v.ID prize.Weight = v.DrawOdds prize.InitStock = v.TotalStock prize.Stock = v.Stock prizeList = append(prizeList, prize) } //fmt.Println(prizeList) // 创建抽奖实例 lottery := NewLottery(prizeList) // 执行1次抽奖 results, err := lottery.Draw(1) if err != nil { app.Error(c, 500, err, err.Error()) return } fmt.Println(results) var drawProduct shanghu.MerchantActiveDrawProduct for _, v := range drawProductInfo { if results[0].ID == v.ID { outData.ID = v.ID outData.DrawUrl = v.DrawUrl outData.DrawProductName = v.DrawProductName drawProduct = v break } } //库存减一 并且新增记录 err = subStockAddLog(drawProduct, inData.ClientOpenID) if err != nil { app.Error(c, 500, err, err.Error()) return } app.OK(c, outData, app.Success) } type Prize struct { ID int64 // 奖品ID Name string // 奖品名称 Weight int // 权重 Stock int // 库存数量 InitStock int // 初始库存(用于展示) } // 带权重和库存的抽奖 type Lottery struct { Prizes []Prize TotalWeight int } func NewLottery(prizes []Prize) *Lottery { totalWeight := 0 for _, prize := range prizes { totalWeight += prize.Weight } // 深拷贝奖品列表并初始化初始库存 prizeCopy := make([]Prize, len(prizes)) for i, p := range prizes { prizeCopy[i] = p prizeCopy[i].InitStock = p.Stock } return &Lottery{ Prizes: prizeCopy, TotalWeight: totalWeight, } } func (l *Lottery) Draw(times int) ([]Prize, error) { // 检查总库存是否足够 totalStock := 0 for _, prize := range l.Prizes { totalStock += prize.Stock } if totalStock < times { return nil, fmt.Errorf("剩余总库存(%d)不足以支持%d次抽奖", totalStock, times) } rand.Seed(time.Now().UnixNano()) results := make([]Prize, 0, times) for i := 0; i < times; i++ { // 如果没有可用奖品了就停止 if l.TotalWeight <= 0 { break } randNum := rand.Intn(l.TotalWeight) currentWeight := 0 for j := range l.Prizes { if l.Prizes[j].Stock <= 0 { continue } currentWeight += l.Prizes[j].Weight if randNum < currentWeight { // 中奖 results = append(results, l.Prizes[j]) // 减少库存 l.Prizes[j].Stock-- // 如果库存耗尽,更新总权重 if l.Prizes[j].Stock == 0 { l.TotalWeight -= l.Prizes[j].Weight } break } } } return results, nil } // 新建记录,减库存 func subStockAddLog(drawProductInfo shanghu.MerchantActiveDrawProduct, clientOpenId string) error { var draw shanghu.ClientActiveDrawLog //var drawProduct shanghu.MerchantActiveDrawProduct if drawProductInfo.Stock <= 0 { return errors.New("库存不足") } //减库存 err := drawProductInfo.SubStock() if err != nil { return err } // 新建记录 draw.DrawProductID = drawProductInfo.ID draw.ActiveConfigID = drawProductInfo.ActiveConfigID draw.ClientOpenID = clientOpenId if drawProductInfo.IsPrize { draw.IsPrize = models.NotClaimed //未兑奖 必中一定是要能兑奖的。 } else { draw.IsPrize = models.NotWon //未中奖 } draw.CreatedAt = time.Now() draw.UpdatedAt = time.Now() _, err = draw.Create() //创建完成 if err != nil { return err } return nil } func GetClientActiveQR(c *gin.Context) { var inData models.ClientActiveQRRequest //var sqlData shanghu.MerchantClientCard var outData models.ClientCardQRReply var qr models.QRRequest var getQR models.GetQRRequest err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } // sqlData.ID = inData.ActiveConfigId val, err := pdd.DuoDuoGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx8595c589dd736486&secret=668f87d2bc24199688e53ee8a88434b8") if err != nil { app.Error(c, 500, err, err.Error()) return } err = tools.JsonUnmarshal(val, &outData) if err != nil { app.Error(c, 500, err, err.Error()) return } if outData.AccessToken == "" { app.Error(c, 500, err, "token error") return } url := "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + outData.AccessToken qr.CheckPath = true qr.Page = "pages/home/home" qr.EnvVersion = "release" qr.Scene = strconv.FormatInt(inData.ActiveConfigId, 10) data, err := json.Marshal(&qr) if err != nil { app.Error(c, 500, err, err.Error()) return } reply, err := common.Post(data, url) if err != nil { app.Error(c, 500, err, err.Error()) return } //err = json.Unmarshal(reply, &getQR) //if err != nil { // app.Error(c, 500, err, err.Error()) // return //} if getQR.Errcode != 0 { app.Error(c, 500, err, getQR.Errmsg) return } //fmt.Println(string(reply)) encoded := base64.StdEncoding.EncodeToString(reply) app.OK(c, encoded, app.Success) } func GroupByUnifiedOrder(c *gin.Context) { var inData models.GroupBuyUnifiedOrderRequest var sqlData shanghu.ClientActivePayTrans var activeConfig shanghu.MerchantActiveConfig var outData models.GroupBuyUnifiedOrderReply err := c.ShouldBindJSON(&inData) if err != nil { app.Error(c, 400, err, err.Error()) return } //校验活动id activeConfig.ID = inData.ActiveConfigId activeConfigInfo, err := activeConfig.GetConfigInfoById() if err != nil { app.Error(c, 400, err, err.Error()) return } if activeConfigInfo.DrawMode != 1 { //虚拟开团 app.Error(c, 500, errors.New("非虚拟开团,不允许下单"), "非虚拟开团,不允许下单.") return } if inData.ClientOpenId == "" { app.OK(c, nil, app.Success) return } //校验 防止同一笔记录存在 sqlData.RequestID = inData.RequestId if sqlData.GetRequestNum() > 0 { app.Error(c, 400, errors.New("交易已存在"), "交易已存在") return } //校验金额 //检查微信相关参数 if !inData.Amount.GreaterThan(decimal.NewFromInt(0)) { app.Error(c, 400, errors.New("amount:金额必须大于0"), "amount:金额必须大于0") return } if inData.Amount.Round(2).String() != inData.Amount.String() { app.Error(c, 400, errors.New("total_fee:金额最多只能保留两位小数"), "total_fee:金额最多只能保留两位小数") return } //创建支付记录 sqlData.ClientOpenID = inData.ClientOpenId sqlData.RequestID = inData.RequestId sqlData.CreatedAt = time.Now() sqlData.UpdatedAt = time.Now() sqlData.Amount = inData.Amount sqlData.OutTradeNo = strconv.FormatInt(inData.ActiveConfigId, 10) + strconv.FormatInt(time.Now().UnixNano(), 10) sqlData.Status = 1 //未支付 sqlData.ActiveConfigID = inData.ActiveConfigId sqlData.InvitationCode = inData.InvitationCode _, err = sqlData.Create() if err != nil { app.Error(c, 400, err, "创建支付失败") return } fmt.Println(sqlData.OutTradeNo) bm := make(gopay.BodyMap) bm.Set("nonce_str", common.GetRandomString(32)) bm.Set("body", "商户卡") bm.Set("out_trade_no", sqlData.OutTradeNo) bm.Set("total_fee", inData.Amount.Mul(decimal.NewFromInt(100)).IntPart()) bm.Set("spbill_create_ip", "127.0.0.1") bm.Set("notify_url", "https://tao1024.com/v1/client/pay/callback") bm.Set("device_info", "WEB") bm.Set("trade_type", "JSAPI") bm.Set("sign_type", wechat.SignType_MD5) bm.Set("openid", inData.ClientOpenId) client := NewWechatService() //请求支付下单,成功后得到结果 wxResp, err := client.UnifiedOrder(c, bm) if err != nil { app.Error(c, 400, err, "下单失败") return } if wxResp.ReturnCode != "SUCCESS" { app.Error(c, 400, errors.New(wxResp.ReturnMsg), "下单失败") return } if wxResp.ResultCode != "SUCCESS" { app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "下单失败") return } timestamp := strconv.FormatInt(time.Now().Unix(), 10) pac := "prepay_id=" + wxResp.PrepayId paySign := wechat.GetMiniPaySign("wx8595c589dd736486", wxResp.NonceStr, pac, wechat.SignType_MD5, timestamp, "33c424fAa69942086f82A003e283E9C8") outData.Timestamp = timestamp outData.NonceStr = wxResp.NonceStr outData.Package = pac outData.PaySign = paySign outData.SignType = wechat.SignType_MD5 //merchant, count, err := sqlData.GetOpenIdList(pageSize, pageIndex) //if err != nil { // app.Error(c, 500, err, err.Error()) // return //} app.OK(c, outData, app.Success) } func Pay(c *gin.Context) { // 获取参数 // }