feat(fight): 调整技能效果命中逻辑与回合开始处理

- 修改了技能效果命中的判定顺序,确保暴击计算在效果添加之前执行
- 修复了回合开始时敌我双方状态结算的上下文错误
- 优化了效果缓存初始化逻辑,避免重复添加相同效果
- 增加了效果去重判断,防止完全相同的效果被重复添加
- 调整了战斗循环中结束逻辑的位置,确保广播和通道关闭正确执行
- 更新了部分日志提示信息,使其更符合实际业务含义
- 移除了部分无用代码和注释,提高
This commit is contained in:
2025-11-11 01:10:26 +08:00
parent 996e342459
commit da9286d3d8
18 changed files with 150 additions and 99 deletions

View File

@@ -8,11 +8,11 @@ import (
"github.com/antlabs/cronex"
"github.com/gogf/gf/v2/os/glog"
"github.com/xiaoqidun/limit"
sensitive "github.com/zmexing/go-sensitive-word"
)
var Limiter = limit.New()
// ar Limiter = ratelimit.New(100) // per second
// var Limiter = limit.New()
var ctx = context.TODO()
var (

View File

@@ -10,12 +10,14 @@ require github.com/antchfx/xmlquery v1.4.4
require (
github.com/antchfx/xpath v1.3.3 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/butoften/array v1.0.9 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/stretchr/testify v1.11.1 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.uber.org/ratelimit v0.3.1 // indirect
)
require (

View File

@@ -8,6 +8,8 @@ github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fus
github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/butoften/array v1.0.9 h1:/kPHAc+fHz72u5B23p2W1RzIoT2eOYvhsY0tKMvsHEc=
github.com/butoften/array v1.0.9/go.mod h1:RgJ3XIUy/Z2rQllTkXmS4LtfqJeD3mjYJ4XoP3odTqM=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
@@ -86,6 +88,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
@@ -187,3 +191,4 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -59,7 +59,7 @@ func init() { //默认初始化扫描
for _, func_cmd := range getcmd(methodValue.Type().In(0)) {
if func_cmd == 0 { //说明不是注册方法
glog.Warning(context.Background(), "方法参数必须是结构体", method.Name, "跳过注册")
glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册")
continue
}

View File

@@ -26,7 +26,7 @@ func (e *Effect20) OnSkill(input.Ctx) bool {
return true
}
t := input.Geteffect(input.EffectType.Status, int(info.PetStatus.Tired))
t.Duration(e.SideEffectArgs[1] + 1)
t.Duration(e.SideEffectArgs[1])
e.Input.AddEffect(t)
return true
}

View File

@@ -4,6 +4,7 @@ import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"fmt"
"sync"
"github.com/shopspring/decimal"
@@ -30,7 +31,7 @@ type Effect62_sub struct {
// 这个实际上在对方回合执行的
func (e *Effect62_sub) OnSkill(ctx input.Ctx) bool {
defer e.Alive(false)
//defer e.Alive(false)
if e.bindpet == e.bind.CurrentPet { //说明对方没有切换精灵
//直接扣除所有血量OnSkill
@@ -50,46 +51,55 @@ func init() {
}
func (e *Effect62) Turn_Start(ctx input.Ctx) {
if ctx.Player != e.Input.Player {
return
}
if e.Duration() != 1 { //说明还没到生效节点
//如果对面还是我方放技能时候的玩家
// if ctx.Player != e.opp.Player {
// return
// }
fmt.Println(e.Duration(), "镇魂歌剩余回合")
if e.Duration() != 0 { //说明还没到生效节点
e.Hide = true //隐藏效果
} else {
e.Hide = false
}
if !e.Hide { //说明是自身回合//如果还在隐藏,就直接返回
//t.Duration(e.SideEffectArgs[0])
e.opp.AddEffect(e.e)
//defer e.EffectNode.NotALive() //失效
//应该是对方固定伤害等于自身血量
//e.Input.Death() //本只死亡
//否则触发秒杀 在对面使用技能后
//return true
}
// if !e.Hide { //说明是自身回合//如果还在隐藏,就直接返回
// //t.Duration(e.SideEffectArgs[0])
// e.opp.AddEffect(e.e)
// //defer e.EffectNode.NotALive() //失效
// //应该是对方固定伤害等于自身血量
// //e.Input.Death() //本只死亡
// //否则触发秒杀 在对面使用技能后
// //return true
// }
}
func (e *Effect62) OnSkill(ctx input.Ctx) bool {
if !e.Hit() {
//e.Alive(false)
return true
}
e.opp = ctx.Input
e.e = &Effect62_sub{
EffectNode: node.EffectNode{},
bindpet: ctx.CurrentPet,
bind: ctx.Input,
}
e.e.ID(e.ID() + int(input.EffectType.Sub)) //子效果ID
e.e.EffectNode.Duration(e.EffectNode.SideEffectArgs[0])
//给对方添加我方施加的buff
e.e.SetArgs(e.Input, e.SideEffectArgs...)
e.l.Do(func() {
//e.Duration(1) //必须保持到下一回合,这样才会被复制
e.opp = ctx.Input
e.e = &Effect62_sub{
EffectNode: node.EffectNode{},
bindpet: ctx.CurrentPet,
bind: ctx.Input,
}
e.e.ID(e.ID() + int(input.EffectType.Sub)) //子效果ID
//e.e.EffectNode.Duration(e.EffectNode.SideEffectArgs[0])
//给对方添加我方施加的buff
e.e.SetArgs(e.Input, e.SideEffectArgs...)
})
return true
}
func (e *Effect62) SetArgs(t *input.Input, a ...int) {
e.EffectNode.SetArgs(t, a...)
e.EffectNode.Duration(e.EffectNode.SideEffectArgs[0])
}
// // 因为对方切精灵,这个效果也要无效掉
// func (this *Effect62) OnSwitchIn(input.Ctx) bool {

View File

@@ -277,8 +277,8 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *info.Ski
// 记录技能信息
attacker.SkillID = uint32(a.ID) //获取技能ID
if attacker.AttackTime > 0 { //如果命中
attacker.AddEffects(attacker.EffectCache...) //命中再添加效果
attacker.CalculateCrit(defender, a) //暴击计算
attacker.CalculateCrit(defender, a) //暴击计算
attacker.AttackValue.IsCritical = a.Crit
attacker.DamageZone.Damage = attacker.CalculatePower(defender, a)
@@ -299,6 +299,7 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *info.Ski
}
}
attacker.AddEffects(attacker.EffectCache...) //命中再添加效果
for _, e := range attacker.EffectCache {
//这里实现应该参考本地技能是否命中,然后
e.Hit(a.AttackTime != 0) //我方效果命中
@@ -421,13 +422,13 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
f.First.ExecCace(func(t input.Effect) bool { //回合开始前
//结算状态
t.Turn_Start(input.Ctx{Input: f.First})
t.Turn_Start(input.Ctx{Input: f.Second})
return true
})
f.Second.ExecCace(func(t input.Effect) bool { //回合开始前
//结算状态
t.Turn_Start(input.Ctx{Input: f.Second})
t.Turn_Start(input.Ctx{Input: f.First})
return true
})
//开始回合操作
@@ -446,7 +447,7 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
currentskill = oldskill
//fmt.Println("开始攻击威力", oldskill.Power)
// fmt.Println("开始攻击威力", oldskill.Power)
canuseskill := true
// 实际上攻击方 还有系统选择放弃出手的
if !action.CanUse(currentskill) || attacker.CurrentPet.Info.Hp <= 0 {
@@ -520,7 +521,7 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
f.FightOverInfo.WinnerId = defender.UserID
f.closefight = true
break
// break
}
}
@@ -567,8 +568,7 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
f.First.GenInfo()
f.Second.GenInfo()
// ret.FAttack = *f.First.AttackValue
// ret.SAttack = *f.Second.AttackValue
ret := info.AttackValueS{
FAttack: *f.First.AttackValue,

View File

@@ -151,6 +151,7 @@ func getSkillName(move *SkillEntity) string {
// 计算是否命中
func (s *SkillEntity) AttackTimeC(level int) {
s.AttackTime = 0 //先重置上一次的
if s.MustHit != 0 {
s.AttackTime = 2
}

View File

@@ -128,11 +128,10 @@ func (c *Input) AddEffects(e ...Effect) {
for _, v := range e {
// v.Alive()
c.AddEffect(v)
}
}
}
func (c *Input) AddEffect(e Effect) {
@@ -142,11 +141,14 @@ func (c *Input) AddEffect(e Effect) {
fmt.Println("产生回合数", e.ID(), e.Duration())
// 如果已有同 ID 的效果,尝试叠加
for _, v := range c.Effects {
if v == e {
return //完全相同,跳过执行
}
//如果效果相同,id相同,参数相同,就是同一个,确认是否可以叠加,正常来说本身就可以共存
//衰弱本身参数也是相同的,区别只是传入的回合数不一样和层数不一样
if e.ID() != 0 && v.ID() == e.ID() &&
v.Alive() &&
if v.ID() == e.ID() && //找到相同的效果id
v.Alive() && //如果之前的效果还存活
equalInts(v.GetArgs(), e.GetArgs()) { //如果层数可以叠加或者是无限层数
if v.MaxStack() == 0 {
v.Alive(false) //不允许叠层,取消效果
@@ -196,12 +198,9 @@ func (c *Input) Exec(fn func(Effect) bool) bool {
func (c *Input) ExecCace(fn func(Effect) bool) bool {
result := true
for _, value := range c.EffectCache {
if value.Alive() {
if !fn(value) { //存在false,但是仍然要向下执行
result = false //如果是false,说明存在阻止向下执行的effect比如免疫能力提升效果
}
if !fn(value) { //存在false,但是仍然要向下执行
result = false //如果是false,说明存在阻止向下执行的effect比如免疫能力提升效果
}
}

View File

@@ -11,16 +11,19 @@ import (
)
type Input struct {
CanChange bool //是否可以死亡切换CanChange
CurrentPet *info.BattlePetEntity //当前精灵
AllPet []*info.BattlePetEntity
Player common.PlayerI
EffectCache []Effect
Finished bool //是否加载完成
CanChange bool //是否可以死亡切换CanChange
CurrentPet *info.BattlePetEntity //当前精灵
AllPet []*info.BattlePetEntity
Player common.PlayerI
Finished bool //是否加载完成
*info.AttackValue
FightC common.FightI
// info.BattleActionI
Effects []Effect //effects 实际上全局就是effect无限回合 //effects容器 技能的
Effects []Effect //effects 实际上全局就是effect无限回合 //effects容器 技能的
EffectCache []Effect //这里是命中前执行的容器,也就是命中前执行的所有逻辑相关,理论上一个effect被激活,就应该同时将其他的effect取消激活
//NewEffects []Effect
DamageZone struct {
Damage decimal.Decimal //伤害
BeforeADD decimal.Decimal //攻击伤害
@@ -60,15 +63,11 @@ func (input *Input) GenSataus() {
t := input.GetEffect(EffectType.Status, i)
if t != nil { //状态都是叠层类的
if t != nil && t.Alive() { //状态都是叠层类的
input.AttackValue.Status[i] = int8(t.Duration())
input.Status[i] = int8(t.Duration())
}
// t = f.Second.GetEffect(input.EffectType.Status, i)
// if t != nil {
// ret.SAttack.Status[i] = int8(t.Duration()) + 1
// }
}
@@ -87,6 +86,8 @@ func (input *Input) GenInfo() {
func (i *Input) ResetAttackValue() {
i.AttackValue.SkillID = 0
i.AttackValue.IsCritical = 0
i.AttackValue.GainHp = 0
i.AttackValue.LostHp = 0
}
@@ -135,9 +136,26 @@ func (i *Input) GetStatusBonus() float64 {
return maxBonus
}
func (i *Input) initeffectcache() {
i.EffectCache = make([]Effect, 0) //先把上一回合数据清空,但是应该把本身延续类效果集成过来
for _, v := range i.Effects {
if v.Alive() { //说明存活效果而且是延续类效果,将之添加到初始化列表中
//这里添加的效果是已经生效的效果对effect的复制,相当于技能施的效果的前置比如改命中的效果等
i.EffectCache = append(i.EffectCache, v)
}
}
}
// 解析并 施加effect
func (i *Input) Parseskill(defender *Input, skill *action.SelectSkillAction) {
i.EffectCache = make([]Effect, 0) //先把上一回合数据清空
i.initeffectcache() //这里说明是延续的效果,每次复制出来一个新的就好了
//i.NewEffects = make([]Effect, 0) //这里说明是新增的效果
temparg := skill.SideEffectArgS
for _, v := range skill.SideEffectS {
@@ -159,8 +177,9 @@ func (i *Input) Parseskill(defender *Input, skill *action.SelectSkillAction) {
//i.AddEffect(t)
// }
//这里是临时缓存buff,后面确认命中后修改HIT状态
// t.Alive() //先让效果保持存活
i.EffectCache = append(i.EffectCache, t)
// i.NewEffects = append(i.NewEffects, t)
}
temparg = temparg[args:]

View File

@@ -15,16 +15,7 @@ import (
)
func (f *FightC) battleLoop() {
defer func() {
f.Broadcast(func(ff *input.Input) {
//todo 将血量和技能pp传回enterturn
//<-time.After(10000)
ff.Player.SendFightEndInfo(f.FightOverInfo)
})
close(f.actionChan)
close(f.overchan)
}()
f.actionChan = make(chan action.BattleActionI, 2)
fmt.Println("战斗开始精灵", f.Our.Player.GetInfo().PetList[0].CatchTime)
@@ -32,10 +23,6 @@ func (f *FightC) battleLoop() {
oppID := f.Opp.Player.GetInfo().UserID
for {
if f.closefight {
break
}
f.Round++
fmt.Printf("—— 第 %d 回合开始 ——\n", f.Round)
@@ -43,9 +30,21 @@ func (f *FightC) battleLoop() {
actions := f.collectPlayerActions(ourID, oppID)
f.resolveRound(actions[ourID], actions[oppID])
if f.closefight {
break
}
}
fmt.Println("战斗循环结束")
f.Broadcast(func(ff *input.Input) {
//todo 将血量和技能pp传回enterturn
ff.Player.SendFightEndInfo(f.FightOverInfo)
})
close(f.actionChan)
close(f.overchan)
}
// 收集玩家动作(含超时判定)

View File

@@ -15,12 +15,18 @@ func (e *EffectNode) Turn_Start(ctx input.Ctx) {
func (e *EffectNode) Turn_End(ctx input.Ctx) {
if e.duration == 0 { // 保留 (负数表示永久)
if ctx.Input.FightC.IsFirst(ctx.Input.Player) { //如果对方先手
e.duration++
return
}
e.Alive(false)
} else {
e.trunl.Do(func() {
if ctx.Input.FightC.IsFirst(ctx.Input.Player) { //如果对方先手
e.duration++
// e.Alive(true)
}
})
e.duration--
}

View File

@@ -3,6 +3,7 @@ package node
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"sync"
)
// 检查,激活,延后
@@ -21,7 +22,7 @@ type EffectNode struct {
Flag int //过滤掉的战斗类型 pvp pve boss战斗,野怪全部生效
alive bool // 是否失效 effect返回值是否被取消是否被删除
hit bool
trunl sync.Once
//增加owner target如果owner target都为自身就回合效果结束后再使用回合效果
}

View File

@@ -1,14 +1,12 @@
package maps
import (
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/player"
"blazing/logic/service/space"
"blazing/modules/blazing/model"
"github.com/gogf/gf/v2/util/gconv"
"golang.org/x/time/rate"
)
@@ -24,10 +22,12 @@ type WalkInInfo struct {
Path string
}
func (t *WalkInInfo) Broadcast(mapid uint32, o WalkOutInfo) {
var limiter = rate.NewLimiter(rate.Limit(10), 1)
r := cool.Limiter.Get("Broadcast"+gconv.String(mapid), rate.Limit(10), 5)
if !r.Allow() {
func (t *WalkInInfo) Broadcast(mapid uint32, o WalkOutInfo) {
// cool.Limiter.Take()
//r := cool.Limiter.Get("Broadcast"+gconv.String(mapid), rate.Limit(10), 5)
if !limiter.Allow() {
return
}
space.GetSpace(mapid).User.IterCb(func(playerID uint32, player common.PlayerI) {

View File

@@ -1,9 +1,12 @@
package pet
import "blazing/logic/service/player"
// PetBargeListInboundInfo 对应Java的PetBargeListInboundInfo实现InboundMessage接口
type PetBargeListInboundInfo struct {
StartPetId uint32 `description:"开始精灵id" codec:"startPetId"` // @UInt long 对应Go的uint32无符号64位
EndPetId uint32 `description:"结束精灵id" codec:"endPetId"` // 字段标签模拟注解功能(描述、编解码标识
Head player.TomeeHeader `cmd:"2309" struc:"[0]pad"`
StartPetId uint32 `description:"开始精灵id" codec:"startPetId"` // @UInt long 对应Go的uint32无符号64位
EndPetId uint32 `description:"结束精灵id" codec:"endPetId"` // 字段标签模拟注解功能(描述、编解码标识)
}
// PetBargeListInfo 对应Java的PetBargeListInfo类

View File

@@ -1,6 +1,8 @@
package player
import "blazing/logic/service/fight/info"
import (
"blazing/logic/service/fight/info"
)
func (p *Player) SendAttackValue(b info.AttackValueS) {
t1 := NewTomeeHeader(2505, p.Info.UserID)
@@ -26,6 +28,7 @@ func (p *Player) SendNoteReadyToFightInfo(b info.NoteReadyToFightInfo) {
}
func (p *Player) SendFightEndInfo(b info.FightOverInfo) {
p.FightC = nil
//<-time.After(10000)
t1 := NewTomeeHeader(2506, p.Info.UserID)
p.SendPack(t1.Pack(&b))

View File

@@ -52,15 +52,16 @@ func beforeServeHook(r *ghttp.Request) {
r.Response.CORSDefault()
}
var limiter = rate.NewLimiter(rate.Limit(10), 1)
// Limiter is a middleware that implements rate limiting for all HTTP requests.
// It returns HTTP 429 (Too Many Requests) when the rate limit is exceeded.
func Limiter(r *ghttp.Request) {
// 3. 为任意键 "some-key" 获取一个速率限制器
// - rate.Limit(2): 表示速率为 "每秒2个请求"
// - 2: 表示桶的容量 (Burst)允许瞬时处理2个请求
rateLimiter := cool.Limiter.Get(r.GetClientIp(), rate.Limit(10), 5)
if !rateLimiter.Allow() {
if !limiter.Allow() {
r.Response.WriteStatusExit(429) // Return 429 Too Many Requests
r.ExitAll()
}

View File

@@ -18,8 +18,8 @@ import (
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/util/gconv"
"github.com/hashicorp/golang-lru/v2/expirable"
)
func (s *BaseSysLoginService) Reg(ctx context.Context, req *v1.BaseOpenLoginReq) (result *TokenResult, err error) {
@@ -56,7 +56,7 @@ func (s *BaseSysLoginService) Reg(ctx context.Context, req *v1.BaseOpenLoginReq)
// 10分钟内限制请求 ip->次数 邮箱->次数
// 验证码对redis传,防止被重复操作
// var emailcache = expirable.NewLRU[string, int](0, nil, time.Millisecond*10)
var ipcache = expirable.NewLRU[string, int](0, nil, time.Millisecond*10)
var ipcache = gcache.New(1000)
func (s *BaseSysLoginService) Email(ctx context.Context, req *v1.BaseOpenEmailReq) (result *TokenResult, err error) {
var (
@@ -65,14 +65,16 @@ func (s *BaseSysLoginService) Email(ctx context.Context, req *v1.BaseOpenEmailRe
)
ip := r.GetClientIp()
v, ok := ipcache.Get(ip)
if ok && v > 3 {
v, ok2 := ipcache.Get(context.Background(), ip)
if ok2 != nil || v.Int() > 3 {
return nil, gerror.New("操作过于频繁,请稍后再试")
}
ok, _ = share.ShareManager.EmailCodeExists(req.Email)
if ok {
ipcache.Set(context.Background(), ip, v.Int()+1, 10*time.Minute)
ok1, _ := share.ShareManager.EmailCodeExists(req.Email)
if ok1 {
return nil, gerror.New("注册码已下发 ")
}
// 发送验证码