1
0
mirror of synced 2026-05-20 17:28:28 +08:00

🎨 #3977 【企业微信】修复「人事助手」相关接口在响应反序列化时字段结构与实际 API 返回不一致导致的解析问题

This commit is contained in:
softboy99
2026-05-09 10:22:10 +08:00
committed by GitHub
parent 63e8291248
commit 7a4204a6c2
7 changed files with 380 additions and 46 deletions

View File

@@ -125,6 +125,15 @@
<suiteXmlFiles> <suiteXmlFiles>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles> </suiteXmlFiles>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.security=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.management/javax.management=ALL-UNNAMED
--add-opens java.naming/javax.naming=ALL-UNNAMED
</argLine>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -5,10 +5,9 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
/** /**
* 人事助手-员工档案数据(单个员工. * 人事助手-员工档案数据(单个字段.
* *
* @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01 * @author <a href="https://github.com/leejoker">leejoker</a> created on 2024-01-01
*/ */
@@ -18,19 +17,98 @@ public class WxCpHrEmployeeFieldData implements Serializable {
private static final long serialVersionUID = 4593693598671765396L; private static final long serialVersionUID = 4593693598671765396L;
/** /**
* 员工userid. * 字段ID.
*/ */
@SerializedName("fieldid")
private Integer fieldId;
/**
* 子字段索引.
*/
@SerializedName("sub_idx")
private Integer subIdx;
/**
* 结果状态1表示成功.
*/
@SerializedName("result")
private Integer result;
/**
* 值类型1-字符串2-uint643-uint324-int645-mobile.
*/
@SerializedName("value_type")
private Integer valueType;
/**
* 字符串值value_type=1时使用.
*/
@SerializedName("value_string")
private String valueString;
/**
* 无符号32位整数值value_type=3时使用.
*/
@SerializedName("value_uint32")
private Long valueUint32;
/**
* 有符号64位整数值value_type=4时使用.
*/
@SerializedName("value_int64")
private Long valueInt64;
/**
* 无符号64位整数值value_type=2时使用.
*/
@SerializedName("value_uint64")
private Long valueUint64;
/**
* 手机号值value_type=5时使用.
*/
@SerializedName("value_mobile")
private MobileValue valueMobile;
/**
* 手机号值.
*/
@Data
@NoArgsConstructor
public static class MobileValue implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 国家代码.
*/
@SerializedName("value_country_code")
private String valueCountryCode;
/**
* 手机号.
*/
@SerializedName("value_mobile")
private String valueMobile;
}
/**
* 员工userid兼容旧版本实际API不返回此字段.
* @deprecated 此字段在API响应中不存在
*/
@Deprecated
@SerializedName("userid") @SerializedName("userid")
private String userid; private String userid;
/** /**
* 字段数据列表. * 字段数据列表兼容旧版本实际API不返回此字段.
* @deprecated 此字段在API响应中不存在
*/ */
@Deprecated
@SerializedName("field_list") @SerializedName("field_list")
private List<FieldItem> fieldList; private java.util.List<FieldItem> fieldList;
/** /**
* 字段数据项. * 字段数据项(用于更新员工档案).
*/ */
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@@ -38,15 +116,21 @@ public class WxCpHrEmployeeFieldData implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* 字段key. * 字段ID.
*/ */
@SerializedName("field_key") @SerializedName("fieldid")
private String fieldKey; private Integer fieldId;
/** /**
* 字段值. * 字段值对象(推荐使用,支持多种类型).
*/ */
@SerializedName("field_value") @SerializedName("field_value")
private WxCpHrEmployeeFieldValue fieldValue; private WxCpHrEmployeeFieldValue fieldValue;
/**
* 字符串值(简化用法,适用于文本类型字段).
*/
@SerializedName("value_string")
private String valueString;
} }
} }

View File

@@ -21,10 +21,28 @@ public class WxCpHrEmployeeFieldDataResp extends WxCpBaseResp {
private static final long serialVersionUID = 6593693598671765396L; private static final long serialVersionUID = 6593693598671765396L;
/** /**
* 员工档案数据列表. * 字段数据列表API实际返回field_info.
*/ */
@SerializedName("employee_field_list") @SerializedName("field_info")
private List<WxCpHrEmployeeFieldData> employeeFieldList; private List<WxCpHrEmployeeFieldData> fieldInfoList;
/**
* 员工档案数据列表(兼容旧版本方法名).
* @deprecated 请使用 getFieldInfoList()
*/
@Deprecated
public List<WxCpHrEmployeeFieldData> getEmployeeFieldList() {
return this.fieldInfoList;
}
/**
* 员工档案数据列表(兼容旧版本方法名).
* @deprecated 请使用 setFieldInfoList()
*/
@Deprecated
public void setEmployeeFieldList(List<WxCpHrEmployeeFieldData> employeeFieldList) {
this.fieldInfoList = employeeFieldList;
}
/** /**
* From json wx cp hr employee field data resp. * From json wx cp hr employee field data resp.

View File

@@ -18,30 +18,43 @@ public class WxCpHrEmployeeFieldInfo implements Serializable {
private static final long serialVersionUID = 2593693598671765396L; private static final long serialVersionUID = 2593693598671765396L;
/** /**
* 字段key. * 字段ID.
*/ */
@SerializedName("field_key") @SerializedName("fieldid")
private String fieldKey; private Integer fieldId;
/** /**
* 字段英文名称. * 字段名称.
*/ */
@SerializedName("field_en_name") @SerializedName("field_name")
private String fieldEnName; private String fieldName;
/**
* 字段中文名称.
*/
@SerializedName("field_zh_name")
private String fieldZhName;
/** /**
* 字段类型. * 字段类型.
* 具体取值参见 {@link WxCpHrFieldType} * 1: 文本
* 2: 单选/多选
* 3: 日期
*/ */
@SerializedName("field_type") @SerializedName("field_type")
private Integer fieldType; private Integer fieldType;
/**
* 是否必填.
*/
@SerializedName("is_must")
private Boolean isMust;
/**
* 值类型.
* 1: 字符串
* 2: uint64
* 3: uint32
* 4: int64
* 5: mobile
*/
@SerializedName("value_type")
private Integer valueType;
/** /**
* 获取字段类型枚举. * 获取字段类型枚举.
* *
@@ -52,22 +65,79 @@ public class WxCpHrEmployeeFieldInfo implements Serializable {
} }
/** /**
* 是否系统字段. * 选项列表(单选/多选字段专用).
* 0: 否
* 1: 是
*/ */
@SerializedName("option_list")
private List<Option> optionList;
/**
* 选项.
*/
@Data
@NoArgsConstructor
public static class Option implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 选项ID.
*/
@SerializedName("id")
private Integer id;
/**
* 选项值.
*/
@SerializedName("value")
private String value;
}
// ===== 以下字段为兼容旧版本 =====
/**
* 字段key兼容旧版本实际API不返回此字段.
* @deprecated 使用 fieldId 代替
*/
@Deprecated
@SerializedName("field_key")
private String fieldKey;
/**
* 字段英文名称兼容旧版本实际API不返回此字段.
* @deprecated 此字段在API响应中不存在
*/
@Deprecated
@SerializedName("field_en_name")
private String fieldEnName;
/**
* 字段中文名称(兼容旧版本).
* @deprecated 使用 fieldName 代替
*/
@Deprecated
@SerializedName("field_zh_name")
private String fieldZhName;
/**
* 是否系统字段兼容旧版本实际API不返回此字段.
* @deprecated 此字段在API响应中不存在
*/
@Deprecated
@SerializedName("is_sys") @SerializedName("is_sys")
private Integer isSys; private Integer isSys;
/** /**
* 字段详情. * 字段详情(兼容旧版本).
* @deprecated 使用 optionList 直接访问选项列表
*/ */
@Deprecated
@SerializedName("field_detail") @SerializedName("field_detail")
private FieldDetail fieldDetail; private FieldDetail fieldDetail;
/** /**
* 字段详情. * 字段详情(兼容旧版本).
* @deprecated 使用 optionList 代替
*/ */
@Deprecated
@Data @Data
@NoArgsConstructor @NoArgsConstructor
public static class FieldDetail implements Serializable { public static class FieldDetail implements Serializable {
@@ -77,15 +147,17 @@ public class WxCpHrEmployeeFieldInfo implements Serializable {
* 选项列表(单选/多选字段专用). * 选项列表(单选/多选字段专用).
*/ */
@SerializedName("option_list") @SerializedName("option_list")
private List<Option> optionList; private List<OldOption> optionList;
} }
/** /**
* 选项. * 旧版选项(兼容旧版本).
* @deprecated 使用 Option 代替
*/ */
@Deprecated
@Data @Data
@NoArgsConstructor @NoArgsConstructor
public static class Option implements Serializable { public static class OldOption implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**

View File

@@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import me.chanjar.weixin.cp.bean.WxCpBaseResp; import me.chanjar.weixin.cp.bean.WxCpBaseResp;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List; import java.util.List;
/** /**
@@ -21,10 +22,56 @@ public class WxCpHrEmployeeFieldInfoResp extends WxCpBaseResp {
private static final long serialVersionUID = 5593693598671765396L; private static final long serialVersionUID = 5593693598671765396L;
/** /**
* 字段信息列表. * 字段分组列表API实际返回group_list.
*/ */
@SerializedName("field_info_list") @SerializedName("group_list")
private List<WxCpHrEmployeeFieldInfo> fieldInfoList; private List<FieldGroup> groupList;
/**
* 字段信息列表(兼容旧版本方法名).
* @deprecated 请使用 getGroupList() 获取分组后的字段信息
*/
@Deprecated
public List<WxCpHrEmployeeFieldInfo> getFieldInfoList() {
if (groupList == null || groupList.isEmpty()) {
return null;
}
// 兼容旧版本:将所有分组的字段合并到一个列表
List<WxCpHrEmployeeFieldInfo> allFields = new java.util.ArrayList<>();
for (FieldGroup group : groupList) {
if (group.getFieldList() != null) {
allFields.addAll(group.getFieldList());
}
}
return allFields.isEmpty() ? null : allFields;
}
/**
* 字段分组.
*/
@Data
@NoArgsConstructor
public static class FieldGroup implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分组ID.
*/
@SerializedName("group_id")
private Integer groupId;
/**
* 分组名称.
*/
@SerializedName("group_name")
private String groupName;
/**
* 字段列表.
*/
@SerializedName("field_list")
private List<WxCpHrEmployeeFieldInfo> fieldList;
}
/** /**
* From json wx cp hr employee field info resp. * From json wx cp hr employee field info resp.

View File

@@ -48,8 +48,16 @@ public class WxCpHrServiceImplTest {
// 获取所有字段信息 // 获取所有字段信息
WxCpHrEmployeeFieldInfoResp resp = hrService.getFieldInfo(null); WxCpHrEmployeeFieldInfoResp resp = hrService.getFieldInfo(null);
assertThat(resp).isNotNull(); assertThat(resp).isNotNull();
assertThat(resp.getFieldInfoList()).isNotNull(); assertThat(resp.getGroupList()).isNotNull();
log.info("获取所有字段信息: {}", resp); System.out.println("获取所有字段信息: " + resp);
// 打印分组信息示例
if (resp.getGroupList() != null) {
for (WxCpHrEmployeeFieldInfoResp.FieldGroup group : resp.getGroupList()) {
System.out.println("分组: " + group.getGroupName() + " (ID: " + group.getGroupId() + "), 字段数量: " +
(group.getFieldList() != null ? group.getFieldList().size() : 0));
}
}
} }
/** /**
@@ -63,7 +71,7 @@ public class WxCpHrServiceImplTest {
// 获取指定字段信息 // 获取指定字段信息
WxCpHrEmployeeFieldInfoResp resp = hrService.getFieldInfo(Collections.singletonList("sys_field_name")); WxCpHrEmployeeFieldInfoResp resp = hrService.getFieldInfo(Collections.singletonList("sys_field_name"));
assertThat(resp).isNotNull(); assertThat(resp).isNotNull();
log.info("获取指定字段信息: {}", resp); System.out.println("获取指定字段信息: " + resp);
} }
/** /**
@@ -74,11 +82,68 @@ public class WxCpHrServiceImplTest {
@Test @Test
public void testGetEmployeeFieldInfo() throws WxErrorException { public void testGetEmployeeFieldInfo() throws WxErrorException {
WxCpHrService hrService = this.wxCpService.getHrService(); WxCpHrService hrService = this.wxCpService.getHrService();
// 先获取字段定义信息建立字段ID到字段信息的映射
WxCpHrEmployeeFieldInfoResp fieldInfoResp = hrService.getFieldInfo(null);
java.util.Map<Integer, me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfo> fieldInfoMap = new java.util.HashMap<>();
java.util.Map<Integer, String> groupNameMap = new java.util.HashMap<>();
if (fieldInfoResp != null && fieldInfoResp.getGroupList() != null) {
for (WxCpHrEmployeeFieldInfoResp.FieldGroup group : fieldInfoResp.getGroupList()) {
if (group.getFieldList() != null) {
for (me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfo field : group.getFieldList()) {
if (field.getFieldId() != null) {
fieldInfoMap.put(field.getFieldId(), field);
groupNameMap.put(field.getFieldId(), group.getGroupName() + " (ID: " + group.getGroupId() + ")");
}
}
}
}
}
// 获取员工档案数据
WxCpHrEmployeeFieldDataResp resp = hrService.getEmployeeFieldInfo( WxCpHrEmployeeFieldDataResp resp = hrService.getEmployeeFieldInfo(
this.configStorage.getUserId(), null); this.configStorage.getUserId(), true, null);
assertThat(resp).isNotNull(); assertThat(resp).isNotNull();
assertThat(resp.getEmployeeFieldList()).isNotNull(); assertThat(resp.getFieldInfoList()).isNotNull();
log.info("获取员工档案数据: {}", resp);
// 按组组织字段数据
java.util.Map<String, java.util.List<WxCpHrEmployeeFieldData>> groupedFields = new java.util.LinkedHashMap<>();
if (resp.getFieldInfoList() != null) {
for (WxCpHrEmployeeFieldData field : resp.getFieldInfoList()) {
String groupName = groupNameMap.get(field.getFieldId());
if (groupName == null) {
groupName = "未分组";
}
groupedFields.computeIfAbsent(groupName, k -> new java.util.ArrayList<>()).add(field);
}
}
// 按组打印所有字段信息
System.out.println("=== 员工花名册数据 ===");
int totalFields = 0;
for (java.util.Map.Entry<String, java.util.List<WxCpHrEmployeeFieldData>> entry : groupedFields.entrySet()) {
String groupName = entry.getKey();
java.util.List<WxCpHrEmployeeFieldData> fields = entry.getValue();
System.out.println("\n分组: " + groupName);
for (WxCpHrEmployeeFieldData field : fields) {
Object value = field.getValueString() != null ? field.getValueString() :
field.getValueUint32() != null ? field.getValueUint32() :
field.getValueUint64() != null ? field.getValueUint64() :
field.getValueInt64();
// 根据字段ID获取字段名称和类型
me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfo fieldInfo = fieldInfoMap.get(field.getFieldId());
String fieldName = fieldInfo != null ? fieldInfo.getFieldName() : "未知字段";
Integer fieldType = fieldInfo != null ? fieldInfo.getFieldType() : field.getValueType();
System.out.println(" - 字段: " + fieldName + " (ID: " + field.getFieldId() + ", 类型: " + fieldType + ")");
System.out.println(" 值: " + value);
totalFields++;
}
}
System.out.println("\n总计: " + totalFields + " 个字段");
} }
/** /**
@@ -89,12 +154,50 @@ public class WxCpHrServiceImplTest {
@Test @Test
public void testUpdateEmployeeFieldInfo() throws WxErrorException { public void testUpdateEmployeeFieldInfo() throws WxErrorException {
WxCpHrService hrService = this.wxCpService.getHrService(); WxCpHrService hrService = this.wxCpService.getHrService();
// 先获取字段信息找到要更新的字段ID
WxCpHrEmployeeFieldInfoResp fieldInfoResp = hrService.getFieldInfo(null);
assertThat(fieldInfoResp).isNotNull();
// 打印所有可用字段,方便调试
System.out.println("=== 可用字段列表 ===");
Integer firstFieldId = null;
if (fieldInfoResp.getGroupList() != null) {
for (WxCpHrEmployeeFieldInfoResp.FieldGroup group : fieldInfoResp.getGroupList()) {
System.out.println("分组: " + group.getGroupName() + " (ID: " + group.getGroupId() + ")");
if (group.getFieldList() != null) {
for (me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfo field : group.getFieldList()) {
System.out.println(
" - 字段: " + field.getFieldName()
+ " (ID: " + field.getFieldId()
+ ", 类型: " + field.getFieldType() + ")"
);
// 记录第一个字段ID用于测试
if (firstFieldId == null && field.getFieldId() != null) {
firstFieldId = field.getFieldId();
}
}
}
}
}
// 使用第一个可用的字段ID进行测试
assertThat(firstFieldId).as("至少应该有一个可用字段").isNotNull();
System.out.println("使用字段ID进行更新测试: " + firstFieldId);
// 创建更新对象
WxCpHrEmployeeFieldData.FieldItem fieldItem = new WxCpHrEmployeeFieldData.FieldItem(); WxCpHrEmployeeFieldData.FieldItem fieldItem = new WxCpHrEmployeeFieldData.FieldItem();
fieldItem.setFieldKey("sys_field_name"); fieldItem.setFieldId(firstFieldId);
// 方式1: 使用 fieldValue 对象(推荐,支持多种类型)
WxCpHrEmployeeFieldValue fieldValue = new WxCpHrEmployeeFieldValue(); WxCpHrEmployeeFieldValue fieldValue = new WxCpHrEmployeeFieldValue();
fieldValue.setTextValue("测试姓名"); fieldValue.setTextValue("测试更新值-" + System.currentTimeMillis());
fieldItem.setFieldValue(fieldValue); fieldItem.setFieldValue(fieldValue);
// 方式2: 直接使用 valueString简化用法
// fieldItem.setValueString("测试姓名");
hrService.updateEmployeeFieldInfo(this.configStorage.getUserId(), Arrays.asList(fieldItem)); hrService.updateEmployeeFieldInfo(this.configStorage.getUserId(), Arrays.asList(fieldItem));
log.info("更新员工档案数据成功"); System.out.println("更新员工档案数据成功");
} }
} }

View File

@@ -9,6 +9,7 @@
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceApacheHttpClientImplTest"/> <class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceApacheHttpClientImplTest"/>
<class name="me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImplTest"/> <class name="me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImplTest"/>
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpTagServiceImplTest"/> <class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpTagServiceImplTest"/>
<class name="me.chanjar.weixin.cp.api.impl.WxCpHrServiceImplTest"/>
</classes> </classes>
</test> </test>