🎨 添加联系人同步功能,更新联系人模型以支持更多字段;优化定时任务管理
All checks were successful
BuildImage / build-image (push) Successful in 2m0s
All checks were successful
BuildImage / build-image (push) Successful in 2m0s
This commit is contained in:
parent
80a3de9906
commit
dc28090064
@ -384,3 +384,56 @@ func GetWechatMessages(ctx context.Context, containerHost, robotWxId string) (ms
|
||||
msg = response.Data.AddMsgs
|
||||
return
|
||||
}
|
||||
|
||||
// GetContactList
|
||||
// @description: 获取联系人微信Id
|
||||
// @param ctx
|
||||
// @param containerHost
|
||||
// @param robotWxId
|
||||
// @return list
|
||||
// @return err
|
||||
func GetContactList(ctx context.Context, containerHost, robotWxId string) (list []string, err error) {
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s/GetContractList", containerHost)
|
||||
|
||||
var response BaseResponse[ContactListResponse]
|
||||
_, err = client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(map[string]any{
|
||||
"CurrentChatroomContactSeq": 0,
|
||||
"CurrentWxcontactSeq": 0,
|
||||
"Wxid": robotWxId,
|
||||
}).
|
||||
SetResult(&response).
|
||||
Post(url)
|
||||
|
||||
list = response.Data.ContactUsernameList
|
||||
return
|
||||
}
|
||||
|
||||
// GetContactDetail
|
||||
// @description: 获取联系人详细信息
|
||||
// @param ctx
|
||||
// @param containerHost
|
||||
// @param robotWxId
|
||||
// @param wxId
|
||||
// @return info
|
||||
// @return err
|
||||
func GetContactDetail(ctx context.Context, containerHost, robotWxId string, wxId []string) (info []ContactDetailInfoItem, err error) {
|
||||
client := newHTTPClient()
|
||||
url := fmt.Sprintf("http://%s/GetContact", containerHost)
|
||||
|
||||
var response BaseResponse[ContactDetailInfoResponse]
|
||||
_, err = client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(map[string]any{
|
||||
"Chatroom": "",
|
||||
"RequestWxids": strings.Join(wxId, ","),
|
||||
"Wxid": robotWxId,
|
||||
}).
|
||||
SetResult(&response).
|
||||
Post(url)
|
||||
|
||||
info = response.Data.ContactList
|
||||
return
|
||||
}
|
||||
|
@ -103,3 +103,116 @@ type Message struct {
|
||||
MsgSeq int `json:"MsgSeq"`
|
||||
PushContent string `json:"PushContent,omitempty"`
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
type ContactListResponse struct {
|
||||
BaseResponse struct {
|
||||
Ret int `json:"ret"`
|
||||
ErrMsg struct {
|
||||
String string `json:"string"`
|
||||
} `json:"errMsg"`
|
||||
} `json:"BaseResponse"`
|
||||
CurrentWxcontactSeq int `json:"CurrentWxcontactSeq"`
|
||||
CurrentChatRoomContactSeq int `json:"CurrentChatRoomContactSeq"`
|
||||
CountinueFlag int `json:"CountinueFlag"`
|
||||
ContactUsernameList []string `json:"ContactUsernameList"` // 联系人微信Id列表
|
||||
}
|
||||
|
||||
// ContactDetailInfo
|
||||
// @description: 联系人详情
|
||||
type ContactDetailInfoResponse struct {
|
||||
BaseResponse struct {
|
||||
Ret int `json:"ret"`
|
||||
ErrMsg struct {
|
||||
} `json:"errMsg"`
|
||||
} `json:"BaseResponse"`
|
||||
ContactCount int `json:"ContactCount"`
|
||||
ContactList []ContactDetailInfoItem `json:"ContactList"`
|
||||
Ret []int `json:"Ret"`
|
||||
Ticket []struct {
|
||||
} `json:"Ticket"`
|
||||
}
|
||||
|
||||
// ContactDetailInfoItem
|
||||
// @description: 详情
|
||||
type ContactDetailInfoItem struct {
|
||||
UserName struct {
|
||||
String string `json:"string"`
|
||||
} `json:"UserName"` // 微信Id
|
||||
NickName struct {
|
||||
String string `json:"string"`
|
||||
} `json:"NickName"` // 昵称
|
||||
Pyinitial struct {
|
||||
String string `json:"string"`
|
||||
} `json:"Pyinitial"` // 昵称拼音首字母大写
|
||||
QuanPin struct {
|
||||
String string `json:"string"`
|
||||
} `json:"QuanPin"` // 昵称拼音全拼小写
|
||||
Sex int `json:"Sex"` // 性别 0:未知 1:男 2:女
|
||||
ImgBuf struct {
|
||||
ILen int `json:"iLen"`
|
||||
} `json:"ImgBuf"`
|
||||
BitMask int64 `json:"BitMask"`
|
||||
BitVal int `json:"BitVal"`
|
||||
ImgFlag int `json:"ImgFlag"`
|
||||
Remark struct {
|
||||
} `json:"Remark"`
|
||||
RemarkPyinitial struct {
|
||||
} `json:"RemarkPyinitial"`
|
||||
RemarkQuanPin struct {
|
||||
} `json:"RemarkQuanPin"`
|
||||
ContactType int `json:"ContactType"`
|
||||
RoomInfoCount int `json:"RoomInfoCount"`
|
||||
DomainList struct {
|
||||
} `json:"DomainList"`
|
||||
ChatRoomNotify int `json:"ChatRoomNotify"`
|
||||
AddContactScene int `json:"AddContactScene"`
|
||||
Province string `json:"Province"` // 省份
|
||||
City string `json:"City"` // 城市
|
||||
Signature string `json:"Signature"` // 个性签名
|
||||
PersonalCard int `json:"PersonalCard"`
|
||||
HasWeiXinHdHeadImg int `json:"HasWeiXinHdHeadImg"`
|
||||
VerifyFlag int `json:"VerifyFlag"`
|
||||
Level int `json:"Level"`
|
||||
Source int `json:"Source"`
|
||||
Alias string `json:"Alias"` // 微信号
|
||||
WeiboFlag int `json:"WeiboFlag"`
|
||||
AlbumStyle int `json:"AlbumStyle"`
|
||||
AlbumFlag int `json:"AlbumFlag"`
|
||||
SnsUserInfo struct {
|
||||
SnsFlag int `json:"SnsFlag"`
|
||||
SnsBgimgId string `json:"SnsBgimgId"` // 朋友圈背景图
|
||||
SnsBgobjectId float64 `json:"SnsBgobjectId"`
|
||||
SnsFlagEx int `json:"SnsFlagEx"`
|
||||
} `json:"SnsUserInfo"`
|
||||
Country string `json:"Country"` // 国家
|
||||
BigHeadImgUrl string `json:"BigHeadImgUrl"` // 大头像地址
|
||||
SmallHeadImgUrl string `json:"SmallHeadImgUrl"` // 小头像地址
|
||||
MyBrandList string `json:"MyBrandList"`
|
||||
CustomizedInfo struct {
|
||||
BrandFlag int `json:"BrandFlag"`
|
||||
} `json:"CustomizedInfo"`
|
||||
HeadImgMd5 string `json:"HeadImgMd5"`
|
||||
EncryptUserName string `json:"EncryptUserName"`
|
||||
AdditionalContactList struct {
|
||||
LinkedinContactItem struct {
|
||||
} `json:"LinkedinContactItem"`
|
||||
} `json:"AdditionalContactList"`
|
||||
ChatroomVersion int `json:"ChatroomVersion"`
|
||||
ChatroomMaxCount int `json:"ChatroomMaxCount"`
|
||||
ChatroomAccessType int `json:"ChatroomAccessType"`
|
||||
NewChatroomData struct {
|
||||
MemberCount int `json:"MemberCount"`
|
||||
InfoMask int `json:"InfoMask"`
|
||||
} `json:"NewChatroomData"`
|
||||
DeleteFlag int `json:"DeleteFlag"`
|
||||
LabelIdlist string `json:"LabelIdlist"`
|
||||
PhoneNumListInfo struct {
|
||||
Count int `json:"Count"`
|
||||
} `json:"PhoneNumListInfo"`
|
||||
ChatroomInfoVersion int `json:"ChatroomInfoVersion"`
|
||||
DeleteContactScene int `json:"DeleteContactScene"`
|
||||
ChatroomStatus int `json:"ChatroomStatus"`
|
||||
ExtFlag int `json:"ExtFlag"`
|
||||
}
|
||||
|
@ -11,12 +11,21 @@ const (
|
||||
// Contact 表示微信联系人,包括好友和群组
|
||||
type Contact struct {
|
||||
BaseModel
|
||||
RobotID uint `gorm:"column:robot_id;index" json:"robot_id"`
|
||||
WechatID string `gorm:"column:wechat_id;index:idx_contact_wechat_id,length:64" json:"wechat_id"` // 添加索引长度
|
||||
Nickname string `gorm:"column:nickname" json:"nickname"`
|
||||
Avatar string `gorm:"column:avatar" json:"avatar"`
|
||||
Type ContactType `gorm:"column:type" json:"type"`
|
||||
Remark string `gorm:"column:remark" json:"remark"`
|
||||
RobotID uint `gorm:"column:robot_id;index:deleted,unique" json:"robot_id"`
|
||||
WechatID string `gorm:"column:wechat_id;index:deleted,unique" json:"wechat_id"` // 添加索引长度
|
||||
Alias string `gorm:"column:alias" json:"alias"` // 微信号
|
||||
Nickname string `gorm:"column:nickname" json:"nickname"`
|
||||
Avatar string `gorm:"column:avatar" json:"avatar"`
|
||||
Type ContactType `gorm:"column:type" json:"type"`
|
||||
Remark string `gorm:"column:remark" json:"remark"`
|
||||
Pyinitial string `gorm:"column:pyinitial" json:"pyinitial"` // 昵称拼音首字母大写
|
||||
QuanPin string `gorm:"column:quan_pin" json:"quan_pin"` // 昵称拼音全拼小写
|
||||
Sex int `gorm:"column:sex" json:"sex"` // 性别 0:未知 1:男 2:女
|
||||
Country string `gorm:"column:country" json:"country"` // 国家
|
||||
Province string `gorm:"column:province" json:"province"` // 省份
|
||||
City string `gorm:"column:city" json:"city"` // 城市
|
||||
Signature string `gorm:"column:signature" json:"signature"` // 个性签名
|
||||
SnsBackground string `gorm:"column:sns_background" json:"sns_background"` // 朋友圈背景图
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
115
internal/tasks/contract.go
Normal file
115
internal/tasks/contract.go
Normal file
@ -0,0 +1,115 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// syncContact
|
||||
// @description: 同步联系人
|
||||
// @param containerHost 容器接口地址
|
||||
// @param robotWxId 机器人微信号
|
||||
// @param robotId 机器人ID
|
||||
func syncContact(containerHost, robotWxId string, robotId uint) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// 先获取全部id
|
||||
ids, err := docker.GetContactList(ctx, containerHost, robotWxId)
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
log.Errorf("[%s]获取联系人列表失败: %v", robotWxId, err)
|
||||
return
|
||||
}
|
||||
// 过滤掉特殊微信Id
|
||||
var specialId = []string{"filehelper", "newsapp", "fmessage", "weibo", "qqmail", "tmessage", "qmessage", "qqsync",
|
||||
"floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp",
|
||||
"meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "officialaccounts",
|
||||
"notification_messages", "wxitil", "userexperience_alarm", "notification_messages", "exmail_tool", "mphelper"}
|
||||
ids = slices.DeleteFunc(ids, func(id string) bool {
|
||||
return slices.Contains(specialId, id) || strings.HasPrefix(id, "gh_") || strings.TrimSpace(id) == ""
|
||||
})
|
||||
|
||||
// 获取昵称等详细信息
|
||||
contacts, err := docker.GetContactDetail(ctx, containerHost, robotWxId, ids)
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
log.Errorf("[%s]获取联系人详情失败: %v", robotWxId, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 循环联系人信息,打印一下
|
||||
db := model.GetDB()
|
||||
nowIds := make([]string, 0)
|
||||
for _, contact := range contacts {
|
||||
//log.Infof("[%s]联系人信息: %+v", robotWxId, contact)
|
||||
|
||||
if strings.TrimSpace(contact.UserName.String) == "" {
|
||||
// 微信号为空,跳过
|
||||
continue
|
||||
}
|
||||
nowIds = append(nowIds, contact.UserName.String)
|
||||
|
||||
// 判断数据库是否存在当前数据,不存在就新建,存在就更新
|
||||
var existId uint
|
||||
db.Model(&model.Contact{}).Where("robot_id = ?", robotId).Where("wechat_id = ?", contact.UserName.String).Pluck("id", &existId)
|
||||
if existId > 0 {
|
||||
// 存在,修改
|
||||
pm := map[string]any{
|
||||
"alias": contact.Alias,
|
||||
"nickname": contact.NickName.String,
|
||||
"avatar": contact.BigHeadImgUrl,
|
||||
"pyinitial": contact.Pyinitial.String,
|
||||
"quan_pin": contact.QuanPin.String,
|
||||
"sex": contact.Sex,
|
||||
"country": contact.Country,
|
||||
"province": contact.Province,
|
||||
"city": contact.City,
|
||||
"signature": contact.Signature,
|
||||
"sns_background": contact.SnsUserInfo.SnsBgimgId,
|
||||
}
|
||||
if contact.BigHeadImgUrl == "" {
|
||||
pm["avatar"] = contact.SmallHeadImgUrl
|
||||
}
|
||||
err = db.Model(&model.Contact{}).Where("id = ?", existId).Updates(pm).Error
|
||||
} else {
|
||||
// 组装联系人信息,然后存入数据库
|
||||
var c model.Contact
|
||||
c.RobotID = robotId
|
||||
c.WechatID = contact.UserName.String
|
||||
c.Alias = contact.Alias
|
||||
c.Nickname = contact.NickName.String
|
||||
c.Avatar = contact.BigHeadImgUrl
|
||||
if contact.BigHeadImgUrl == "" {
|
||||
c.Avatar = contact.SmallHeadImgUrl
|
||||
}
|
||||
c.Type = "friend"
|
||||
if strings.HasSuffix(contact.UserName.String, "@chatroom") {
|
||||
// 群聊
|
||||
c.Type = "group"
|
||||
}
|
||||
c.Pyinitial = contact.Pyinitial.String
|
||||
c.QuanPin = contact.QuanPin.String
|
||||
c.Sex = contact.Sex
|
||||
c.Country = contact.Country
|
||||
c.Province = contact.Province
|
||||
c.City = contact.City
|
||||
c.Signature = contact.Signature
|
||||
c.SnsBackground = contact.SnsUserInfo.SnsBgimgId
|
||||
|
||||
err = db.Create(&c).Error
|
||||
}
|
||||
if err != nil {
|
||||
log.Debugf("[%s]保存联系人失败: %v", robotWxId, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 清理掉不存在的联系人
|
||||
db.Model(&model.Contact{}).Where("robot_id = ?", robotId).Where("wechat_id NOT IN ?", nowIds).Delete(&model.Contact{})
|
||||
}
|
@ -12,7 +12,8 @@ import (
|
||||
var scheduler gocron.Scheduler
|
||||
|
||||
// 已启动定时任务的微信机器人
|
||||
var enabledMap = sync.Map{}
|
||||
var enabledSyncMessageMap = sync.Map{}
|
||||
var enabledSyncContactMap = sync.Map{}
|
||||
|
||||
// Start
|
||||
// @description: 启动任务
|
||||
@ -41,7 +42,18 @@ func Start() {
|
||||
log.Panicf("添加定时任务失败: %v", err)
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
enabledMap.Store(robot.ID, job.ID())
|
||||
enabledSyncMessageMap.Store(robot.ID, job.ID())
|
||||
|
||||
// 添加联系人同步任务
|
||||
job, err = scheduler.NewJob(
|
||||
gocron.CronJob("0 */1 * * *", true), // 每小时同步一次
|
||||
gocron.NewTask(syncContact, robot.ContainerHost, robot.WechatID, robot.ID),
|
||||
)
|
||||
if err != nil {
|
||||
log.Panicf("添加联系人同步任务失败: %v", err)
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
enabledSyncContactMap.Store(robot.ID, job.ID())
|
||||
}
|
||||
|
||||
// 启动定时任务
|
||||
@ -61,7 +73,18 @@ func AddJob(robot model.Robot) {
|
||||
return
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
enabledMap.Store(robot.ID, job.ID())
|
||||
enabledSyncMessageMap.Store(robot.ID, job.ID())
|
||||
|
||||
// 添加联系人同步任务
|
||||
job, err = scheduler.NewJob(
|
||||
gocron.CronJob("0 */1 * * *", true), // 每小时同步一次
|
||||
gocron.NewTask(syncContact, robot.ContainerHost, robot.WechatID, robot.ID),
|
||||
)
|
||||
if err != nil {
|
||||
log.Panicf("添加联系人同步任务失败: %v", err)
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
enabledSyncContactMap.Store(robot.ID, job.ID())
|
||||
}
|
||||
|
||||
// DeleteJob
|
||||
@ -69,7 +92,7 @@ func AddJob(robot model.Robot) {
|
||||
// @param robotId
|
||||
func DeleteJob(robotId uint) {
|
||||
// 先取出任务Id
|
||||
jobId, ok := enabledMap.Load(robotId)
|
||||
jobId, ok := enabledSyncMessageMap.Load(robotId)
|
||||
if !ok {
|
||||
log.Printf("定时任务不存在,robotId: %d", robotId)
|
||||
return
|
||||
@ -78,4 +101,19 @@ func DeleteJob(robotId uint) {
|
||||
log.Printf("删除定时任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 删除已启动的任务列表
|
||||
enabledSyncMessageMap.Delete(robotId)
|
||||
|
||||
// 删除联系人同步任务
|
||||
jobId, ok = enabledSyncContactMap.Load(robotId)
|
||||
if !ok {
|
||||
log.Printf("联系人同步任务不存在,robotId: %d", robotId)
|
||||
return
|
||||
}
|
||||
if err := scheduler.RemoveJob(jobId.(uuid.UUID)); err != nil {
|
||||
log.Printf("删除联系人同步任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 删除已启动的任务列表
|
||||
enabledSyncContactMap.Delete(robotId)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user