You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
18 KiB
477 lines
18 KiB
package user
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
"yunyan/comm"
|
|
"yunyan/lego/core"
|
|
"yunyan/lego/core/cbase"
|
|
"yunyan/lego/sys/mysql"
|
|
"yunyan/lego/sys/postgres"
|
|
redissys "yunyan/lego/sys/redis"
|
|
"yunyan/pb"
|
|
"yunyan/utils/devcode"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// 代理模型
|
|
type modelUserComp struct {
|
|
cbase.ModuleCompBase
|
|
module *User
|
|
}
|
|
|
|
// 组件初始化接口
|
|
func (this *modelUserComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) {
|
|
this.ModuleCompBase.Init(service, module, comp, opt)
|
|
this.module = module.(*User)
|
|
if err = mysql.CreateTable(comm.TableUser, &pb.DBUser{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableUserdevice, &pb.DBUserDivice{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableUserStatistics, &pb.DBUserStatistics{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableUserUseLog, &pb.DBUserUseLog{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) findforid(uid string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "uid=?", uid)
|
|
return
|
|
}
|
|
func (this *modelUserComp) findformac(mac string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "phonemac=?", mac)
|
|
return
|
|
}
|
|
|
|
// findtouristformac 按设备硬件 id 找回**游客账号**。
|
|
//
|
|
// 不能直接用 findformac:phonemac 是所有登录方式建号时都会写的字段,
|
|
// 同一台设备上先用手机号登录过,再点游客登录就会串进那个正式账号里去。
|
|
// 所以这里额外要求所有身份标识都为空——只有游客账号满足这个条件。
|
|
func (this *modelUserComp) findtouristformac(mac string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user,
|
|
"phonemac=? AND phone='' AND mail='' AND account='' AND wxopenid='' "+
|
|
"AND googleopenid='' AND facebookopenid='' AND appleopenid=''", mac)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) findforphone(phone string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "phone=?", phone)
|
|
return
|
|
}
|
|
func (this *modelUserComp) findformail(mail string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "mail=?", mail)
|
|
return
|
|
}
|
|
func (this *modelUserComp) findforwx(openid string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "wxopenid=?", openid)
|
|
return
|
|
}
|
|
func (this *modelUserComp) findforgoogle(openid string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "googleopenid=?", openid)
|
|
return
|
|
}
|
|
func (this *modelUserComp) findforapple(openid string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "appleopenid=?", openid)
|
|
return
|
|
}
|
|
func (this *modelUserComp) findforfacebook(openid string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "facebookopenid=?", openid)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) add(user *pb.DBUser) (err error) {
|
|
err = mysql.Insert(comm.TableUser, user)
|
|
return
|
|
}
|
|
func (this *modelUserComp) save(user *pb.DBUser) (err error) {
|
|
err = mysql.Save(comm.TableUser, user)
|
|
return
|
|
}
|
|
|
|
// 软注销:清理用户业务数据 + 个人痕迹,保留身份锚点与 Isgiveaway
|
|
// 保留 user 行作为防刷锚点,下次同一身份再登录时可复活
|
|
func (this *modelUserComp) cancel(user *pb.DBUser) (err error) {
|
|
uid := user.Uid
|
|
// 1) 删除用户模块自己管的三张表(按 uid)
|
|
if err = mysql.Delete(comm.TableUserdevice, "uid=?", uid); err != nil {
|
|
return
|
|
}
|
|
if err = mysql.Delete(comm.TableUserStatistics, "uid=?", uid); err != nil {
|
|
return
|
|
}
|
|
if err = mysql.Delete(comm.TableUserUseLog, "uid=?", uid); err != nil {
|
|
return
|
|
}
|
|
// 2) 跨模块:allhelp_task 表、chat_summary 表、echomeet_record 表(同一 ServiceDB)
|
|
if err = mysql.Delete(comm.TableAllhelpTask, "uid=?", uid); err != nil {
|
|
return
|
|
}
|
|
if err = mysql.Delete(comm.TableChatSummary, "uid=?", uid); err != nil {
|
|
return
|
|
}
|
|
if err = mysql.Delete(comm.TableEchomeetRecord, "uid=?", uid); err != nil {
|
|
return
|
|
}
|
|
// 3) 清理 DBUser 上的业务字段与 PII,保留身份锚点、Createtime、Isgiveaway
|
|
user.Name = ""
|
|
user.Avatar = ""
|
|
user.Phonemodel = ""
|
|
user.Phonemac = ""
|
|
user.Viplv = 0
|
|
user.Vipexptime = 0
|
|
user.Aichatintegral = 0
|
|
user.Aichattotalintegral = 0
|
|
user.Tradeintegral = 0
|
|
user.Tradetotalintegral = 0
|
|
user.Meetintegral = 0
|
|
user.Meettotalintegral = 0
|
|
user.State = pb.UserState_Cancelled
|
|
user.Canceltime = time.Now().Unix()
|
|
// 保留:Uid / Phone / Mail / Wxopenid / Googleopenid / Facebookopenid / Appleopenid
|
|
// Createtime / Isgiveaway / Isactivatecode / Language / Channel / Version / Lastsgintime
|
|
err = mysql.Save(comm.TableUser, user)
|
|
return
|
|
}
|
|
|
|
// 添加设备
|
|
func (this *modelUserComp) addDevice(divice *pb.DBUserDivice) (err error) {
|
|
err = mysql.Insert(comm.TableUserdevice, divice)
|
|
return
|
|
}
|
|
|
|
// 获取用户设备列表
|
|
func (this *modelUserComp) getDevices(uid string) (divices []*pb.DBUserDivice, err error) {
|
|
divices = make([]*pb.DBUserDivice, 0)
|
|
err = mysql.Find(comm.TableUserdevice, &divices, "uid=?", uid)
|
|
return
|
|
}
|
|
|
|
// 获取用户设备列表
|
|
func (this *modelUserComp) getDevice(id uint64) (divice *pb.DBUserDivice, err error) {
|
|
divice = &pb.DBUserDivice{}
|
|
err = mysql.FindOne(comm.TableUserdevice, divice, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 保存用户设备
|
|
func (this *modelUserComp) saveDevice(divice *pb.DBUserDivice) (err error) {
|
|
err = mysql.Save(comm.TableUserdevice, divice)
|
|
return
|
|
}
|
|
|
|
// 获取用户设备列表
|
|
func (this *modelUserComp) delDevice(id uint64) (err error) {
|
|
err = mysql.Delete(comm.TableUserdevice, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// -------------------
|
|
// 合作商绑定
|
|
//
|
|
// 设备/授权码统一住在**全局一张** device_mac 表里(2026-09-04 由 license_<pid> 分表合并而来)。
|
|
// 主键 code 就是设备 MAC,本来就全局唯一,所以定位一台设备**不需要也不应该带 pid**——
|
|
// pid 是从查到的那一行的 productid 列**读出来的结果**,不是查询条件。
|
|
//
|
|
// 分表时代的教训:客户端得先猜出 pid 才能校验设备,猜错就报「这台 MAC 没登记在该产品下」,
|
|
// 提示词指向数据缺失、实际是 pid 错了,极难排查。详见 registry.go 的 migrateLicenseToDeviceMac。
|
|
//
|
|
// 这两个函数是绑定/解绑路径上的"读后写同一行"校验读,用 FindOnePrimary 强制走主库,
|
|
// 避免读到只读副本复制延迟内的旧状态造成重复绑定竞态。
|
|
|
|
// getFactoryDevice 按授权码(= 主键 code)取设备行。
|
|
func (this *modelUserComp) getFactoryDevice(code string) (model *pb.DBAuthCode, err error) {
|
|
model = &pb.DBAuthCode{}
|
|
err = postgres.FindOnePrimary(comm.TableDeviceMac, model, "code=?", code)
|
|
return
|
|
}
|
|
|
|
// getFactoryDeviceformac 按 MAC 全局反查这台设备。**恒玄耳机的绑定校验就走这里**。
|
|
//
|
|
// devicemac 是精确字符串匹配,两端的写法必须逐字一致。落库形态统一是「大写 + 冒号」
|
|
// (mac.Generate 的输出,导入模式也已规范化成同一形态),客户端 Android 侧送上来的
|
|
// BluetoothDevice.getAddress() 本来也是这个形态。但两端各自演进容易走偏(换 SDK、
|
|
// iOS 补上 MAC 来源、运维手工塞数据…),一旦写法差一个大小写或少个冒号,
|
|
// 表现是「这台 MAC 没登记」——提示词指向数据缺失,实际是格式不匹配。
|
|
//
|
|
// 所以这里多加一次兜底:精确匹配没中,就把入参规范化后再查一次。
|
|
// 顺序不能反——先精确保证存量非规范数据(如果有)仍然查得到,规范化只用于补救。
|
|
func (this *modelUserComp) getFactoryDeviceformac(devicemac string) (model *pb.DBAuthCode, err error) {
|
|
model = &pb.DBAuthCode{}
|
|
if err = postgres.FindOnePrimary(comm.TableDeviceMac, model, "devicemac=?", devicemac); err == nil {
|
|
return model, nil
|
|
}
|
|
norm, ok := devcode.NormalizeMac(devicemac)
|
|
if !ok || norm == devicemac {
|
|
return model, err // 规范化不了,或本来就是规范形态:保留第一次的错误
|
|
}
|
|
model = &pb.DBAuthCode{}
|
|
err = postgres.FindOnePrimary(comm.TableDeviceMac, model, "devicemac=?", norm)
|
|
return
|
|
}
|
|
|
|
// getProductionBatch 按批次号取生产批次台账(console 主库与 license 分表同一个 postgres)。
|
|
// 绑定赠送已改成按批次配置,绑定时靠它取这一批送什么。
|
|
// batchno 为空(存量授权码没有批次号)直接返回 nil,由调用方回退到产品上的旧字段。
|
|
func (this *modelUserComp) getProductionBatch(batchno string) (model *pb.DBProductionBatch, err error) {
|
|
if strings.TrimSpace(batchno) == "" {
|
|
return nil, nil
|
|
}
|
|
model = &pb.DBProductionBatch{}
|
|
if err = postgres.FindOne(comm.TableProductionBatch, model, "batchno=?", batchno); err != nil {
|
|
return nil, err
|
|
}
|
|
return model, nil
|
|
}
|
|
|
|
func (this *modelUserComp) saveFactoryDevice(code *pb.DBAuthCode) (err error) {
|
|
err = postgres.Save(comm.TableDeviceMac, code)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) saveCode(addr string, code string) (err error) {
|
|
err = redissys.Conn().Set(context.Background(), redissys.RKey(fmt.Sprintf("%s:%s", comm.Redis_Verification, addr)), code, time.Minute*2).Err()
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) getCode(addr string) (code string, err error) {
|
|
code, err = redissys.Conn().Get(context.Background(), redissys.RKey(fmt.Sprintf("%s:%s", comm.Redis_Verification, addr))).Result()
|
|
return
|
|
}
|
|
|
|
// 以下三个产品读取口的结果都会下发到客户端,出库即过 compatProduct 回填老客户端的 factoryid 字段(见 compat.go)。
|
|
func (this *modelUserComp) getProduct(id int32) (model *pb.DBProduct, err error) {
|
|
model = &pb.DBProduct{}
|
|
err = postgres.FindOne(comm.TableProduct, model, "id=?", id)
|
|
compatProduct(model)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) getProducts() (models []*pb.DBProduct, err error) {
|
|
models = make([]*pb.DBProduct, 0)
|
|
err = postgres.Find(comm.TableProduct, &models, "")
|
|
compatProducts(models)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) getProductForName(devicename string) (model *pb.DBProduct, err error) {
|
|
model = &pb.DBProduct{}
|
|
err = postgres.FindOne(comm.TableProduct, model, "devicename=?", devicename)
|
|
compatProduct(model)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) getProductVersions(pid uint32) (models []*pb.DBProductVersion, err error) {
|
|
models = make([]*pb.DBProductVersion, 0)
|
|
err = postgres.Find(comm.TableProductVersion, &models, "productid=?", pid)
|
|
return
|
|
}
|
|
|
|
// 渠道分发配置的数据源已从「应用业务库 mysql 的 channelapp 表」迁到「console 主库 postgres 的
|
|
// channel_app 表」(comm.ChannelApp),按 app_name 作用域读、应用行覆盖全局行——与 agent / 第三方服务
|
|
// 下发同语义。下发给客户端的仍是 pb.DBChannelApp,由 toPbChannelApp 做映射。
|
|
|
|
// loadChannelApps 读本应用作用域 + 全局默认的全部渠道配置行。
|
|
// 应用名走 comm.AppName()——即 .env 的 ANALYZE_APP_NAME(APP_NAME 已于 2026-08-12 删除,
|
|
// 别再照旧注释去找)。它须与 app_registry 的 app_name 列一致,否则读不到自己那一层配置、
|
|
// 只剩全局默认行。
|
|
func (this *modelUserComp) loadChannelApps() ([]*comm.ChannelApp, error) {
|
|
app := comm.AppName()
|
|
rows := make([]*comm.ChannelApp, 0)
|
|
err := postgres.Find(comm.TableChannelAppCfg, &rows, "(app_name=? OR app_name='')", app)
|
|
if err != nil && err != postgres.ErrNoDocuments {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
// loadAppRelease 取该应用生效的发版配置(应用行覆盖全局行);都没配返回 nil。
|
|
//
|
|
// 版本/强更/送审版本/游客显隐这几项按**应用**存(不分渠道),见 comm.AppRelease。
|
|
func (this *modelUserComp) loadAppRelease() (*comm.AppRelease, error) {
|
|
app := comm.AppName()
|
|
rows := make([]*comm.AppRelease, 0)
|
|
err := postgres.Find(comm.TableAppRelease, &rows, "(app_name=? OR app_name='')", app)
|
|
if err != nil && err != postgres.ErrNoDocuments {
|
|
return nil, err
|
|
}
|
|
return comm.PickAppRelease(rows, app), nil
|
|
}
|
|
|
|
// toPbChannelApp 把后台配置映射成下发结构;两张表都没配才返回 nil。
|
|
//
|
|
// **下发结构是两张表拼出来的**:因渠道而异的(下载地址/上架状态/支付渠道/跳过绑定)来自
|
|
// [comm.ChannelApp],整个应用一套的(版本/强更/更新日志/送审版本/游客显隐)来自
|
|
// [comm.AppRelease]。任一为 nil 就只填另一边,对应字段留零值 ——
|
|
// 客户端据此走「无更新 / 不显示入口」分支,不会因为配置缺失就把人挡在门外。
|
|
//
|
|
// ⚠️ **渠道行不存在时绝不能整个返回 nil**。应用级的开关(尤其是游客登录)与渠道无关,
|
|
// 一旦让它取决于「渠道分发里有没有这一行」,就等于把刚拆开的耦合又接了回去:
|
|
// 运营在「游客显隐控制」里明明配好了,只因为没给这台设备的品牌渠道建过行,
|
|
// 入口就永远不出现,而且不报任何错。这个坑真踩过,别改回去。
|
|
//
|
|
// channel 由调用方给(而不是从 c 里取):渠道行缺失时 c 是 nil,但客户端仍然需要知道
|
|
// 这条回的是哪个渠道。
|
|
//
|
|
// clientVer 是客户端上报的当前版本号,只用于把 Tourists 从「后台的原始配置」换算成
|
|
// 「这一版客户端到底该不该显示游客登录入口」——下发的 Tourists 是结论而非配置,
|
|
// 客户端直接照着显隐即可,不需要也不应该再拿 Touristsversion 自己比一遍。
|
|
// Touristsversion 仍原样下发,仅供排查「为什么这版看不到入口」。
|
|
func toPbChannelApp(channel int32, c *comm.ChannelApp, rel *comm.AppRelease, clientVer string) *pb.DBChannelApp {
|
|
if c == nil && rel == nil {
|
|
return nil
|
|
}
|
|
out := &pb.DBChannelApp{Channel: channel}
|
|
channelAddress := ""
|
|
if c != nil {
|
|
out.Description = c.Description
|
|
out.Allowskipdevicebinding = c.AllowSkipDeviceBinding
|
|
out.Paychannels = c.PayChannels
|
|
out.Status = c.Status
|
|
channelAddress = c.Address
|
|
}
|
|
// 渠道填了本渠道商店链接就用它,否则回退应用默认地址。
|
|
out.Address = rel.DownloadAddress(channelAddress)
|
|
if rel != nil {
|
|
out.Version = rel.Version
|
|
out.Minversion = rel.MinVersion
|
|
out.Forceupdate = rel.ForceUpdate
|
|
out.Changelog = rel.Changelog
|
|
out.Reviewversion = rel.ReviewVersion
|
|
out.Tourists = rel.ShowTourists(clientVer)
|
|
out.Touristsversion = rel.TouristsVersion
|
|
}
|
|
return out
|
|
}
|
|
|
|
// getChannelAppCfg 取该渠道生效的原始配置(应用行覆盖全局行);未配置或处于草稿态返回 nil。
|
|
// 返回 comm.ChannelApp 而非 pb 结构,是为了让接口层能用 NeedForceUpdate/IsReviewing 做升级判定。
|
|
func (this *modelUserComp) getChannelAppCfg(channel int32) (*comm.ChannelApp, error) {
|
|
rows, err := this.loadChannelApps()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
picked := comm.PickChannelApp(rows, channel)
|
|
if picked == nil || !picked.Enable || picked.Status == comm.ChannelStatusDraft {
|
|
return nil, nil
|
|
}
|
|
return picked, nil
|
|
}
|
|
|
|
func (this *modelUserComp) getChannelApp(channel int32, clientVer string) (model *pb.DBChannelApp, err error) {
|
|
cfg, err := this.getChannelAppCfg(channel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rel, err := this.loadAppRelease()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return toPbChannelApp(channel, cfg, rel, clientVer), nil
|
|
}
|
|
|
|
// getChannelApps 下发全部渠道:同一渠道应用行覆盖全局行;草稿状态不下发。
|
|
// clientVer 供游客登录的版本限定判定,见 toPbChannelApp。
|
|
//
|
|
// 发版配置只查一次,拼进每一条渠道记录 —— 它是按应用存的,与渠道无关。
|
|
func (this *modelUserComp) getChannelApps(clientVer string) (models []*pb.DBChannelApp, err error) {
|
|
rows, err := this.loadChannelApps()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rel, err := this.loadAppRelease()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seen := map[int32]bool{}
|
|
models = make([]*pb.DBChannelApp, 0, len(rows))
|
|
for _, r := range rows {
|
|
if seen[r.Channel] {
|
|
continue
|
|
}
|
|
seen[r.Channel] = true
|
|
picked := comm.PickChannelApp(rows, r.Channel)
|
|
if picked == nil || !picked.Enable || picked.Status == comm.ChannelStatusDraft {
|
|
continue
|
|
}
|
|
models = append(models, toPbChannelApp(picked.Channel, picked, rel, clientVer))
|
|
}
|
|
return models, nil
|
|
}
|
|
func (this *modelUserComp) getStatistics(uid string) (statistics *pb.DBUserStatistics, err error) {
|
|
statistics = &pb.DBUserStatistics{Uid: uid}
|
|
mysql.FindOne(comm.TableUserStatistics, statistics, "uid=?", uid)
|
|
return
|
|
}
|
|
|
|
func (this *modelUserComp) updateStatistics(statistics *pb.DBUserStatistics) (err error) {
|
|
err = mysql.Table(comm.TableUserStatistics).
|
|
Clauses(clause.OnConflict{UpdateAll: true}).
|
|
Create(statistics).Error
|
|
return
|
|
}
|
|
|
|
// 添加积分日志
|
|
func (this *modelUserComp) addIntegralLog(log *pb.DBUserUseLog) (err error) {
|
|
err = mysql.Insert(comm.TableUserUseLog, log)
|
|
return
|
|
}
|
|
|
|
// -------------------
|
|
// 厂家公码(公码定义在 AdminDB;防重领靠 DBUser.Isgiveaway,审计靠 DBUserUseLog)
|
|
func (this *modelUserComp) getFactoryPublicCode(code string) (model *pb.DBFactoryPublicCode, err error) {
|
|
model = &pb.DBFactoryPublicCode{}
|
|
err = postgres.FindOne(comm.TableFactoryPublicCode, model, "code=?", code)
|
|
return
|
|
}
|
|
|
|
// 原子自增 useduses 并防超发:仅当 maxuses=0 或 useduses<maxuses 时才允许 +1
|
|
// 返回 affected:0 表示已达上限或公码不存在,1 表示扣减成功
|
|
func (this *modelUserComp) incrFactoryPublicCodeUsed(code string) (affected int64, err error) {
|
|
tx := postgres.Table(comm.TableFactoryPublicCode).
|
|
Where("code=? AND (maxuses=0 OR useduses<maxuses)", code).
|
|
UpdateColumn("useduses", gorm.Expr("useduses+1"))
|
|
affected = tx.RowsAffected
|
|
err = tx.Error
|
|
return
|
|
}
|
|
|
|
// 原子置位 user.isactivatecode:仅当当前为 false 时才允许置 true,
|
|
// 用于公码扫码的并发兜底(同账号同时发起多次扫码请求时只过一个)
|
|
func (this *modelUserComp) tryMarkActivateCode(uid string) (affected int64, err error) {
|
|
tx := mysql.Table(comm.TableUser).
|
|
Where("uid=? AND isactivatecode=?", uid, false).
|
|
UpdateColumn("isactivatecode", true)
|
|
affected = tx.RowsAffected
|
|
err = tx.Error
|
|
return
|
|
}
|
|
|
|
// 回滚 user.isactivatecode:在公码核销失败(如撞上限)时调用,仅当前为 true 才能改回 false
|
|
func (this *modelUserComp) rollbackActivateCode(uid string) (affected int64, err error) {
|
|
tx := mysql.Table(comm.TableUser).
|
|
Where("uid=? AND isactivatecode=?", uid, true).
|
|
UpdateColumn("isactivatecode", false)
|
|
affected = tx.RowsAffected
|
|
err = tx.Error
|
|
return
|
|
}
|
|
|