package db import ( "encoding/base64" "encoding/json" "errors" "fmt" "math/rand" "reflect" "strings" "sync" "time" "xiawan/wx/api/req" "xiawan/wx/clientsdk/baseinfo" "xiawan/wx/db/table" "xiawan/wx/protobuf/wechat" pb "xiawan/wx/protobuf/wechat" "xiawan/wx/srv" "xiawan/wx/srv/srvconfig" "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "github.com/lunny/log" ) // MysqlDB MySQL数据库实例 var ( MysqlDB *gorm.DB dbMutex sync.Mutex // 用于保护连接重置的互斥锁 contactMutex sync.Mutex // 联系人存储锁 mutexMap = make(map[string]*sync.Mutex) // 联系人存储锁 maxRetry = 10 // 最大重试次数 retryDelay = time.Second * 2 // 每次重试的间隔时间 ) // 联系人存储锁,uuid 区分 func getContactMutex(uuid string) *sync.Mutex { contactMutex.Lock() defer contactMutex.Unlock() m, exists := mutexMap[uuid] if !exists { m = &sync.Mutex{} mutexMap[uuid] = m } return m } // InitDB 初始化数据库 func InitDB() { dbMutex.Lock() // 加锁,防止并发重连 defer dbMutex.Unlock() var mysqlUrl = srvconfig.GlobalSetting.MysqlConnectStr var err error for i := 0; i < maxRetry; i++ { MysqlDB, err = gorm.Open("mysql", mysqlUrl) if err == nil { break } fmt.Printf("Failed to connect MySQL, retrying... (%d/%d)\n", i+1, maxRetry) time.Sleep(retryDelay) } if err != nil { log.Errorf("Failed to connect MySQL after %d attempts: %v", maxRetry, err) return } MysqlDB.DB().SetConnMaxLifetime(time.Minute * 5) //设置连接池中的最大闲置连接数 MysqlDB.DB().SetMaxIdleConns(10) //设置数据库的最大连接数量 MysqlDB.DB().SetMaxOpenConns(120) fmt.Println("connect MySQL success") MysqlDB.SingularTable(true) MysqlDB.AutoMigrate(&table.UserInfoEntity{}) //用户信息 自动建表 MysqlDB.AutoMigrate(&table.DeviceInfoEntity{}) //设备信息 自动建表 MysqlDB.AutoMigrate(&table.UserLoginLog{}) //登录日志 自动建表 MysqlDB.AutoMigrate(&table.UserBusinessLog{}) //用户 自动建表 MysqlDB.AutoMigrate(&table.LicenseKey{}) //自动建表 MysqlDB.AutoMigrate(&table.UserMessageLog{}) //自动建表 MysqlDB.AutoMigrate(&table.Command{}) //自动建表 MysqlDB.AutoMigrate(&table.ModContactDB{}) //自动建表 MysqlDB.AutoMigrate(&table.AddMsgDB{}) //自动建表 MysqlDB.AutoMigrate(&table.ProxyMapping{}) //代理映射表 自动建表 MysqlDB.AutoMigrate(&table.MessageCallbackConfig{}) //消息回调配置表 自动建表 fmt.Println("auto create MySQL tables success") MysqlDB.LogMode(false) // 注意:回调配置的初始化移到了 InitAnewLogin 完成后 // 这样可以确保所有账号连接都已建立 } // InitMessageCallbacks 初始化所有消息回调配置 func InitMessageCallbacks() { // 账号已经初始化完成,稍微等待一下确保连接稳定 time.Sleep(3 * time.Second) log.Infof("开始初始化消息回调配置...") // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { log.Errorf("Failed to initialize message callbacks: %v", err) return } // 获取所有启用的回调配置 var configs []table.MessageCallbackConfig result := MysqlDB.Where("enabled = ?", true).Find(&configs) if result.Error != nil { log.Errorf("Failed to query enabled callbacks: %v", result.Error) return } if len(configs) == 0 { log.Infof("未找到启用的消息回调配置") return } log.Infof("找到 %d 个启用的回调配置,开始重新激活...", len(configs)) // 重新保存每个配置,相当于重新设置一遍,激活回调 successCount := 0 for _, config := range configs { // 确保Key字段正确 if config.Key == "" { config.Key = config.UUID } // 重新保存配置(相当于执行 SetCallback 操作) err := SaveMessageCallbackConfig(&config) if err != nil { log.Errorf("✗ 重新激活回调失败 [UUID: %s]: %v", config.UUID, err) } else { log.Infof("✓ 重新激活回调成功 [UUID: %s] -> %s", config.UUID, config.CallbackURL) successCount++ } } log.Infof("========================================") log.Infof("回调配置激活完成: 成功 %d/%d", successCount, len(configs)) log.Infof("========================================") } // checkAndReconnect 检查并在需要时重新连接 func checkAndReconnect() error { if MysqlDB == nil { InitDB() // 初始化连接 } // Ping数据库,验证连接是否有效 if err := MysqlDB.DB().Ping(); err != nil { log.Error("Database connection lost, reconnecting...") InitDB() // 重新连接 if MysqlDB == nil { return errors.New("failed to reconnect to database") } } return nil } // Command 更新命令 func UpdateCommand(uuid string, key string, intValue int, strValue string) (bool, error) { fmt.Println("UpdateCommand uuid:", uuid, "key:", key, "intValue:", intValue, "strValue:", strValue) // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return false, err } commandInfo := &table.Command{ UUID: uuid, } if err := MysqlDB.Model(commandInfo).Where("uuid = ?", uuid).First(&commandInfo).Error; err != nil { if err == gorm.ErrRecordNotFound { commandInfo.UUID = uuid commandInfo.A301Str = "" if err := MysqlDB.Create(&commandInfo).Error; err != nil { // 创建失败 return false, err } // 创建成功,继续执行 } // 错误 return false, err } // 记录找到 if key == "A101" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a101": intValue, }) return true, nil } if key == "A102" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a102": intValue, }) return true, nil } if key == "A103" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a103": intValue, }) return true, nil } if key == "A104" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a104": intValue, }) return true, nil } if key == "A104Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a104_str": strValue, }) return true, nil } if key == "A105" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a105": intValue, }) return true, nil } if key == "A106" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a106": intValue, }) return true, nil } if key == "A107" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a107": intValue, }) return true, nil } if key == "A109" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a109": intValue, }) return true, nil } if key == "A111" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a111": intValue, }) return true, nil } if key == "A116" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a116": intValue, }) return true, nil } if key == "A116Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a116_str": strValue, }) return true, nil } if key == "A118" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a118": intValue, }) return true, nil } if key == "A118Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a118_str": strValue, }) return true, nil } if key == "A301" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a301": intValue, }) return true, nil } if key == "A301Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a301_str": strValue, }) return true, nil } if key == "A401" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a401": intValue, }) return true, nil } if key == "A402" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a402": intValue, }) return true, nil } if key == "A403" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a403": intValue, }) return true, nil } if key == "A601" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a601": intValue, }) return true, nil } if key == "A801" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a801": intValue, }) return true, nil } if key == "B001" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b001": intValue, }) return true, nil } if key == "B001Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b001_str": strValue, }) return true, nil } if key == "A811" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "a811": intValue, }) return true, nil } if key == "B002" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b002": intValue, }) return true, nil } if key == "B002Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b002_str": strValue, }) return true, nil } if key == "B003" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b003": intValue, }) return true, nil } if key == "B003Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b003_str": strValue, }) return true, nil } if key == "B004" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b004": intValue, }) return true, nil } if key == "B004Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b004_str": strValue, }) return true, nil } if key == "B005" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b005": intValue, }) return true, nil } if key == "B005Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b005_str": strValue, }) return true, nil } if key == "B006" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b006": intValue, }) return true, nil } if key == "B006Str" { MysqlDB.Model(&table.Command{}).Where("uuid = ? ", uuid).Updates(map[string]interface{}{ "b006_str": strValue, }) return true, nil } return true, nil } // Command 根据 uuid 查询命令 func QueryCommand(uuid string) (table.Command, error) { commandInfo := &table.Command{ UUID: uuid, } // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return *commandInfo, err } if err := MysqlDB.Model(commandInfo).Where("uuid = ?", uuid).First(&commandInfo).Error; err != nil { if err == gorm.ErrRecordNotFound { commandInfo.UUID = uuid commandInfo.A118Str = "词1,词2,词3" commandInfo.A116Str = "" commandInfo.A301Str = "" commandInfo.B001Str = "" commandInfo.B002Str = "" commandInfo.B003Str = "" if err := MysqlDB.Create(&commandInfo).Error; err != nil { // 创建失败 return *commandInfo, err } // 创建成功,继续执行 } // 错误 return *commandInfo, err } // 记录找到 return *commandInfo, nil } // 提交登录日志 func SetLoginLog(loginType string, acc *srv.WXAccount, errMsg string, state int32) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } var userName string userInfo := acc.GetUserInfo() if len(userInfo.LoginDataInfo.UserName) > 0 { userName = userInfo.LoginDataInfo.UserName } else { userName = userInfo.WxId } // 更新登录时间信息 UpdateLoginTimeInfo(userInfo) userLog := &table.UserLoginLog{ UUId: userInfo.UUID, UserName: userName, NickName: userInfo.NickName, LoginType: loginType, RetCode: state, ErrMsg: errMsg, } userLog.TargetIp = srvconfig.GlobalSetting.TargetIp MysqlDB.Save(userLog) } // UpdateLoginTimeInfo 更新用户的登录时间相关信息 func UpdateLoginTimeInfo(userInfo *baseinfo.UserInfo) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } _ = MysqlDB.Model(&table.UserInfoEntity{}). Where("wxId = ? ", userInfo.WxId). Updates(map[string]interface{}{ "device_create_time": userInfo.DeviceCreateTime, "last_login_time": userInfo.LastLoginTime, "last_auth_time": userInfo.LastAuthTime, }) } // 获取登录日志 func GetLoginJournal(userName string) []table.UserLoginLog { logs := make([]table.UserLoginLog, 0) // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return logs } MysqlDB.Where("user_name=?", userName).Find(&logs) return logs } // 获取登录错误信息 func GetUSerLoginErrMsg(userName string) string { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return "" } userInfoEntity := &table.UserInfoEntity{ WxId: userName, } MysqlDB.Model(userInfoEntity).First(userInfoEntity) return userInfoEntity.ErrMsg } // 保存登录状态 func UpdateLoginStatus(userName string, state int32, errMsg string) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } data := make(map[string]interface{}) data["State"] = state //零值字段 data["ErrMsg"] = errMsg v := GetUserInfoEntityByWxId(userName) // *table.UserInfoEntity if v != nil && v.State != state { MysqlDB.Model(&table.UserInfoEntity{ WxId: userName, }).Update(data) _ = PublishSyncMsgLoginState(userName, uint32(state)) } } // 保存初始化联系人结果 func UpdateInitContactStatus(uuid string, state int32) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } _ = MysqlDB.Model(&table.UserInfoEntity{}). Where("uuid = ? ", uuid). Updates(map[string]interface{}{ "initcontact": state, }) } // 查询初始化是否完成 func QueryInitContactStatus(uuid string) int32 { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return 1 } var userInfoEntity table.UserInfoEntity // 获取消息 if err := MysqlDB.Where("uuid = ?", uuid).First(&userInfoEntity).Error; err != nil { return 1 // 发生其他错误 } if userInfoEntity.InitContact == 0 { fmt.Println("["+uuid+"]", "开始初始化联系人") } else { fmt.Println("["+uuid+"]", "无需初始化联系人") } return userInfoEntity.InitContact } // 更新同步消息Key func UpdateSyncMsgKey() { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } } // 更新用户信息 func UpdateUserInfo(userInfo *baseinfo.UserInfo) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } if userInfo.WxId == "" { userInfo.WxId = userInfo.GetUserName() } var userInfoEntity table.UserInfoEntity SimpleCopyProperties(&userInfoEntity, userInfo) userInfoEntity.AutoAuthKey = base64.StdEncoding.EncodeToString(userInfo.AutoAuthKey) userInfoEntity.SyncKey = base64.StdEncoding.EncodeToString(userInfo.SyncKey) userInfoEntity.FavSyncKey = base64.StdEncoding.EncodeToString(userInfo.FavSyncKey) userInfoEntity.TargetIp = srvconfig.GlobalSetting.TargetIp userInfoEntity.UserName = userInfo.LoginDataInfo.UserName userInfoEntity.Password = userInfo.LoginDataInfo.PassWord userInfoEntity.State = int32(userInfo.GetLoginState()) // userInfoEntity.Proxy = userInfo.ProxyInfo.ProxyUrl userInfoEntity.ClientVersion = userInfo.ClientVersion if !(int(userInfoEntity.ClientVersion) > 0) { userInfoEntity.ClientVersion = baseinfo.ClientVersion } // log.Infof("更新用户信息:%v", userInfoEntity.ClientVersion) MysqlDB.Model(new(table.UserInfoEntity)).Update(&userInfoEntity) } // 更新用户信息,手机号和微信号 func UpdateUserInfoByPhone(modUserInfo *wechat.ModUserInfo) { // 用户信息为空 if modUserInfo == nil { return } // 微信id 为空 if modUserInfo.GetUserName() == nil { return } // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } wxId := modUserInfo.GetUserName().GetStr() bindmobile := "" if modUserInfo.GetBindMobile() != nil { bindmobile = modUserInfo.GetBindMobile().GetStr() } alias := modUserInfo.GetAlias() MysqlDB.Model(new(table.UserInfoEntity)).Where("wxId = ?", wxId).Updates(map[string]interface{}{ "bindmobile": bindmobile, "alias": alias, }) // wx_id MysqlDB.Model(new(table.LicenseKey)).Where("wx_id = ?", wxId).Updates(map[string]interface{}{ "bindmobile": bindmobile, "alias": alias, }) } // SaveUserInfo 保存用户信息 func SaveUserInfo(userInfo *baseinfo.UserInfo) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } if userInfo.WxId == "" { userInfo.WxId = userInfo.GetUserName() } var userInfoEntity table.UserInfoEntity SimpleCopyProperties(&userInfoEntity, userInfo) userInfoEntity.AutoAuthKey = base64.StdEncoding.EncodeToString(userInfo.AutoAuthKey) userInfoEntity.SyncKey = base64.StdEncoding.EncodeToString(userInfo.SyncKey) userInfoEntity.FavSyncKey = base64.StdEncoding.EncodeToString(userInfo.FavSyncKey) userInfoEntity.TargetIp = srvconfig.GlobalSetting.TargetIp userInfoEntity.UserName = userInfo.LoginDataInfo.UserName userInfoEntity.Password = userInfo.LoginDataInfo.PassWord MysqlDB.Save(&userInfoEntity) //判断是62还是A16 if strings.HasPrefix(userInfo.LoginDataInfo.LoginData, "A") { //A16存redis key := fmt.Sprintf("%s%s", "wechat:a16DeviceInfo:", userInfo.WxId) error := SETExpirationObj(key, &userInfo.DeviceInfoA16, 60*60*24*15) if error != nil { log.Error("保存redis is error=" + error.Error()) } } else { //62存DB var deviceInfoEntity table.DeviceInfoEntity deviceInfoEntity.WxId = userInfo.WxId SimpleCopyProperties(&deviceInfoEntity, userInfo.DeviceInfo) deviceInfoEntity.SoftTypeXML = base64.StdEncoding.EncodeToString([]byte(deviceInfoEntity.SoftTypeXML)) deviceInfoEntity.ClientCheckDataXML = base64.StdEncoding.EncodeToString([]byte(deviceInfoEntity.ClientCheckDataXML)) MysqlDB.Save(&deviceInfoEntity) } } // 获取所有登录用户 func QueryListUserInfo() []table.UserInfoEntity { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return make([]table.UserInfoEntity, 0) } // 判断 srvconfig.GlobalSetting.TargetIp 相同 var userInfoEntity = make([]table.UserInfoEntity, 0) MysqlDB.Where("state = ? OR (state = 2 AND wxId IS NOT NULL AND wxId != '' AND autoauthkey IS NOT NULL AND autoauthkey != '')", 1).Find(&userInfoEntity) for _, v := range userInfoEntity { if v.State == 2 { v.State = 1 } } // 过滤 var newUserInfoEntity = make([]table.UserInfoEntity, 0) for _, v := range userInfoEntity { if srvconfig.GlobalSetting.TargetIp == "" || v.TargetIp == srvconfig.GlobalSetting.TargetIp { newUserInfoEntity = append(newUserInfoEntity, v) } } return newUserInfoEntity } // 获取所有用户 func QueryUserList() []table.UserInfoEntity { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return make([]table.UserInfoEntity, 0) } var userInfoEntity = make([]table.UserInfoEntity, 0) MysqlDB.Where("1=1").Find(&userInfoEntity) return userInfoEntity } // GetUserInfoEntity 获取登录信息 func GetUserInfoEntity(uuid string) *table.UserInfoEntity { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } var userInfoEntity table.UserInfoEntity if err := MysqlDB.Where("uuid=?", uuid).First(&userInfoEntity).Error; err != nil { return nil } return &userInfoEntity } // GetUserInfoEntity 获取登录信息 func GetUserInfoEntityByWxId(wxId string) *table.UserInfoEntity { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } var userInfoEntity table.UserInfoEntity if err := MysqlDB.Where("wxId=?", wxId).First(&userInfoEntity).Error; err != nil { return nil } return &userInfoEntity } // GetUserInfo 从数据库获取UserInfo func GetUserInfo(uuid string) *baseinfo.UserInfo { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } userInfo := &baseinfo.UserInfo{} var userInfoEntity table.UserInfoEntity userInfoEntity.UUID = uuid if err := MysqlDB.First(&userInfoEntity).Error; err != nil { return nil } var deviceInfoEntity table.DeviceInfoEntity deviceInfoEntity.WxId = userInfoEntity.WxId if err := MysqlDB.First(&deviceInfoEntity).Error; err != nil { return nil } key := fmt.Sprintf("%s%s", "wechat:a16DeviceInfo:", userInfoEntity.WxId) //判断是62还是A16 exists, _ := Exists(key) if exists { //A16存redis deviceInfoA16 := &baseinfo.AndroidDeviceInfo{} error := GETObj(key, &deviceInfoA16) if error != nil { log.Error("保存redis is error=" + error.Error()) } userInfo.DeviceInfoA16 = deviceInfoA16 } else { deviceInfo := &baseinfo.DeviceInfo{} SimpleCopyProperties(deviceInfo, deviceInfoEntity) decodeSoftTypeXML, _ := base64.StdEncoding.DecodeString(deviceInfo.SoftTypeXML) deviceInfo.SoftTypeXML = string(decodeSoftTypeXML) decodeClientCheckDataXML, _ := base64.StdEncoding.DecodeString(deviceInfo.ClientCheckDataXML) deviceInfo.ClientCheckDataXML = string(decodeClientCheckDataXML) userInfo.DeviceInfo = deviceInfo } SimpleCopyProperties(userInfo, userInfoEntity) userInfo.AutoAuthKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.AutoAuthKey) userInfo.SyncKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.SyncKey) userInfo.FavSyncKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.FavSyncKey) return userInfo } func GetUSerInfoByUUID(uuid string) *baseinfo.UserInfo { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } userInfo := &baseinfo.UserInfo{} var userInfoEntity table.UserInfoEntity userInfoEntity.UUID = uuid if srvconfig.GlobalSetting.TargetIp != "" { userInfoEntity.TargetIp = srvconfig.GlobalSetting.TargetIp } if err := MysqlDB.Model(&table.UserInfoEntity{}).Where(&userInfoEntity).First(&userInfoEntity).Error; err != nil { return nil } //判断是62还是A16 key := fmt.Sprintf("%s%s", "wechat:a16DeviceInfo:", userInfoEntity.WxId) exists, _ := Exists(key) if exists { //A16存redis deviceInfoA16 := &baseinfo.AndroidDeviceInfo{} error := GETObj(key, &deviceInfoA16) if error != nil { log.Error("保存redis is error=" + error.Error()) } userInfo.DeviceInfoA16 = deviceInfoA16 } else { var deviceInfoEntity table.DeviceInfoEntity deviceInfoEntity.WxId = userInfoEntity.WxId if err := MysqlDB.First(&deviceInfoEntity).Error; err != nil { return nil } deviceInfo := &baseinfo.DeviceInfo{} SimpleCopyProperties(deviceInfo, deviceInfoEntity) decodeSoftTypeXML, _ := base64.StdEncoding.DecodeString(deviceInfo.SoftTypeXML) deviceInfo.SoftTypeXML = string(decodeSoftTypeXML) decodeClientCheckDataXML, _ := base64.StdEncoding.DecodeString(deviceInfo.ClientCheckDataXML) deviceInfo.ClientCheckDataXML = string(decodeClientCheckDataXML) userInfo.DeviceInfo = deviceInfo } SimpleCopyProperties(userInfo, userInfoEntity) userInfo.AutoAuthKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.AutoAuthKey) userInfo.SyncKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.SyncKey) userInfo.FavSyncKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.FavSyncKey) userInfo.SetLoginState(uint32(userInfoEntity.State)) userInfo.ClientVersion = userInfoEntity.ClientVersion if !(int(userInfo.ClientVersion) > 0) { userInfo.ClientVersion = baseinfo.ClientVersion userInfoEntity.ClientVersion = baseinfo.ClientVersion } return userInfo } // GetUserInfoByWXID 从数据库获取UserInfo func GetUserInfoByWXID(wxID string) *baseinfo.UserInfo { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } userInfo := &baseinfo.UserInfo{} var userInfoEntity table.UserInfoEntity userInfoEntity.WxId = wxID if err := MysqlDB.First(&userInfoEntity).Error; err != nil { return nil } key := fmt.Sprintf("%s%s", "wechat:a16DeviceInfo:", wxID) exists, _ := Exists(key) if exists { //A16存redis key := fmt.Sprintf("%s%s", "wechat:a16DeviceInfo:", wxID) deviceInfoA16 := &baseinfo.AndroidDeviceInfo{} error := GETObj(key, &deviceInfoA16) if error != nil { log.Error("获取redis is error=" + error.Error()) } userInfo.DeviceInfoA16 = deviceInfoA16 } else { var deviceInfoEntity table.DeviceInfoEntity deviceInfoEntity.WxId = userInfoEntity.WxId if err := MysqlDB.First(&deviceInfoEntity).Error; err != nil { return nil } deviceInfo := &baseinfo.DeviceInfo{} SimpleCopyProperties(deviceInfo, deviceInfoEntity) decodeSoftTypeXML, _ := base64.StdEncoding.DecodeString(deviceInfo.SoftTypeXML) deviceInfo.SoftTypeXML = string(decodeSoftTypeXML) decodeClientCheckDataXML, _ := base64.StdEncoding.DecodeString(deviceInfo.ClientCheckDataXML) deviceInfo.ClientCheckDataXML = string(decodeClientCheckDataXML) userInfo.DeviceInfo = deviceInfo } SimpleCopyProperties(userInfo, userInfoEntity) userInfo.AutoAuthKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.AutoAuthKey) userInfo.SyncKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.SyncKey) userInfo.FavSyncKey, _ = base64.StdEncoding.DecodeString(userInfoEntity.FavSyncKey) return userInfo } // GetDeviceInfo 根据URLkey查询DeviceInfo 查不到返回nil func GetDeviceInfo(queryKey string) *baseinfo.DeviceInfo { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } var userInfoEntity table.UserInfoEntity MysqlDB.Where("querykey = ?", "queryKey").First(&userInfoEntity) var count int MysqlDB.Model(&table.UserInfoEntity{}).Where("querykey = ?", "queryKey").Count(&count) if count == 0 { return nil } deviceInfo := &baseinfo.DeviceInfo{} _ = SimpleCopyProperties(deviceInfo, userInfoEntity) return deviceInfo } // SimpleCopyProperties 拷贝属性 func SimpleCopyProperties(dst, src interface{}) (err error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } // 防止意外panic defer func() { if e := recover(); e != nil { err = fmt.Errorf("%v", e) } }() dstType, dstValue := reflect.TypeOf(dst), reflect.ValueOf(dst) srcType, srcValue := reflect.TypeOf(src), reflect.ValueOf(src) // dst必须结构体指针类型 if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct { return errors.New("dst type should be a struct pointer") } // src必须为结构体或者结构体指针 if srcType.Kind() == reflect.Ptr { srcType, srcValue = srcType.Elem(), srcValue.Elem() } if srcType.Kind() != reflect.Struct { return errors.New("src type should be a struct or a struct pointer") } // 取具体内容 dstType, dstValue = dstType.Elem(), dstValue.Elem() // 属性个数 propertyNums := dstType.NumField() for i := 0; i < propertyNums; i++ { // 属性 property := dstType.Field(i) // 待填充属性值 propertyValue := srcValue.FieldByName(property.Name) // 无效,说明src没有这个属性 || 属性同名但类型不同 if !propertyValue.IsValid() || property.Type != propertyValue.Type() { // 确保 propertyValue 是有效的并且类型支持 Name() 方法 if propertyValue.IsValid() && propertyValue.Kind() == reflect.Struct { if propertyValue.Type().Name() == "LocalTime" { // 数据库字段为 LocalTime 类型结构体 if !dstValue.Field(i).CanSet() { continue } dstValue.Field(i).Set(propertyValue.Field(0)) } } continue } if dstValue.Field(i).CanSet() { dstValue.Field(i).Set(propertyValue) } } return nil } // IsLicenseBind 查询许可证是否绑定 func IsLicenseBind(license string) (*table.LicenseKey, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var licenseKey table.LicenseKey result := MysqlDB.Where("license = ? AND status = 1", license).First(&licenseKey) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, nil } return nil, result.Error } return &licenseKey, nil } // UpdateLicenseBindStatus 更新许可证绑定状态 func UpdateLicenseBindStatus(deviceToken, license string) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } result := MysqlDB.Model(&table.LicenseKey{}).Where("license = ? ", license).Updates(map[string]interface{}{ "device_token": deviceToken, }) if result.Error != nil { return result.Error } return nil } // 如果没有过期时间,则生成过期时间 func UpdateLicenseExpiryDate(license string) (*table.LicenseKey, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var licenseKey table.LicenseKey resultObj := MysqlDB.Where("license = ?", license).First(&licenseKey) if resultObj.Error != nil { if resultObj.Error == gorm.ErrRecordNotFound { return &licenseKey, nil } return &licenseKey, resultObj.Error } if licenseKey.ExpiryDate == "" { // 生成过期时间 licenseKey.ExpiryDate = generateExpiryDate3(licenseKey.Type) // 生成开始时间 licenseKey.StartDate = time.Now().Format("2006-01-02 15:04:05") result := MysqlDB.Model(&table.LicenseKey{}).Where("license = ?", license).Updates(map[string]interface{}{ "expiry_date": licenseKey.ExpiryDate, "start_date": licenseKey.StartDate, }) if result.Error != nil { return &licenseKey, result.Error } } return &licenseKey, nil } // 绑定微信 func UpdateLicenseBindWxid(wxid, nickName, license string) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } MysqlDB.Model(&table.LicenseKey{}).Where("license = ? AND status = 0", license).Updates(map[string]interface{}{ "status": 1, "wx_id": wxid, "nick_name": nickName, }) return nil } // HasLicense 查询是否存在许可证 func HasLicense(license string) (*table.LicenseKey, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var licenseKey table.LicenseKey result := MysqlDB.Where("license = ?", license).First(&licenseKey) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, nil } return nil, result.Error } return &licenseKey, nil } // CheckExpiry 检查日期是否过期 func CheckExpiry(expiryDateString string, itype int) bool { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return false } //1日 7 周 30月 90季 180 半年 365年 30000 types := []int{1, 7, 30, 90, 180, 365, 30000} // 判断 itype 在 types 中 isType := false for _, t := range types { if t == itype { isType = true break } } // type 类型存在 同时 不存在 过期时间 if isType && expiryDateString == "" { return false } // 将字符串解析为时间对象 expiryDate, err := time.Parse("2006-01-02", expiryDateString) if err != nil { fmt.Println("Error parsing expiry date:", err) return false } // 获取当前时间 currentTime := time.Now() // 比较过期日期和当前日期 if expiryDate.Before(currentTime) { return true } return false } func generateExpiryDate(expiryDays int) string { // 获取当前日期 currentDate := time.Now() // 计算有效期后的日期 expiryDate := currentDate.AddDate(0, 0, expiryDays) // 格式化日期字符串 expiryDateStr := expiryDate.Format("2006-01-02") return expiryDateStr } // generateExpiryDate 根据特定时间段生成有效期日期 func generateExpiryDate3(duration int) string { // 获取当前日期 currentDate := time.Now() var expiryDate time.Time // 根据输入的周期计算有效期后的日期 switch duration { case 1: expiryDate = currentDate.AddDate(0, 0, 1) case 7: expiryDate = currentDate.AddDate(0, 0, 7) case 30: expiryDate = currentDate.AddDate(0, 1, 0) case 90: expiryDate = currentDate.AddDate(0, 3, 0) case 180: expiryDate = currentDate.AddDate(0, 6, 0) case 365: expiryDate = currentDate.AddDate(1, 0, 0) case 30000: expiryDate = currentDate.AddDate(100, 0, 0) default: // 默认过期,时间为当前日期的前10天 expiryDate = currentDate.AddDate(0, 0, -10) } // 格式化日期字符串 expiryDateStr := expiryDate.Format("2006-01-02") return expiryDateStr } //func generateActivationCode(length int, count int) []string { // activationCodes := make([]string, count) // // for i := 0; i < count; i++ { // code := generateSingleActivationCode() // activationCodes[i] = code // } // // return activationCodes //} func generateSingleActivationCode() string { chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var sb strings.Builder for i := 0; i < 20; i++ { sb.WriteByte(chars[rand.Intn(len(chars))]) } return sb.String() } // generateRandomString 生成指定长度的随机字符串 func generateRandomString(length int) string { // 设置种子(仅需设置一次) rand.Seed(time.Now().UnixNano()) // 定义字符集 charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var builder strings.Builder for i := 0; i < length; i++ { builder.WriteByte(charset[rand.Intn(len(charset))]) // 从字符集中随机选择一个字符 } return builder.String() } func CreateLicense(count, expiryDays int) string { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return "" } ExpiryDate := generateExpiryDate(expiryDays) licenses := make([]string, 0, count) for { if count == 0 { break } if len(licenses) >= count { break } //key := generateSingleActivationCode() // key := guuid.New().String() key := "HB" + generateRandomString(10) licenseKey := table.LicenseKey{ Status: 0, License: key, ExpiryDate: ExpiryDate, } result := MysqlDB.Create(&licenseKey) if result.Error != nil { if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { if mysqlErr.Number == 1062 { // 唯一性 License 冲突 fmt.Println("License key already exists!") continue } } fmt.Println("failed to create license key") break } licenses = append(licenses, key) } return strings.Join(licenses, "\n\t") } func CreateLicense3(count, itype int) string { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return "" } // ExpiryDate := generateExpiryDate(itype) licenses := make([]string, 0, count) for { if count == 0 { break } if len(licenses) >= count { break } //key := generateSingleActivationCode() // key := guuid.New().String() key := "HB" + generateRandomString(10) licenseKey := table.LicenseKey{ Status: 0, License: key, ExpiryDate: "", Type: itype, } result := MysqlDB.Create(&licenseKey) if result.Error != nil { if mysqlErr, ok := result.Error.(*mysql.MySQLError); ok { if mysqlErr.Number == 1062 { // 唯一性 License 冲突 fmt.Println("License key already exists!") continue } } fmt.Println("failed to create license key") break } licenses = append(licenses, key) } return strings.Join(licenses, "\n\t") } func DelayLicense(m req.DelayAuthKeyModel) (string, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return "", err } origin, err := HasLicense(m.Key) if origin == nil || err != nil { return "", errors.New(fmt.Sprintf("%s 该 key 无效! 请 检查正确性 或 联系管理员生成", m.Key)) } expiryDateStr := origin.ExpiryDate if m.Days > 0 { // 获取当前的过期日期 originDate, err := time.Parse("2006-01-02", origin.ExpiryDate) if err != nil { return "", err } // 计算延期后的日期 expiryDate := originDate.AddDate(0, 0, m.Days) // 格式化日期字符串 expiryDateStr = expiryDate.Format("2006-01-02") } else { expiryDateStr = m.ExpiryDate } result := MysqlDB.Model(&table.LicenseKey{}). Where("license = ?", origin.License). Updates(map[string]interface{}{ "expiry_date": expiryDateStr, }) if result.Error != nil { return "", result.Error } return expiryDateStr, nil } func DeleteLicense(key string, opt int) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } has, err := HasLicense(key) if has == nil || err != nil { return errors.New(fmt.Sprintf("%s 该 key 无效! 请 检查正确性 或 联系管理员生成", key)) } // 目前仅支持 0 1 2 三个删除操作 新增不删除授权码 删除userinfo deviceinfo 以及 登录日志3 if opt < 0 || opt > 3 { return errors.New("不存在该授权码 删除操作") } if opt != 3 { // 删除 license_key 数据库表的授权码信息 err = MysqlDB.Where("license = ?", key).Delete(&table.LicenseKey{}).Error if err != nil { return err } } if opt == 0 { // 仅删除 License 信息 return err } // op=1 时, 删除该 key 授权码对应的 UserInfoEntity DeviceInfoEntity if has.Status == 0 || len(strings.TrimSpace(has.WxId)) == 0 { // 该授权码还没有绑定微信号 return nil } // 删除 user_info_entity 数据库表的数据 _ = MysqlDB.Where("wxId = ?", has.WxId).Delete(&table.UserInfoEntity{}).Error // 删除 device_info_entity 数据库表的数据 _ = MysqlDB.Where("wxid = ?", has.WxId).Delete(&table.DeviceInfoEntity{}).Error if opt == 1 { return nil } // 删除 UserLoginLog 数据库表的数据 _ = MysqlDB.Where("user_name = ?", has.WxId).Delete(&table.UserLoginLog{}).Error return nil } // 禁用1 启用0 func DisableLicense(key string, opt int) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } result := MysqlDB.Model(&table.LicenseKey{}). Where("license = ?", key). Updates(map[string]interface{}{ "is_banned": opt, }) if result.Error != nil { return result.Error } return nil } // 创建黑名单 func CreateSB(owner string, blacker map[string]string) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } blackerJSON, err := json.Marshal(blacker) if err != nil { return err } // 在这里执行 SQL 查询,直接将 blackerJSON 作为参数赋值给 Blacker 字段 record := table.BlackList{Owner: owner, Blacker: string(blackerJSON)} result := MysqlDB.Create(&record) if result.Error != nil { return result.Error } key := owner + "_blacklist" SETExpirationObj(key, blacker, -1) return nil } // 查询黑名单 func QuerySB(owner string) (*table.BlackList, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var record table.BlackList result := MysqlDB.Where("owner = ?", owner).First(&record) if result.Error != nil { return nil, result.Error } return &record, nil } // 更新黑名单 func UpdateSB(owner string, blacker map[string]string) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } record, err := QuerySB(owner) if err != nil { return err } if record == nil { CreateSB(owner, blacker) return nil } blackerJSON, err := json.Marshal(blacker) if err != nil { return err } result := MysqlDB.Model(record).Update("Blacker", string(blackerJSON)) if result.Error != nil { return result.Error } key := owner + "_blacklist" SETExpirationObj(key, blacker, -1) return nil } // 联系人操作 func SaveOrUpdateContact(contact *pb.ModContact, uuid string) error { // 捕获 panic,防止程序崩溃 defer func() { if r := recover(); r != nil { fmt.Printf("Recovered from panic: %v\n", r) // 这里可以记录日志或者执行其他的恢复操作 } }() if contact == nil { return errors.New("contact cannot be nil") } if contact.UserName == nil || contact.UserName.GetStr() == "" { return errors.New("userName cannot be nil or empty") } contactJson, err := json.Marshal(contact) if err != nil { return err } m := getContactMutex(uuid) m.Lock() defer m.Unlock() // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } combinedKey := uuid + "-" + *contact.UserName.Str // 进行更新或插入操作 err = MysqlDB.Where(table.ModContactDB{UserUUIDCombined: combinedKey}). Assign(table.ModContactDB{Data: string(contactJson), UUID: uuid, UserName: *contact.UserName.Str}). FirstOrCreate(&table.ModContactDB{}).Error if err != nil { return err } return nil } func GetContact(userName string, uuid string) *pb.ModContact { // m := getContactMutex(uuid) // m.Lock() // defer m.Unlock() // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } var contactDB table.ModContactDB combinedKey := uuid + "-" + userName result := MysqlDB.First(&contactDB, "user_uuid_combined = ?", combinedKey) if gorm.IsRecordNotFoundError(result.Error) { return nil } if result.Error != nil { return nil } var contact pb.ModContact err := json.Unmarshal([]byte(contactDB.Data), &contact) if err != nil { return nil } return &contact } // 删除联系人 func DeleteContact(userName string, uuid string) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } combinedKey := uuid + "-" + userName result := MysqlDB.Where("user_uuid_combined = ?", combinedKey).Delete(&table.ModContactDB{}) if result.Error != nil { return result.Error } return nil } // 消息操作 func SaveMsg(msg pb.AddMsg, uuid string) error { if msg.NewMsgId == nil { return errors.New("NewMsgId cannot be nil") } // 序列化消息为 JSON msgJson, err := json.Marshal(msg) if err != nil { return err } // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } combinedKey := uuid + fmt.Sprintf("-%d", *msg.NewMsgId) // m := getContactMutex(uuid + "-msg") // m.Lock() // defer m.Unlock() // 检查消息是否已存在 var existingMsg table.AddMsgDB // 如果没有找到记录,会返回 gorm.ErrRecordNotFound err = MysqlDB.Where("msg_uuid_combined = ?", combinedKey).First(&existingMsg).Error if err == nil { return nil // 已存在,不更新,直接返回 } if !gorm.IsRecordNotFoundError(err) { return err // 返回数据库操作中的其他错误 } // 保存新消息 newMsg := &table.AddMsgDB{ UUID: uuid, NewMsgId: msg.GetNewMsgId(), MsgUUIDCombined: combinedKey, Data: string(msgJson), CreateTime: msg.GetCreateTime(), } if err := MysqlDB.Create(newMsg).Error; err != nil { return err // 返回创建消息时的任何错误 } return nil } func GetAndDeleteMsg(newMsgId int64, uuid string) *pb.AddMsg { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil } combinedKey := uuid + fmt.Sprintf("-%d", newMsgId) // m := getContactMutex(combinedKey) // m.Lock() // defer m.Unlock() var msgDB table.AddMsgDB // 获取消息 if err := MysqlDB.Where("msg_uuid_combined = ?", combinedKey).First(&msgDB).Error; err != nil { if gorm.IsRecordNotFoundError(err) { return nil // 消息不存在 } return nil // 发生其他错误 } // 删除消息 if err := MysqlDB.Delete(&msgDB).Error; err != nil { return nil // 发生删除错误 } // 解析消息内容 var msg pb.AddMsg if err := json.Unmarshal([]byte(msgDB.Data), &msg); err != nil { return nil // 解析错误 } return &msg } // StartCleanupTask 定时清理消息 func StartCleanupTask() { // 多个协议,启动一个即可 if srvconfig.GlobalSetting.OpenClearMsg == false { return } ticker := time.NewTicker(3 * time.Minute) go func() { for range ticker.C { cleanupOldMessages() } }() } func cleanupOldMessages() { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } cutoffTime := uint32(time.Now().Add(-3 * time.Minute).Unix()) tx := MysqlDB.Begin() if tx.Error != nil { return } if err := tx.Where("create_time < ?", cutoffTime).Delete(&table.AddMsgDB{}).Error; err != nil { tx.Rollback() return } tx.Commit() log.Println("Old messages cleaned up successfully.") } // 消息回调配置相关操作 // SaveMessageCallbackConfig 保存或更新消息回调配置 func SaveMessageCallbackConfig(config *table.MessageCallbackConfig) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } // 确保UUID和Key字段为相同的值 if config.UUID == "" { return errors.New("UUID字段不能为空") } // 设置Key字段和UUID保持一致 config.Key = config.UUID var existingConfig table.MessageCallbackConfig result := MysqlDB.Where("uuid = ?", config.UUID).First(&existingConfig) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { // 记录不存在,创建新记录 log.Infof("[回调调试] 创建新配置记录 [UUID: %s]", config.UUID) newConfig := table.MessageCallbackConfig{ UUID: config.UUID, Key: config.UUID, // 确保Key和UUID相同 CallbackURL: config.CallbackURL, Enabled: config.Enabled, } return MysqlDB.Create(&newConfig).Error } return result.Error } return MysqlDB.Model(&existingConfig).Updates(map[string]interface{}{ "key": config.UUID, // 更新Key字段 "callback_url": config.CallbackURL, "enabled": config.Enabled, }).Error } // GetMessageCallbackConfig 获取消息回调配置 func GetMessageCallbackConfig(uuid string) (*table.MessageCallbackConfig, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var config table.MessageCallbackConfig result := MysqlDB.Where("uuid = ?", uuid).First(&config) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { log.Debugf("[回调调试] 数据库中未找到配置 [UUID: %s]", uuid) return nil, nil } log.Errorf("[回调调试] 查询配置出错 [UUID: %s]: %v", uuid, result.Error) return nil, result.Error } return &config, nil } // DeleteMessageCallbackConfig 删除消息回调配置 func DeleteMessageCallbackConfig(uuid string) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } return MysqlDB.Where("uuid = ?", uuid).Delete(&table.MessageCallbackConfig{}).Error } // GetAllEnabledCallbacks 获取所有启用的回调配置 func GetAllEnabledCallbacks() ([]table.MessageCallbackConfig, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var configs []table.MessageCallbackConfig result := MysqlDB.Where("enabled = ?", true).Find(&configs) if result.Error != nil { return nil, result.Error } return configs, nil } // GetActiveLicenseKeys 获取所有激活状态的卡密 func GetActiveLicenseKeys() ([]*table.LicenseKey, error) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return nil, err } var licenseKeys []*table.LicenseKey err := MysqlDB.Where("status = ?", 1).Find(&licenseKeys).Error if err != nil { return nil, err } return licenseKeys, nil } // SaveShortLinkStatusToDB 将短连接状态保存到数据库 func SaveShortLinkStatusToDB(uuid string, isEnabled bool) { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return } data := make(map[string]interface{}) data["short_link_enabled"] = isEnabled // 更新用户信息表的短连接状态 _ = MysqlDB.Model(&table.UserInfoEntity{}). Where("uuid = ? ", uuid). Updates(data) // 同时更新缓存 SaveShortLinkStatus(uuid, isEnabled) } // LoadShortLinkStatusFromDB 从数据库加载短连接状态 func LoadShortLinkStatusFromDB(uuid string) *ShortLinkStatus { // 先尝试从缓存中获取 status := GetShortLinkStatus(uuid) if status != nil { return status } // 缓存中不存在,从数据库加载 if err := checkAndReconnect(); err != nil { return nil } var userInfoEntity table.UserInfoEntity if err := MysqlDB.Where("uuid = ?", uuid).First(&userInfoEntity).Error; err != nil { return nil } // 创建短连接状态对象并缓存 status = &ShortLinkStatus{ IsEnabled: userInfoEntity.ShortLinkEnabled, } SaveShortLinkStatus(uuid, status.IsEnabled) return status } // SaveDeviceInfo 保存设备信息到数据库 func SaveDeviceInfo(deviceInfo *table.DeviceInfoEntity) error { // 检查并重新连接数据库 if err := checkAndReconnect(); err != nil { return err } // 使用 Upsert 操作,如果记录存在则更新,不存在则插入 result := MysqlDB.Save(deviceInfo) return result.Error }