fyz пре 2 недеља
родитељ
комит
bf65907155

+ 8 - 4
mjava-lanyun/src/main/java/com/malk/lanyun/delegate/DDDelegate.java

@@ -1,6 +1,7 @@
 package com.malk.lanyun.delegate;
 
 import com.malk.delegate.DDEvent;
+import com.malk.lanyun.service.DingTalkService;
 import com.malk.lanyun.service.TimerService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +22,9 @@ public class DDDelegate implements DDEvent {
 
     @Autowired
     TimerService timerService;
+
+    @Autowired
+    DingTalkService dingTalkService;
     // 审批任务回调执行业务逻辑
     @Async
     @Override
@@ -57,10 +61,10 @@ public class DDDelegate implements DDEvent {
         if  (isTerminate){
             log.info("开始执行撤销回调");
             timerService.terminateDataById(processInstanceId);
-        } else if (isAgree) {
-            if (processCode.equals("PROC-15DD2D3E-1432-42A9-83B8-3B443AEA110F")){
-
-            }
+        } else if (isAgree &&
+                "PROC-15DD2D3E-1432-42A9-83B8-3B443AEA110F".equals(processCode)) {
+//            int updatedCount = this.dingTalkService.syncApprovalFormToRoster(processInstanceId);
+//            log.info("processInstanceId={}, updatedCount={}", processInstanceId, Integer.valueOf(updatedCount));
         }
         log.info("审批实例回调执行业务逻辑, {}", approveResult);
     }

+ 3 - 0
mjava-lanyun/src/main/java/com/malk/lanyun/service/DingTalkService.java

@@ -27,4 +27,7 @@ public interface DingTalkService {
      */
     List<Map> getDDUserInfoById(String access_token, String userId, Number appAgentId);
 
+
+
+    int syncApprovalFormToRoster(String paramString);
 }

+ 199 - 3
mjava-lanyun/src/main/java/com/malk/lanyun/service/impl/DingTalkServiceImpl.java

@@ -1,26 +1,48 @@
 package com.malk.lanyun.service.impl;
 
-
+import com.alibaba.fastjson.JSON;
 import com.malk.lanyun.service.DingTalkService;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
-import com.malk.server.dingtalk.DDR_New;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Personnel;
+import com.malk.service.dingtalk.DDClient_Workflow;
 import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 @Service
 @Slf4j
 public class DingTalkServiceImpl implements DingTalkService {
 
+    private final DDClient ddClient;
+
+    private final DDClient_Workflow ddClientWorkflow;
+
+    private final DDClient_Personnel ddClientPersonnel;
+
+    private final DDConf ddConf;
+
+    public DingTalkServiceImpl(DDClient ddClient, DDClient_Workflow ddClientWorkflow,
+                               DDClient_Personnel ddClientPersonnel, DDConf ddConf) {
+        this.ddClient = ddClient;
+        this.ddClientWorkflow = ddClientWorkflow;
+        this.ddClientPersonnel = ddClientPersonnel;
+        this.ddConf = ddConf;
+    }
 
     @Override
     public Map getAllUserIdInfo(String access_token, String status_list, Number offset, Number size) {
         Map param = UtilMap.map("access_token", access_token);
-        Map body = UtilMap.map("status_list, offset, size", status_list, offset,size);
+        Map body = UtilMap.map("status_list, offset, size", status_list, offset, size);
         return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob", null, param, body).getResult();
     }
 
@@ -31,4 +53,178 @@ public class DingTalkServiceImpl implements DingTalkService {
         return (List<Map>) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list", null, param, body).getResult();
     }
 
+    /**
+     * 审批通过后: 将审批单字段按名称回写到发起人的人员花名册
+     *
+     * @param processInstanceId 审批实例ID
+     * @return 成功写入花名册的字段数量
+     */
+    @Override
+    public int syncApprovalFormToRoster(String processInstanceId) {
+        if (isBlank(processInstanceId)) {
+            throw new IllegalArgumentException("processInstanceId不能为空");
+        }
+        // 审批应用凭证: 用于查询审批实例
+        String approvalAccessToken = this.ddClient.getAccessToken(this.ddConf.getApprovalAppKey(), this.ddConf.getApprovalAppSecret());
+        Map processData = this.ddClientWorkflow.getProcessInstanceId(approvalAccessToken, processInstanceId);
+        if (processData == null || processData.isEmpty()) {
+            throw new IllegalStateException("未查询到审批实例, processInstanceId=" + processInstanceId);
+        }
+        String userId = stringValue(processData.get("originatorUserId"));
+        if (isBlank(userId)) {
+            throw new IllegalStateException("审批实例缺少发起人, processInstanceId=" + processInstanceId);
+        }
+        // 花名册应用凭证: 用于读写智能人事花名册
+        String personnelAccessToken = this.ddClient.getAccessToken(this.ddConf.getAppKey(), this.ddConf.getAppSecret());
+        List<Map> formComponentValues = asMapList(processData.get("formComponentValues"));
+        List<Map> metadataGroups = this.ddClientPersonnel.getPersonnelMeta(personnelAccessToken, this.ddConf.getAgentId());
+
+        // 花名册字段: 按字段名称建索引, 名称重复或为派生字段时忽略
+        Map<String, FieldMeta> metadataByName = indexMetadata(metadataGroups);
+        Map<String, LinkedHashMap<String, String>> fieldsByGroup = new LinkedHashMap<>();
+        int matchedCount = 0;
+        for (Map component : formComponentValues) {
+            String name = stringValue(component.get("name"));
+            String value = fieldValue(component.get("value"));
+            if (isBlank(name) || value == null) {
+                continue;
+            }
+            FieldMeta metadata = metadataByName.get(name.trim());
+            if (metadata == null) {
+                continue;
+            }
+            LinkedHashMap<String, String> groupFields = fieldsByGroup.computeIfAbsent(metadata.groupId, key -> new LinkedHashMap<>());
+            if (!groupFields.containsKey(metadata.fieldCode)) {
+                matchedCount++;
+            }
+            groupFields.put(metadata.fieldCode, value);
+        }
+        if (matchedCount == 0) {
+            log.info("审批单无匹配的花名册字段, processInstanceId={}", processInstanceId);
+            return 0;
+        }
+        List<Map> groups = buildUpdateGroups(fieldsByGroup);
+        boolean updated = this.ddClientPersonnel.updateEmployeeRoster(personnelAccessToken, userId, this.ddConf.getAgentId(), groups);
+        if (!updated) {
+            throw new IllegalStateException("花名册更新失败, processInstanceId=" + processInstanceId);
+        }
+        return matchedCount;
+    }
+
+    /**
+     * 花名册字段索引: 字段名称 -> 分组ID + 字段编码
+     */
+    private Map<String, FieldMeta> indexMetadata(List<Map> metadataGroups) {
+        Map<String, FieldMeta> metadataByName = new LinkedHashMap<>();
+        Set<String> ambiguousNames = new HashSet<>();
+        for (Map group : safeList(metadataGroups)) {
+            String groupId = stringValue(group.get("group_id"));
+            if (isBlank(groupId)) {
+                continue;
+            }
+            for (Map field : metadataFields(group)) {
+                String fieldName = stringValue(field.get("field_name"));
+                String fieldCode = stringValue(field.get("field_code"));
+                if (isBlank(fieldName) || isBlank(fieldCode) || isDerived(field.get("derived"))) {
+                    continue;
+                }
+                String normalizedName = fieldName.trim();
+                FieldMeta existing = metadataByName.get(normalizedName);
+                if (existing != null && (!existing.groupId.equals(groupId) || !existing.fieldCode.equals(fieldCode))) {
+                    ambiguousNames.add(normalizedName);
+                    continue;
+                }
+                if (existing == null) {
+                    metadataByName.put(normalizedName, new FieldMeta(groupId, fieldCode));
+                }
+            }
+        }
+        for (String ambiguousName : ambiguousNames) {
+            metadataByName.remove(ambiguousName);
+            log.warn("花名册字段名称存在歧义, 已忽略: {}", ambiguousName);
+        }
+        return metadataByName;
+    }
+
+    private List<Map> metadataFields(Map group) {
+        Object detail = group.get("detail");
+        if (detail instanceof Map) {
+            return asMapList(((Map) detail).get("field_meta_info_list"));
+        }
+        if (detail instanceof List) {
+            return asMapList(detail);
+        }
+        return asMapList(group.get("field_meta_info_list"));
+    }
+
+    /**
+     * 组装花名册更新入参: groups[].sections[].section[].{field_code, value}
+     */
+    private List<Map> buildUpdateGroups(Map<String, LinkedHashMap<String, String>> fieldsByGroup) {
+        List<Map> groups = new ArrayList<>();
+        for (Map.Entry<String, LinkedHashMap<String, String>> groupEntry : fieldsByGroup.entrySet()) {
+            List<Map> fields = new ArrayList<>();
+            for (Map.Entry<String, String> fieldEntry : groupEntry.getValue().entrySet()) {
+                fields.add(UtilMap.map("field_code, value", fieldEntry.getKey(), fieldEntry.getValue()));
+            }
+            Map section = UtilMap.map("section", fields);
+            groups.add(UtilMap.map("group_id, sections", groupEntry.getKey(), Collections.singletonList(section)));
+        }
+        return groups;
+    }
+
+    private String fieldValue(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof String) {
+            String string = (String) value;
+            return isBlank(string) ? null : string;
+        }
+        return JSON.toJSONString(value);
+    }
+
+    private boolean isDerived(Object value) {
+        return (Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value)));
+    }
+
+    private List<Map> asMapList(Object value) {
+        if (!(value instanceof List)) {
+            return Collections.emptyList();
+        }
+        List<Map> result = new ArrayList<>();
+        for (Object item : (List) value) {
+            if (item instanceof Map) {
+                result.add((Map) item);
+            }
+        }
+        return result;
+    }
+
+    private List<Map> safeList(List<Map> value) {
+        return (value == null) ? Collections.<Map>emptyList() : value;
+    }
+
+    private String stringValue(Object value) {
+        return (value == null) ? null : String.valueOf(value);
+    }
+
+    private boolean isBlank(String value) {
+        return (value == null || value.trim().isEmpty());
+    }
+
+    /**
+     * 花名册字段元信息
+     */
+    private static final class FieldMeta {
+
+        private final String groupId;
+
+        private final String fieldCode;
+
+        private FieldMeta(String groupId, String fieldCode) {
+            this.groupId = groupId;
+            this.fieldCode = fieldCode;
+        }
+    }
 }

+ 2 - 1
mjava-lanyun/src/main/java/com/malk/lanyun/service/impl/FImplService.java

@@ -359,7 +359,8 @@ public class FImplService implements FService {
             msg = "账龄表任务完成";
         } catch (Exception e) {
             msg = "账龄表任务失败";
-            e.printStackTrace();
+            e.printStackTrace();           // 打印到err控制台
+            log.error("msg", e);           // 日志框架输出(规范做法)
         }
         Map<String, String> map = UtilMap.map("content", msg);
         Map<String, Object> msgMap = new HashMap<>();

+ 3 - 0
mjava-lanyun/src/main/resources/application-dev.yml

@@ -49,6 +49,9 @@ dingtalk:
   agentId: 4005967931
   appKey: dingwgp5gch0q6w7dkpd
   appSecret: 4hNChWyxdA3nqjhVVqSerhTday-haHTsfWYH2TfqvgarLda7c4rejOzP1EzfyusR
+  # 审批应用凭证 [审批完成后同步花名册, 与当前应用为同一应用时沿用上面的凭证]
+  approvalAppKey: ${dingtalk.appKey}
+  approvalAppSecret: ${dingtalk.appSecret}
   corpId: dingef2eb0ecba261b8935c2f4657eb6378f
   aesKey: cfzM5OQusToWofDd4wx8J8s2yyLYHekDGQAjCAt4FRD
   token: onG7mhhVNjtD7Z5vl

+ 3 - 0
mjava-lanyun/src/main/resources/application-prod.yml

@@ -35,6 +35,9 @@ dingtalk:
   agentId: 4005967931
   appKey: dingwgp5gch0q6w7dkpd
   appSecret: 4hNChWyxdA3nqjhVVqSerhTday-haHTsfWYH2TfqvgarLda7c4rejOzP1EzfyusR
+  # 审批应用凭证 [审批完成后同步花名册, 与当前应用为同一应用时沿用上面的凭证]
+  approvalAppKey: ${dingtalk.appKey}
+  approvalAppSecret: ${dingtalk.appSecret}
   corpId: dingef2eb0ecba261b8935c2f4657eb6378f
   aesKey: cfzM5OQusToWofDd4wx8J8s2yyLYHekDGQAjCAt4FRD
   token: onG7mhhVNjtD7Z5vl

+ 183 - 46
mjava/src/main/java/com/malk/server/dingtalk/DDConf.java

@@ -1,26 +1,29 @@
 package com.malk.server.dingtalk;
 
 import com.malk.utils.UtilMap;
-import lombok.Data;
+import java.util.Map;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
-import java.util.Map;
-
-/**
- * 读取配置文件参考FilePah
- */
-@Data
 @Component
 @ConfigurationProperties(prefix = "dingtalk")
 public class DDConf {
-
     private Number agentId;
 
     private String appKey;
 
     private String appSecret;
 
+    /**
+     * 审批应用 appKey [审批完成后用于查询审批实例]
+     */
+    private String approvalAppKey;
+
+    /**
+     * 审批应用 appSecret [审批完成后用于查询审批实例]
+     */
+    private String approvalAppSecret;
+
     private String corpId;
 
     private String corpToken;
@@ -29,67 +32,201 @@ public class DDConf {
 
     private String token;
 
-    // 操作人, 需要为OA后台管理员
     private String operator;
 
-    // 机器人编号
     private String robotCode;
 
-    /**
-     * 钉钉一级部门: 1
-     */
     public static final long TOP_DEPARTMENT = 1L;
 
-    /**
-     * 钉钉回调响应
-     */
     public static final String CALLBACK_RESPONSE = "success";
 
-    /**
-     * 验证注册地址, 通过开发平台配置触发
-     */
     public static final String CALLBACK_CHECK = "check_url";
 
-    /**
-     * 审批任务回调 [审批任务开始、结束、转交]
-     */
     public static final String BPMS_TASK_CHANGE = "bpms_task_change";
 
-    /**
-     * 审批实例回调 [审批实例开始、结束]
-     */
     public static final String BPMS_INSTANCE_CHANGE = "bpms_instance_change";
- /**
-     * 部门新增回调
-     */
+
     public static final String ORG_DEPT_CREATE = "org_dept_create";
- /**
-     * 部门修改回调
-     */
+
     public static final String ORG_DEPT_MODIFY = "org_dept_modify";
- /**
-     * 部门删除回调
-     */
+
     public static final String ORG_DEPT_REMOVE = "org_dept_remove";
-    /**
-     * 员工新增回调
-     */
+
     public static final String ORG_PERSON_CREATE = "user_add_org";
- /**
-     * 员工删除回调
-     */
+
     public static final String ORG_PERSON_REMOVE = "user_leave_org";
 
-    /**
-     * token授权参数: 旧版本
-     */
+    public void setAgentId(Number agentId) {
+        this.agentId = agentId;
+    }
+
+    public void setAppKey(String appKey) {
+        this.appKey = appKey;
+    }
+
+    public void setAppSecret(String appSecret) {
+        this.appSecret = appSecret;
+    }
+
+    public void setApprovalAppKey(String approvalAppKey) {
+        this.approvalAppKey = approvalAppKey;
+    }
+
+    public void setApprovalAppSecret(String approvalAppSecret) {
+        this.approvalAppSecret = approvalAppSecret;
+    }
+
+    public void setCorpId(String corpId) {
+        this.corpId = corpId;
+    }
+
+    public void setCorpToken(String corpToken) {
+        this.corpToken = corpToken;
+    }
+
+    public void setAesKey(String aesKey) {
+        this.aesKey = aesKey;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public void setOperator(String operator) {
+        this.operator = operator;
+    }
+
+    public void setRobotCode(String robotCode) {
+        this.robotCode = robotCode;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this)
+            return true;
+        if (!(o instanceof DDConf))
+            return false;
+        DDConf other = (DDConf)o;
+        if (!other.canEqual(this))
+            return false;
+        Object this$agentId = getAgentId(), other$agentId = other.getAgentId();
+        if ((this$agentId == null) ? (other$agentId != null) : !this$agentId.equals(other$agentId))
+            return false;
+        Object this$appKey = getAppKey(), other$appKey = other.getAppKey();
+        if ((this$appKey == null) ? (other$appKey != null) : !this$appKey.equals(other$appKey))
+            return false;
+        Object this$appSecret = getAppSecret(), other$appSecret = other.getAppSecret();
+        if ((this$appSecret == null) ? (other$appSecret != null) : !this$appSecret.equals(other$appSecret))
+            return false;
+        Object this$approvalAppKey = getApprovalAppKey(), other$approvalAppKey = other.getApprovalAppKey();
+        if ((this$approvalAppKey == null) ? (other$approvalAppKey != null) : !this$approvalAppKey.equals(other$approvalAppKey))
+            return false;
+        Object this$approvalAppSecret = getApprovalAppSecret(), other$approvalAppSecret = other.getApprovalAppSecret();
+        if ((this$approvalAppSecret == null) ? (other$approvalAppSecret != null) : !this$approvalAppSecret.equals(other$approvalAppSecret))
+            return false;
+        Object this$corpId = getCorpId(), other$corpId = other.getCorpId();
+        if ((this$corpId == null) ? (other$corpId != null) : !this$corpId.equals(other$corpId))
+            return false;
+        Object this$corpToken = getCorpToken(), other$corpToken = other.getCorpToken();
+        if ((this$corpToken == null) ? (other$corpToken != null) : !this$corpToken.equals(other$corpToken))
+            return false;
+        Object this$aesKey = getAesKey(), other$aesKey = other.getAesKey();
+        if ((this$aesKey == null) ? (other$aesKey != null) : !this$aesKey.equals(other$aesKey))
+            return false;
+        Object this$token = getToken(), other$token = other.getToken();
+        if ((this$token == null) ? (other$token != null) : !this$token.equals(other$token))
+            return false;
+        Object this$operator = getOperator(), other$operator = other.getOperator();
+        if ((this$operator == null) ? (other$operator != null) : !this$operator.equals(other$operator))
+            return false;
+        Object this$robotCode = getRobotCode(), other$robotCode = other.getRobotCode();
+        return !((this$robotCode == null) ? (other$robotCode != null) : !this$robotCode.equals(other$robotCode));
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof DDConf;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $agentId = getAgentId();
+        result = result * 59 + (($agentId == null) ? 43 : $agentId.hashCode());
+        Object $appKey = getAppKey();
+        result = result * 59 + (($appKey == null) ? 43 : $appKey.hashCode());
+        Object $appSecret = getAppSecret();
+        result = result * 59 + (($appSecret == null) ? 43 : $appSecret.hashCode());
+        Object $approvalAppKey = getApprovalAppKey();
+        result = result * 59 + (($approvalAppKey == null) ? 43 : $approvalAppKey.hashCode());
+        Object $approvalAppSecret = getApprovalAppSecret();
+        result = result * 59 + (($approvalAppSecret == null) ? 43 : $approvalAppSecret.hashCode());
+        Object $corpId = getCorpId();
+        result = result * 59 + (($corpId == null) ? 43 : $corpId.hashCode());
+        Object $corpToken = getCorpToken();
+        result = result * 59 + (($corpToken == null) ? 43 : $corpToken.hashCode());
+        Object $aesKey = getAesKey();
+        result = result * 59 + (($aesKey == null) ? 43 : $aesKey.hashCode());
+        Object $token = getToken();
+        result = result * 59 + (($token == null) ? 43 : $token.hashCode());
+        Object $operator = getOperator();
+        result = result * 59 + (($operator == null) ? 43 : $operator.hashCode());
+        Object $robotCode = getRobotCode();
+        return result * 59 + (($robotCode == null) ? 43 : $robotCode.hashCode());
+    }
+
+    public String toString() {
+        return "DDConf(agentId=" + getAgentId() + ", appKey=" + getAppKey() + ", appSecret=" + getAppSecret()
+                + ", approvalAppKey=" + getApprovalAppKey() + ", approvalAppSecret=" + getApprovalAppSecret()
+                + ", corpId=" + getCorpId() + ", corpToken=" + getCorpToken() + ", aesKey=" + getAesKey() + ", token=" + getToken() + ", operator=" + getOperator() + ", robotCode=" + getRobotCode() + ")";
+    }
+
+    public Number getAgentId() {
+        return this.agentId;
+    }
+
+    public String getAppKey() {
+        return this.appKey;
+    }
+
+    public String getAppSecret() {
+        return this.appSecret;
+    }
+
+    public String getApprovalAppKey() {
+        return this.approvalAppKey;
+    }
+
+    public String getApprovalAppSecret() {
+        return this.approvalAppSecret;
+    }
+
+    public String getCorpId() {
+        return this.corpId;
+    }
+
+    public String getCorpToken() {
+        return this.corpToken;
+    }
+
+    public String getAesKey() {
+        return this.aesKey;
+    }
+
+    public String getToken() {
+        return this.token;
+    }
+
+    public String getOperator() {
+        return this.operator;
+    }
+
+    public String getRobotCode() {
+        return this.robotCode;
+    }
+
     public static Map initTokenParams(String access_token) {
         return UtilMap.map("access_token", access_token);
     }
 
-    /**
-     * token授权参数: 新版本
-     */
     public static Map initTokenHeader(String access_token) {
         return UtilMap.map("x-acs-dingtalk-access-token", access_token);
     }

+ 9 - 0
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Personnel.java

@@ -10,6 +10,15 @@ public interface DDClient_Personnel {
      */
     List<Map> getPersonnelMeta(String access_token, Number agentid);
 
+    /**
+     * 更新员工花名册信息
+     *
+     * @param groups 分组入参: groups[].sections[].section[].{field_code, value}
+     * @return 是否更新成功
+     * @apiNote https://open.dingtalk.com/document/isvapp-server/intelligent-personnel-update-employee-file-information
+     */
+    boolean updateEmployeeRoster(String access_token, String userId, Number agentId, List<Map> groups);
+
     /**
      * 获取员工花名册字段信息
      *

+ 13 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Personnel.java

@@ -33,6 +33,19 @@ public class DDImplClient_Personnel implements DDClient_Personnel {
         return (List<Map>) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/roster/meta/get", null, DDConf.initTokenParams(access_token), UtilMap.map("agentid", agentid)).getResult();
     }
 
+    /**
+     * 更新员工花名册信息 [支持明细分组]
+     */
+    @Override
+    public boolean updateEmployeeRoster(String access_token, String userId, Number agentId, List<Map> groups) {
+        log.info("正在更新员工花名册, userId={}", userId);
+        // param: 花名册入参, groups[].sections[].section[].{field_code, value}
+        Map param = UtilMap.map("userid, groups", userId, groups);
+        Map body = UtilMap.map("agentid, param", agentId, param);
+        DDR ddr = DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/update", null, DDConf.initTokenParams(access_token), body);
+        return Boolean.TRUE.equals(ddr.getResult());
+    }
+
     /**
      * 获取员工花名册字段信息
      *