2026-02-17 13:06:23 +08:00
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 )
2026-02-26 10:44:13 +08:00
// 注意:回调配置的初始化移到了 InitAnewLogin 完成后
// 这样可以确保所有账号连接都已建立
2026-02-17 13:06:23 +08:00
}
// InitMessageCallbacks 初始化所有消息回调配置
func InitMessageCallbacks ( ) {
2026-02-26 10:44:13 +08:00
// 账号已经初始化完成,稍微等待一下确保连接稳定
time . Sleep ( 3 * time . Second )
log . Infof ( "开始初始化消息回调配置..." )
2026-02-17 13:06:23 +08:00
// 检查并重新连接数据库
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
}
2026-02-26 10:44:13 +08:00
if len ( configs ) == 0 {
log . Infof ( "未找到启用的消息回调配置" )
return
}
log . Infof ( "找到 %d 个启用的回调配置,开始重新激活..." , len ( configs ) )
// 重新保存每个配置,相当于重新设置一遍,激活回调
successCount := 0
2026-02-17 13:06:23 +08:00
for _ , config := range configs {
2026-02-26 10:44:13 +08:00
// 确保Key字段正确
2026-02-17 13:06:23 +08:00
if config . Key == "" {
2026-02-26 10:44:13 +08:00
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 ++
2026-02-17 13:06:23 +08:00
}
}
2026-02-26 10:44:13 +08:00
log . Infof ( "========================================" )
log . Infof ( "回调配置激活完成: 成功 %d/%d" , successCount , len ( configs ) )
log . Infof ( "========================================" )
2026-02-17 13:06:23 +08:00
}
// 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 {
// 记录不存在,创建新记录
2026-02-26 10:44:13 +08:00
log . Infof ( "[回调调试] 创建新配置记录 [UUID: %s]" , config . UUID )
2026-02-17 13:06:23 +08:00
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 {
2026-02-26 10:44:13 +08:00
log . Debugf ( "[回调调试] 数据库中未找到配置 [UUID: %s]" , uuid )
return nil , nil
2026-02-17 13:06:23 +08:00
}
2026-02-26 10:44:13 +08:00
log . Errorf ( "[回调调试] 查询配置出错 [UUID: %s]: %v" , uuid , result . Error )
2026-02-17 13:06:23 +08:00
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
}