🎨 添加联系人同步功能,更新联系人模型以支持更多字段;优化定时任务管理
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
|
msg = response.Data.AddMsgs
|
||||||
return
|
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"`
|
MsgSeq int `json:"MsgSeq"`
|
||||||
PushContent string `json:"PushContent,omitempty"`
|
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 表示微信联系人,包括好友和群组
|
// Contact 表示微信联系人,包括好友和群组
|
||||||
type Contact struct {
|
type Contact struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
RobotID uint `gorm:"column:robot_id;index" json:"robot_id"`
|
RobotID uint `gorm:"column:robot_id;index:deleted,unique" json:"robot_id"`
|
||||||
WechatID string `gorm:"column:wechat_id;index:idx_contact_wechat_id,length:64" json:"wechat_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"`
|
Nickname string `gorm:"column:nickname" json:"nickname"`
|
||||||
Avatar string `gorm:"column:avatar" json:"avatar"`
|
Avatar string `gorm:"column:avatar" json:"avatar"`
|
||||||
Type ContactType `gorm:"column:type" json:"type"`
|
Type ContactType `gorm:"column:type" json:"type"`
|
||||||
Remark string `gorm:"column:remark" json:"remark"`
|
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 指定表名
|
// 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 scheduler gocron.Scheduler
|
||||||
|
|
||||||
// 已启动定时任务的微信机器人
|
// 已启动定时任务的微信机器人
|
||||||
var enabledMap = sync.Map{}
|
var enabledSyncMessageMap = sync.Map{}
|
||||||
|
var enabledSyncContactMap = sync.Map{}
|
||||||
|
|
||||||
// Start
|
// Start
|
||||||
// @description: 启动任务
|
// @description: 启动任务
|
||||||
@ -41,7 +42,18 @@ func Start() {
|
|||||||
log.Panicf("添加定时任务失败: %v", err)
|
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
|
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
|
// DeleteJob
|
||||||
@ -69,7 +92,7 @@ func AddJob(robot model.Robot) {
|
|||||||
// @param robotId
|
// @param robotId
|
||||||
func DeleteJob(robotId uint) {
|
func DeleteJob(robotId uint) {
|
||||||
// 先取出任务Id
|
// 先取出任务Id
|
||||||
jobId, ok := enabledMap.Load(robotId)
|
jobId, ok := enabledSyncMessageMap.Load(robotId)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("定时任务不存在,robotId: %d", robotId)
|
log.Printf("定时任务不存在,robotId: %d", robotId)
|
||||||
return
|
return
|
||||||
@ -78,4 +101,19 @@ func DeleteJob(robotId uint) {
|
|||||||
log.Printf("删除定时任务失败: %v", err)
|
log.Printf("删除定时任务失败: %v", err)
|
||||||
return
|
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