first commit
This commit is contained in:
120
db/cache.go
Normal file
120
db/cache.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"time"
|
||||
"xiawan/wx/clientsdk/baseinfo"
|
||||
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
)
|
||||
|
||||
var cache *gcache.Cache
|
||||
|
||||
// 添加一个持久缓存,用于保存最后的登录状态
|
||||
var lastLoginStatusCache *gcache.Cache
|
||||
|
||||
// 添加短连接状态缓存
|
||||
var shortLinkStatusCache *gcache.Cache
|
||||
|
||||
// ShortLinkStatus 短连接状态结构
|
||||
type ShortLinkStatus struct {
|
||||
IsEnabled bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
cache = gcache.New()
|
||||
lastLoginStatusCache = gcache.New()
|
||||
shortLinkStatusCache = gcache.New()
|
||||
}
|
||||
|
||||
// AddCheckStatusCache 添加扫码状态
|
||||
func AddCheckStatusCache(queryKey string, val *baseinfo.CheckLoginQrCodeResult) {
|
||||
_ = PublishSyncMsgCheckLogin(queryKey, val)
|
||||
|
||||
isContains := cache.Contains(queryKey)
|
||||
if !isContains {
|
||||
cache.Set(queryKey, val, time.Second*200)
|
||||
// 当扫码状态为2(扫码成功)时,持久化保存状态
|
||||
if val.GetState() == 2 {
|
||||
SaveLastLoginStatus(queryKey, val)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
// 不要先删除再添加,这可能导致竞态条件
|
||||
// 先设置新的,再删除旧的(如果需要)
|
||||
tempVal := cache.Get(queryKey)
|
||||
cache.Set(queryKey, val, time.Second*200)
|
||||
|
||||
// 当扫码状态为2(扫码成功)时,持久化保存状态
|
||||
if val.GetState() == 2 {
|
||||
SaveLastLoginStatus(queryKey, val)
|
||||
}
|
||||
|
||||
// 如果旧值和新值相同,不需要进一步处理
|
||||
if tempVal == val {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if val.Ret == -3003 {
|
||||
cache.Set(queryKey, val, time.Second*1000)
|
||||
return
|
||||
}
|
||||
//扫码时间到
|
||||
if val.GetEffectiveTime() == 0 {
|
||||
//checkStatusCache.Remove(queryKey)
|
||||
return
|
||||
}
|
||||
cache.Set(queryKey, val, time.Second*time.Duration(val.GetEffectiveTime()))
|
||||
}
|
||||
|
||||
// GetCheckStatusCache 获取扫码状态
|
||||
func GetCheckStatusCache(queryKey string) *baseinfo.CheckLoginQrCodeResult {
|
||||
isContains := cache.Contains(queryKey)
|
||||
if !isContains {
|
||||
return nil
|
||||
}
|
||||
return cache.Get(queryKey).(*baseinfo.CheckLoginQrCodeResult)
|
||||
}
|
||||
|
||||
// ClearCheckStatusCache 清除指定queryKey的状态缓存
|
||||
func ClearCheckStatusCache(queryKey string) {
|
||||
cache.Remove(queryKey)
|
||||
}
|
||||
|
||||
// 获取
|
||||
func GetCaChe() *gcache.Cache {
|
||||
return cache
|
||||
}
|
||||
|
||||
// SaveLastLoginStatus 持久化保存最后的登录状态
|
||||
func SaveLastLoginStatus(queryKey string, val *baseinfo.CheckLoginQrCodeResult) {
|
||||
// 使用更长的过期时间,确保在整个登录过程中状态可用
|
||||
lastLoginStatusCache.Set(queryKey, val, time.Hour*24)
|
||||
}
|
||||
|
||||
// GetLastLoginStatus 获取最后保存的登录状态
|
||||
func GetLastLoginStatus(queryKey string) *baseinfo.CheckLoginQrCodeResult {
|
||||
isContains := lastLoginStatusCache.Contains(queryKey)
|
||||
if !isContains {
|
||||
return nil
|
||||
}
|
||||
return lastLoginStatusCache.Get(queryKey).(*baseinfo.CheckLoginQrCodeResult)
|
||||
}
|
||||
|
||||
// SaveShortLinkStatus 保存短链接状态
|
||||
func SaveShortLinkStatus(queryKey string, isEnabled bool) {
|
||||
status := &ShortLinkStatus{
|
||||
IsEnabled: isEnabled,
|
||||
}
|
||||
// 使用长期缓存,确保在服务重启后仍能恢复短连接状态
|
||||
shortLinkStatusCache.Set(queryKey, status, time.Hour*24*30) // 保存30天
|
||||
}
|
||||
|
||||
// GetShortLinkStatus 获取短链接状态
|
||||
func GetShortLinkStatus(queryKey string) *ShortLinkStatus {
|
||||
isContains := shortLinkStatusCache.Contains(queryKey)
|
||||
if !isContains {
|
||||
return nil
|
||||
}
|
||||
return shortLinkStatusCache.Get(queryKey).(*ShortLinkStatus)
|
||||
}
|
||||
21
db/callback_wrapper.go
Normal file
21
db/callback_wrapper.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"xiawan/wx/protobuf/wechat"
|
||||
)
|
||||
|
||||
// CallbackMessageWrapper callback消息包装结构体,用于包含UUID信息
|
||||
type CallbackMessageWrapper struct {
|
||||
Key string `json:"key"` // UUID标识,用于区分是哪个微信收到的消息
|
||||
Message *wechat.AddMsg `json:"message"` // 原始消息内容
|
||||
Type string `json:"type"` // 消息类型,如"message"、"contact_sync"等
|
||||
}
|
||||
|
||||
// NewCallbackMessageWrapper 创建新的callback消息包装器
|
||||
func NewCallbackMessageWrapper(key string, message *wechat.AddMsg, msgType string) *CallbackMessageWrapper {
|
||||
return &CallbackMessageWrapper{
|
||||
Key: key,
|
||||
Message: message,
|
||||
Type: msgType,
|
||||
}
|
||||
}
|
||||
109
db/message_callback.go
Normal file
109
db/message_callback.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
"xiawan/wx/protobuf/wechat"
|
||||
|
||||
"github.com/lunny/log"
|
||||
)
|
||||
|
||||
// MessageCallbackPayload 消息回调内容体
|
||||
type MessageCallbackPayload struct {
|
||||
UUID string `json:"uuid"` // 微信UUID标识
|
||||
MsgId string `json:"msg_id"` // 消息ID
|
||||
FromUser string `json:"from_user"` // 发送方
|
||||
ToUser string `json:"to_user"` // 接收方
|
||||
MsgType int `json:"msg_type"` // 消息类型
|
||||
Content string `json:"content"` // 消息内容
|
||||
CreateTime int64 `json:"create_time"` // 消息创建时间
|
||||
IsGroup bool `json:"is_group"` // 是否群消息
|
||||
Attachments map[string]interface{} `json:"attachments,omitempty"` // 附件信息
|
||||
RawData map[string]interface{} `json:"raw_data,omitempty"` // 原始数据
|
||||
}
|
||||
|
||||
// decodeUnicodeEscapes 解码Unicode转义字符(复用自 wxsocketmsg.go)
|
||||
func decodeUnicodeEscapes(s string) string {
|
||||
// 处理JSON中的Unicode转义字符,如\u003c
|
||||
re := regexp.MustCompile(`\\u([0-9a-fA-F]{4})`)
|
||||
result := re.ReplaceAllStringFunc(s, func(match string) string {
|
||||
// 提取十六进制数字部分
|
||||
hexStr := match[2:] // 去掉\u前缀
|
||||
// 将十六进制转换为整数
|
||||
if codePoint, err := strconv.ParseInt(hexStr, 16, 32); err == nil {
|
||||
// 转换为Unicode字符
|
||||
return string(rune(codePoint))
|
||||
}
|
||||
return match // 如果转换失败,返回原字符串
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GenerateSignature 生成签名
|
||||
func GenerateSignature(payload []byte, key string) string {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
h.Write(payload)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// SendMessageCallback 发送消息回调
|
||||
func SendMessageCallback(msg *wechat.AddMsg, uuid string) {
|
||||
// 获取回调配置
|
||||
config, err := GetMessageCallbackConfig(uuid)
|
||||
if err != nil || config == nil || !config.Enabled {
|
||||
return // 没有配置或未启用回调,直接返回
|
||||
}
|
||||
|
||||
// 使用消息包装器包含UUID信息
|
||||
msgWrapper := NewCallbackMessageWrapper(uuid, msg, "message")
|
||||
jsonBytes, err := json.Marshal(msgWrapper)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to marshal CallbackMessageWrapper: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 对JSON内容进行Unicode解码处理(与 WebSocket 保持一致)
|
||||
decodedJSON := decodeUnicodeEscapes(string(jsonBytes))
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest("POST", config.CallbackURL, bytes.NewBuffer([]byte(decodedJSON)))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create callback request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", time.Now().Unix()))
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to send callback request: %v", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应内容
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read callback response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 记录回调结果
|
||||
log.Infof("Message callback sent to %s for message %d, status: %d", config.CallbackURL, msg.GetMsgId(), resp.StatusCode)
|
||||
}
|
||||
1747
db/mysql_db.go
Normal file
1747
db/mysql_db.go
Normal file
File diff suppressed because it is too large
Load Diff
52
db/proxy.go
Normal file
52
db/proxy.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"time"
|
||||
"xiawan/wx/db/table"
|
||||
)
|
||||
|
||||
// GetProxyMapping 获取代理映射
|
||||
func GetProxyMapping(proxyNumber string) (*table.ProxyMapping, error) {
|
||||
if err := checkAndReconnect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var mapping table.ProxyMapping
|
||||
if err := MysqlDB.Where("proxy_number = ?", proxyNumber).First(&mapping).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mapping, nil
|
||||
}
|
||||
|
||||
// AddProxyMapping 添加代理映射
|
||||
func AddProxyMapping(proxyNumber, proxyValue string) error {
|
||||
if err := checkAndReconnect(); err != nil {
|
||||
return err
|
||||
}
|
||||
mapping := &table.ProxyMapping{
|
||||
ProxyNumber: proxyNumber,
|
||||
ProxyValue: proxyValue,
|
||||
CreateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
UpdateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
return MysqlDB.Create(mapping).Error
|
||||
}
|
||||
|
||||
// UpdateProxyMapping 更新代理映射
|
||||
func UpdateProxyMapping(proxyNumber, proxyValue string) error {
|
||||
if err := checkAndReconnect(); err != nil {
|
||||
return err
|
||||
}
|
||||
mapping := &table.ProxyMapping{
|
||||
ProxyValue: proxyValue,
|
||||
UpdateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
return MysqlDB.Model(&table.ProxyMapping{}).Where("proxy_number = ?", proxyNumber).Updates(mapping).Error
|
||||
}
|
||||
|
||||
// DeleteProxyMapping 删除代理映射
|
||||
func DeleteProxyMapping(proxyNumber string) error {
|
||||
if err := checkAndReconnect(); err != nil {
|
||||
return err
|
||||
}
|
||||
return MysqlDB.Where("proxy_number = ?", proxyNumber).Delete(&table.ProxyMapping{}).Error
|
||||
}
|
||||
707
db/redisOperation.go
Normal file
707
db/redisOperation.go
Normal file
@@ -0,0 +1,707 @@
|
||||
/**
|
||||
* @author mii
|
||||
* @date 2020/2/29 0029
|
||||
*/
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
"xiawan/wx/clientsdk/baseinfo"
|
||||
"xiawan/wx/db/table"
|
||||
"xiawan/wx/srv"
|
||||
"xiawan/wx/srv/srvconfig"
|
||||
|
||||
"github.com/gogf/gf/database/gredis"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
REDIS_OPERATION_SET = "SET"
|
||||
REDIS_OPERATION_GET = "GET"
|
||||
REDIS_OPERATION_LIST_LLEN = "llen"
|
||||
REDIS_OPERATION_LIST_LPSUH = "LPUSH"
|
||||
REDIS_OPERATION_LIST_LPOP = "lpop"
|
||||
REDIS_OPERATION_LIST_RPOP = "rpop"
|
||||
REDIS_OPERATION_LIST_LTRIM = "ltrim"
|
||||
REDIS_OPERATION_CHANNEL_PUBLISH = "publish"
|
||||
REDIS_OPERATION_EXISTS = "EXISTS"
|
||||
REDIS_OPERATION_DELETE = "DEL"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_GROUP_NAME = "default" // Default configuration group name.
|
||||
DEFAULT_REDIS_PORT = 6379 // Default redis port configuration if not passed.
|
||||
)
|
||||
|
||||
type Obj struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func RedisSetup() {
|
||||
//配置redis
|
||||
// MaxIdle: 10, // 设置最大空闲连接数
|
||||
// MaxActive: 100, // 设置最大活跃连接数
|
||||
// IdleTimeout: 300, // 空闲连接超时时间(单位:秒)
|
||||
// MaxConnLifetime: 300, // 连接最大存活期(单位:秒)
|
||||
// WaitTimeout: 5, // 连接等待超时时间(单位:秒)
|
||||
// ReadTimeout: 10, // 读操作超时时间(单位:秒)
|
||||
// WriteTimeout: 10, // 写操作超时时间(单位:秒)
|
||||
gredis.SetConfig(srvconfig.GlobalSetting.RedisConfig, DEFAULT_GROUP_NAME)
|
||||
conn := g.Redis().GetConn()
|
||||
err := conn.Err()
|
||||
if err != nil {
|
||||
//logger.Errorln(err)
|
||||
fmt.Println("failed to connect Redis:", err)
|
||||
}
|
||||
fmt.Println("connect Redis success")
|
||||
go scheduleSaveToRedis()
|
||||
}
|
||||
|
||||
// 尝试获取锁
|
||||
func AcquireLock(key string, timeout time.Duration) (bool, error) {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
lockValue := fmt.Sprintf("%d", time.Now().UnixNano()) // Unique lock value
|
||||
result, err := redisConn.Do(REDIS_OPERATION_SET, key, lockValue, "NX", "PX", int64(timeout.Seconds()*1000))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result != nil, nil
|
||||
}
|
||||
|
||||
// 释放锁
|
||||
func ReleaseLock(key string) error {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
_, err := redisConn.Do(REDIS_OPERATION_DELETE, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// acquireLockWithRetry 尝试获取锁,带有重试机制
|
||||
func acquireLockWithRetry(key string, timeout time.Duration, retryInterval time.Duration, maxRetries int) (bool, error) {
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
lockAcquired, err := AcquireLock(key, timeout)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if lockAcquired {
|
||||
return true, nil
|
||||
}
|
||||
time.Sleep(retryInterval) // 等待一段时间后重试
|
||||
}
|
||||
return false, fmt.Errorf("could not acquire lock after %d retries", maxRetries)
|
||||
}
|
||||
|
||||
// CreateSyncMsgList 创建一个同步信息保存列表
|
||||
func CreateSyncMsgList(exId string) bool {
|
||||
ok, err := LPUSH(exId, "撒大声地")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// LPOPObj 从列表中取出对象然后反序列化成对象
|
||||
func LPOPObj(k string, i interface{}) error {
|
||||
_var, err := LPOP(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gjson.DecodeTo(_var.Bytes(), &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RPOPObj 从列表尾部 取出对象然后反序列化成对象
|
||||
func RPOPObj(k string, i interface{}) error {
|
||||
_var, err := RPOP(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gjson.DecodeTo(_var.Bytes(), &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LPUSHObj 将对象序列化成json保存
|
||||
func LPUSHObj(k string, i interface{}) (bool, error) {
|
||||
iData, err := gjson.Encode(i)
|
||||
// 判断 k 的尾号是 _syncMsg
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ok, err := LPUSH(k, iData)
|
||||
if err != nil {
|
||||
return ok, err
|
||||
}
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func LPUSH(k string, i interface{}) (bool, error) {
|
||||
lockKey := k + "_lock"
|
||||
maxRetries := 200 // 最大重试次数
|
||||
retryInterval := 50 * time.Millisecond // 重试间隔时间为500毫秒
|
||||
// 使用带重试机制的获取锁函数
|
||||
acquireLockWithRetry(lockKey, 5*time.Second, retryInterval, maxRetries)
|
||||
defer ReleaseLock(lockKey) // 确保方法结束时释放锁
|
||||
|
||||
maxLength := 500 // 设置最大列表长度
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
result, err := redisConn.Do(REDIS_OPERATION_LIST_LPSUH, k, i)
|
||||
if err != nil {
|
||||
//logger.Errorln(err)
|
||||
return false, err
|
||||
}
|
||||
_len, err := LLEN(k)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if _len == 0 {
|
||||
return true, nil
|
||||
}
|
||||
// 如果列表长度超过 maxLength,则循环执行 LPOP 操作
|
||||
for _len > int32(maxLength) {
|
||||
_, err = redisConn.Do(REDIS_OPERATION_LIST_LPOP, k)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_len = _len - int32(1)
|
||||
}
|
||||
return gconv.String(result) == "OK", nil
|
||||
}
|
||||
|
||||
// LPOP 从列表中取出一个值
|
||||
func LPOP(k string) (*g.Var, error) {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
r, err := redisConn.DoVar(REDIS_OPERATION_LIST_LPOP, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// RPOP 从列表尾部 取出一个元素
|
||||
func RPOP(k string) (*g.Var, error) {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
r, err := redisConn.DoVar(REDIS_OPERATION_LIST_RPOP, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// LLEN 取列表长度
|
||||
func LLEN(k string) (int32, error) {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
_var, err := redisConn.DoVar(REDIS_OPERATION_LIST_LLEN, k)
|
||||
if err != nil {
|
||||
//logger.Errorln(err)
|
||||
return 0, err
|
||||
}
|
||||
return _var.Int32(), nil
|
||||
}
|
||||
|
||||
// PUBLISH 发布消息
|
||||
func PUBLISH(k string, i interface{}) error {
|
||||
return PushQueue(k, i)
|
||||
}
|
||||
|
||||
func SETExpirationObj(k string, i interface{}, expiration int64) error {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
iData, err := gjson.Encode(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result interface{}
|
||||
if expiration > 0 {
|
||||
result, err = redisConn.Do(REDIS_OPERATION_SET, k, iData, "EX", expiration)
|
||||
} else {
|
||||
result, err = redisConn.Do(REDIS_OPERATION_SET, k, iData)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
//logger.Errorln(err)
|
||||
return err
|
||||
}
|
||||
if gconv.String(result) == "OK" {
|
||||
return nil
|
||||
}
|
||||
return errors.New(gconv.String(result))
|
||||
}
|
||||
|
||||
func SETObj(k string, i interface{}) error {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
iData, err := gjson.Encode(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := redisConn.Do(REDIS_OPERATION_SET, k, iData)
|
||||
if err != nil {
|
||||
//logger.Errorln(err)
|
||||
return err
|
||||
}
|
||||
if gconv.String(result) == "OK" {
|
||||
return nil
|
||||
}
|
||||
return errors.New(gconv.String(result))
|
||||
}
|
||||
|
||||
func GETObj(k string, i interface{}) error {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
_var, err := redisConn.Do(REDIS_OPERATION_GET, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gjson.DecodeTo(_var, &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DelObj(k string) error {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
_, err := redisConn.Do(REDIS_OPERATION_DELETE, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Exists(k string) (bool, error) {
|
||||
//检查是否存在key值
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
exists, err := redisConn.Do(REDIS_OPERATION_EXISTS, k)
|
||||
if err != nil {
|
||||
fmt.Println("illegal exception")
|
||||
return false, err
|
||||
}
|
||||
//fmt.Printf("exists or not: %v \n", exists)
|
||||
if exists.(int64) == 1 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func getRedisConn() *gredis.Conn {
|
||||
return g.Redis().GetConn()
|
||||
}
|
||||
|
||||
// PublishSyncMsgWxMessage 发布微信消息; 这里就是接收到 微信后台 推送来的消息(包括其他用户私聊消息、群聊消息、收款消息等等)
|
||||
func PublishSyncMsgWxMessage(acc *srv.WXAccount, response table.SyncMessageResponse) error {
|
||||
if acc == nil {
|
||||
return errors.New("PublishSyncMsgWxMessage acc == nil")
|
||||
}
|
||||
response.UUID = acc.GetUserInfo().UUID
|
||||
response.UserName = acc.GetUserInfo().GetUserName()
|
||||
response.LoginState = acc.GetLoginState()
|
||||
response.Type = table.RedisPushSyncTypeWxMsg
|
||||
response.TargetIp = srvconfig.GlobalSetting.TargetIp
|
||||
if len(response.GetAddMsgs()) != 0 || len(response.GetContacts()) != 0 {
|
||||
if srvconfig.GlobalSetting.HttpSyncMsg {
|
||||
LPUSHObj(response.UUID+"_syncHttp", &response) //缓存reids
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishFavItem 发布收藏消息
|
||||
func PublishFavItem(acc *srv.WXAccount, favItem *baseinfo.FavItem) error {
|
||||
if acc == nil {
|
||||
return errors.New("PublishSyncMsgWxMessage acc == nil")
|
||||
}
|
||||
response := table.SyncMessageResponse{}
|
||||
response.UUID = acc.GetUserInfo().UUID
|
||||
response.UserName = acc.GetUserInfo().GetUserName()
|
||||
response.LoginState = acc.GetLoginState()
|
||||
response.TargetIp = srvconfig.GlobalSetting.TargetIp
|
||||
response.Type = table.RedisPushFavitem
|
||||
response.FavItem = favItem
|
||||
// 缓存reids
|
||||
if srvconfig.GlobalSetting.NewsSynWxId {
|
||||
// 缓存reids
|
||||
LPUSHObj(response.UUID+"_syncMsg", &response)
|
||||
return PUBLISH(response.UUID+"_wx_sync_msg_topic", &response)
|
||||
}
|
||||
return PUBLISH("wx_sync_msg_topic", &response)
|
||||
}
|
||||
|
||||
// PublishSyncMsgLoginState 微信状态
|
||||
func PublishSyncMsgLoginState(wxid string, state uint32) error {
|
||||
//fmt.Println("推送->wxid=="+wxid+"--->", state)
|
||||
response := table.SyncMessageResponse{}
|
||||
response.TargetIp = srvconfig.GlobalSetting.TargetIp
|
||||
response.Type = table.RedisPushSyncTypeLoginState
|
||||
response.UserName = wxid
|
||||
response.LoginState = state
|
||||
// 缓存reids
|
||||
if srvconfig.GlobalSetting.NewsSynWxId {
|
||||
// 缓存reids
|
||||
LPUSHObj(response.UUID+"_syncMsg", &response)
|
||||
return PUBLISH(response.UUID+"_wx_sync_msg_topic", &response)
|
||||
}
|
||||
return PUBLISH("wx_sync_msg_topic", &response)
|
||||
}
|
||||
|
||||
// PublishWxInItOk 初始化完成
|
||||
func PublishWxInItOk(UUID string, state uint32) error {
|
||||
if UUID == "" || state == 0 {
|
||||
return errors.New("uuid || state == nil")
|
||||
}
|
||||
response := table.SyncMessageResponse{}
|
||||
response.TargetIp = srvconfig.GlobalSetting.TargetIp
|
||||
response.Type = table.RedisPushWxInItOk
|
||||
response.LoginState = state
|
||||
response.UUID = UUID
|
||||
response.UserName = "初始化完成!"
|
||||
// 缓存reids
|
||||
if srvconfig.GlobalSetting.NewsSynWxId {
|
||||
_, _ = LPUSHObj(response.UUID+"_wx_sync_msg_topic", &response)
|
||||
return PUBLISH(response.UUID+"_wx_sync_msg_topic", &response)
|
||||
}
|
||||
return PUBLISH("wx_sync_msg_topic", &response)
|
||||
}
|
||||
|
||||
// PublishSyncMsgCheckLogin 扫码结果
|
||||
func PublishSyncMsgCheckLogin(UUID string, result *baseinfo.CheckLoginQrCodeResult) error {
|
||||
if UUID == "" || result == nil {
|
||||
return errors.New("uuid || result == nil")
|
||||
}
|
||||
response := table.SubMessageCheckLoginQrCode{}
|
||||
response.TargetIp = srvconfig.GlobalSetting.TargetIp
|
||||
response.Type = table.RedisPushSyncTypeCheckLogin
|
||||
response.CheckLoginResult = result
|
||||
response.UUID = UUID
|
||||
// 缓存reids
|
||||
if srvconfig.GlobalSetting.NewsSynWxId {
|
||||
_, _ = LPUSHObj(response.UUID+"_wx_sync_msg_topic", &response)
|
||||
return PUBLISH(response.UUID+"_wx_sync_msg_topic", &response)
|
||||
}
|
||||
return PUBLISH("wx_sync_msg_topic", &response)
|
||||
}
|
||||
|
||||
// GETSyncMsg 获取指定号缓存在redis的消息
|
||||
func GETSyncMsg(uuid string) (int, []*table.SyncMessageResponse, error) {
|
||||
_len, err := LLEN(uuid + "_syncMsg")
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if _len == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
syncMsgLen := _len
|
||||
if syncMsgLen > 10 {
|
||||
syncMsgLen = 10
|
||||
}
|
||||
opValues := []*table.SyncMessageResponse{}
|
||||
for i := int32(0); i <= syncMsgLen; i++ {
|
||||
opVal := &table.SyncMessageResponse{}
|
||||
err = LPOPObj(uuid+"_wx_sync_msg_topic", opVal)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
opValues = append(opValues, opVal)
|
||||
}
|
||||
ContinueFlag := 0
|
||||
if (_len % 10) > 0 {
|
||||
ContinueFlag = 1
|
||||
}
|
||||
|
||||
return ContinueFlag, opValues, nil
|
||||
}
|
||||
|
||||
// PublishLicenseKey 使用状态, 主要是过期时间和开始时间
|
||||
func PublishLicenseKey(licenseKey *table.LicenseKey) error {
|
||||
lockKey := "uuid_syncLicenseKey_lock"
|
||||
maxRetries := 200 // 最大重试次数
|
||||
retryInterval := 50 * time.Millisecond // 重试间隔时间为500毫秒
|
||||
// 使用带重试机制的获取锁函数
|
||||
acquireLockWithRetry(lockKey, 5*time.Second, retryInterval, maxRetries)
|
||||
|
||||
defer ReleaseLock(lockKey) // 确保方法结束时释放锁
|
||||
// 缓存 reids
|
||||
LPUSHObj("uuid_syncLicenseKey", licenseKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HttpSyncLicenseKey HTTP-轮询 同步激活状态 获取 Redis 内的消息
|
||||
func HttpSyncLicenseKey() ([]*table.LicenseKey, error) {
|
||||
lockKey := "uuid_syncLicenseKey_lock"
|
||||
maxRetries := 200 // 最大重试次数
|
||||
retryInterval := 50 * time.Millisecond // 重试间隔时间为500毫秒
|
||||
// 使用带重试机制的获取锁函数
|
||||
acquireLockWithRetry(lockKey, 5*time.Second, retryInterval, maxRetries)
|
||||
|
||||
defer ReleaseLock(lockKey) // 确保方法结束时释放锁
|
||||
|
||||
syncMsgLen, err := LLEN("uuid_syncLicenseKey")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgInfos := []*table.LicenseKey{}
|
||||
if !(syncMsgLen > int32(0)) {
|
||||
return msgInfos, nil
|
||||
}
|
||||
|
||||
for i := int32(0); i < syncMsgLen; i++ {
|
||||
msg := &table.LicenseKey{}
|
||||
err = RPOPObj("uuid_syncLicenseKey", msg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
msgInfos = append(msgInfos, msg)
|
||||
}
|
||||
return msgInfos, nil
|
||||
}
|
||||
|
||||
// HttpSyncMsy HTTP-轮询 同步消息时 获取 Redis 内的消息
|
||||
func HttpSyncMsy(uuid string, count int) ([]*table.SyncMessageResponse, error) {
|
||||
lockKey := uuid + "_syncHttp_lock"
|
||||
maxRetries := 200 // 最大重试次数
|
||||
retryInterval := 50 * time.Millisecond // 重试间隔时间为500毫秒
|
||||
// 使用带重试机制的获取锁函数
|
||||
acquireLockWithRetry(lockKey, 5*time.Second, retryInterval, maxRetries)
|
||||
|
||||
defer ReleaseLock(lockKey) // 确保方法结束时释放锁
|
||||
|
||||
_len, err := LLEN(uuid + "_syncHttp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncMsgLen := int32(count)
|
||||
if syncMsgLen == 0 { // count=0 时获取所有消息
|
||||
syncMsgLen = _len
|
||||
} else if syncMsgLen > _len {
|
||||
syncMsgLen = _len
|
||||
}
|
||||
|
||||
msgInfos := []*table.SyncMessageResponse{}
|
||||
if _len == 0 {
|
||||
return msgInfos, nil
|
||||
}
|
||||
|
||||
for i := int32(0); i < syncMsgLen; i++ {
|
||||
msg := &table.SyncMessageResponse{}
|
||||
err = RPOPObj(uuid+"_syncHttp", msg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
msgInfos = append(msgInfos, msg)
|
||||
}
|
||||
|
||||
return msgInfos, nil
|
||||
}
|
||||
|
||||
// PushQueue 发送队例
|
||||
func PushQueue(queueName string, i interface{}) (err error) {
|
||||
con := getRedisConn()
|
||||
defer con.Close()
|
||||
|
||||
maxLength := 200 // 最大 200 条
|
||||
if i == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = con.Do("rpush", queueName, gconv.String(i))
|
||||
if err != nil {
|
||||
return fmt.Errorf("PushQueue redis error: %s", err)
|
||||
}
|
||||
// 使用 ltrim 裁剪列表,以保持其最大长度
|
||||
_, err = con.Do("LTRIM", queueName, -maxLength, -1)
|
||||
return err
|
||||
}
|
||||
|
||||
// ClearSyncMsgCache 清理所有以 nameKey(_syncMsg) 结尾的 Redis 缓存键
|
||||
func ClearSyncMsgCache(nameKey string) error {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
// SCAN 命令用于增量迭代 key,效率较高
|
||||
iter := 0
|
||||
for {
|
||||
// 使用 redigo 的 Do 方法执行 SCAN 命令
|
||||
reply, err := redis.Values(redisConn.Do("SCAN", iter, "MATCH", "*"+nameKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during SCAN: %v", err)
|
||||
}
|
||||
|
||||
// 解析出 SCAN 的结果集
|
||||
var keys []string
|
||||
_, err = redis.Scan(reply, &iter, &keys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing SCAN result: %v", err)
|
||||
}
|
||||
|
||||
// 删除匹配的 key
|
||||
for _, key := range keys {
|
||||
_, err = redisConn.Do(REDIS_OPERATION_DELETE, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting key %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
// iter 为 0 时表示 SCAN 已迭代完所有 key
|
||||
if iter == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
maxMessagesPerUser = 125
|
||||
saveInterval = 5 * time.Second
|
||||
)
|
||||
|
||||
type userCache struct {
|
||||
mutex sync.RWMutex
|
||||
msgs map[string]struct{}
|
||||
}
|
||||
|
||||
var (
|
||||
msgCache = make(map[string]*userCache) // 存储每个用户的缓存和锁
|
||||
globalMutex sync.RWMutex // 保证对 msgCache 的安全访问
|
||||
)
|
||||
|
||||
// AddOrUpdateMsgId 添加消息ID到 Redis 集合,如果集合中不存在则更新
|
||||
func AddOrUpdateMsgId(userName string, newMsgId string) (bool, error) {
|
||||
|
||||
// 获取或创建用户缓存
|
||||
userCache := getUserCache(userName)
|
||||
|
||||
userCache.mutex.Lock()
|
||||
defer userCache.mutex.Unlock()
|
||||
|
||||
// 检查消息 ID 是否存在
|
||||
if _, exists := userCache.msgs[newMsgId]; exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 添加消息 ID 到缓存
|
||||
userCache.msgs[newMsgId] = struct{}{}
|
||||
|
||||
// 确保缓存不会超过 25 条 // 删除最旧的消息 ID
|
||||
if len(userCache.msgs) > maxMessagesPerUser {
|
||||
for msgID := range userCache.msgs {
|
||||
delete(userCache.msgs, msgID)
|
||||
if len(userCache.msgs) <= maxMessagesPerUser {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func getUserCache(userName string) *userCache {
|
||||
// 使用全局锁来安全地访问或初始化用户缓存
|
||||
globalMutex.RLock()
|
||||
cache, exists := msgCache[userName]
|
||||
globalMutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
// 使用写锁来修改 msgCache
|
||||
globalMutex.Lock()
|
||||
defer globalMutex.Unlock()
|
||||
|
||||
// 再次检查,防止竞争条件
|
||||
if cache, exists = msgCache[userName]; !exists {
|
||||
cache = &userCache{
|
||||
msgs: make(map[string]struct{}),
|
||||
mutex: sync.RWMutex{},
|
||||
}
|
||||
// 从 Redis 加载用户消息
|
||||
loadFromRedis(userName, cache.msgs)
|
||||
msgCache[userName] = cache
|
||||
}
|
||||
}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func loadFromRedis(userName string, userMsgs map[string]struct{}) {
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
key := fmt.Sprintf("user:%s:msg_ids", userName)
|
||||
msgs, err := redis.Strings(redisConn.Do("SMEMBERS", key))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, msgID := range msgs {
|
||||
userMsgs[msgID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func saveToRedis() {
|
||||
// 锁定整个操作以安全地遍历 msgCache
|
||||
globalMutex.RLock()
|
||||
defer globalMutex.RUnlock()
|
||||
|
||||
redisConn := getRedisConn()
|
||||
defer redisConn.Close()
|
||||
|
||||
for userName, userCache := range msgCache {
|
||||
// 锁定每个用户的缓存数据
|
||||
userCache.mutex.RLock()
|
||||
|
||||
key := fmt.Sprintf("user:%s:msg_ids", userName)
|
||||
msgIDs := make([]interface{}, 0, len(userCache.msgs)+1)
|
||||
msgIDs = append(msgIDs, key)
|
||||
for msgID := range userCache.msgs {
|
||||
msgIDs = append(msgIDs, msgID)
|
||||
}
|
||||
|
||||
// 更新 Redis
|
||||
redisConn.Do("DEL", key)
|
||||
_, err := redisConn.Do("SADD", msgIDs...)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing to Redis:", err)
|
||||
}
|
||||
userCache.mutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleSaveToRedis() {
|
||||
ticker := time.NewTicker(saveInterval)
|
||||
for range ticker.C {
|
||||
saveToRedis()
|
||||
}
|
||||
}
|
||||
9
db/table/base.go
Normal file
9
db/table/base.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package table
|
||||
|
||||
const (
|
||||
RedisPushSyncTypeLoginState int = 10000
|
||||
RedisPushSyncTypeWxMsg int = 10001
|
||||
RedisPushSyncTypeCheckLogin int = 10002
|
||||
RedisPushFavitem int = 10003
|
||||
RedisPushWxInItOk int = 10009
|
||||
)
|
||||
19
db/table/messageCallback.go
Normal file
19
db/table/messageCallback.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package table
|
||||
|
||||
import "time"
|
||||
|
||||
// MessageCallbackConfig 消息回调配置表
|
||||
type MessageCallbackConfig struct {
|
||||
ID int64 `json:"id" gorm:"primary_key;auto_increment"` // 主键ID
|
||||
UUID string `json:"uuid" gorm:"not null;index"` // 微信UUID标识
|
||||
Key string `json:"key" gorm:"not null"` // 对应的key值
|
||||
CallbackURL string `json:"callback_url" gorm:"not null"` // 回调URL
|
||||
Enabled bool `json:"enabled" gorm:"default:false"` // 是否启用回调
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` // 更新时间
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (MessageCallbackConfig) TableName() string {
|
||||
return "message_callback_config"
|
||||
}
|
||||
259
db/table/mysql_table.go
Normal file
259
db/table/mysql_table.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//发送文本消息
|
||||
MYSQL_BUSINESS_TYPE_SENDTEXTMSG = "SendTextMessage"
|
||||
//发送图片信息
|
||||
MYSQL_BUSINESS_TYPE_SENDIMGMSG = "SendImageMessage"
|
||||
)
|
||||
|
||||
type LocalTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON on LocalTime format Time field with %Y-%m-%d %H:%M:%S
|
||||
func (t LocalTime) MarshalJSON() ([]byte, error) {
|
||||
formatted := fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05"))
|
||||
return []byte(formatted), nil
|
||||
}
|
||||
|
||||
// Value insert timestamp into mysql need this function.
|
||||
func (t LocalTime) Value() (driver.Value, error) {
|
||||
var zeroTime time.Time
|
||||
if t.Time.UnixNano() == zeroTime.UnixNano() {
|
||||
return nil, nil
|
||||
}
|
||||
return t.Time, nil
|
||||
}
|
||||
|
||||
// Scan valueof time.Time
|
||||
func (t *LocalTime) Scan(v interface{}) error {
|
||||
switch v.(type) {
|
||||
case []byte:
|
||||
timeBytes, ok := v.([]byte)
|
||||
if ok {
|
||||
todayZero, _ := time.ParseInLocation("2006-01-02 15:04:05", string(timeBytes), time.Local)
|
||||
*t = LocalTime{Time: todayZero}
|
||||
return nil
|
||||
}
|
||||
case time.Time:
|
||||
value, ok := v.(time.Time)
|
||||
if ok {
|
||||
*t = LocalTime{Time: value}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return fmt.Errorf("can not convert %v to timestamp", v)
|
||||
}
|
||||
|
||||
func NewLocalTime() *LocalTime {
|
||||
return &LocalTime{time.Now()}
|
||||
}
|
||||
|
||||
type CdnSnsImageInfo struct {
|
||||
Ver uint32
|
||||
Seq uint32
|
||||
RetCode uint32
|
||||
FileKey string
|
||||
RecvLen uint32
|
||||
FileURL string
|
||||
ThumbURL string
|
||||
EnableQuic uint32
|
||||
RetrySec uint32
|
||||
IsRetry uint32
|
||||
IsOverLoad uint32
|
||||
IsGetCDN uint32
|
||||
XClientIP string
|
||||
ImageMD5 string `gorm:"primary_key"`
|
||||
ImageWidth uint32
|
||||
ImageHeight uint32
|
||||
}
|
||||
|
||||
type MysqlBase struct {
|
||||
TargetIp string `gorm:"column:targetIp"`
|
||||
}
|
||||
|
||||
// USerBusinessLog 用户行为日志
|
||||
type UserBusinessLog struct {
|
||||
Id uint `gorm:"primary_key,AUTO_INCREMENT"` //自增Id
|
||||
UUID string `gorm:"column:uuid" json:"uuid"` //用户链接Id
|
||||
UserName string `gorm:"column:user_name" json:"userName"` //登录的WXID
|
||||
BusinessType string `gorm:"column:business_type" json:"businessType"` //业务类型 所调用的接口
|
||||
ExecuteResult string `gorm:"column:ex_result" json:"executeResult"` //执行结果
|
||||
}
|
||||
|
||||
// UserMessageLog 用户消息日志,用于去重
|
||||
type UserMessageLog struct {
|
||||
UserName string `gorm:"primary_key;column:user_name;unique" json:"userName"` // 唯一的用户名
|
||||
MsgIds string `gorm:"column:msg_ids;type:text" json:"msgIds"` // 消息ID,以逗号分隔
|
||||
}
|
||||
|
||||
// 用户登录日志
|
||||
type UserLoginLog struct {
|
||||
MysqlBase
|
||||
Id uint `gorm:"primary_key,AUTO_INCREMENT" json:"id"`
|
||||
UUId string
|
||||
UserName string
|
||||
NickName string
|
||||
LoginType string
|
||||
UpdatedAt LocalTime `json:"loginTime"`
|
||||
RetCode int32 `gorm:"column:ret_code"`
|
||||
ErrMsg string `gorm:"column:err_msg;type:text"`
|
||||
}
|
||||
|
||||
// UserInfoEntity 用户信息
|
||||
type UserInfoEntity struct {
|
||||
MysqlBase
|
||||
UUID string `gorm:"column:uuid" json:"uuid"`
|
||||
Uin uint32 `gorm:"column:uin" json:"uin"`
|
||||
WxId string `gorm:"column:wxId;primary_key" json:"wxId"`
|
||||
NickName string `gorm:"column:nickname" json:"nickname"`
|
||||
BindMobile string `gorm:"column:bindmobile" json:"bindmobile"`
|
||||
Alias string `gorm:"column:alias" json:"alias"`
|
||||
UserName string `gorm:"column:userName" json:"user_name"`
|
||||
Password string `gorm:"column:password" json:"password"`
|
||||
HeadURL string `gorm:"column:headurl" json:"headurl"`
|
||||
Session []byte `gorm:"column:cookie" json:"cookie"`
|
||||
SessionKey []byte `gorm:"column:sessionKey" json:"sessionKey"`
|
||||
Proxy string `gorm:"column:proxy" json:"proxy"`
|
||||
ClientVersion uint32 `gorm:"column:clientVersion" json:"clientVersion"`
|
||||
ShortHost string `gorm:"column:shorthost" json:"shorthost"`
|
||||
LongHost string `gorm:"column:longhost" json:"longhost"`
|
||||
EcPublicKey []byte `gorm:"column:ecpukey" json:"ecpukey"`
|
||||
EcPrivateKey []byte `gorm:"column:ecprkey" json:"ecprkey"`
|
||||
CheckSumKey []byte `gorm:"column:checksumkey" json:"checksumkey"`
|
||||
AutoAuthKey string `gorm:"column:autoauthkey;type:varchar(2048)" json:"autoauthkey"`
|
||||
State int32 `gorm:"column:state" json:"state"`
|
||||
InitContact int32 `gorm:"column:initcontact;default:0" json:"initcontact"`
|
||||
SyncKey string `gorm:"column:synckey;type:varchar(1024)" json:"synckey"`
|
||||
FavSyncKey string `gorm:"column:favsynckey;type:varchar(100)" json:"favsynckey"`
|
||||
// 登录的Rsa 密钥版本
|
||||
LoginRsaVer uint32
|
||||
ErrMsg string `gorm:"type:text"`
|
||||
|
||||
// `gorm:"type:timestamp;default:CURRENT_TIMESTAMP"`
|
||||
// `gorm:"type:timestamp;default:CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
|
||||
|
||||
// 新设备创建时间
|
||||
DeviceCreateTime LocalTime `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"deviceCreateTime"`
|
||||
// 上次手动登录时间
|
||||
LastLoginTime LocalTime `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"lastLoginTime"`
|
||||
// 上次手动登录时间
|
||||
LastAuthTime LocalTime `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"lastAuthTime"`
|
||||
// 短连接启用状态
|
||||
ShortLinkEnabled bool `gorm:"column:short_link_enabled;default:true" json:"shortLinkEnabled"`
|
||||
}
|
||||
|
||||
// DeviceInfoEntity 设备信息
|
||||
type DeviceInfoEntity struct {
|
||||
WxId string `gorm:"column:wxid;primary_key" json:"wxid"`
|
||||
UUIDOne string `gorm:"column:uuidone" json:"uuidone"`
|
||||
UUIDTwo string `gorm:"column:uuidtwo" json:"uuidtwo"`
|
||||
Imei string `gorm:"column:imei" json:"imei"`
|
||||
DeviceID []byte `gorm:"column:deviceid" json:"deviceid"`
|
||||
DeviceName string `gorm:"column:devicename" json:"devicename"`
|
||||
TimeZone string `gorm:"column:timezone" json:"timezone"`
|
||||
Language string `gorm:"column:language" json:"language"`
|
||||
DeviceBrand string `gorm:"column:devicebrand" json:"devicebrand"`
|
||||
RealCountry string `gorm:"column:realcountry" json:"realcountry"`
|
||||
IphoneVer string `gorm:"column:iphonever" json:"iphonever"`
|
||||
BundleID string `gorm:"column:boudleid" json:"boudleid"`
|
||||
OsType string `gorm:"column:ostype" json:"ostype"`
|
||||
AdSource string `gorm:"column:adsource" json:"adsource"`
|
||||
OsTypeNumber string `gorm:"column:ostypenumber" json:"ostypenumber"`
|
||||
CoreCount uint32 `gorm:"column:corecount" json:"corecount"`
|
||||
CarrierName string `gorm:"column:carriername" json:"carriername"`
|
||||
SoftTypeXML string `gorm:"column:softtypexml;type:varchar(2048)" json:"softtypexml"`
|
||||
ClientCheckDataXML string `gorm:"column:clientcheckdataxml;type:varchar(4096)" json:"clientcheckdataxml"`
|
||||
// extInfo
|
||||
GUID2 string `json:"GUID2"`
|
||||
}
|
||||
|
||||
// 授权key
|
||||
type LicenseKey struct {
|
||||
ID int `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
DeviceToken string `gorm:"column:device_token;type:varchar(255)" json:"device_token"`
|
||||
Status int `gorm:"column:status" json:"status"`
|
||||
License string `gorm:"column:license;type:varchar(255);unique;not null" json:"license"`
|
||||
ExpiryDate string `gorm:"column:expiry_date;type:varchar(255)" json:"expiry_date"`
|
||||
StartDate string `gorm:"column:start_date;type:varchar(255)" json:"start_date"`
|
||||
WxId string `gorm:"column:wx_id;type:varchar(255)" json:"wx_id"`
|
||||
NickName string `gorm:"column:nick_name;type:varchar(255)" json:"nick_name"`
|
||||
BindMobile string `gorm:"column:bindmobile" json:"bindmobile"`
|
||||
Alias string `gorm:"column:alias" json:"alias"`
|
||||
// 类型 1日 7 周 30月 90季 180 半年 365年 30000永久(数字为标识,非准确天数)
|
||||
Type int `gorm:"column:type;type:int;default:0" json:"type"`
|
||||
IsBanned int `gorm:"column:is_banned;type:int;default:0" json:"is_banned"`
|
||||
}
|
||||
|
||||
type BlackList struct {
|
||||
ID int `gorm:"primaryKey"`
|
||||
Owner string `gorm:"column:owner;type:varchar(255)"`
|
||||
Blacker string `gorm:"column:blacker;type:json"`
|
||||
}
|
||||
|
||||
func (BlackList) TableName() string {
|
||||
return "black_list"
|
||||
}
|
||||
|
||||
// 指令:Command key str_value int_value
|
||||
type Command struct {
|
||||
UUID string `gorm:"column:uuid;primary_key" json:"uuid"`
|
||||
A101 int `gorm:"column:a101;type:int;default:0" json:"a101"`
|
||||
A111 int `gorm:"column:a111;type:int;default:0" json:"a111"`
|
||||
A102 int `gorm:"column:a102;type:int;default:0" json:"a102"`
|
||||
A103 int `gorm:"column:a103;type:int;default:0" json:"a103"`
|
||||
A104 int `gorm:"column:a104;type:int;default:0" json:"a104"`
|
||||
A104Str string `gorm:"column:a104_str;type:varchar(255)" json:"a104_str"`
|
||||
A105 int `gorm:"column:a105;type:int;default:0" json:"a105"`
|
||||
A106 int `gorm:"column:a106;type:int;default:0" json:"a106"`
|
||||
A107 int `gorm:"column:a107;type:int;default:0" json:"a107"`
|
||||
A109 int `gorm:"column:a109;type:int;default:0" json:"a109"`
|
||||
A116 int `gorm:"column:a116;type:int;default:0" json:"a116"`
|
||||
A116Str string `gorm:"column:a116_str;type:varchar(255)" json:"a116_str"`
|
||||
A118 int `gorm:"column:a118;type:int;default:0" json:"a118"`
|
||||
A118Str string `gorm:"column:a118_str;type:varchar(255)" json:"a118_str"`
|
||||
A301 int `gorm:"column:a301;type:int;default:0" json:"a301"`
|
||||
A301Str string `gorm:"column:a301_str;type:varchar(255)" json:"a301_str"`
|
||||
A401 int `gorm:"column:a401;type:int;default:0" json:"a401"`
|
||||
A402 int `gorm:"column:a402;type:int;default:0" json:"a402"`
|
||||
A403 int `gorm:"column:a403;type:int;default:0" json:"a403"`
|
||||
A601 int `gorm:"column:a601;type:int;default:0" json:"a601"`
|
||||
A801 int `gorm:"column:a801;type:int;default:0" json:"a801"`
|
||||
A811 int `gorm:"column:a811;type:int;default:0" json:"a811"`
|
||||
B001 int `gorm:"column:b001;type:int;default:0" json:"b001"`
|
||||
B001Str string `gorm:"column:b001_str;type:varchar(255)" json:"b001_str"`
|
||||
B002 int `gorm:"column:b002;type:int;default:0" json:"b002"`
|
||||
B002Str string `gorm:"column:b002_str;type:text" json:"b002_str"` // JSON string storing keyword-reply pairs
|
||||
B003 int `gorm:"column:b003;type:int;default:0" json:"b003"` // Enable welcome message (0: disabled, 1: enabled)
|
||||
B003Str string `gorm:"column:b003_str;type:text" json:"b003_str"` // JSON string storing welcome message config
|
||||
B004 int `gorm:"column:b004;type:int;default:0" json:"b004"` // Enable admin keyword (0: disabled, 1: enabled)
|
||||
B004Str string `gorm:"column:b004_str;type:text" json:"b004_str"` // JSON string storing admin keyword config
|
||||
B005 int `gorm:"column:b005;type:int;default:0" json:"b005"` // Enable invite keyword (0: disabled, 1: enabled)
|
||||
B005Str string `gorm:"column:b005_str;type:text" json:"b005_str"` // JSON string storing invite keyword config
|
||||
B006 int `gorm:"column:b006;type:int;default:0" json:"b006"` // Enable kick keyword (0: disabled, 1: enabled)
|
||||
B006Str string `gorm:"column:b006_str;type:text" json:"b006_str"` // JSON string storing kick keyword config
|
||||
}
|
||||
|
||||
type ModContactDB struct {
|
||||
UUID string `gorm:"type:varchar(36)" json:"uuid"` // 新增 UUID 字段
|
||||
UserName string `gorm:"type:varchar(255)" json:"userName"`
|
||||
UserUUIDCombined string `gorm:"primary_key;type:varchar(291);index" json:"user_uuid_combined"` // 新的组合字段,设置为主键
|
||||
Data string `gorm:"type:json" json:"data"`
|
||||
}
|
||||
|
||||
type AddMsgDB struct {
|
||||
UUID string `gorm:"type:varchar(36)" json:"uuid"`
|
||||
NewMsgId int64 `gorm:"type:bigint" json:"new_msg_id"`
|
||||
MsgUUIDCombined string `gorm:"primary_key;type:varchar(291);index" json:"msg_uuid_combined"` // 新的组合字段,设置为主键
|
||||
Data string `gorm:"type:json" json:"data"`
|
||||
CreateTime uint32 `gorm:"type:int" json:"create_time"`
|
||||
}
|
||||
14
db/table/proxy_mapping.go
Normal file
14
db/table/proxy_mapping.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package table
|
||||
|
||||
// ProxyMapping 代理映射表
|
||||
type ProxyMapping struct {
|
||||
ID int `xorm:"pk autoincr 'id'"`
|
||||
ProxyNumber string `xorm:"varchar(50) notnull unique 'proxy_number'"` // 代理编号
|
||||
ProxyValue string `xorm:"varchar(255) notnull 'proxy_value'"` // 代理值
|
||||
CreateTime string `xorm:"varchar(50) 'create_time'"` // 创建时间
|
||||
UpdateTime string `xorm:"varchar(50) 'update_time'"` // 更新时间
|
||||
}
|
||||
|
||||
func (ProxyMapping) TableName() string {
|
||||
return "proxy_mapping"
|
||||
}
|
||||
151
db/table/redis_table.go
Normal file
151
db/table/redis_table.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"xiawan/wx/clientsdk/baseinfo"
|
||||
pb "xiawan/wx/protobuf/wechat"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type SubMessageCheckLoginQrCode struct {
|
||||
Type int
|
||||
TargetIp string
|
||||
UUID string
|
||||
CheckLoginResult *baseinfo.CheckLoginQrCodeResult
|
||||
}
|
||||
|
||||
type SyncMessageResponse struct {
|
||||
Type int
|
||||
TargetIp string
|
||||
UUID string
|
||||
UserName string `json:"userName"`
|
||||
LoginState uint32 `json:"loginState"`
|
||||
ModUserInfos []*pb.ModUserInfo
|
||||
ModContacts []*pb.ModContact
|
||||
AddMsgs []*pb.AddMsg
|
||||
ModUserImgs []*pb.ModUserImg
|
||||
UserInfoExts []*pb.UserInfoExt
|
||||
SnsObjects []*pb.SnsObject
|
||||
SnsActionGroups []*pb.SnsActionGroup
|
||||
FavItem *baseinfo.FavItem
|
||||
Key *pb.SKBuiltinString_
|
||||
}
|
||||
|
||||
func (sync *SyncMessageResponse) GetContacts() []*pb.ModContact {
|
||||
return sync.ModContacts
|
||||
}
|
||||
|
||||
// 根据 UserName 查询 Contacts
|
||||
func (sync *SyncMessageResponse) GetModContacts(UserName string) *pb.ModContact {
|
||||
for _, userInfo := range sync.ModContacts {
|
||||
if userInfo.UserName.GetStr() == UserName {
|
||||
return userInfo
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据 UserName 更新或新增 Contacts
|
||||
func (sync *SyncMessageResponse) SetModContacts(userInfo *pb.ModContact) {
|
||||
for i, u := range sync.ModContacts {
|
||||
if u.UserName.GetStr() == userInfo.UserName.GetStr() {
|
||||
sync.ModContacts[i] = userInfo
|
||||
return
|
||||
}
|
||||
}
|
||||
sync.ModContacts = append(sync.ModContacts, userInfo)
|
||||
}
|
||||
|
||||
func (sync *SyncMessageResponse) GetAddMsgs() []*pb.AddMsg {
|
||||
return sync.AddMsgs
|
||||
}
|
||||
|
||||
func (sync *SyncMessageResponse) SetMessage(data []byte, cmdId int32) {
|
||||
switch cmdId {
|
||||
case 1:
|
||||
userInfo := new(pb.ModUserInfo)
|
||||
if err := proto.Unmarshal(data, userInfo); err != nil {
|
||||
//z.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
/*fmt.Printf("登录微信:[%s] 昵称 [%s] 手机 [%s] 别名 [%s]\n",
|
||||
userInfo.UserName.GetStr(),
|
||||
userInfo.NickName.GetStr(),
|
||||
userInfo.BindMobile.GetStr(),
|
||||
userInfo.GetAlias())*/
|
||||
sync.ModUserInfos = append(sync.ModUserInfos, userInfo)
|
||||
// 限制长度
|
||||
if len(sync.ModUserInfos) > 5000 {
|
||||
sync.ModUserInfos = sync.ModUserInfos[len(sync.ModUserInfos)-5000:]
|
||||
}
|
||||
case 2:
|
||||
contact := new(pb.ModContact)
|
||||
if err := proto.Unmarshal(data, contact); err != nil {
|
||||
//z.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
sync.ModContacts = append(sync.ModContacts, contact)
|
||||
case 5:
|
||||
addMsg := new(pb.AddMsg)
|
||||
if err := proto.Unmarshal(data, addMsg); err != nil {
|
||||
//z.Println(err)
|
||||
return
|
||||
}
|
||||
sync.AddMsgs = append(sync.AddMsgs, addMsg)
|
||||
// 限制长度
|
||||
if len(sync.AddMsgs) > 5000 {
|
||||
sync.AddMsgs = sync.AddMsgs[len(sync.AddMsgs)-5000:]
|
||||
}
|
||||
case 35:
|
||||
userImg := new(pb.ModUserImg)
|
||||
if err := proto.Unmarshal(data, userImg); err != nil {
|
||||
//z.Println(err)
|
||||
return
|
||||
}
|
||||
sync.ModUserImgs = append(sync.ModUserImgs, userImg)
|
||||
// 限制长度
|
||||
if len(sync.ModUserImgs) > 5000 {
|
||||
sync.ModUserImgs = sync.ModUserImgs[len(sync.ModUserImgs)-5000:]
|
||||
}
|
||||
case 44:
|
||||
userInfoExt := new(pb.UserInfoExt)
|
||||
if err := proto.Unmarshal(data, userInfoExt); err != nil {
|
||||
//z.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
sync.UserInfoExts = append(sync.UserInfoExts, userInfoExt)
|
||||
// 限制长度
|
||||
if len(sync.UserInfoExts) > 5000 {
|
||||
sync.UserInfoExts = sync.UserInfoExts[len(sync.UserInfoExts)-5000:]
|
||||
}
|
||||
case 45:
|
||||
snsObject := new(pb.SnsObject)
|
||||
if err := proto.Unmarshal(data, snsObject); err != nil {
|
||||
//z.Println(err)
|
||||
return
|
||||
}
|
||||
sync.SnsObjects = append(sync.SnsObjects, snsObject)
|
||||
// 限制长度
|
||||
if len(sync.SnsObjects) > 5000 {
|
||||
sync.SnsObjects = sync.SnsObjects[len(sync.SnsObjects)-5000:]
|
||||
}
|
||||
case 46:
|
||||
snsActionGroup := new(pb.SnsActionGroup)
|
||||
if err := proto.Unmarshal(data, snsActionGroup); err != nil {
|
||||
//z.Println(err)
|
||||
return
|
||||
}
|
||||
sync.SnsActionGroups = append(sync.SnsActionGroups, snsActionGroup)
|
||||
// 限制长度
|
||||
if len(sync.SnsActionGroups) > 5000 {
|
||||
sync.SnsActionGroups = sync.SnsActionGroups[len(sync.SnsActionGroups)-5000:]
|
||||
}
|
||||
default:
|
||||
/*empty := new(pb.EmptyMesssage)
|
||||
_ = proto.Unmarshal(data, empty)*/
|
||||
//logger.Printf("收到未处理类型:%d 的数据:%s\n", id, empty.String())
|
||||
//保存消息
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user