dengbiao 5 дні тому
джерело
коміт
d29631cb22
6 змінених файлів з 356 додано та 6 видалено
  1. +163
    -0
      app/hdl/hdl_wallet.go
  2. +5
    -0
      app/md/md.wallet.go
  3. +1
    -0
      app/router/router.go
  4. +167
    -0
      app/svc/svc_withdraw_apply.go
  5. +16
    -3
      app/utils/time.go
  6. +4
    -3
      go.mod

+ 163
- 0
app/hdl/hdl_wallet.go Переглянути файл

@@ -4,10 +4,17 @@ import (
"applet/app/db"
"applet/app/e"
"applet/app/md"
"applet/app/svc"
"applet/app/utils"
"applet/app/utils/cache"
"code.fnuoos.com/EggPlanet/egg_models.git/src/implement"
"code.fnuoos.com/EggPlanet/egg_models.git/src/model"
"code.fnuoos.com/EggPlanet/egg_system_rules.git/enum"
md2 "code.fnuoos.com/EggPlanet/egg_system_rules.git/md"
"code.fnuoos.com/EggPlanet/egg_system_rules.git/rule"
"fmt"
"github.com/gin-gonic/gin"
"time"
)

// GetAmountFlow
@@ -127,3 +134,159 @@ func WithdrawGetAmount(c *gin.Context) {
}
e.OutSuc(c, resp, nil)
}

// WithdrawApply
// @Summary 蛋蛋星球-钱包-发起提现
// @Tags 钱包
// @Description 发起提现
// @Accept json
// @Produce json
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Param req body md.WithdrawApplyReq true "具体参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /api/v1/wallet/withdraw/apply [POST]
func WithdrawApply(c *gin.Context) {
var req md.WithdrawApplyReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = svc.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
user := svc.GetUser(c)
var userId, openId string
//sysCfgDb := implement.NewSysCfgDb(db.Db, cache.GetPool().Get())
//sysCfgMap := sysCfgDb.SysCfgFindWithDb(enum.AlipayAppId, enum.WxAppId)
if req.Kind == int(enum.FinWithdrawApplyWithdrawKindForAli) {
alipayUserInfoDb := implement.NewAlipayUserInfoDb(db.Db)
aliInfo, err := alipayUserInfoDb.GetAlipayUserInfo(user.Id)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}
if aliInfo == nil {
e.OutErr(c, e.ERR, "支付宝用户信息未授权")
return
}
//appId = sysCfgMap[enum.AlipayAppId]
userId = aliInfo.UserId
openId = aliInfo.OpenId
} else if req.Kind == int(enum.FinWithdrawApplyWithdrawKindForWx) {
wxUserInfoDb := implement.NewWxUserInfoDb(db.Db)
wxInfo, err := wxUserInfoDb.GetWxUserInfo(user.Id)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}
if wxInfo == nil {
e.OutErr(c, e.ERR, "微信用户信息未授权")
return
}
//appId = sysCfgMap[enum.WxAppId]
userId = wxInfo.UserId
openId = wxInfo.OpenId
} else {
e.OutErr(c, e.ERR, "未知的提现类型")
return
}

//1、判断是否可以提现
err, realAmount, fee, isAuto, isFirst := svc.CheckWithdraw(c, req.Amount)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}

// 2、加锁 防止并发提取
mutexKey := fmt.Sprintf("egg_app_withdraw_apply:%s", utils.Int64ToStr(user.Id))
withdrawAvailable, err := cache.Do("SET", mutexKey, 1, "EX", 5, "NX")
if err != nil {
e.OutErr(c, e.ERR, err)
return
}
if withdrawAvailable != "OK" {
e.OutErr(c, e.ERR, e.NewErr(400000, "请求过于频繁,请稍后再试"))
return
}

// 开启事务
session := db.Db.NewSession()
defer session.Close()
err = session.Begin()
if err != nil {
session.Rollback()
e.OutErr(c, e.ERR_DB_ORM, err)
return
}

//3、处理用户余额
dealUserWalletReq := md2.DealUserWalletReq{
Direction: "sub",
Kind: int(enum.UserWithdrawApply),
Title: enum.UserWithdrawApply.String(),
Uid: user.Id,
Amount: utils.StrToFloat64(req.Amount),
}
err = rule.DealUserWallet(session, dealUserWalletReq)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
session.Rollback()
return
}

//4、新增提现记录
now := time.Now()
finWithdrawApplyDb := implement.NewFinWithdrawApplyDb(db.Db)
insertAffected, err := finWithdrawApplyDb.FinWithdrawApplyInsertOneBySession(session, &model.FinWithdrawApply{
Uid: user.Id,
AdmId: 0,
Amount: req.Amount,
RealAmount: realAmount,
Fee: fee,
Type: func(isAuto bool) int {
if isAuto {
return 2
}
return 1
}(isAuto),
WithdrawAccount: userId,
WithdrawName: openId,
Reason: 0,
PaymentDate: "",
State: 0,
WithdrawKind: req.Kind,
IsFirst: func(isFirst bool) int {
if isFirst {
return 1
}
return 0
}(isFirst),
Memo: "",
UpdateAt: now.Format("2006-01-02 15:04:05"),
CreateAt: now.Format("2006-01-02 15:04:05"),
})
if err != nil {
session.Rollback()
e.OutErr(c, e.ERR_DB_ORM, err)
return
}
if insertAffected <= 0 {
session.Rollback()
e.OutErr(c, e.ERR_DB_ORM, "生成提现单失败")
return
}
err = session.Begin()
if err != nil {
session.Rollback()
e.OutErr(c, e.ERR_DB_ORM, err)
return
}

//5、推入mq
if isAuto {

}
e.OutSuc(c, "success", nil)
}

+ 5
- 0
app/md/md.wallet.go Переглянути файл

@@ -25,3 +25,8 @@ type GetAmountFlowResp struct {
type WithdrawGetAmountResp struct {
Amount string `json:"amount"` // 余额
}

type WithdrawApplyReq struct {
Amount string `json:"amount"` // 金额
Kind int `json:"kind"` // 提现方式(1:支付宝 2:微信)
}

+ 1
- 0
app/router/router.go Переглянути файл

@@ -124,6 +124,7 @@ func route(r *gin.RouterGroup) {
rWithdraw := r.Group("/withdraw")
{
rWithdraw.GET("/index", hdl.WithdrawGetAmount)
rWithdraw.POST("/apply", hdl.WithdrawApply)
}
}
rCollege := r.Group("/college") //学院


+ 167
- 0
app/svc/svc_withdraw_apply.go Переглянути файл

@@ -0,0 +1,167 @@
package svc

import (
"applet/app/db"
"applet/app/e"
"applet/app/utils"
"code.fnuoos.com/EggPlanet/egg_models.git/src/implement"
"code.fnuoos.com/EggPlanet/egg_models.git/src/model"
"code.fnuoos.com/EggPlanet/egg_system_rules.git/rule/egg_energy/md"
"errors"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"math"
"strings"
"time"
)

func CheckWithdraw(c *gin.Context, amount string) (err error, realAmount, fee string, isAuto, isFirst bool) {
realAmount = amount
user := GetUser(c)
now := time.Now()
amountValue, _ := decimal.NewFromString(amount)
//1、查询 fin_withdraw_setting 提现设置表
finWithdrawSettingDb := implement.NewFinWithdrawSettingDb(db.Db)
withdrawSetting, err := finWithdrawSettingDb.FinWithdrawSettingGetOne()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}

//2、判断“是否实名”
if withdrawSetting.IsRealName == 1 && user.IsRealName != 1 {
return errors.New("非实名用户不可提现"), realAmount, fee, isAuto, isFirst
}

//3、判断“提现金额”
if utils.StrToFloat64(withdrawSetting.WithdrawAmountLimit) > 0 {
withdrawAmountLimitValue, _ := decimal.NewFromString(withdrawSetting.WithdrawAmountLimit)
if amountValue.GreaterThan(withdrawAmountLimitValue) {
return errors.New("非可提现金额"), realAmount, fee, isAuto, isFirst
}
}

//4、判断“提现倍数”
if utils.StrToFloat64(withdrawSetting.WithdrawMultipleLimit) > 0 {
result := utils.StrToFloat64(amount) / utils.StrToFloat64(withdrawSetting.WithdrawMultipleLimit)
// 检查结果是否非常接近一个整数
roundedResult := math.Round(result)
if !(math.Abs(roundedResult-result) < 1e-9) {
return errors.New("非可提现倍数"), realAmount, fee, isAuto, isFirst
}
}

//5、验证会员等级
if withdrawSetting.VipLevelLimit > 0 && withdrawSetting.VipLevelLimit > user.Level {
return errors.New("非可提现会员等级"), realAmount, fee, isAuto, isFirst
}

//6、 验证小数点
if withdrawSetting.IsSupportDecimalPoint > 0 && strings.Contains(amount, ".") {
return errors.New("不支持的提现金额"), realAmount, fee, isAuto, isFirst
}

//7、验证时段
if withdrawSetting.WithdrawTimeInterval != "" {
withdrawTimeInterval := strings.Split(withdrawSetting.WithdrawTimeInterval, "-")
// 定义要比较的时间格式
layout := "15:04"
// 解析给定的时间字符串
start, _ := time.Parse(layout, withdrawTimeInterval[0])
end, _ := time.Parse(layout, withdrawTimeInterval[1])
// 设置为今天的日期
start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
end = time.Date(now.Year(), now.Month(), now.Day(), end.Hour(), end.Minute(), 0, 0, now.Location())

// 如果结束时间小于开始时间,说明跨天了
if end.Before(start) {
if !(now.After(start) || now.Before(end)) {
return errors.New("非可提现时间段"), realAmount, fee, isAuto, isFirst
}
} else { // 否则,在同一天内比较
if !(now.After(start) && now.Before(end)) {
return errors.New("非可提现时间段"), realAmount, fee, isAuto, isFirst
}
}
}

//8、验证“提现频率”
var frequency md.WithdrawFrequencySettingStruct
utils.Unserialize([]byte(withdrawSetting.FrequencySet), &frequency)
if frequency.Duration == 2 {
day := now.Weekday()
if !utils.InArr(utils.IntToStr(int(day)), frequency.Num) {
return errors.New("非可提现日期"), realAmount, fee, isAuto, isFirst
}
}
if frequency.Duration == 3 {
day := now.Day()
if !utils.InArr(utils.IntToStr(day), frequency.Num) {
return errors.New("非可提现日期"), realAmount, fee, isAuto, isFirst
}
}

if withdrawSetting.WithdrawNumsLimit > 0 {
var withdrawNums int64
var startOfDay, endOfDay time.Time
if frequency.Duration == 1 { //按天
startOfDay = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
endOfDay = startOfDay.Add(24 * time.Hour)
}
if frequency.Duration == 2 { //按周
startOfDay = utils.GetStartOfWeek(now)
endOfDay = startOfDay.Add(7 * 24 * time.Hour)
}
if frequency.Duration == 3 { //按月
startOfDay = utils.GetFirstDateOfMonth(now)
endOfDay = utils.GetLastDateOfMonth(now)
}
withdrawNums, err = db.Db.Where("create_at >= ?", startOfDay.Format("2006-01-02 15:04:05")).
And("create_at < ?", endOfDay.Format("2006-01-02 15:04:05")).
And("uid =?", user.Id).
And("state != 3"). //失败不计入
Count(&model.FinWithdrawApply{})
if err != nil {
return err, realAmount, fee, isAuto, isFirst
}
if int(withdrawNums) >= withdrawSetting.WithdrawNumsLimit {
return errors.New("当前已无可提现次数"), realAmount, fee, isAuto, isFirst
}
}

//9、计算手续费
var feeSet md.WithdrawFeeSetStruct
utils.Unserialize([]byte(withdrawSetting.WithdrawFeeSet), &feeSet)
if feeSet.Value > 0 {
feeSerValue := decimal.NewFromInt(int64(feeSet.Value))
if feeSet.Kind == 2 { //固定比例
feeSerValue = amountValue.Mul(feeSerValue.Div(decimal.NewFromInt(100)))
fee = amountValue.Mul(feeSerValue.Div(decimal.NewFromInt(100))).String()
}

fee = feeSerValue.String()
realAmount = amountValue.Sub(feeSerValue).String()
}

//10、判断是否为自动提现
if withdrawSetting.IsAuto > 0 {
autoAmountLimit, _ := decimal.NewFromString(withdrawSetting.IsAutoAmountLimit)
if amountValue.GreaterThanOrEqual(autoAmountLimit) {
isAuto = true
}
}

//11、判断是否为第一次提现
has, err := db.Db.Where("uid = ? and ", user.Id).
And("uid =?", user.Id).
Get(model.FinWithdrawApply{})
if has {
isFirst = true
}

if utils.StrToFloat64(realAmount) <= 0 {
return errors.New("当前提现金额有误"), realAmount, fee, isAuto, isFirst
}

return
}

+ 16
- 3
app/utils/time.go Переглянути файл

@@ -209,18 +209,31 @@ func GetDateTimeRangeStr(s string) (string, string) {
return stime.Format("2006-01-02 15:04:05"), etime.Format("2006-01-02 15:04:05")
}

//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。
// 获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。
func GetFirstDateOfMonth(d time.Time) time.Time {
d = d.AddDate(0, 0, -d.Day()+1)
return GetZeroTime(d)
}

//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。
// 获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。
func GetLastDateOfMonth(d time.Time) time.Time {
return GetFirstDateOfMonth(d).AddDate(0, 1, -1)
}

//获取某一天的0点时间
// GetStartOfWeek 返回给定时间所在周的周一0点时间
func GetStartOfWeek(t time.Time) time.Time {
// 调整到当天的0点
t = t.Truncate(24 * time.Hour)
// 计算与周一相差的天数
offset := int(time.Monday - t.Weekday())
if offset > 0 {
offset -= 7
}
// 调整到本周一
return t.AddDate(0, 0, offset)
}

// 获取某一天的0点时间
func GetZeroTime(d time.Time) time.Time {
return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
}

+ 4
- 3
go.mod Переглянути файл

@@ -2,9 +2,10 @@ module applet

go 1.19

//replace code.fnuoos.com/EggPlanet/egg_models.git => E:/company/Egg/egg_models
replace code.fnuoos.com/EggPlanet/egg_models.git => E:/company/Egg/egg_models

//
//replace code.fnuoos.com/EggPlanet/egg_system_rules.git => E:/company/Egg/egg_system_rules
replace code.fnuoos.com/EggPlanet/egg_system_rules.git => E:/company/Egg/egg_system_rules

require (
github.com/boombuler/barcode v1.0.1
@@ -32,7 +33,7 @@ require (
)

require (
code.fnuoos.com/EggPlanet/egg_models.git v0.2.1-0.20241128030209-743f36ef9dad
code.fnuoos.com/EggPlanet/egg_models.git v0.2.1-0.20241128102555-fc839292d728
code.fnuoos.com/EggPlanet/egg_system_rules.git v0.0.4-0.20241128063602-ed11b9dad994
code.fnuoos.com/go_rely_warehouse/zyos_go_es.git v1.0.1-0.20241118083738-0f22da9ba0be
code.fnuoos.com/go_rely_warehouse/zyos_go_mq.git v0.0.5


Завантаження…
Відмінити
Зберегти