Tai.WeCom.EntityFrameworkCore 2.2.0

dotnet add package Tai.WeCom.EntityFrameworkCore --version 2.2.0
                    
NuGet\Install-Package Tai.WeCom.EntityFrameworkCore -Version 2.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tai.WeCom.EntityFrameworkCore" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tai.WeCom.EntityFrameworkCore" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Tai.WeCom.EntityFrameworkCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tai.WeCom.EntityFrameworkCore --version 2.2.0
                    
#r "nuget: Tai.WeCom.EntityFrameworkCore, 2.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Tai.WeCom.EntityFrameworkCore@2.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tai.WeCom.EntityFrameworkCore&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Tai.WeCom.EntityFrameworkCore&version=2.2.0
                    
Install as a Cake Tool

Tai.WeCom

自动发布使用模块版本标签和 NuGet Trusted Publishing,配置与操作见发布指南。

ABP 可复用模块,为企业微信(WeCom)提供外部联系人、客户群的数据同步与管理功能。

WeCom 是 tenant-only 模块:所有业务数据和凭据都必须属于具体租户,不支持 Host 侧使用。

特性

  • 外部联系人同步 — 通过企业微信 API 全量拉取外部联系人(客户)数据,支持分页批量获取
  • 客户群同步 — 全量同步客户群(外部群)信息及群成员,支持游标分页
  • 回调事件处理 — 接收企业微信回调事件(URL 验证 + 事件接收),AES 解密与签名校验
  • 增量更新 — 通过 change_external_contact / change_external_chat 回调事件实现联系人、客户群的增量同步
  • 变更日志审计 — 记录外部联系人与客户群的每一次变更(新增、编辑、删除、解散等),支持回溯
  • 同步任务管理 — 可触发、查询全量同步任务,跟踪进度(Pending → Running → Completed/Failed)
  • Access Token 管理 — 自动获取、缓存和提前刷新企业微信 Access Token
  • 客户联系群发 — 创建、提醒、停止企业群发;群发记录、成员任务和发送结果持久化,支持定时同步和分页查询
  • 客户朋友圈 — 创建文本/图片/视频/链接发表任务,按成员、部门和客户标签限定范围;停止发表,持久化成员发表情况和可见客户
  • 定时发送与截止日期 — 群发和朋友圈均可预设发送时间与截止时间,到点自动创建、到期自动停止
  • 朋友圈规则组与企业客户标签 — 透传管理企业微信规则组及客户标签,不新增本地表
  • 失败回调重试 — 支持手动触发失败回调事件的重新处理

项目结构

src/
├── Tai.WeCom.Domain.Shared                     # 常量、枚举、错误码、本地化
├── Tai.WeCom.Domain                            # 实体、领域服务、API 客户端、Token 服务、加解密、Setting 定义
├── Tai.WeCom.Application.Contracts             # 应用服务接口与 DTO
├── Tai.WeCom.Application                       # 应用服务实现、后台同步任务
├── Tai.WeCom.EntityFrameworkCore              # EF Core DbContext、实体映射、仓储实现
├── Tai.WeCom.HttpApi                           # API 控制器
├── Tai.WeCom.HttpApi.Client                    # C# HTTP 客户端代理
└── Tai.WeCom.Installer                         # 便捷安装模块(聚合所有依赖)
test/
├── Tai.WeCom.TestBase
├── Tai.WeCom.Domain.Tests
├── Tai.WeCom.Application.Tests
└── Tai.WeCom.EntityFrameworkCore.Tests

快速开始

1. 安装模块

将以下项目引用添加到宿主应用的对应层:

dotnet add package Tai.WeCom.Application
dotnet add package Tai.WeCom.HttpApi
# 仅需要客户端代理时
dotnet add package Tai.WeCom.HttpApi.Client

在宿主模块中添加 DependsOn:

[DependsOn(typeof(TaiWeComApplicationModule))]
[DependsOn(typeof(TaiWeComHttpApiModule))]
public class YourHostModule : AbpModule { }

也可使用 TaiWeComInstallerModule(已聚合上述所有模块依赖):

[DependsOn(typeof(TaiWeComInstallerModule))]
public class YourHostModule : AbpModule { }

2. 配置 Settings

在目标租户上下文中,通过 ABP Setting Management 配置以下项。模块只读取 Tenant Setting 和代码内默认值,不会回退到 Host、Global 或 appsettings.json 中的 WeCom 凭据。

Setting Key 说明
WeCom.Enabled 是否启用企业微信模块(默认 false)
WeCom.CorpId 企业 ID(企业微信管理后台获取)
WeCom.CorpSecret 应用 Secret(加密存储)
WeCom.AgentId 应用 AgentId
WeCom.CallbackToken 回调 Token(加密存储)
WeCom.CallbackEncodingAesKey 回调消息 AES 加解密 Key(加密存储)
WeCom.HttpTimeoutMs HTTP 请求超时毫秒数(默认 30000)
WeCom.TokenRefreshAdvanceSeconds Access Token 提前刷新秒数(默认 300)

3. 配置企业微信回调 URL

在企业微信管理后台,将回调 URL 配置为:

https://your-domain/api/wecom/callback

回调 URL 必须携带 ABP 租户解析参数,例如:

https://your-domain/api/wecom/callback?__tenant=your-tenant

模块自动处理 URL 验证(GET)和事件接收(POST),通过 Token + AES 签名保证安全。缺少有效 租户上下文时,回调会被拒绝。

4. 从非多租户版本升级

2.0.0 是破坏性升级。生成并执行宿主 EF Core migration 前必须:

  1. 排空或显式清理旧版已入队的 WeCom 后台任务;旧 payload 没有 TenantId,升级后不会按 Host 任务继续执行。
  2. 为旧 WeCom 表中 TenantId IS NULL 的记录选择明确策略:回填到指定租户、删除,或中止 迁移等待人工处理。禁止静默回填为 Guid.Empty。
  3. 删除旧 TenantKey 列(若曾部署过包含该列的预览版本),将所有 WeCom 表的 TenantId 改为 NOT NULL,并重新创建以 TenantId 开头的业务唯一索引。
  4. 更新所有直接构造 WeCom 领域实体的宿主代码,为公开构造函数传入非空 tenantId。

API 参考

回调接口(匿名访问)

# URL 验证(企业微信回调配置时调用)
GET /api/wecom/callback?__tenant=your-tenant&msg_signature=xxx&timestamp=xxx&nonce=xxx&echostr=xxx

# 事件接收(外部联系人/客户群变更回调)
POST /api/wecom/callback?__tenant=your-tenant&msg_signature=xxx&timestamp=xxx&nonce=xxx

同步管理

# 触发全量同步外部联系人
POST /api/wecom/sync/contacts

# 触发全量同步客户群
POST /api/wecom/sync/group-chats

# 查询同步任务详情
GET /api/wecom/sync/tasks/{id}

# 查询同步任务列表
GET /api/wecom/sync/tasks

# 重试失败的回调事件
POST /api/wecom/sync/retry-failed-callbacks

外部联系人

# 分页查询外部联系人列表
GET /api/wecom/external-contacts

# 根据 ID 获取外部联系人详情
GET /api/wecom/external-contacts/{id}

# 根据 ExternalUserId 获取外部联系人详情
GET /api/wecom/external-contacts/by-external-user-id/{externalUserId}

# 获取外部联系人的跟进成员列表
GET /api/wecom/external-contacts/{id}/follow-users

# 分页查询外部联系人变更日志
GET /api/wecom/external-contacts/change-logs

客户群

# 分页查询客户群列表
GET /api/wecom/external-group-chats

# 根据 ID 获取客户群详情
GET /api/wecom/external-group-chats/{id}

# 根据 ChatId 获取客户群详情
GET /api/wecom/external-group-chats/by-chat-id/{chatId}

# 获取客户群成员列表
GET /api/wecom/external-group-chats/{id}/members

# 分页查询客户群变更日志
GET /api/wecom/external-group-chats/change-logs

客户联系 / 消息推送

所有接口要求有效租户上下文与 WeCom.MassMessages 权限。创建、提醒、停止和同步另需 对应的 .Create、.Remind、.Stop、.Sync 子权限,Host 侧不能调用。

POST /api/wecom/mass-messages                       # 创建企业群发
POST /api/wecom/mass-messages/{id}/remind           # 提醒成员群发
POST /api/wecom/mass-messages/{id}/stop             # 停止企业群发
GET  /api/wecom/mass-messages                      # 分页查询数据库记录
GET  /api/wecom/mass-messages/{id}                 # 查询数据库记录详情
GET  /api/wecom/mass-messages/{id}/tasks           # 分页查询成员发送任务
GET  /api/wecom/mass-messages/{id}/results         # 分页查询接收者发送详情
POST /api/wecom/mass-messages/sync                # 立即刷新本模块创建的未完成群发

id 是模块返回的本地 GUID,messageId 是企业微信的 msgid,定时群发在实际创建前为 null。 列表查询支持 messageId、chatType、status、startTime、endTime、skipCount 和 maxResultCount。 列表时间筛选使用 Unix 秒;发送详情支持 userId、status 和分页参数。所有 GET 接口直接 查询数据库,不访问企业微信,因此同步完成后的记录可长期查询。列表只包含通过本模块创建的 群发,不包含企业微信管理后台或成员个人创建的群发。

创建请求沿用企业微信的字段名称,例如发送给客户:

{
  "chat_type": "single",
  "sender": "member-userid",
  "external_userid": ["customer-external-userid"],
  "text": { "content": "客户通知内容" },
  "attachments": [
    { "msgtype": "image", "image": { "media_id": "uploaded-image-media-id" } }
  ]
}

发送给客户群时设 chat_type=group,必须提供 sender,可通过 chat_id_list 指定客户群。 支持 tag_filter.group_list[].tag_list 筛选客户;标签组内为或关系,组间为且关系。 文字最多 4000 个 UTF-8 字节,附件最多 9 个,支持 image、link、miniprogram、video、file。 附件内容使用对应的企业微信原始 JSON 字段;图片可先通过下述上传 API 获取 media_id。创建结果中的 failListJson 保存无法发送的客户或群 ID,requestJson 保存原始创建请求,contentJson 保存最新消息快照。

创建接口只创建任务并通知成员,成员确认后才发送给客户或客户群。提醒频率限制由 企业微信校验(24 小时内每个群发最多三次);停止只阻止继续发送,不撤回已发送的消息。 企业微信返回失败时,不更新本地提醒时间或停止时间。外部调用与数据库提交不能组成 原子事务:如果企业微信已经成功但本地提交失败,本地不会留下该群发的 msgid,同步也不会 从企业微信导入它,需要人工在企业微信后台核对;不要自动重试创建而生成重复的外部任务。

定时同步与执行状态

同步只针对本模块创建并已保存到本地的群发,不调用群发记录列表接口,也不导入其他群发。 默认 ABP Background Worker 每 5 分钟扫描存在未完成群发的租户,逐条通过 msgid 查询成员 任务和接收者发送结果,两层接口的游标分页都会完整读取。数据保存在数据库中,服务重启后 仍能继续;没有未完成群发的租户不会访问企业微信。

群发达到完成状态后不再刷新;停止后还会刷新一次最终结果。按租户、msgid 去重记录, 按记录、成员、接收者去重发送详情。任务状态为 0 未发送、2 已发送;接收者状态为 0 未发送、1 已发送、2 客户不是好友、3 客户已收到其他 群发。客户群未选择目标时,官方不会返回未发送的接收者记录。resultsComplete 仅在至少 存在一项成员任务、全部成员已发送且已知接收者都处于终态时成立。

每条群发先读完全部分页,再一次性写入任务和结果;调用失败的群发不写入本次数据,保持 未完成状态,后续周期重新同步,同租户的其他群发照常处理,全部处理后再返回首个失败。 每个租户有独立作用域、工作单元和同步锁,一个租户失败不阻断其他租户。同步时间由 ABP IClock 提供;宿主停止时 Worker 会取消正在进行的同步。

宿主可配置周期,或在只承担 HTTP 请求的进程中关闭 Worker:

Configure<WeComMassMessageSyncOptions>(options =>
{
    options.IsEnabled = true;
    options.PeriodMilliseconds = 300000;
});

默认调度从共享 WeCom 数据库中读取存在未完成群发的租户 ID,仅在此步骤临时关闭租户过滤器; 群发数据与凭据访问始终在恢复后的租户作用域内进行。独立数据库租户需要宿主在对应的 租户作用域中调度 WeComMassMessageManager.SyncAsync()。同步锁依赖 ABP Distributed Locking 抽象,默认使用进程内实现;多实例宿主应配置共享锁 Provider(如 Redis)并加载 AbpDistributedLockingModule。企业微信应用必须具有客户联系消息推送权限及相应成员 可见范围,模块使用现有租户凭据和 Token 服务。

数据库接入与升级

新增三张表:WeComMassMessages、WeComMassMessageTasks、WeComMassMessageResults (实际前缀由 WeComDbProperties 决定)。所有表的 TenantId 非空,业务唯一索引以租户开头, 子表通过群发记录关联并级联删除。

从早期版本升级的宿主需要生成一次迁移:删除 WeComMassMessageSyncStates 表,以及 WeComMassMessages 的 Creator、CreateType 列。旧版本从企业微信列表导入的、非本模块创建的 群发记录仍留在 WeComMassMessages 中;如需清理,宿主应按 RequestJson IS NULL 识别后自行删除, 否则其中未完成的记录会继续被同步。

直接使用 WeComDbContext 的宿主会获得新模型;实现 IWeComDbContext 并使用 ReplaceDbContext 的宿主需补充以下 DbSet,继续调用 builder.ConfigureWeCom():

public DbSet<WeComMassMessage> MassMessages { get; set; } = null!;
public DbSet<WeComMassMessageTask> MassMessageTasks { get; set; } = null!;
public DbSet<WeComMassMessageResult> MassMessageResults { get; set; } = null!;

这些类型位于 Tai.WeCom.MassMessaging。数据库驱动、迁移和 ModelSnapshot 属于实际 消费宿主,本模块不绑定数据库驱动或发布宿主迁移。部署前必须在宿主的迁移项目生成 并应用 migration,确保三张表存在,再启动 Worker,例如:

dotnet ef migrations add AddWeComMassMessaging --project <host-migrations-project> --startup-project <host-project>
dotnet ef database update --project <host-migrations-project> --startup-project <host-project>

官方协议参考:创建企业群发、 提醒成员群发、 停止企业群发、 群发记录、任务和执行结果。

定时发送与截止日期

创建群发和创建朋友圈都支持两个可选字段,单位为 Unix 秒:

字段 含义
scheduled_time 预定发送时间。早于或等于当前时间时立即创建;未到时间时只保存本地记录,状态为 Scheduled
expire_time 截止时间,必须晚于当前时间及 scheduled_time。到期后自动停止,已发送内容不撤回
{
  "chat_type": "single",
  "sender": "member-userid",
  "text": { "content": "活动通知" },
  "scheduled_time": 1800000000,
  "expire_time": 1800086400
}

这两个字段只在本模块使用,不转发给企业微信;requestJson 保存的也是不含这两个字段的原始请求。 WeComPublishScheduleWorker 默认每 60 秒按租户扫描到期项,因此实际发送和停止最多延迟一个周期。 扫描逐条在独立工作单元中处理:调用企业微信前先将状态保存为 Dispatching,成功后改为 Created(朋友圈为 Creating),企业微信返回业务错误时改为 Failed,并记录 errorMessage。 进程在调用期间崩溃时,记录会停留在 Dispatching 并且不会自动重试,以免重复群发,需要人工到企业微信后台核对。 如果处理时已经超过截止时间,记录直接取消,不再发送。

尚未发送的定时记录调用停止接口时只在本地取消,状态为 Canceled。已创建的记录调用企业微信停止接口。 图片、视频等 media_id 只有三天有效期,预约发送时间应留在有效期内,或使用长期有效的图片 URL。

状态 群发 WeComMassMessageStatus 朋友圈 WeComMomentStatus
待发送 Scheduled = 1 Scheduled = 0
调用中 Dispatching = 2 Dispatching = 1
等待企业微信异步结果 — Creating = 2
已创建 Created = 0(兼容旧数据) Created = 3
失败 Failed = 3 Failed = 4
本地取消 Canceled = 4 Canceled = 5

在只承担 HTTP 的进程中可关闭扫描,或调整周期:

Configure<WeComPublishScheduleOptions>(options =>
{
    options.IsEnabled = true;
    options.PeriodMilliseconds = 60000;
});

同一租户的群发调度和同步共用一把分布式锁,朋友圈调度和同步共用另一把。多实例部署必须配置共享锁 Provider。 独立数据库租户需要宿主在租户作用域中调用 WeComMassMessageManager.ProcessScheduleAsync() 和 WeComMomentManager.ProcessScheduleAsync()。

客户朋友圈

所有接口要求租户上下文与 WeCom.Moments 权限,写操作还需要 .Create、.Stop、.Sync 子权限。

POST /api/wecom/moments                      # 创建发表任务(支持定时与截止)
POST /api/wecom/moments/{id}/stop            # 停止发表
GET  /api/wecom/moments                      # 分页查询发表记录
GET  /api/wecom/moments/{id}                 # 查询记录详情
GET  /api/wecom/moments/{id}/tasks           # 成员发表情况
GET  /api/wecom/moments/{id}/results         # 成员发表后的可见客户
POST /api/wecom/moments/sync                 # 立即解析创建结果并刷新未完成记录
POST /api/wecom/media/moment-attachments     # 上传朋友圈图片/视频附件

创建请求使用创建发表任务的字段:

{
  "text": { "content": "新品上市" },
  "attachments": [
    { "msgtype": "link", "link": { "title": "详情", "url": "https://example.com", "media_id": "image-media-id" } }
  ],
  "visible_range": {
    "sender_list": { "user_list": ["zhangsan"], "department_list": [2] },
    "external_contact_list": { "tag_list": ["etXXXX"] }
  }
}

text 和 attachments 至少提供一项,文本最多 4000 字节。附件只能是同一种类型:最多 9 张图片、 1 个视频或 1 个链接。链接的 URL 必须为 http(s),标题最多 128 字节,media_id 必须是图片附件。 sender_list 为空时由企业微信按可见范围处理;tag_list 用于按客户标签筛选可见客户。

附件上传接口接收 multipart 字段 file 和 type(image 或 video),需要 WeCom.Moments.UploadAttachment 权限。它调用 upload_attachment(attachment_type=1),返回的 mediaId 三天有效,只能用于朋友圈附件。

企业微信异步创建朋友圈:模块保存返回的 jobid,状态为 Creating;WeComMomentSyncWorker 默认每 5 分钟 查询任务结果并保存 momentId。jobid 24 小时后失效,届时仍没有结果的记录标记为 Failed。 停止仍在 Creating 的记录时,模块先尝试获取结果;仍未完成时返回 WeCom:MomentNotReady(409),可以稍后重试。

已创建的记录会同步成员发表任务(publishStatus 0 未发表、1 已发表),并读取已发表成员的可见客户。 所有成员都已发表后记录完成,不再刷新;停止后还会刷新一次最终结果。列表支持 momentId、status、 startTime、endTime 和分页,可见客户支持 userId 筛选。和群发一样,模块只同步本模块创建的朋友圈, 外部调用与本地提交之间没有原子事务。

Configure<WeComMomentSyncOptions>(options =>
{
    options.IsEnabled = true;
    options.PeriodMilliseconds = 300000;
});

官方协议参考:客户朋友圈。

客户朋友圈规则组

规则组接口直接调用企业微信,不保存本地数据。读取需要 WeCom.MomentStrategies,写操作需要 .Create、.Update、.Delete 子权限。

GET    /api/wecom/moment-strategies?cursor=&limit=1000     # 规则组 ID 列表
GET    /api/wecom/moment-strategies/{strategyId}           # 规则组详情
GET    /api/wecom/moment-strategies/{strategyId}/range     # 管理范围(cursor、limit 分页)
POST   /api/wecom/moment-strategies                        # 创建,返回 strategyId
PUT    /api/wecom/moment-strategies/{strategyId}           # 编辑名称、管理员、权限,并增删管理范围
DELETE /api/wecom/moment-strategies/{strategyId}

管理员最多 20 人,每次范围节点最多 3000 个。范围节点 type=1 时传 userId,type=2 时传 partyId。 企业微信要求创建、编辑规则组串行执行,模块对同一租户加锁,最多等待 30 秒;超时返回 WeCom:OperationInProgress(409)。 协议参考:客户朋友圈规则组管理。

企业客户标签

标签接口同样直接调用企业微信。读取需要 WeCom.CorpTags,写操作需要 .Create、.Update、.Delete 子权限。

GET    /api/wecom/corp-tags?tagIds=..&groupIds=..   # 查询标签库,不传参数时返回全部
POST   /api/wecom/corp-tags                         # 添加标签;传 groupId 加入已有组,或传 groupName 新建组
PUT    /api/wecom/corp-tags/{id}                    # 编辑标签或标签组的名称、排序
DELETE /api/wecom/corp-tags?tagIds=..&groupIds=..   # 删除标签或标签组

名称最多 30 个字符,order 取值范围为 0 到 2^32-1。删除标签组中的全部标签时,企业微信会同时删除该组。 返回的标签 ID 可用于朋友圈 tag_list 和群发 tag_filter。 协议参考:管理企业标签。

朋友圈与定时发送升级迁移

宿主需要生成一次 EF Core 迁移,例如 dotnet ef migrations add AddWeComMomentsAndSchedule,并确认包含:

  • WeComMassMessages:MessageId 改为可空;新增 Status(默认 0,即已有记录为 Created)、 ScheduledTime、ExpireTime、ErrorMessage(最大 2048)列。
  • 新表 WeComMoments、WeComMomentTasks、WeComMomentResults,子表级联删除。

WeComMassMessages 和 WeComMoments 使用相同的索引:

索引 用途
(TenantId, MessageId) / (TenantId, MomentId) 唯一 防止重复记录,并按企业微信 ID 查询
(TenantId, WeComCreateTime) 租户内的列表查询和排序
(Status, ScheduledTime) 调度 Worker 查找到期的定时记录
(Status, ResultsComplete, StoppedTime, ExpireTime) 查找到截止时间、待停止的记录,以及同步时查找未完成的记录

后两个索引以状态开头,因为 Worker 先跨租户查找到期记录,再进入各租户处理。这样只需定位少量未完成的记录, 不用每个周期扫描全部历史记录。升级时会删除旧的 WeComMassMessages(TenantId, ResultsComplete) 索引, 它的用途已由最后一个索引覆盖。

朋友圈子表的唯一索引是 (MomentRecordId, UserId) 和 (MomentRecordId, UserId, ExternalUserId)。 父记录 ID 已经确定了租户,不需要再加 TenantId。这个唯一索引同时用于外键查找和级联删除, EF 不会再另建一个外键索引。

实现 IWeComDbContext 的宿主需要补充以下 DbSet(类型位于 Tai.WeCom.Moments):

public DbSet<WeComMoment> Moments { get; set; } = null!;
public DbSet<WeComMomentTask> MomentTasks { get; set; } = null!;
public DbSet<WeComMomentResult> MomentResults { get; set; } = null!;

源码兼容性变化:WeComMassMessageDto.MessageId 改为 string?;IWeComApiClient 新增 UploadAttachmentAsync,自定义实现需要适配;WeComMassMessageManager 改为继承 WeComPublishManagerBase,构造函数不变。WeCom:InvalidPublishState、WeCom:MomentNotReady、 WeCom:OperationInProgress 映射为 HTTP 409。应用迁移后再启动调度和同步 Worker。

数据模型

同步任务生命周期

Pending → Running → Completed
                 → Failed

全量同步通过 ABP 后台作业执行,适合大量数据的分页分批处理。每个同步任务记录已处理数、错误数和总数,便于追踪进度。

回调事件处理

收到事件 → Pending → Processing → Completed
                                → Failed

回调事件首先落库(5 秒内返回 success),然后通过后台作业异步处理。处理失败的记录可手动重试。

变更日志

每次外部联系人或客户群数据变更(无论是通过 API 回调还是全量同步)都会写入变更日志,记录变更前后的数据快照、变更摘要、变更来源(Callback / FullSync)。

回调事件类型

事件类型 ChangeType 说明
change_external_contact add_external_contact 添加外部联系人
add_half_external_contact 添加未互发消息的外部联系人
edit_external_contact 编辑外部联系人
del_external_contact 企业成员删除外部联系人
del_follow_user 外部联系人删除企业成员
transfer_fail 客户接替失败
change_external_chat create 创建客户群
update 更新客户群信息
dismiss 解散客户群

技术栈

  • .NET 10 / ABP 10.6.1
  • 企业微信服务端 API(外部联系人、客户群、回调)
  • EF Core(数据持久化与仓储)
  • ABP Distributed Cache(Access Token 缓存)
  • ABP Background Jobs(长耗时全量同步异步执行)
  • AES 加解密(回调消息安全)

消费宿主需使用 .NET 10 SDK,并将 ABP 包统一升级到 10.6.1。现有 AutoMapper 映射方式保持不变;Volo.Abp.AutoMapper 10.6.1 仍依赖 AutoMapper 14.0.0,NuGet 会报告 GHSA-rvv3-g6hj-g44x。升级 ABP 本身不会消除该警告,未将其屏蔽。

Nuget

dotnet pack Tai.WeCom.slnx --configuration Release --output nupkgs

dotnet nuget push "nupkgs/*.nupkg" --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate

License

MIT

按客户添加时间查询

GET /api/wecom/external-contacts 新增以下可选查询参数(Unix 秒,区间为左闭右开):

参数 含义
addedStartTime / addedEndTime 当前保存的成员关系的添加时间;任一关系满足整个区间即可,包含已标记删除的关系
firstAddedStartTime / firstAddedEndTime 企业内已知首次添加时间

两组条件同时传入时取交集;在数据库筛选后统计、分页,每个客户只返回一次。时间上界必须大于 下界,单边筛选也受支持。列表和详情均返回 firstAddedTime,列表中的 externalUserId 可以用于 POST /api/wecom/mass-messages 的 external_userid。

GET /api/wecom/external-contacts?firstAddedStartTime=1700000000&firstAddedEndTime=1700086400&skipCount=0&maxResultCount=100

FirstAddedTime 是可空的 Unix 秒字段,保存所有已知成员关系的最早添加时间。全量同步和客户 回调都会更新它,只允许向更早更新;删除关系和重新添加不会使它变晚。成员关系自己的 WeComCreateTime 则按最新快照更新,因此可以查询重新添加的时间。首次时间未知的客户不会 匹配首次添加时间筛选。此前未保存且企业微信不再返回的历史关系无法还原;该字段代表已知最早时间。

首次添加时间升级迁移

本仓库没有 wecom 数据库宿主、数据库驱动或迁移基线,迁移和 ModelSnapshot 必须在实际消费 宿主生成。在宿主的 EF Core 迁移项目执行:

dotnet ef migrations add AddWeComExternalContactFirstAddedTime

确认生成的迁移和 ModelSnapshot 包含:

  • ExternalContacts.FirstAddedTime 可空 long 列,不设置默认值。
  • ExternalContacts(TenantId, FirstAddedTime) 索引。
  • ContactFollowUsers(TenantId, WeComCreateTime, ExternalContactId) 索引。

实际表前缀和 Schema 以宿主配置为准。在部署新版应用和开启同步前应用迁移;租户独立数据库 需要逐库升级。本次不自动回填历史数据,由消费方自行处理。历史客户首次时间初始为 null, 后续同步会逐步记录已知最早时间。首次时间筛选不会返回尚未回填或同步的未知客户。

Down 使用生成的删除索引和列操作;回滚会丢失已积累的首次时间,回滚前应保留数据备份。 模块测试使用 SQLite 验证模型、筛选和同步,不能代替实际宿主的 Schema 迁移验证。

上传群发图片

POST /api/wecom/media/images 接收 multipart/form-data,文件字段名为 file。要求租户上下文, 并具备 WeCom.MassMessages 和 WeCom.MassMessages.UploadImage 权限。

curl -X POST 'https://your-host/api/wecom/media/images' \
  -H 'Authorization: Bearer <access-token>' \
  -H '__tenant: <tenant-id>' \
  -F 'file=@image.png'

响应示例(时间为 Unix 秒):

{
  "mediaId": "uploaded-image-media-id",
  "createdAt": 1700000000,
  "expiresAt": 1700259200
}

将 mediaId 放入创建群发的图片附件:

{
  "msgtype": "image",
  "image": { "media_id": "uploaded-image-media-id" }
}

按企业微信上传临时素材文档, 图片支持 JPG/JPEG、PNG,大小必须大于 5 字节且不超过 10MB,media_id 有效期为三天。 本接口检查扩展名、文件头和大小,再以 media 字段转发给企业微信;最终图片有效性由企业微信 校验。创建时间来自企业微信响应,过期时间为创建时间加三天,过期后需要重新上传。

上传只转发到企业微信,不保存图片文件或新增数据库记录,因此不需要数据库迁移。宿主或反向 代理的请求体限制需要允许 10MB 图片及 multipart 开销;模块接口限制为 10MB + 64KB。 进程内调用 IWeComMediaAppService.UploadImageAsync(UploadWeComImageDto) 使用文件名及字节数组; 该进程内入口不生成 JSON 远程接口,外部客户端使用上面的显式 multipart API。 自定义 IWeComApiClient 实现需要适配新增 UploadImageAsync 方法。

素材管理的 REST API

素材使用独立权限 WeCom.Media;同步上传还需 WeCom.Media.Upload,创建异步任务还需 WeCom.Media.CreateUploadJob。所有调用都要求租户上下文,使用该租户的企业微信凭据。 已有 /api/wecom/media/images 保留原有群发权限和响应行为,新调用建议使用以下资源接口。

方法和路径 输入 成功响应
POST /api/wecom/media multipart:file、type 201;type、mediaId、createdAt、expiresAt
POST /api/wecom/media/image-urls multipart:file 201;url,Location 指向返回的图片 URL
POST /api/wecom/media/jobs JSON:scene、type、fileName、url、md5 202;jobId、status,Location 指向任务查询 API
GET /api/wecom/media/jobs/{jobId} 任务 ID 200;任务状态和可用结果/失败详情

资源名称使用复数名词,创建使用 POST,查询使用 GET。按 HTTP Semantics 区分同步创建和异步受理, 不将 202 当成上传成功。任务失败是资源状态,查询仍返回 200,status=failed 并带 errorCode、errorMessage;接口调用本身失败则使用 ABP 错误响应。本模块将 WeCom:ApiCallFailed 映射为 502、WeCom:TenantRequired 映射为 400,这些映射也适用于模块其他接口。 参数校验使用 400,认证/授权错误使用 401/403;超过 HTTP 请求体限制由宿主返回 413。

同步临时素材

依据上传临时素材:

type 限制
image 大于 5 字节、不超过 10MB;JPG/JPEG、PNG
voice 大于 5 字节、不超过 2MB;AMR,不超过 60 秒
video 大于 5 字节、不超过 10MB;MP4
file 大于 5 字节、不超过 20MB

模块校验大小、扩展名和图片文件头;语音时长、媒体完整性等由企业微信校验。type 默认为 image。media_id 从企业微信创建时间起三天有效,过期需重新上传。语音上传能力不代表客户群发 支持语音附件;群发附件类型仍以群发接口协议为准。

curl -X POST 'https://your-host/api/wecom/media' \
  -H 'Authorization: Bearer <access-token>' -H '__tenant: <tenant-id>' \
  -F 'type=file' -F 'file=@document.pdf'
永久图片

企业微信当前上传图片接口返回长期有效 的图片 URL,不返回永久 media_id;它与微信公众号的永久素材接口不同。图片不超过 2MB, 返回 URL 用于企业微信允许的图片展示场景,不能作为通用外部站点图片 CDN。官方限制为每企业 每天最多 1000 张、每月最多 3000 张,由企业微信校验。

curl -X POST 'https://your-host/api/wecom/media/image-urls' \
  -H 'Authorization: Bearer <access-token>' -H '__tenant: <tenant-id>' \
  -F 'file=@image.png'

创建企业群发的图片附件可以使用此 接口返回的 url 填入 image.pic_url;不能使用任意外部图片 URL 代替。

异步上传任务

依据异步上传临时素材,目前仅支持 scene=1(客户联系入群欢迎语素材)、type=video|file,大小大于 5 字节且不超过 200MB。 视频要求 MP4,图片和语音不支持异步上传。CDN URL 必须支持 Range 分块下载,下载和 MD5、大小 检查由企业微信执行;本模块不访问或下载 URL。腾讯云 COS 链接需要公有读权限。

文件名上限 128 UTF-8 字节、URL 上限 1024 UTF-8 字节,MD5 为 32 位十六进制字符串。 jobId 上限 128 UTF-8 字节,官方查询有效期为 60 分钟,调用方需要及时查询并保存完成结果。 本模块采用主动查询方式,不保存任务或定时刷新,也不自动处理 upload_media_job_finish 回调。

POST /api/wecom/media/jobs
Content-Type: application/json

{
  "scene": 1,
  "type": "video",
  "fileName": "large.mp4",
  "url": "https://your-cdn/large.mp4",
  "md5": "00000000000000000000000000000000"
}

202 响应的 Location 可用于 GET 查询,status 为 processing、completed 或 failed。 仅完成状态返回 mediaId、createdAt、expiresAt;失败状态返回企业微信的任务错误码和消息。 异步产出的 media_id 同样三天有效,但使用范围与普通临时素材不同,不能直接用于企业群发。

三种模式都不新增数据库记录或保存本地文件,无新增迁移。宿主上传限制需要允许最大 20MB 同步文件及 multipart 开销。上传和任务查询为显式 HTTP API;进程内入口仍为 IWeComMediaAppService,新增接口方法及 IWeComApiClient 的同步素材方法需要自定义实现适配。

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Tai.WeCom.EntityFrameworkCore:

Package Downloads
Tai.WeCom.Installer

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.0 44 10/9/2026
2.1.1 76 10/8/2026
2.1.0 83 10/7/2026
2.0.0 85 10/7/2026
1.0.5 91 10/5/2026
1.0.4 121 9/16/2026
1.0.3 133 8/24/2026
1.0.2 148 5/11/2026
1.0.1 137 5/10/2026
1.0.0 139 5/10/2026