Compare commits

...

7 Commits

Author SHA1 Message Date
Copilot
5bb8b301c9 🎨 #3884 【微信支付】医保混合收款成功通知结果类里增加缺失的字段
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2026-02-28 17:29:29 +08:00
Copilot
c19d46bdb8 🎨 #3882 【小程序】多媒体内容安全识别异步回调结果类增加result 和 detail 字段 2026-02-28 17:28:01 +08:00
Copilot
60f7497b12 🆕 #3879 【公众号】被动回复消息增加「转接AI回复」支持 2026-02-28 17:26:26 +08:00
Copilot
389f1785b6 🆕 #3892 【企业微信】实现企业微信人事助手 API 2026-02-28 17:01:23 +08:00
Binary Wang
8ef5a33dd5 更新my-agent.agent.md,增加作者信息说明
新增关于作者信息的注意事项,确保作者名为GitHub Copilot。
2026-02-28 16:59:40 +08:00
Copilot
3233384b17 🎨 #3880 【企业微信】修复MemChangeList群成员变更ID解析为空字符串的问题 2026-02-28 16:55:43 +08:00
Copilot
5ff4114b6c 🎨 #3886 【企业微信】客服消息新增视频号消息类型(channels)支持 2026-02-28 16:53:17 +08:00
27 changed files with 1246 additions and 3 deletions

View File

@@ -10,5 +10,6 @@ description: 需要用中文包括PR标题和分析总结过程
# My Agent
1、请使用中文输出思考过程和总结包括PR标题提交commit信息也要使用中文
2、生成代码时需要提供必要的单元测试代码
- 1、请使用中文输出思考过程和总结包括PR标题提交commit信息也要使用中文
- 2、生成代码时需要提供必要的单元测试代码
- 3、新增加的代码如果标记作者信息请注意不要把作者名设为binarywang或者其他无关人员要改为 Github Copilot。

View File

@@ -53,6 +53,7 @@ public class WxConsts {
public static final String DEVICE_STATUS = "device_status";
public static final String HARDWARE = "hardware";
public static final String TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service";
public static final String TRANSFER_BIZ_AI_IVR = "transfer_biz_ai_ivr";
public static final String UPDATE_TASKCARD = "update_taskcard";
public static final String UPDATE_BUTTON = "update_button";
}

View File

@@ -0,0 +1,54 @@
package me.chanjar.weixin.common.util.xml;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
/**
* 兼容两种格式的字符串列表转换器:
* <ul>
* <li>旧格式4.8.0之前):&lt;MemChangeList&gt;&lt;![CDATA[id1,id2]]&gt;&lt;/MemChangeList&gt;</li>
* <li>新格式4.8.0起):&lt;MemChangeList&gt;&lt;Item&gt;&lt;![CDATA[id1]]&gt;&lt;/Item&gt;&lt;/MemChangeList&gt;</li>
* </ul>
* 解析结果统一为逗号分隔的字符串。
*/
public class XStreamCDataListConverter implements Converter {
@Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
if (source != null) {
writer.setValue("<![CDATA[" + source + "]]>");
}
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
if (reader.hasMoreChildren()) {
// 新格式:含有 <Item> 子元素
StringBuilder sb = new StringBuilder();
while (reader.hasMoreChildren()) {
reader.moveDown();
String value = reader.getValue();
if (value != null && !value.isEmpty()) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(value);
}
reader.moveUp();
}
return sb.length() > 0 ? sb.toString() : null;
} else {
// 旧格式:直接 CDATA 文本
String value = reader.getValue();
return (value != null && !value.isEmpty()) ? value : null;
}
}
@Override
public boolean canConvert(Class type) {
return type == String.class;
}
}

View File

@@ -0,0 +1,60 @@
package me.chanjar.weixin.cp.api;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldData;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldDataResp;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfoResp;
import java.util.List;
/**
* 人事助手相关接口.
* 官方文档https://developer.work.weixin.qq.com/document/path/99132
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
public interface WxCpHrService {
/**
* 获取员工档案字段信息.
* <p>
* 请求方式POSTHTTPS
* 请求地址https://qyapi.weixin.qq.com/cgi-bin/hr/employee/get_field_info?access_token=ACCESS_TOKEN
* 权限说明:
* 需要配置人事助手的secret调用接口前需给对应成员赋予人事小助手应用的权限。
*
* @param fields 指定字段key列表不填则返回全部字段
* @return 字段信息响应 wx cp hr employee field info resp
* @throws WxErrorException the wx error exception
*/
WxCpHrEmployeeFieldInfoResp getFieldInfo(List<String> fields) throws WxErrorException;
/**
* 获取员工档案数据.
* <p>
* 请求方式POSTHTTPS
* 请求地址https://qyapi.weixin.qq.com/cgi-bin/hr/employee/get_employee_field_info?access_token=ACCESS_TOKEN
* 权限说明:
* 需要配置人事助手的secret调用接口前需给对应成员赋予人事小助手应用的权限。
*
* @param userids 员工userid列表不超过20个
* @param fields 指定字段key列表不填则返回全部字段
* @return 员工档案数据响应 wx cp hr employee field data resp
* @throws WxErrorException the wx error exception
*/
WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(List<String> userids, List<String> fields) throws WxErrorException;
/**
* 更新员工档案数据.
* <p>
* 请求方式POSTHTTPS
* 请求地址https://qyapi.weixin.qq.com/cgi-bin/hr/employee/update_employee_field_info?access_token=ACCESS_TOKEN
* 权限说明:
* 需要配置人事助手的secret调用接口前需给对应成员赋予人事小助手应用的权限。
*
* @param userid 员工userid
* @param fieldList 字段数据列表
* @throws WxErrorException the wx error exception
*/
void updateEmployeeFieldInfo(String userid, List<WxCpHrEmployeeFieldData.FieldItem> fieldList) throws WxErrorException;
}

View File

@@ -594,4 +594,11 @@ public interface WxCpService extends WxService {
* @return 智能机器人服务 intelligent robot service
*/
WxCpIntelligentRobotService getIntelligentRobotService();
/**
* 获取人事助手服务
*
* @return 人事助手服务 hr service
*/
WxCpHrService getHrService();
}

View File

@@ -75,6 +75,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this);
private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this);
private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this);
private final WxCpHrService hrService = new WxCpHrServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
@@ -708,4 +709,9 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
public WxCpIntelligentRobotService getIntelligentRobotService() {
return this.intelligentRobotService;
}
@Override
public WxCpHrService getHrService() {
return this.hrService;
}
}

View File

@@ -0,0 +1,77 @@
package me.chanjar.weixin.cp.api.impl;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpHrService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldData;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldDataResp;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfoResp;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.util.List;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Hr.*;
/**
* 人事助手相关接口实现类.
* 官方文档https://developer.work.weixin.qq.com/document/path/99132
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@RequiredArgsConstructor
public class WxCpHrServiceImpl implements WxCpHrService {
private final WxCpService cpService;
@Override
public WxCpHrEmployeeFieldInfoResp getFieldInfo(List<String> fields) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
if (fields != null && !fields.isEmpty()) {
jsonObject.add("fields", WxCpGsonBuilder.create().toJsonTree(fields));
}
String response = this.cpService.post(
this.cpService.getWxCpConfigStorage().getApiUrl(GET_FIELD_INFO),
jsonObject.toString()
);
return WxCpHrEmployeeFieldInfoResp.fromJson(response);
}
@Override
public WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(List<String> userids, List<String> fields) throws WxErrorException {
if (userids == null || userids.isEmpty()) {
throw new IllegalArgumentException("userids 不能为空");
}
if (userids.size() > 20) {
throw new IllegalArgumentException("userids 每次最多传入20个");
}
JsonObject jsonObject = new JsonObject();
jsonObject.add("userids", WxCpGsonBuilder.create().toJsonTree(userids));
if (fields != null && !fields.isEmpty()) {
jsonObject.add("fields", WxCpGsonBuilder.create().toJsonTree(fields));
}
String response = this.cpService.post(
this.cpService.getWxCpConfigStorage().getApiUrl(GET_EMPLOYEE_FIELD_INFO),
jsonObject.toString()
);
return WxCpHrEmployeeFieldDataResp.fromJson(response);
}
@Override
public void updateEmployeeFieldInfo(String userid, List<WxCpHrEmployeeFieldData.FieldItem> fieldList) throws WxErrorException {
if (userid == null || userid.trim().isEmpty()) {
throw new IllegalArgumentException("userid 不能为空");
}
if (fieldList == null || fieldList.isEmpty()) {
throw new IllegalArgumentException("fieldList 不能为空");
}
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("userid", userid);
jsonObject.add("field_list", WxCpGsonBuilder.create().toJsonTree(fieldList));
this.cpService.post(
this.cpService.getWxCpConfigStorage().getApiUrl(UPDATE_EMPLOYEE_FIELD_INFO),
jsonObject.toString()
);
}
}

View File

@@ -0,0 +1,52 @@
package me.chanjar.weixin.cp.bean.hr;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 人事助手-员工档案数据(单个员工).
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Data
@NoArgsConstructor
public class WxCpHrEmployeeFieldData implements Serializable {
private static final long serialVersionUID = 4593693598671765396L;
/**
* 员工userid.
*/
@SerializedName("userid")
private String userid;
/**
* 字段数据列表.
*/
@SerializedName("field_list")
private List<FieldItem> fieldList;
/**
* 字段数据项.
*/
@Data
@NoArgsConstructor
public static class FieldItem implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 字段key.
*/
@SerializedName("field_key")
private String fieldKey;
/**
* 字段值.
*/
@SerializedName("field_value")
private WxCpHrEmployeeFieldValue fieldValue;
}
}

View File

@@ -0,0 +1,38 @@
package me.chanjar.weixin.cp.bean.hr;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.util.List;
/**
* 人事助手-获取员工档案数据响应.
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class WxCpHrEmployeeFieldDataResp extends WxCpBaseResp {
private static final long serialVersionUID = 6593693598671765396L;
/**
* 员工档案数据列表.
*/
@SerializedName("employee_field_list")
private List<WxCpHrEmployeeFieldData> employeeFieldList;
/**
* From json wx cp hr employee field data resp.
*
* @param json the json
* @return the wx cp hr employee field data resp
*/
public static WxCpHrEmployeeFieldDataResp fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpHrEmployeeFieldDataResp.class);
}
}

View File

@@ -0,0 +1,103 @@
package me.chanjar.weixin.cp.bean.hr;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 人事助手-员工档案字段信息.
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Data
@NoArgsConstructor
public class WxCpHrEmployeeFieldInfo implements Serializable {
private static final long serialVersionUID = 2593693598671765396L;
/**
* 字段key.
*/
@SerializedName("field_key")
private String fieldKey;
/**
* 字段英文名称.
*/
@SerializedName("field_en_name")
private String fieldEnName;
/**
* 字段中文名称.
*/
@SerializedName("field_zh_name")
private String fieldZhName;
/**
* 字段类型.
* 具体取值参见 {@link WxCpHrFieldType}
*/
@SerializedName("field_type")
private Integer fieldType;
/**
* 获取字段类型枚举.
*
* @return 字段类型枚举,未匹配时返回 null
*/
public WxCpHrFieldType getFieldTypeEnum() {
return fieldType == null ? null : WxCpHrFieldType.fromCode(fieldType);
}
/**
* 是否系统字段.
* 0: 否
* 1: 是
*/
@SerializedName("is_sys")
private Integer isSys;
/**
* 字段详情.
*/
@SerializedName("field_detail")
private FieldDetail fieldDetail;
/**
* 字段详情.
*/
@Data
@NoArgsConstructor
public static class FieldDetail implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 选项列表(单选/多选字段专用).
*/
@SerializedName("option_list")
private List<Option> optionList;
}
/**
* 选项.
*/
@Data
@NoArgsConstructor
public static class Option implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 选项key.
*/
@SerializedName("key")
private String key;
/**
* 选项值.
*/
@SerializedName("value")
private String value;
}
}

View File

@@ -0,0 +1,38 @@
package me.chanjar.weixin.cp.bean.hr;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.util.List;
/**
* 人事助手-获取员工档案字段信息响应.
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class WxCpHrEmployeeFieldInfoResp extends WxCpBaseResp {
private static final long serialVersionUID = 5593693598671765396L;
/**
* 字段信息列表.
*/
@SerializedName("field_info_list")
private List<WxCpHrEmployeeFieldInfo> fieldInfoList;
/**
* From json wx cp hr employee field info resp.
*
* @param json the json
* @return the wx cp hr employee field info resp
*/
public static WxCpHrEmployeeFieldInfoResp fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpHrEmployeeFieldInfoResp.class);
}
}

View File

@@ -0,0 +1,94 @@
package me.chanjar.weixin.cp.bean.hr;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 人事助手-员工档案字段值.
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Data
@NoArgsConstructor
public class WxCpHrEmployeeFieldValue implements Serializable {
private static final long serialVersionUID = 3593693598671765396L;
/**
* 文本/数字/手机/邮箱类型字段值.
*/
@SerializedName("text_value")
private String textValue;
/**
* 单选类型字段值.
*/
@SerializedName("option_value")
private OptionValue optionValue;
/**
* 多选类型字段值列表.
*/
@SerializedName("option_value_list")
private List<OptionValue> optionValueList;
/**
* 日期类型字段值.
*/
@SerializedName("date_value")
private DateValue dateValue;
/**
* 附件类型字段值.
*/
@SerializedName("attachment_value")
private AttachmentValue attachmentValue;
/**
* 单选/多选选项值.
*/
@Data
@NoArgsConstructor
public static class OptionValue implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 选项key.
*/
@SerializedName("key")
private String key;
}
/**
* 日期值.
*/
@Data
@NoArgsConstructor
public static class DateValue implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 时间戳(字符串格式).
*/
@SerializedName("timestamp")
private String timestamp;
}
/**
* 附件值.
*/
@Data
@NoArgsConstructor
public static class AttachmentValue implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 附件id列表.
*/
@SerializedName("id_list")
private List<String> idList;
}
}

View File

@@ -0,0 +1,64 @@
package me.chanjar.weixin.cp.bean.hr;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 人事助手-员工档案字段类型枚举.
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Getter
@AllArgsConstructor
public enum WxCpHrFieldType {
/**
* 文本.
*/
TEXT("文本", 1),
/**
* 日期.
*/
DATE("日期", 2),
/**
* 数字.
*/
NUMBER("数字", 3),
/**
* 单选.
*/
SINGLE_SELECT("单选", 4),
/**
* 多选.
*/
MULTI_SELECT("多选", 5),
/**
* 附件.
*/
ATTACHMENT("附件", 6),
/**
* 手机.
*/
PHONE("手机", 7),
/**
* 邮箱.
*/
EMAIL("邮箱", 8);
private final String typeName;
private final int code;
/**
* 根据数值获取字段类型枚举.
*
* @param code 字段类型数值
* @return 字段类型枚举,未匹配时返回 null
*/
public static WxCpHrFieldType fromCode(int code) {
for (WxCpHrFieldType type : WxCpHrFieldType.values()) {
if (type.code == code) {
return type;
}
}
return null;
}
}

View File

@@ -67,6 +67,7 @@ public class WxCpKfMsgListResp extends WxCpBaseResp {
private WxCpKfChannelsShopProductMsg channelsShopProduct;
@SerializedName("channels_shop_order")
private WxCpKfChannelsShopOrderMsg channelsShopOrder;
private WxCpKfChannelsMsg channels;
}
/**

View File

@@ -0,0 +1,52 @@
package me.chanjar.weixin.cp.bean.kf.msg;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 视频号消息
*
* @author liuzhao created on 2024/2/25
*/
@NoArgsConstructor
@Data
public class WxCpKfChannelsMsg {
/**
* 视频号名称
*/
@SerializedName("nickname")
private String nickname;
/**
* 视频/直播标题
*/
@SerializedName("title")
private String title;
/**
* 视频/直播描述
*/
@SerializedName("desc")
private String desc;
/**
* 封面图片url
*/
@SerializedName("cover_url")
private String coverUrl;
/**
* 视频/直播链接
*/
@SerializedName("url")
private String url;
/**
* 视频号账号名称
*/
@SerializedName("find_username")
private String findUsername;
}

View File

@@ -11,6 +11,7 @@ import me.chanjar.weixin.common.util.XmlUtils;
import me.chanjar.weixin.common.util.xml.IntegerArrayConverter;
import me.chanjar.weixin.common.util.xml.LongArrayConverter;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import me.chanjar.weixin.common.util.xml.XStreamCDataListConverter;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
@@ -156,7 +157,7 @@ public class WxCpXmlMessage implements Serializable {
private String memChangeCnt;
@XStreamAlias("MemChangeList")
@XStreamConverter(value = XStreamCDataConverter.class)
@XStreamConverter(value = XStreamCDataListConverter.class)
private String memChangeList;
@XStreamAlias("LastMemVer")

View File

@@ -1701,4 +1701,25 @@ public interface WxCpApiPathConsts {
*/
String SEND_MESSAGE = "/cgi-bin/intelligent_robot/send_message";
}
/**
* 人事助手相关接口.
* 官方文档https://developer.work.weixin.qq.com/document/path/99132
*/
interface Hr {
/**
* 获取员工档案字段信息.
*/
String GET_FIELD_INFO = "/cgi-bin/hr/employee/get_field_info";
/**
* 获取员工档案数据.
*/
String GET_EMPLOYEE_FIELD_INFO = "/cgi-bin/hr/employee/get_employee_field_info";
/**
* 更新员工档案数据.
*/
String UPDATE_EMPLOYEE_FIELD_INFO = "/cgi-bin/hr/employee/update_employee_field_info";
}
}

View File

@@ -0,0 +1,100 @@
package me.chanjar.weixin.cp.api.impl;
import com.google.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.WxCpHrService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldData;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldDataResp;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfoResp;
import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldValue;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 人事助手接口单元测试类.
*
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/
@Slf4j
@Guice(modules = ApiTestModule.class)
public class WxCpHrServiceImplTest {
/**
* The Wx service.
*/
@Inject
private WxCpService wxCpService;
/**
* The Config storage.
*/
@Inject
protected ApiTestModule.WxXmlCpInMemoryConfigStorage configStorage;
/**
* 测试获取员工档案字段信息.
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testGetFieldInfo() throws WxErrorException {
WxCpHrService hrService = this.wxCpService.getHrService();
// 获取所有字段信息
WxCpHrEmployeeFieldInfoResp resp = hrService.getFieldInfo(null);
assertThat(resp).isNotNull();
assertThat(resp.getFieldInfoList()).isNotNull();
log.info("获取所有字段信息: {}", resp);
}
/**
* 测试获取指定字段信息.
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testGetFieldInfoWithFilter() throws WxErrorException {
WxCpHrService hrService = this.wxCpService.getHrService();
// 获取指定字段信息
WxCpHrEmployeeFieldInfoResp resp = hrService.getFieldInfo(Collections.singletonList("sys_field_name"));
assertThat(resp).isNotNull();
log.info("获取指定字段信息: {}", resp);
}
/**
* 测试获取员工档案数据.
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testGetEmployeeFieldInfo() throws WxErrorException {
WxCpHrService hrService = this.wxCpService.getHrService();
WxCpHrEmployeeFieldDataResp resp = hrService.getEmployeeFieldInfo(
Collections.singletonList(this.configStorage.getUserId()), null);
assertThat(resp).isNotNull();
assertThat(resp.getEmployeeFieldList()).isNotNull();
log.info("获取员工档案数据: {}", resp);
}
/**
* 测试更新员工档案数据.
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testUpdateEmployeeFieldInfo() throws WxErrorException {
WxCpHrService hrService = this.wxCpService.getHrService();
WxCpHrEmployeeFieldData.FieldItem fieldItem = new WxCpHrEmployeeFieldData.FieldItem();
fieldItem.setFieldKey("sys_field_name");
WxCpHrEmployeeFieldValue fieldValue = new WxCpHrEmployeeFieldValue();
fieldValue.setTextValue("测试姓名");
fieldItem.setFieldValue(fieldValue);
hrService.updateEmployeeFieldInfo(this.configStorage.getUserId(), Arrays.asList(fieldItem));
log.info("更新员工档案数据成功");
}
}

View File

@@ -570,5 +570,53 @@ public class WxCpXmlMessageTest {
assertEquals(wxMessage3.getUpdateDetail(), "change_name");
// 当XML中没有MemChangeList元素时字段应该为null而不是空字符串
assertThat(wxMessage3.getMemChangeList()).isNull();
// 测试企业微信4.8.0新格式MemChangeList使用<Item>子元素(加群场景)
String xmlNewFormatAddMember = "<xml>"
+ "<ToUserName><![CDATA[c2e112dad808119117371bbcd6]]></ToUserName>"
+ "<FromUserName><![CDATA[sys]]></FromUserName>"
+ "<CreateTime>9811170016713</CreateTime>"
+ "<MsgType><![CDATA[event]]></MsgType>"
+ "<Event><![CDATA[change_external_chat]]></Event>"
+ "<ChatId><![CDATA[wrxUBwDQAAa44T11Ziaed811rhUr8-3Igmug]]></ChatId>"
+ "<ChangeType><![CDATA[update]]></ChangeType>"
+ "<UpdateDetail><![CDATA[add_member]]></UpdateDetail>"
+ "<JoinScene>3</JoinScene>"
+ "<MemChangeCnt>1</MemChangeCnt>"
+ "<MemChangeList><Item><![CDATA[wmxUBwDQAAO-Hn5_wFJz4wvo5TxLFibw]]></Item></MemChangeList>"
+ "<LastMemVer><![CDATA[5807afd2ab75771d5e8ac623f534ac0b]]></LastMemVer>"
+ "<CurMemVer><![CDATA[ea36e8b6062b803cda0ee45e9418d637]]></CurMemVer>"
+ "</xml>";
WxCpXmlMessage wxMessage4 = WxCpXmlMessage.fromXml(xmlNewFormatAddMember);
assertEquals(wxMessage4.getEvent(), WxCpConsts.EventType.CHANGE_EXTERNAL_CHAT);
assertEquals(wxMessage4.getChangeType(), "update");
assertEquals(wxMessage4.getUpdateDetail(), "add_member");
assertEquals(wxMessage4.getJoinScene(), "3");
assertEquals(wxMessage4.getMemChangeCnt(), "1");
// 新格式:<Item>子元素中的成员ID应被正确解析
assertEquals(wxMessage4.getMemChangeList(), "wmxUBwDQAAO-Hn5_wFJz4wvo5TxLFibw");
// 测试企业微信4.8.0新格式:多个<Item>子元素(多成员变更)
String xmlNewFormatMultiMember = "<xml>"
+ "<ToUserName><![CDATA[toUser]]></ToUserName>"
+ "<FromUserName><![CDATA[sys]]></FromUserName>"
+ "<CreateTime>1403610513</CreateTime>"
+ "<MsgType><![CDATA[event]]></MsgType>"
+ "<Event><![CDATA[change_external_chat]]></Event>"
+ "<ChangeType><![CDATA[update]]></ChangeType>"
+ "<ChatId><![CDATA[wrOgQhDgAAMYQiS5ol9G7gK9JVAAAA]]></ChatId>"
+ "<UpdateDetail><![CDATA[del_member]]></UpdateDetail>"
+ "<QuitScene>1</QuitScene>"
+ "<MemChangeCnt>2</MemChangeCnt>"
+ "<MemChangeList>"
+ "<Item><![CDATA[wmEJiCwAAA9KG2qlSq6rKwASSgAAAA]]></Item>"
+ "<Item><![CDATA[wmEJiCwAAA9KG2qlSq6rKwBBBBBBB]]></Item>"
+ "</MemChangeList>"
+ "</xml>";
WxCpXmlMessage wxMessage5 = WxCpXmlMessage.fromXml(xmlNewFormatMultiMember);
assertEquals(wxMessage5.getUpdateDetail(), "del_member");
assertEquals(wxMessage5.getMemChangeCnt(), "2");
// 多个<Item>元素应被解析为逗号分隔字符串
assertEquals(wxMessage5.getMemChangeList(), "wmEJiCwAAA9KG2qlSq6rKwASSgAAAA,wmEJiCwAAA9KG2qlSq6rKwBBBBBBB");
}
}

View File

@@ -9,6 +9,7 @@ import lombok.Data;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import java.io.Serializable;
import java.util.List;
/**
* @author borisbao
@@ -26,6 +27,18 @@ public class WxMaMediaAsyncCheckResult implements Serializable {
/**
* 综合结果
*/
@SerializedName("result")
private ResultBean result;
/**
* 详细检测结果列表
*/
@SerializedName("detail")
private List<DetailBean> detail;
/**
* 综合结果内部类定义
*/
@Data
@Builder
@XStreamAlias("result")

View File

@@ -0,0 +1,52 @@
package cn.binarywang.wx.miniapp.bean;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
/**
* 测试多媒体内容安全异步检测结果解析
*
* @author copilot
*/
@Test
public class WxMaMediaAsyncCheckResultTest {
public void testFromJsonWithResultAndDetail() {
String json = "{\n"
+ " \"trace_id\": \"test_trace_id_001\",\n"
+ " \"result\": {\n"
+ " \"suggest\": \"risky\",\n"
+ " \"label\": 20001\n"
+ " },\n"
+ " \"detail\": [\n"
+ " {\n"
+ " \"strategy\": \"content_model\",\n"
+ " \"errcode\": 0,\n"
+ " \"suggest\": \"risky\",\n"
+ " \"label\": 20006,\n"
+ " \"prob\": 90\n"
+ " }\n"
+ " ]\n"
+ "}";
WxMaMediaAsyncCheckResult result = WxMaMediaAsyncCheckResult.fromJson(json);
assertNotNull(result);
assertEquals(result.getTraceId(), "test_trace_id_001");
assertNotNull(result.getResult());
assertEquals(result.getResult().getSuggest(), "risky");
assertEquals(result.getResult().getLabel(), "20001");
assertNotNull(result.getDetail());
assertEquals(result.getDetail().size(), 1);
WxMaMediaAsyncCheckResult.DetailBean detail = result.getDetail().get(0);
assertEquals(detail.getStrategy(), "content_model");
assertEquals(detail.getErrcode(), Integer.valueOf(0));
assertEquals(detail.getSuggest(), "risky");
assertEquals(detail.getLabel(), "20006");
assertEquals(detail.getProb(), Integer.valueOf(90));
}
}

View File

@@ -131,6 +131,15 @@ public abstract class WxMpXmlOutMessage implements Serializable {
return new TransferCustomerServiceBuilder();
}
/**
* 获得转接AI回复消息builder
*
* @return 转接AI回复消息构建器
*/
public static TransferBizAiIvrBuilder TRANSFER_BIZ_AI_IVR() {
return new TransferBizAiIvrBuilder();
}
/**
* 获得设备消息builder
*

View File

@@ -0,0 +1,29 @@
package me.chanjar.weixin.mp.bean.message;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Data;
import lombok.EqualsAndHashCode;
import me.chanjar.weixin.common.api.WxConsts;
/**
* 转接AI回复消息.
* <pre>
* 当用户发送消息给公众号时公众号开发者服务器回复如下内容会触发微信公众平台的AI回复。
* 注意需要公众号在微信公众平台上已开启AI回复功能并且AI已学习完毕历史发表文章。
* 官方文档https://developers.weixin.qq.com/doc/subscription/guide/product/message/Passive_user_reply_message.html
* </pre>
*
* @author copilot
*/
@Data
@XStreamAlias("xml")
@JacksonXmlRootElement(localName = "xml")
@EqualsAndHashCode(callSuper = true)
public class WxMpXmlOutTransferBizAiIvrMessage extends WxMpXmlOutMessage {
private static final long serialVersionUID = 8275281170988017563L;
public WxMpXmlOutTransferBizAiIvrMessage() {
this.msgType = WxConsts.XmlMsgType.TRANSFER_BIZ_AI_IVR;
}
}

View File

@@ -0,0 +1,22 @@
package me.chanjar.weixin.mp.builder.outxml;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTransferBizAiIvrMessage;
/**
* 转接AI回复消息builder.
* <pre>
* 用法: WxMpXmlOutTransferBizAiIvrMessage m = WxMpXmlOutMessage.TRANSFER_BIZ_AI_IVR().toUser("").fromUser("").build();
* </pre>
*
* @author copilot
*/
public final class TransferBizAiIvrBuilder
extends BaseBuilder<TransferBizAiIvrBuilder, WxMpXmlOutTransferBizAiIvrMessage> {
@Override
public WxMpXmlOutTransferBizAiIvrMessage build() {
WxMpXmlOutTransferBizAiIvrMessage m = new WxMpXmlOutTransferBizAiIvrMessage();
setCommon(m);
return m;
}
}

View File

@@ -15,6 +15,7 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMusicMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTransferKefuMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTransferBizAiIvrMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutVideoMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutVoiceMessage;
@@ -30,6 +31,7 @@ public class XStreamTransformer {
registerClass(WxMpXmlOutVideoMessage.class);
registerClass(WxMpXmlOutVoiceMessage.class);
registerClass(WxMpXmlOutTransferKefuMessage.class);
registerClass(WxMpXmlOutTransferBizAiIvrMessage.class);
}
/**

View File

@@ -0,0 +1,44 @@
package me.chanjar.weixin.mp.bean.message;
import org.testng.Assert;
import org.testng.annotations.Test;
/**
* 转接AI回复消息测试.
*
* @author copilot
*/
public class WxMpXmlOutTransferBizAiIvrMessageTest {
@Test
public void test() {
WxMpXmlOutTransferBizAiIvrMessage m = new WxMpXmlOutTransferBizAiIvrMessage();
m.setCreateTime(1399197672L);
m.setFromUserName("fromuser");
m.setToUserName("touser");
String expected = "<xml>" +
"<ToUserName><![CDATA[touser]]></ToUserName>" +
"<FromUserName><![CDATA[fromuser]]></FromUserName>" +
"<CreateTime>1399197672</CreateTime>" +
"<MsgType><![CDATA[transfer_biz_ai_ivr]]></MsgType>" +
"</xml>";
System.out.println(m.toXml());
Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", ""));
}
@Test
public void testBuild() {
WxMpXmlOutTransferBizAiIvrMessage m = WxMpXmlOutMessage.TRANSFER_BIZ_AI_IVR()
.fromUser("fromuser").toUser("touser").build();
m.setCreateTime(1399197672L);
String expected = "<xml>" +
"<ToUserName><![CDATA[touser]]></ToUserName>" +
"<FromUserName><![CDATA[fromuser]]></FromUserName>" +
"<CreateTime>1399197672</CreateTime>" +
"<MsgType><![CDATA[transfer_biz_ai_ivr]]></MsgType>" +
"</xml>";
System.out.println(m.toXml());
Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", ""));
}
}

View File

@@ -99,6 +99,260 @@ public class MiPayNotifyV3Result implements Serializable, WxPayBaseNotifyV3Resul
@SerializedName(value = "mix_trade_no")
private String mixTradeNo;
/**
* <pre>
* 字段名:医保自费混合订单支付状态
* 变量名mix_pay_status
* 是否必填:是
* 类型string
* 描述:
* 医保自费混合订单支付状态,枚举值:
* UNKNOWN_MIX_PAY_STATUS未知类型需报错
* MIX_PAY_CREATED等待支付
* MIX_PAY_SUCCESS支付成功
* MIX_PAY_REFUND自费和医保均已退款
* MIX_PAY_FAIL支付失败
* </pre>
*/
@SerializedName(value = "mix_pay_status")
private String mixPayStatus;
/**
* <pre>
* 字段名:自费部分的支付状态
* 变量名self_pay_status
* 是否必填:否
* 类型string
* 描述:
* 混合订单中自费部分的支付状态,枚举值:
* UNKNOWN_SELF_PAY_STATUS未知类型需报错
* SELF_PAY_CREATED等待支付
* SELF_PAY_SUCCESS支付成功
* SELF_PAY_REFUND已退款
* SELF_PAY_FAIL支付失败
* NO_SELF_PAY没有自费
* </pre>
*/
@SerializedName(value = "self_pay_status")
private String selfPayStatus;
/**
* <pre>
* 字段名:医保部分的支付状态
* 变量名med_ins_pay_status
* 是否必填:否
* 类型string
* 描述:
* 混合订单中医保部分的支付状态,枚举值:
* UNKNOWN_MED_INS_PAY_STATUS未知类型需报错
* MED_INS_PAY_CREATED等待支付
* MED_INS_PAY_SUCCESS支付成功
* MED_INS_PAY_REFUND已退款
* MED_INS_PAY_FAIL支付失败
* NO_MED_INS_PAY没有医保
* </pre>
*/
@SerializedName(value = "med_ins_pay_status")
private String medInsPayStatus;
/**
* <pre>
* 字段名:订单支付时间
* 变量名paid_time
* 是否必填:否
* 类型string(64)
* 描述:
* 订单支付时间遵循rfc3339标准格式
* </pre>
*/
@SerializedName(value = "paid_time")
private String paidTime;
/**
* <pre>
* 字段名:医保局返回内容
* 变量名passthrough_response_content
* 是否必填:否
* 类型string(2048)
* 描述:
* 支付完成后医保局返回内容(透传给医疗机构)
* </pre>
*/
@SerializedName(value = "passthrough_response_content")
private String passthroughResponseContent;
/**
* <pre>
* 字段名:混合支付类型
* 变量名mix_pay_type
* 是否必填:是
* 类型string
* 描述:
* 混合支付类型,枚举值:
* UNKNOWN_MIX_PAY_TYPE未知类型需报错
* CASH_ONLY纯自费
* INSURANCE_ONLY纯医保
* CASH_AND_INSURANCE医保自费混合
* </pre>
*/
@SerializedName(value = "mix_pay_type")
private String mixPayType;
/**
* <pre>
* 字段名:订单类型
* 变量名order_type
* 是否必填:否
* 类型string
* 描述:
* 订单类型,枚举值:
* UNKNOWN_ORDER_TYPE未知类型需报错
* REG_PAY挂号支付
* DIAG_PAY诊间支付
* COVID_EXAM_PAY新冠检测费用核酸
* IN_HOSP_PAY住院费支付
* PHARMACY_PAY药店支付
* INSURANCE_PAY保险费支付
* INT_REG_PAY互联网医院挂号支付
* INT_RE_DIAG_PAY互联网医院复诊支付
* INT_RX_PAY互联网医院处方支付
* COVID_ANTIGEN_PAY新冠抗原检测
* MED_PAY药费支付
* </pre>
*/
@SerializedName(value = "order_type")
private String orderType;
/**
* <pre>
* 字段名:用户标识
* 变量名openid
* 是否必填:否
* 类型string(128)
* 描述:
* 用户在appid下的唯一标识
* </pre>
*/
@SerializedName(value = "openid")
private String openid;
/**
* <pre>
* 字段名:用户子标识
* 变量名sub_openid
* 是否必填:否
* 类型string(128)
* 描述:
* 用户在sub_appid下的唯一标识
* </pre>
*/
@SerializedName(value = "sub_openid")
private String subOpenid;
/**
* <pre>
* 字段名:是否代亲属支付
* 变量名pay_for_relatives
* 是否必填:否
* 类型bool
* 描述:
* 是否代亲属支付,不传默认替本人支付
* </pre>
*/
@SerializedName(value = "pay_for_relatives")
private Boolean payForRelatives;
/**
* <pre>
* 字段名:医疗机构订单号
* 变量名serial_no
* 是否必填:否
* 类型string(20)
* 描述:
* 医疗机构订单号
* </pre>
*/
@SerializedName(value = "serial_no")
private String serialNo;
/**
* <pre>
* 字段名医保局支付单ID
* 变量名pay_order_id
* 是否必填:否
* 类型string(64)
* 描述:
* 医保局返回的支付单ID
* </pre>
*/
@SerializedName(value = "pay_order_id")
private String payOrderId;
/**
* <pre>
* 字段名:医保局支付授权码
* 变量名pay_auth_no
* 是否必填:否
* 类型string(40)
* 描述:
* 医保局返回的支付授权码
* </pre>
*/
@SerializedName(value = "pay_auth_no")
private String payAuthNo;
/**
* <pre>
* 字段名:用户定位信息
* 变量名geo_location
* 是否必填:否
* 类型string(40)
* 描述:
* 用户定位信息,经纬度。格式:经度,纬度
* </pre>
*/
@SerializedName(value = "geo_location")
private String geoLocation;
/**
* <pre>
* 字段名城市ID
* 变量名city_id
* 是否必填:是
* 类型string(8)
* 描述:
* 城市ID
* </pre>
*/
@SerializedName(value = "city_id")
private String cityId;
/**
* <pre>
* 字段名:医疗机构名称
* 变量名med_inst_name
* 是否必填:是
* 类型string(128)
* 描述:
* 医疗机构名称
* </pre>
*/
@SerializedName(value = "med_inst_name")
private String medInstName;
/**
* <pre>
* 字段名:医疗机构编码
* 变量名med_inst_no
* 是否必填:是
* 类型string(32)
* 描述:
* 医疗机构编码
* </pre>
*/
@SerializedName(value = "med_inst_no")
private String medInstNo;
/**
* <pre>
* 字段名:微信支付订单号