瀏覽代碼

同步器增加属性配置管理

llh 7 月之前
父節點
當前提交
e3b9fdb201
共有 39 個文件被更改,包括 4032 次插入1832 次删除
  1. 208 0
      maxkey-core/src/main/java/org/dromara/maxkey/entity/SyncJobConfigField.java
  2. 24 0
      maxkey-persistence/src/main/java/org/dromara/maxkey/persistence/mapper/SyncJobConfigFieldMapper.java
  3. 127 0
      maxkey-persistence/src/main/resources/org/dromara/maxkey/persistence/mapper/xml/mysql/SyncJobConfigFieldMapper.xml
  4. 77 0
      maxkey-synchronizers/maxkey-synchronizer-activedirectory/src/main/java/org/dromara/maxkey/synchronizer/activedirectory/ActiveDirectoryOrganizationService.java
  5. 122 8
      maxkey-synchronizers/maxkey-synchronizer-activedirectory/src/main/java/org/dromara/maxkey/synchronizer/activedirectory/ActiveDirectoryUsersService.java
  6. 74 8
      maxkey-synchronizers/maxkey-synchronizer-dingtalk/src/main/java/org/dromara/maxkey/synchronizer/dingtalk/DingtalkOrganizationService.java
  7. 92 4
      maxkey-synchronizers/maxkey-synchronizer-dingtalk/src/main/java/org/dromara/maxkey/synchronizer/dingtalk/DingtalkUsersService.java
  8. 82 6
      maxkey-synchronizers/maxkey-synchronizer-feishu/src/main/java/org/dromara/maxkey/synchronizer/feishu/FeishuOrganizationService.java
  9. 90 2
      maxkey-synchronizers/maxkey-synchronizer-feishu/src/main/java/org/dromara/maxkey/synchronizer/feishu/FeishuUsersService.java
  10. 161 1
      maxkey-synchronizers/maxkey-synchronizer-jdbc/src/main/java/org/dromara/maxkey/synchronizer/jdbc/JdbcOrganizationService.java
  11. 206 3
      maxkey-synchronizers/maxkey-synchronizer-jdbc/src/main/java/org/dromara/maxkey/synchronizer/jdbc/JdbcUsersService.java
  12. 84 2
      maxkey-synchronizers/maxkey-synchronizer-ldap/src/main/java/org/dromara/maxkey/synchronizer/ldap/LdapOrganizationService.java
  13. 143 7
      maxkey-synchronizers/maxkey-synchronizer-ldap/src/main/java/org/dromara/maxkey/synchronizer/ldap/LdapUsersService.java
  14. 78 8
      maxkey-synchronizers/maxkey-synchronizer-workweixin/src/main/java/org/dromara/maxkey/synchronizer/workweixin/WorkweixinOrganizationService.java
  15. 67 2
      maxkey-synchronizers/maxkey-synchronizer-workweixin/src/main/java/org/dromara/maxkey/synchronizer/workweixin/WorkweixinUsersService.java
  16. 40 0
      maxkey-synchronizers/maxkey-synchronizer/src/main/java/org/dromara/maxkey/synchronizer/service/SyncJobConfigFieldService.java
  17. 108 0
      maxkey-synchronizers/maxkey-synchronizer/src/main/java/org/dromara/maxkey/synchronizer/utils/FieldUtil.java
  18. 2 0
      maxkey-web-apis/maxkey-web-api-rest/src/main/java/org/dromara/maxkey/web/apis/identity/rest/RestOrganizationController.java
  19. 1681 1762
      maxkey-web-frontend/maxkey-web-app/package-lock.json
  20. 1 0
      maxkey-web-frontend/maxkey-web-app/package.json
  21. 46 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/entity/JobConfigFeild.ts
  22. 7 1
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/config.module.ts
  23. 41 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/editer/synchronizer-config-field-edit.component.html
  24. 92 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/editer/synchronizer-config-field-edit.component.ts
  25. 41 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.html
  26. 0 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.less
  27. 42 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.spec.ts
  28. 148 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.ts
  29. 5 1
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizers.component.html
  30. 23 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizers.component.ts
  31. 21 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/synchronizers.service.ts
  32. 4 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/theme/layout-default/style/_layout.less
  33. 8 3
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json
  34. 9 2
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json
  35. 7 2
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-TW.json
  36. 3 3
      maxkey-webs/maxkey-web-maxkey/src/main/resources/application-maxkey.properties
  37. 10 0
      maxkey-webs/maxkey-web-mgt/src/main/java/org/dromara/maxkey/autoconfigure/MaxKeyMgtConfig.java
  38. 55 4
      maxkey-webs/maxkey-web-mgt/src/main/java/org/dromara/maxkey/web/config/contorller/SynchronizersController.java
  39. 3 3
      maxkey-webs/maxkey-web-mgt/src/main/resources/application-maxkey-mgt.properties

+ 208 - 0
maxkey-core/src/main/java/org/dromara/maxkey/entity/SyncJobConfigField.java

@@ -0,0 +1,208 @@
+package org.dromara.maxkey.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import org.dromara.mybatis.jpa.entity.JpaEntity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @Description  
+ * @Author  Hunter
+ * @Date 2024-07-16 
+ */
+
+@Entity
+@Table(name = "SYNC_JOB_CONFIG_FIELD")
+public class SyncJobConfigField extends JpaEntity implements Serializable {
+
+	private static final long serialVersionUID =  6784822536779144306L;
+
+	/**
+	 *
+	 * ID
+	 */
+	@Id
+	@Column
+	private Long id;
+
+	/**
+	 * 同步任务ID
+	 */
+	@Column
+	private Long jobId;
+
+	/**
+	 * 规则名
+	 */
+   	@Column
+	private String name;
+
+	/**
+	 * 类型
+	 */
+   	@Column
+	private String objectType;
+
+	/**
+	 * 目标字段
+	 */
+   	@Column
+	private String targetField;
+
+	/**
+	 * 目标字段描述
+	 */
+   	@Column
+	private String targetFieldName;
+
+	/**
+	 * 来源字段
+	 */
+   	@Column
+	private String sourceField;
+
+	/**
+	 * 来源字段描述
+	 */
+   	@Column
+	private String sourceFieldName;
+
+	/**
+	 * 描述
+	 */
+   	@Column
+	private String description;
+
+	/**
+	 * 创建人
+	 */
+   	@Column
+	private Long createUser;
+
+	/**
+	 * 创建时间
+	 */
+   	@Column
+	private Date createTime;
+
+	/**
+	 * 修改人
+	 */
+   	@Column
+	private Long updateUser;
+
+	/**
+	 * 修改时间
+	 */
+   	@Column
+	private Date updateTime;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getJobId() {
+		return jobId;
+	}
+
+	public void setJobId(Long jobId) {
+		this.jobId = jobId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(String objectType) {
+		this.objectType = objectType;
+	}
+
+	public String getTargetField() {
+		return targetField;
+	}
+
+	public void setTargetField(String targetField) {
+		this.targetField = targetField;
+	}
+
+	public String getTargetFieldName() {
+		return targetFieldName;
+	}
+
+	public void setTargetFieldName(String targetFieldName) {
+		this.targetFieldName = targetFieldName;
+	}
+
+	public String getSourceField() {
+		return sourceField;
+	}
+
+	public void setSourceField(String sourceField) {
+		this.sourceField = sourceField;
+	}
+
+	public String getSourceFieldName() {
+		return sourceFieldName;
+	}
+
+	public void setSourceFieldName(String sourceFieldName) {
+		this.sourceFieldName = sourceFieldName;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public Long getCreateUser() {
+		return createUser;
+	}
+
+	public void setCreateUser(Long createUser) {
+		this.createUser = createUser;
+	}
+
+	public Date getCreateTime() {
+		return createTime;
+	}
+
+	public void setCreateTime(Date createTime) {
+		this.createTime = createTime;
+	}
+
+	public Long getUpdateUser() {
+		return updateUser;
+	}
+
+	public void setUpdateUser(Long updateUser) {
+		this.updateUser = updateUser;
+	}
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+}
+

+ 24 - 0
maxkey-persistence/src/main/java/org/dromara/maxkey/persistence/mapper/SyncJobConfigFieldMapper.java

@@ -0,0 +1,24 @@
+package org.dromara.maxkey.persistence.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.mybatis.jpa.IJpaMapper;
+
+import java.util.List;
+
+
+public interface SyncJobConfigFieldMapper extends IJpaMapper<SyncJobConfigField> {
+    /*@Select("SELECT * FROM sync_job_config_field WHERE job_id = #{jobId} AND object_type = #{objectType}")*/
+    public List<SyncJobConfigField> findByJobIdAndObjectType(@Param("jobId") Long jobId, @Param("objectType") String objectType);
+
+    public List<SyncJobConfigField> findByJobId(Long jobId);
+
+
+    void deleteFieldMapById(Long id);
+
+    void deleteFiledMapByjobId(Long jobId);
+
+    void deleteByJobIdAndObjectType(@Param("jobId") Long jobId, @Param("objectType") String objectType);
+}
+
+

+ 127 - 0
maxkey-persistence/src/main/resources/org/dromara/maxkey/persistence/mapper/xml/mysql/SyncJobConfigFieldMapper.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<!--namespace对应接口全类名-->
+<!--
+多行注释快捷键:ctrl + shift + /
+-->
+<mapper namespace="org.dromara.maxkey.persistence.mapper.SyncJobConfigFieldMapper">
+
+    <!--    id要和方法名相同-->
+    <insert id="insertUser">
+
+    </insert>
+
+    <update id="updateUser">
+
+    </update>
+
+
+    <delete id="deleteUser">
+
+    </delete>
+    <delete id="deleteFieldMapById">
+        DELETE FROM sync_job_config_field s WHERE s.id = #{id}
+    </delete>
+
+    <delete id="deleteFiledMapByjobId">
+        DELETE FROM sync_job_config_field s WHERE s.job_id = #{jobId}
+    </delete>
+    <delete id="deleteByJobIdAndObjectType">
+        DELETE FROM sync_job_config_field WHERE job_id = #{jobId} AND object_type = #{objectType}
+    </delete>
+
+
+    <!--多对一映射-->
+    <!--
+    type:处理映射关系的实体类的类型常用的标签
+    id:处理主键和实体类中属性的映射关系
+    result: 处理普通字段和实体类中属性的映射关系
+    column: 设置映射关系中的字段名,必须是sql查询出的某个字段
+    property: 设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
+    -->
+    <!--
+    resultMap + association 处理多对一映射
+    association: 处理多对一的映射关系(处理实体类类型的属性)
+    property: 设置需要处理映射关系的属性的属性名
+    javaType:设置要处理的属性的类型
+    -->
+    <resultMap id="FieldMap" type="org.dromara.maxkey.entity.SyncJobConfigField">
+        <id column="id" property="id"></id>
+        <result column="jobid" property="jobId"></result>
+        <result column="name" property="name"></result>
+        <result column="objecttype" property="objectType"></result>
+        <result column="targetfield" property="targetField"></result>
+        <result column="targetfield_name" property="targetFieldName"></result>
+        <result column="sourcefield" property="sourceField"></result>
+        <result column="sourcefieldname" property="sourceFieldName"></result>
+        <result column="description" property="description"></result>
+        <result column="createuser" property="createUser"></result>
+        <result column="createtime" property="createTime"></result>
+        <result column="updateuser" property="updateUser"></result>
+        <result column="updatetime" property="updateTime"></result>
+    </resultMap>
+
+    <select id="findByJobIdAndObjectType" resultMap="FieldMap" parameterType="map">
+        SELECT * FROM sync_job_config_field WHERE jobid = #{jobId} AND objecttype = #{objectType}
+    </select>
+
+    <select id="findByJobId" resultMap="FieldMap" parameterType="map">
+        SELECT * FROM sync_job_config_field WHERE job_id = #{jobId}
+    </select>
+
+<!--    &lt;!&ndash;
+    resultMap + 分步查询实现一对多映射
+    property: 设置需要处理映射关系的属性的属性名
+    select: 设置分步查询的sgl的唯一标识
+    column:将查询出的某个字段作为分步查询的sql的条件
+    &ndash;&gt;
+    <resultMap id="" type="">
+        <id column="" property=""></id>
+        <result column="" property=""></result>
+        <result column="" property=""></result>
+        <result column="" property=""></result>
+        <association property=""
+                     select=""
+                     column="">
+        </association>
+    </resultMap>
+    <select id="" resultMap="">
+
+    </select>
+
+
+    &lt;!&ndash;处理一对多映射&ndash;&gt;
+    &lt;!&ndash;
+    方法一:resultMap + collection
+    &ndash;&gt;
+    <resultMap id="" type="">
+        <id column="" property=""></id>
+        <result column="" property=""></result>
+        <collection property="" ofType="">
+            <id column="" property=""></id>
+            <result column="" property=""></result>
+            <result column="" property=""></result>
+            <result column="" property=""></result>
+        </collection>
+    </resultMap>
+    <select id="" resultMap="">
+
+    </select>
+
+    &lt;!&ndash;
+    方法二:collection分步查询
+    &ndash;&gt;
+    <resultMap id="" type="">
+        <id column="" property=""></id>
+        <result column="" property=""></result>
+        <collection property=""
+                    select=""
+                    column="">
+        </collection>
+    </resultMap>
+    <select id="" resultMap="">
+
+    </select>-->
+</mapper>

+ 77 - 0
maxkey-synchronizers/maxkey-synchronizer-activedirectory/src/main/java/org/dromara/maxkey/synchronizer/activedirectory/ActiveDirectoryOrganizationService.java

@@ -17,8 +17,11 @@
 
 package org.dromara.maxkey.synchronizer.activedirectory;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.Attribute;
@@ -35,14 +38,23 @@ import org.dromara.maxkey.ldap.LdapUtils;
 import org.dromara.maxkey.ldap.constants.OrganizationalUnit;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.getFieldValue;
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.setFieldValue;
+
 @Service
 public class ActiveDirectoryOrganizationService  extends AbstractSynchronizerService  implements ISynchronizerService{
 	static final  Logger _logger = LoggerFactory.getLogger(ActiveDirectoryOrganizationService.class);
 
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
+	private static final Integer ORG_TYPE = 2;
 	ActiveDirectoryUtils ldapUtils;
 	
 	public void sync() {
@@ -214,6 +226,65 @@ public class ActiveDirectoryOrganizationService  extends AbstractSynchronizerSer
 		}
 		return null;
 	}
+
+	public Organizations buildOrgByFiledMap(HashMap<String,Attribute> attributeMap,String name,String nameInNamespace){
+		Organizations org = new Organizations();
+		Map<String, String> filedMap = getFiledMap(Long.parseLong(synchronizer.getId()));
+		String []namePaths = name.replaceAll(",OU=" , "/")
+				.replaceAll("OU="  , "/")
+				.replaceAll(",ou=" , "/")
+				.replaceAll("ou="  , "/")
+				.split("/");
+		String namePah= "/"+rootOrganization.getOrgName();
+		for(int i = namePaths.length -1 ; i >= 0 ; i --) {
+			namePah = namePah + "/" + namePaths[i];
+		}
+		namePah = namePah.substring(0, namePah.length() - 1);
+
+		org.setLdapDn(nameInNamespace);
+		org.setNamePath(namePah);
+		org.setId(org.generateId());
+		org.setLevel(namePaths.length);
+		org.setType("department");
+		org.setInstId(this.synchronizer.getInstId());
+		org.setStatus(ConstsStatus.ACTIVE);
+
+		for (Map.Entry<String, String> entry : filedMap.entrySet()) {
+			String orgProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+			try {
+				Object sourceValue = null;
+				if(attributeMap.keySet().contains(sourceProperty.toLowerCase())){
+					sourceValue = LdapUtils.getAttributeStringValue(sourceProperty, attributeMap);
+				}else{
+					sourceValue = getFieldValue(org, sourceProperty);
+				}
+				if (sourceValue != null) {
+					setFieldValue(org, orgProperty, sourceValue);
+				}
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | NamingException e) {
+				e.printStackTrace();
+			}
+		}
+		org.setOrgCode(org.getId());
+
+
+
+		return org;
+	}
+
+	public Map<String,String> getFiledMap(Long jobId){
+		Map<String,String> filedMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
+				filedMap.put(element.getTargetField(),element.getSourceField());
+			}
+		}
+		return filedMap;
+	}
 	
 	
 
@@ -225,5 +296,11 @@ public class ActiveDirectoryOrganizationService  extends AbstractSynchronizerSer
 		this.ldapUtils = ldapUtils;
 	}
 
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
 
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 }

+ 122 - 8
maxkey-synchronizers/maxkey-synchronizer-activedirectory/src/main/java/org/dromara/maxkey/synchronizer/activedirectory/ActiveDirectoryUsersService.java

@@ -17,7 +17,10 @@
 
 package org.dromara.maxkey.synchronizer.activedirectory;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.Attribute;
@@ -27,7 +30,6 @@ import javax.naming.directory.SearchResult;
 import org.apache.commons.lang3.StringUtils;
 import org.dromara.maxkey.constants.ConstsStatus;
 import org.dromara.maxkey.crypto.DigestUtils;
-import org.dromara.maxkey.entity.HistorySynchronizer;
 import org.dromara.maxkey.entity.Organizations;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.entity.UserInfo;
@@ -36,14 +38,22 @@ import org.dromara.maxkey.ldap.LdapUtils;
 import org.dromara.maxkey.ldap.constants.ActiveDirectoryUser;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.setFieldValue;
+
 @Service
 public class ActiveDirectoryUsersService extends AbstractSynchronizerService    implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(ActiveDirectoryUsersService.class);
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
 
+	private static final Integer USER_TYPE = 1;
 	ActiveDirectoryUtils ldapUtils;
 	
 	public void sync() {
@@ -133,13 +143,14 @@ public class ActiveDirectoryUsersService extends AbstractSynchronizerService
         //namePah = namePah.substring(0, namePah.length());
         String deptNamePath= namePah.substring(0, namePah.lastIndexOf("/"));
         _logger.info("deptNamePath  " + deptNamePath);
-        Organizations  deptOrg = orgsNamePathMap.get(deptNamePath);
+		//暂时注释
+        /*Organizations  deptOrg = orgsNamePathMap.get(deptNamePath);
         if(deptOrg == null ) {
         	deptOrg = rootOrganization;
 		}
-        
+
         userInfo.setDepartment(deptOrg.getOrgName());
-        userInfo.setDepartmentId(deptOrg.getId());
+        userInfo.setDepartmentId(deptOrg.getId());*/
 		try {
 		    userInfo.setId(userInfo.generateId());
 			userInfo.setFormattedName(LdapUtils.getAttributeStringValue(ActiveDirectoryUser.CN,attributeMap));//cn
@@ -185,8 +196,8 @@ public class ActiveDirectoryUsersService extends AbstractSynchronizerService
 			userInfo.setTimeZone("Asia/Shanghai");
 			userInfo.setStatus(ConstsStatus.ACTIVE);
 			userInfo.setInstId(this.synchronizer.getInstId());
-
-		    HistorySynchronizer historySynchronizer =new HistorySynchronizer();
+			//暂时注释
+		    /*HistorySynchronizer historySynchronizer =new HistorySynchronizer();
             historySynchronizer.setId(historySynchronizer.generateId());
             historySynchronizer.setSyncId(this.synchronizer.getId());
             historySynchronizer.setSyncName(this.synchronizer.getName());
@@ -195,7 +206,7 @@ public class ActiveDirectoryUsersService extends AbstractSynchronizerService
             historySynchronizer.setObjectType(Organizations.class.getSimpleName());
             historySynchronizer.setInstId(synchronizer.getInstId());
             historySynchronizer.setResult("success");
-            this.historySynchronizerService.insert(historySynchronizer);
+            this.historySynchronizerService.insert(historySynchronizer);*/
 
 		} catch (NamingException e) {
 			e.printStackTrace();
@@ -203,6 +214,102 @@ public class ActiveDirectoryUsersService extends AbstractSynchronizerService
 		return userInfo;
 	}
 
+	public UserInfo buildUserInfoByFieldMap(HashMap<String,Attribute> attributeMap,String name,String nameInNamespace){
+		UserInfo userInfo = new  UserInfo();
+		userInfo.setLdapDn(nameInNamespace);
+		userInfo.setId(userInfo.generateId());
+		String []namePaths = name.replaceAll(",OU=" , "/")
+				.replaceAll("OU="  , "/")
+				.replaceAll(",ou=" , "/")
+				.replaceAll("ou="  , "/")
+				.split("/");
+
+		String namePah= "/"+rootOrganization.getOrgName();
+		for(int i = namePaths.length -1 ; i >= 0 ; i --) {
+			namePah = namePah + "/" + namePaths[i];
+		}
+
+		//namePah = namePah.substring(0, namePah.length());
+		String deptNamePath= namePah.substring(0, namePah.lastIndexOf("/"));
+		_logger.info("deptNamePath  " + deptNamePath);
+		//暂时注释
+		/*Organizations  deptOrg = orgsNamePathMap.get(deptNamePath);
+		if(deptOrg == null ) {
+			deptOrg = rootOrganization;
+		}*/
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+			String userInfoProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+			try {
+				//暂时注释
+				/*if(sourceProperty.equals("orgName")){
+					userInfo.setDepartment(deptOrg.getOrgName());
+					continue;
+				}
+				if(sourceProperty.equals("id")){
+					userInfo.setDepartmentId(deptOrg.getId());
+					continue;
+				}*/
+				if(sourceProperty.equals("mobile")){
+					userInfo.setMobile(LdapUtils.getAttributeStringValue(sourceProperty, attributeMap).equals("")?
+							userInfo.getId():LdapUtils.getAttributeStringValue(sourceProperty,attributeMap));
+					continue;
+				}
+				// 获取源属性的值
+				Object sourceValue = LdapUtils.getAttributeStringValue(sourceProperty, attributeMap);
+				// 设置到 UserInfo 对象
+				if (sourceValue != null) {
+					setFieldValue(userInfo, userInfoProperty, sourceValue);
+				}
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			} catch (NamingException e) {
+				e.printStackTrace();
+			}
+		}
+
+		try {
+			userInfo.setLdapDn(nameInNamespace);
+			userInfo.setUserState("RESIDENT");
+			userInfo.setUserType("EMPLOYEE");
+			userInfo.setTimeZone("Asia/Shanghai");
+			userInfo.setStatus(ConstsStatus.ACTIVE);
+			userInfo.setInstId(this.synchronizer.getInstId());
+
+			//暂时注释
+			/*HistorySynchronizer historySynchronizer =new HistorySynchronizer();
+			historySynchronizer.setId(historySynchronizer.generateId());
+			historySynchronizer.setSyncId(this.synchronizer.getId());
+			historySynchronizer.setSyncName(this.synchronizer.getName());
+			historySynchronizer.setObjectId(userInfo.getId());
+			historySynchronizer.setObjectName(userInfo.getUsername());
+			historySynchronizer.setObjectType(Organizations.class.getSimpleName());
+			historySynchronizer.setInstId(synchronizer.getInstId());
+			historySynchronizer.setResult("success");
+			this.historySynchronizerService.insert(historySynchronizer);*/
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+
+
+		return userInfo;
+	}
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> fieldMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == USER_TYPE.intValue()){
+				fieldMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return fieldMap;
+	}
+
 	public ActiveDirectoryUtils getLdapUtils() {
 		return ldapUtils;
 	}
@@ -210,5 +317,12 @@ public class ActiveDirectoryUsersService extends AbstractSynchronizerService
 	public void setLdapUtils(ActiveDirectoryUtils ldapUtils) {
 		this.ldapUtils = ldapUtils;
 	}
-	
+
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 }

+ 74 - 8
maxkey-synchronizers/maxkey-synchronizer-dingtalk/src/main/java/org/dromara/maxkey/synchronizer/dingtalk/DingtalkOrganizationService.java

@@ -17,6 +17,10 @@
 
 package org.dromara.maxkey.synchronizer.dingtalk;
 
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.LinkedBlockingQueue;
 
@@ -25,8 +29,11 @@ import org.dromara.maxkey.entity.Organizations;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.dingtalk.api.DefaultDingTalkClient;
 import com.dingtalk.api.DingTalkClient;
@@ -37,9 +44,15 @@ import com.dingtalk.api.response.OapiV2DepartmentListsubResponse;
 import com.dingtalk.api.response.OapiV2DepartmentListsubResponse.DeptBaseResponse;
 import com.taobao.api.ApiException;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.*;
+
 @Service
 public class DingtalkOrganizationService  extends AbstractSynchronizerService implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(DingtalkOrganizationService.class);
+
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
+	private static final Integer ORG_TYPE = 2;
 	
 	static Long ROOT_DEPT_ID = 1L;
 	
@@ -55,7 +68,7 @@ public class DingtalkOrganizationService  extends AbstractSynchronizerService im
 			OapiV2DepartmentGetResponse rootDeptRsp = requestDepartment(access_token,ROOT_DEPT_ID);
 			_logger.debug("root dept   deptId {} , name {} ,  parentId {}" 
 							,rootDeptRsp.getResult().getDeptId(), 
-							rootDeptRsp.getResult().getName(), 
+							rootDeptRsp.getResult().getName(),
 							rootDeptRsp.getResult().getParentId());
 			//root
 			SynchroRelated rootSynchroRelated = buildSynchroRelated(rootOrganization,
@@ -81,8 +94,11 @@ public class DingtalkOrganizationService  extends AbstractSynchronizerService im
 					SynchroRelated synchroRelated = 
 							synchroRelatedService.findByOriginId(
 									this.synchronizer,dept.getDeptId() + "",Organizations.CLASS_TYPE );
-					
-					Organizations organization = buildOrganization(dept);
+					//Parent
+					SynchroRelated synchroRelatedParent =
+							synchroRelatedService.findByOriginId(
+									this.synchronizer,dept.getParentId() + "",Organizations.CLASS_TYPE);
+					Organizations organization = buildOrgByFieldMap(dept,synchroRelatedParent);
 					if(synchroRelated == null) {
 						organization.setId(organization.generateId());
 						organizationsService.insert(organization);
@@ -147,11 +163,8 @@ public class DingtalkOrganizationService  extends AbstractSynchronizerService im
 				synchronizer.getInstId());
 	}
 	
-	public Organizations buildOrganization(DeptBaseResponse dept) {
-		//Parent
-		SynchroRelated synchroRelatedParent = 
-				synchroRelatedService.findByOriginId(
-				this.synchronizer,dept.getParentId() + "",Organizations.CLASS_TYPE);
+	public Organizations buildOrganization(DeptBaseResponse dept,SynchroRelated synchroRelatedParent) {
+
 		Organizations org = new Organizations();
 		org.setId(dept.getDeptId()+"");
 		org.setOrgCode(dept.getDeptId()+"");
@@ -167,6 +180,51 @@ public class DingtalkOrganizationService  extends AbstractSynchronizerService im
 		return org;
 	}
 
+	public Organizations buildOrgByFieldMap(DeptBaseResponse dept,SynchroRelated synchroRelatedParent){
+		Organizations org = new Organizations();
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+			String orgProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+			try {
+				Object sourceValue = null;
+
+				if (hasField(DeptBaseResponse.class, sourceProperty)) {
+					sourceValue = getFieldValue(dept, sourceProperty);
+				}
+				else if (synchroRelatedParent != null && hasField(SynchroRelated.class, sourceProperty)) {
+					sourceValue = getFieldValue(synchroRelatedParent, sourceProperty);
+				}
+				if (sourceValue != null) {
+					setFieldValue(org, orgProperty, sourceValue);
+				}
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+		org.setType("department");
+		org.setInstId(this.synchronizer.getInstId());
+		org.setStatus(ConstsStatus.ACTIVE);
+		org.setDescription("dingtalk");
+		return org;
+	}
+
+
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> FieldMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
+				FieldMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return FieldMap;
+	}
+
 
 
 	public String getAccess_token() {
@@ -176,5 +234,13 @@ public class DingtalkOrganizationService  extends AbstractSynchronizerService im
 	public void setAccess_token(String access_token) {
 		this.access_token = access_token;
 	}
+
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 	
 }

+ 92 - 4
maxkey-synchronizers/maxkey-synchronizer-dingtalk/src/main/java/org/dromara/maxkey/synchronizer/dingtalk/DingtalkUsersService.java

@@ -17,7 +17,10 @@
 
 package org.dromara.maxkey.synchronizer.dingtalk;
 
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
 import org.dromara.maxkey.constants.ConstsStatus;
@@ -25,10 +28,13 @@ import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.entity.UserInfo;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.joda.time.DateTime;
 import org.joda.time.format.DateTimeFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.dingtalk.api.DefaultDingTalkClient;
 import com.dingtalk.api.DingTalkClient;
@@ -36,11 +42,21 @@ import com.dingtalk.api.request.OapiV2UserListRequest;
 import com.dingtalk.api.response.OapiV2UserListResponse;
 import com.dingtalk.api.response.OapiV2UserListResponse.ListUserResponse;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.*;
+
 @Service
 public class DingtalkUsersService  extends AbstractSynchronizerService implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(DingtalkUsersService.class);
 	
 	String access_token;
+
+	private static final Integer USER_TYPE = 1;
+
+
+
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
+
 	
 	public void sync() {
 		_logger.info("Sync Dingtalk Users...");
@@ -65,7 +81,7 @@ public class DingtalkUsersService  extends AbstractSynchronizerService implement
 					for(ListUserResponse user :rsp.getResult().getList()) {
 						_logger.debug("name : {} , {} , {}", user.getName(),user.getLoginId(),user.getUserid());
 						
-						UserInfo userInfo  = buildUserInfo(user,relatedOrg);
+						UserInfo userInfo  = buildUserInfoByFieldMap(user,relatedOrg);
 						_logger.trace("userInfo {}" , userInfo);
 						userInfo.setPassword(userInfo.getUsername() + UserInfo.DEFAULT_PASSWORD_SUFFIX);
 						userInfoService.saveOrUpdate(userInfo);
@@ -96,7 +112,7 @@ public class DingtalkUsersService  extends AbstractSynchronizerService implement
 		}
 		
 	}
-	
+
 	public UserInfo buildUserInfo(ListUserResponse user,SynchroRelated relatedOrg) {
 		UserInfo userInfo = new  UserInfo();
 
@@ -121,16 +137,88 @@ public class DingtalkUsersService  extends AbstractSynchronizerService implement
 		}else {
 			userInfo.setStatus(ConstsStatus.INACTIVE);
 		}
-		
-		userInfo.setInstId(this.synchronizer.getInstId());
+
+		//userInfo.setInstId(this.synchronizer.getInstId());
 		userInfo.setDescription("dingtalk "+user.getRemark());
 		return userInfo;
 	}
 
 
+	public UserInfo buildUserInfoByFieldMap(ListUserResponse user, SynchroRelated relatedOrg){
+		UserInfo userInfo = new UserInfo();
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+
+			String userInfoProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+
+			try {
+				Object sourceValue = null;
+
+				if(sourceProperty.equals("email")){
+					userInfo.setEmail(StringUtils.isBlank(user.getEmail())? user.getUserid() +"@maxkey.top":user.getEmail());
+					continue;
+				}
+				if(sourceProperty.equals("active")){
+					userInfo.setStatus(user.getActive()?ConstsStatus.ACTIVE:ConstsStatus.INACTIVE);
+					continue;
+				}
+				if(sourceProperty.equals("remark")){
+					userInfo.setDescription("dingtalk "+user.getRemark());
+					continue;
+				}
+				if(sourceProperty.equals("hiredDate")){
+					userInfo.setEntryDate(new DateTime(user.getHiredDate()).toString(DateTimeFormat.forPattern("yyyy-MM-dd")));
+					continue;
+				}
+				if (hasField(OapiV2UserListResponse.ListUserResponse.class, sourceProperty)) {
+					sourceValue = getFieldValue(user, sourceProperty);
+				}
+
+				else if (hasField(SynchroRelated.class, sourceProperty)) {
+					sourceValue = getFieldValue(relatedOrg, sourceProperty);
+				}
+
+				if (sourceValue != null) {
+					setFieldValue(userInfo, userInfoProperty, sourceValue);
+				}
+
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+		userInfo.setInstId(this.synchronizer.getInstId());
+		userInfo.setUserType("EMPLOYEE");
+		userInfo.setUserState("RESIDENT");
+		return userInfo;
+	}
+
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> userFieldMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == USER_TYPE.intValue()){
+				userFieldMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return userFieldMap;
+	}
+
+
 
 	public void setAccess_token(String access_token) {
 		this.access_token = access_token;
 	}
 
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
+
 }

+ 82 - 6
maxkey-synchronizers/maxkey-synchronizer-feishu/src/main/java/org/dromara/maxkey/synchronizer/feishu/FeishuOrganizationService.java

@@ -17,7 +17,10 @@
 
 package org.dromara.maxkey.synchronizer.feishu;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.LinkedBlockingQueue;
 
@@ -26,20 +29,31 @@ import org.dromara.maxkey.entity.Organizations;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
 import org.dromara.maxkey.synchronizer.feishu.entity.FeishuDepts;
 import org.dromara.maxkey.synchronizer.feishu.entity.FeishuDeptsResponse;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.dromara.maxkey.util.AuthorizationHeaderUtils;
 import org.dromara.maxkey.util.JsonUtils;
 import org.dromara.maxkey.web.HttpRequestAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.*;
+
 @Service
 public class FeishuOrganizationService extends AbstractSynchronizerService implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(FeishuOrganizationService.class);
 	
 	String access_token;
+	private static final Integer ORG_TYPE = 2;
+
+
+
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
 	
 	static String DEPTS_URL = "https://open.feishu.cn/open-apis/contact/v3/departments/%s/children?page_size=50";
 	static String ROOT_DEPT_URL = "https://open.feishu.cn/open-apis/contact/v3/departments/%s";
@@ -75,7 +89,11 @@ public class FeishuOrganizationService extends AbstractSynchronizerService imple
 						SynchroRelated synchroRelated = 
 								synchroRelatedService.findByOriginId(
 										this.synchronizer,dept.getOpen_department_id(),Organizations.CLASS_TYPE );
-						Organizations organization = buildOrganization(dept);
+						//Parent
+						SynchroRelated synchroRelatedParent =
+								synchroRelatedService.findByOriginId(
+										this.synchronizer,dept.getParent_department_id(),Organizations.CLASS_TYPE);
+						Organizations organization = buildOrganizationByFieldMap(dept,synchroRelatedParent);
 						if(synchroRelated == null) {
 							organization.setId(organization.generateId());
 							organizationsService.insert(organization);
@@ -138,11 +156,8 @@ public class FeishuOrganizationService extends AbstractSynchronizerService imple
 				synchronizer.getInstId());
 	}
 	
-	public Organizations buildOrganization(FeishuDepts dept) {
-		//Parent
-		SynchroRelated synchroRelatedParent = 
-				synchroRelatedService.findByOriginId(
-				this.synchronizer,dept.getParent_department_id(),Organizations.CLASS_TYPE);
+	public Organizations buildOrganization(FeishuDepts dept,SynchroRelated synchroRelatedParent) {
+
 		
 		Organizations org = new Organizations();
 		org.setOrgCode(dept.getDepartment_id()+"");
@@ -157,6 +172,59 @@ public class FeishuOrganizationService extends AbstractSynchronizerService imple
 		return org;
 	}
 
+	public Organizations buildOrganizationByFieldMap(FeishuDepts dept,SynchroRelated synchroRelatedParent){
+		Map<String, String> fieldMap = getFiledMap(Long.parseLong(synchronizer.getId()));
+		Organizations org = new Organizations();
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+			String   orgProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+			try {
+				Object sourceValue = null;
+
+				if (hasField(dept.getClass(), sourceProperty)) {
+					sourceValue = getFieldValue(dept, sourceProperty);
+				}
+				else if (synchroRelatedParent != null && hasField(SynchroRelated.class, sourceProperty)) {
+					sourceValue = getFieldValue(synchroRelatedParent, sourceProperty);
+				}
+				if (sourceValue != null) {
+					setFieldValue(org, orgProperty, sourceValue);
+				}
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+
+		// 额外处理特定逻辑:意味着这些属性不能出现在属性映射表中
+		try {
+            /*if (synchroRelatedParent != null) {
+                setFieldValue(org, "parentId", synchroRelatedParent.getObjectId());
+                setFieldValue(org, "parentName", synchroRelatedParent.getObjectName());
+            }*/
+			setFieldValue(org, "instId", this.synchronizer.getInstId());
+			setFieldValue(org, "status", ConstsStatus.ACTIVE);
+			setFieldValue(org, "description", "Feishu");
+			org.setType("department");
+		} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+			e.printStackTrace();
+		}
+		return org;
+	}
+
+	public Map<String,String> getFiledMap(Long jobId){
+		//key是maxkey的属性,value是其他应用的属性
+		Map<String,String> filedMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取组织属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
+				filedMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return filedMap;
+	}
+
 	public String getAccess_token() {
 		return access_token;
 	}
@@ -165,4 +233,12 @@ public class FeishuOrganizationService extends AbstractSynchronizerService imple
 		this.access_token = access_token;
 	}
 
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
+
 }

+ 90 - 2
maxkey-synchronizers/maxkey-synchronizer-feishu/src/main/java/org/dromara/maxkey/synchronizer/feishu/FeishuUsersService.java

@@ -17,28 +17,41 @@
 
 package org.dromara.maxkey.synchronizer.feishu;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.dromara.maxkey.constants.ConstsStatus;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.entity.UserInfo;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
 import org.dromara.maxkey.synchronizer.feishu.entity.FeishuUsers;
 import org.dromara.maxkey.synchronizer.feishu.entity.FeishuUsersResponse;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.dromara.maxkey.util.AuthorizationHeaderUtils;
 import org.dromara.maxkey.util.JsonUtils;
 import org.dromara.maxkey.web.HttpRequestAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.*;
+
 @Service
 public class FeishuUsersService extends AbstractSynchronizerService implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(FeishuUsersService.class);
-	
+
+
+
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
 	String access_token;
+	private static final Integer USER_TYPE = 1;
 	
 	static String USERS_URL="https://open.feishu.cn/open-apis/contact/v3/users/find_by_department?department_id=%s&page_size=50";
 	
@@ -57,7 +70,7 @@ public class FeishuUsersService extends AbstractSynchronizerService implements I
 				_logger.trace("response : " + responseBody);
 				if(usersResponse.getCode() == 0 && usersResponse.getData().getItems() != null) {
 					for(FeishuUsers feiShuUser : usersResponse.getData().getItems()) {
-						UserInfo userInfo  = buildUserInfo(feiShuUser,relatedOrg);
+						UserInfo userInfo  = buildUserInfoByFieldMapper(feiShuUser,relatedOrg);
 						_logger.debug("userInfo : " + userInfo);
 						userInfo.setPassword(userInfo.getUsername() + UserInfo.DEFAULT_PASSWORD_SUFFIX);
 						userInfoService.saveOrUpdate(userInfo);
@@ -89,6 +102,11 @@ public class FeishuUsersService extends AbstractSynchronizerService implements I
 		}
 		
 	}
+
+
+
+
+
 	
 	public void postSync(UserInfo userInfo) {
 		
@@ -124,7 +142,77 @@ public class FeishuUsersService extends AbstractSynchronizerService implements I
 		return userInfo;
 	}
 
+	public UserInfo buildUserInfoByFieldMapper(FeishuUsers user,SynchroRelated relatedOrg){
+		UserInfo userInfo = new  UserInfo();
+		Map<String, String> fieldMap = this.getFiledMap(Long.parseLong(synchronizer.getId()));
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+
+			String userInfoProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+
+			try {
+				Object sourceValue = null;
+				if(sourceProperty.equals("status")){
+					if (user.getStatus().isIs_activated()) {
+						setFieldValue(userInfo, "status", ConstsStatus.ACTIVE);
+					} else {
+						setFieldValue(userInfo, "status", ConstsStatus.INACTIVE);
+					}
+					continue;
+				}
+				if (hasField(user.getClass(), sourceProperty)) {
+					sourceValue = getFieldValue(user, sourceProperty);
+				}
+				else if (hasField(SynchroRelated.class, sourceProperty)) {
+					sourceValue = getFieldValue(relatedOrg, sourceProperty);
+				}
+
+				if (sourceValue != null) {
+					setFieldValue(userInfo, userInfoProperty, sourceValue);
+				}
+
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+		// 额外处理特定逻辑 :意味着这些属性映射不能保存在数据库中。
+		try {
+			if(userInfo.getUsername() == null){
+				userInfo.setUsername(user.getOpen_id());
+			}
+			setFieldValue(userInfo, "id", userInfo.generateId());
+			setFieldValue(userInfo, "instId", this.synchronizer.getInstId());
+			userInfo.setUserType("EMPLOYEE");
+			userInfo.setUserState("RESIDENT");
+		} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+			e.printStackTrace();
+		}
+		return userInfo;
+	}
+
+	public Map<String,String> getFiledMap(Long jobId){
+		Map<String,String> fieldMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == USER_TYPE.intValue()){
+				fieldMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return fieldMap;
+	}
+
 	public void setAccess_token(String access_token) {
 		this.access_token = access_token;
 	}
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
+
+
 }

+ 161 - 1
maxkey-synchronizers/maxkey-synchronizer-jdbc/src/main/java/org/dromara/maxkey/synchronizer/jdbc/JdbcOrganizationService.java

@@ -25,9 +25,13 @@ import org.dromara.maxkey.entity.HistorySynchronizer;
 import org.dromara.maxkey.entity.Organizations;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
+import org.dromara.maxkey.synchronizer.utils.MyResultSet;
 import org.dromara.maxkey.util.JdbcUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.sql.Connection;
@@ -35,11 +39,20 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.setFieldValue;
 
 @Service
 public class JdbcOrganizationService extends AbstractSynchronizerService implements ISynchronizerService {
     final static Logger _logger = LoggerFactory.getLogger(JdbcOrganizationService.class);
     static ArrayList<ColumnFieldMapper> mapperList = new ArrayList<>();
+    @Autowired
+    private SyncJobConfigFieldService syncJobConfigFieldService;
+
+    private static final Integer ORG_TYPE = 2;
 
     @Override
     public void sync() {
@@ -74,6 +87,87 @@ public class JdbcOrganizationService extends AbstractSynchronizerService impleme
         }
     }
 
+    public Organizations buildOrgByFieldMap(ResultSet rs) throws SQLException{
+        Organizations org = new Organizations();
+        DbTableMetaData meta = JdbcUtils.getMetaData(rs);
+        Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+        for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+            String column = entry.getValue();
+            String field = entry.getKey();
+            Object value = rs.getObject(column);
+
+            if (value != null) {
+                try {
+                    setFieldValue(org,field,value);
+                } catch (Exception e) {
+                    _logger.error("setProperty {}", e);
+                }
+            }
+        }
+        org.setType("department");
+        org.setId(org.generateId());
+        org.setInstId(synchronizer.getInstId());
+        if (meta.getColumnsMap().containsKey("status")) {
+            org.setStatus(rs.getInt("status"));
+        } else {
+            org.setStatus(ConstsStatus.ACTIVE);
+        }
+        _logger.debug("Organization {}", org);
+
+        HistorySynchronizer historySynchronizer = new HistorySynchronizer();
+        historySynchronizer.setId(historySynchronizer.generateId());
+        historySynchronizer.setSyncId(synchronizer.getId());
+        historySynchronizer.setSyncName(synchronizer.getName());
+        historySynchronizer.setObjectId(org.getId());
+        historySynchronizer.setObjectName(org.getOrgName());
+        historySynchronizer.setObjectType(Organizations.class.getSimpleName());
+        historySynchronizer.setInstId(synchronizer.getInstId());
+        historySynchronizer.setResult("success");
+        historySynchronizerService.insert(historySynchronizer);
+
+        return org;
+    }
+
+    public Organizations buildOrgByFieldMapTemp(MyResultSet rs) throws SQLException{
+        Organizations org = new Organizations();
+        //DbTableMetaData meta = JdbcUtils.getMetaData(rs);
+        Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+        for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+            String column = entry.getValue();
+            String field = entry.getKey();
+            Object value = rs.getObject(column);
+
+            if (value != null) {
+                try {
+                    setFieldValue(org,field,value);
+                } catch (Exception e) {
+                    _logger.error("setProperty {}", e);
+                }
+            }
+        }
+        org.setId(org.generateId());
+        org.setInstId(synchronizer.getInstId());
+        if (rs.getColumnNames().contains("status")) {
+            org.setStatus(rs.getInt("status"));
+        } else {
+            org.setStatus(ConstsStatus.ACTIVE);
+        }
+        _logger.debug("Organization {}", org);
+
+        /*HistorySynchronizer historySynchronizer = new HistorySynchronizer();
+        historySynchronizer.setId(historySynchronizer.generateId());
+        historySynchronizer.setSyncId(synchronizer.getId());
+        historySynchronizer.setSyncName(synchronizer.getName());
+        historySynchronizer.setObjectId(org.getId());
+        historySynchronizer.setObjectName(org.getOrgName());
+        historySynchronizer.setObjectType(Organizations.class.getSimpleName());
+        historySynchronizer.setInstId(synchronizer.getInstId());
+        historySynchronizer.setResult("success");
+        historySynchronizerService.insert(historySynchronizer);*/
+
+        return org;
+    }
+
 
     public Organizations buildOrganization(ResultSet rs) throws SQLException {
         DbTableMetaData meta = JdbcUtils.getMetaData(rs);
@@ -121,6 +215,72 @@ public class JdbcOrganizationService extends AbstractSynchronizerService impleme
 
     }
 
+    public Organizations buildOrganizationTemp(MyResultSet rs) throws SQLException {
+        //DbTableMetaData meta = JdbcUtils.getMetaData(rs);
+        Organizations org = new Organizations();
+
+        for (ColumnFieldMapper mapper : mapperList) {
+            if (rs.getColumnNames().contains(mapper.getColumn())) {
+                Object value = null;
+                if (mapper.getType().equalsIgnoreCase("String")) {
+                    value = rs.getString(mapper.getColumn());
+                } else {
+                    value = rs.getInt(mapper.getColumn());
+                }
+                if (value != null) {
+                    try {
+                        PropertyUtils.setSimpleProperty(org, mapper.getField(), value);
+                    } catch (Exception e) {
+                        _logger.error("setSimpleProperty {}", e);
+                    }
+                }
+            }
+        }
+
+        org.setId(org.generateId());
+        org.setInstId(synchronizer.getInstId());
+        if (rs.getColumnNames().contains("status")) {
+            org.setStatus(rs.getInt("status"));
+        } else {
+            org.setStatus(ConstsStatus.ACTIVE);
+        }
+        _logger.debug("Organization {}", org);
+
+        /*HistorySynchronizer historySynchronizer = new HistorySynchronizer();
+        historySynchronizer.setId(historySynchronizer.generateId());
+        historySynchronizer.setSyncId(synchronizer.getId());
+        historySynchronizer.setSyncName(synchronizer.getName());
+        historySynchronizer.setObjectId(org.getId());
+        historySynchronizer.setObjectName(org.getOrgName());
+        historySynchronizer.setObjectType(Organizations.class.getSimpleName());
+        historySynchronizer.setInstId(synchronizer.getInstId());
+        historySynchronizer.setResult("success");
+        historySynchronizerService.insert(historySynchronizer);*/
+
+        return org;
+
+    }
+
+    public Map<String,String> getFieldMap(Long jobId){
+        Map<String,String> filedMap = new HashMap<>();
+        //根据job id查询属性映射表
+        List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+        //获取用户属性映射
+        for(SyncJobConfigField element:syncJobConfigFieldList){
+            if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
+                filedMap.put(element.getTargetField(), element.getSourceField());
+            }
+        }
+        return filedMap;
+    }
+
+    public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+        return syncJobConfigFieldService;
+    }
+
+    public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+        this.syncJobConfigFieldService = syncJobConfigFieldService;
+    }
 
     static {
         mapperList.add(new ColumnFieldMapper("id", "id", "String"));
@@ -129,7 +289,7 @@ public class JdbcOrganizationService extends AbstractSynchronizerService impleme
         mapperList.add(new ColumnFieldMapper("fullname", "fullName", "String"));
         mapperList.add(new ColumnFieldMapper("parentid", "parentId", "String"));
         mapperList.add(new ColumnFieldMapper("parentcode", "parentCode", "String"));
-        mapperList.add(new ColumnFieldMapper("parentname", "parentName", "String"));
+        mapperList.add(new ColumnFieldMapper(" ", "parentName", "String"));
 
         mapperList.add(new ColumnFieldMapper("type", "type", "String"));
         mapperList.add(new ColumnFieldMapper("codepath", "codePath", "String"));

+ 206 - 3
maxkey-synchronizers/maxkey-synchronizer-jdbc/src/main/java/org/dromara/maxkey/synchronizer/jdbc/JdbcUsersService.java

@@ -24,22 +24,35 @@ import org.dromara.maxkey.entity.HistorySynchronizer;
 import org.dromara.maxkey.entity.UserInfo;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
+import org.dromara.maxkey.synchronizer.utils.MyResultSet;
 import org.dromara.maxkey.util.JdbcUtils;
 import org.dromara.maxkey.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.lang.reflect.InvocationTargetException;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.setFieldValue;
 
 @Service
 public class JdbcUsersService extends AbstractSynchronizerService implements ISynchronizerService {
     final static Logger _logger = LoggerFactory.getLogger(JdbcUsersService.class);
+    @Autowired
+    public SyncJobConfigFieldService syncJobConfigFieldService;
 
+    private static final Integer USER_TYPE = 1;
     static ArrayList<ColumnFieldMapper> mapperList = new ArrayList<>();
 
     @Override
@@ -93,6 +106,65 @@ public class JdbcUsersService extends AbstractSynchronizerService implements ISy
         }
     }
 
+    public UserInfo buildUserInfoTemp(MyResultSet rs) throws SQLException {
+        //DbTableMetaData meta = JdbcUtils.getMetaData(rs);
+        UserInfo user = new UserInfo();
+        //basic
+        for (ColumnFieldMapper mapper : mapperList) {
+            if (rs.getColumnNames().contains(mapper.getColumn())) {
+                Object value = null;
+                if (mapper.getType().equalsIgnoreCase("String")) {
+                    value = rs.getString(mapper.getColumn());
+                } else {
+                    value = rs.getInt(mapper.getColumn());
+                }
+                if (value != null) {
+                    try {
+                        PropertyUtils.setSimpleProperty(user, mapper.getField(), value);
+                    } catch (Exception e) {
+                        _logger.error("setSimpleProperty {}", e);
+                    }
+                }
+            }
+        }
+
+        if (rs.getColumnNames().contains("status")) {
+            user.setStatus(rs.getInt("status"));
+        } else {
+            user.setStatus(ConstsStatus.ACTIVE);
+        }
+        user.setInstId(synchronizer.getInstId());
+
+        //password
+        if (rs.getColumnNames().contains("password")) {
+            user.setPassword(rs.getString("password"));
+        } else {
+            //后4位
+            String last4Char = "6666";
+            if (StringUtils.isNotBlank(user.getIdCardNo())) {
+                last4Char = user.getIdCardNo().substring(user.getIdCardNo().length() - 4);
+            } else if (StringUtils.isNotBlank(user.getMobile())) {
+                last4Char = user.getMobile().substring(user.getMobile().length() - 4);
+            } else if (StringUtils.isNotBlank(user.getEmployeeNumber())) {
+                last4Char = user.getEmployeeNumber().substring(user.getEmployeeNumber().length() - 4);
+            }
+            user.setPassword(user.getUsername() + "@M" + last4Char);
+        }
+
+        /*HistorySynchronizer historySynchronizer = new HistorySynchronizer();
+        historySynchronizer.setId(historySynchronizer.generateId());
+        historySynchronizer.setSyncId(synchronizer.getId());
+        historySynchronizer.setSyncName(synchronizer.getName());
+        historySynchronizer.setObjectId(user.getId());
+        historySynchronizer.setObjectName(user.getUsername());
+        historySynchronizer.setObjectType(UserInfo.class.getSimpleName());
+        historySynchronizer.setInstId(synchronizer.getInstId());
+        historySynchronizer.setResult("success");
+        historySynchronizerService.insert(historySynchronizer);*/
+        _logger.debug("User {} ", user);
+
+        return user;
+    }
     public UserInfo buildUserInfo(ResultSet rs) throws SQLException {
         DbTableMetaData meta = JdbcUtils.getMetaData(rs);
         UserInfo user = new UserInfo();
@@ -153,6 +225,137 @@ public class JdbcUsersService extends AbstractSynchronizerService implements ISy
         return user;
     }
 
+
+    public UserInfo buildUserInfoByFieldMapTemp(MyResultSet rs) throws SQLException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
+        //DbTableMetaData meta = JdbcUtils.getMetaData(rs);
+        UserInfo user = new UserInfo();
+        Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+        for(Map.Entry<String,String> entry: fieldMap.entrySet()){
+            String column = entry.getValue();
+            String field = entry.getKey();
+            Object value = null;
+            if(rs.getColumnNames().contains(column) && !field.equals("status") && !field.equals("password")){
+                value = rs.getObject(column);
+                if(value!=null){
+                    setFieldValue(user,field,value);
+                }
+            }
+
+        }
+        //password的获取和user的其他属性相关,如果在遍历过程中进行属性映射,需要在password映射之前,先完成其他属性的映射
+        if (rs.getColumnNames().contains("status")) {
+            user.setStatus(rs.getInt("status"));
+        } else {
+            user.setStatus(ConstsStatus.ACTIVE);
+        }
+        user.setInstId(synchronizer.getInstId());
+        // password
+        if (rs.getColumnNames().contains("password")) {
+            user.setPassword(rs.getString("password"));
+        } else {
+            String last4Char = "6666";
+            if (StringUtils.isNotBlank(user.getIdCardNo())) {
+                last4Char = user.getIdCardNo().substring(user.getIdCardNo().length() - 4);
+            } else if (StringUtils.isNotBlank(user.getMobile())) {
+                last4Char = user.getMobile().substring(user.getMobile().length() - 4);
+            } else if (StringUtils.isNotBlank(user.getEmployeeNumber())) {
+                last4Char = user.getEmployeeNumber().substring(user.getEmployeeNumber().length() - 4);
+            }
+            user.setPassword(user.getUsername() + "@M" + last4Char);
+        }
+
+        /*HistorySynchronizer historySynchronizer = new HistorySynchronizer();
+        historySynchronizer.setId(historySynchronizer.generateId());
+        historySynchronizer.setSyncId(synchronizer.getId());
+        historySynchronizer.setSyncName(synchronizer.getName());
+        historySynchronizer.setObjectId(user.getId());
+        historySynchronizer.setObjectName(user.getUsername());
+        historySynchronizer.setObjectType(UserInfo.class.getSimpleName());
+        historySynchronizer.setInstId(synchronizer.getInstId());
+        historySynchronizer.setResult("success");
+        historySynchronizerService.insert(historySynchronizer);
+        _logger.debug("User {} ", user);*/
+
+        return user;
+    }
+
+    public UserInfo buildUserInfoByFieldMap(ResultSet rs) throws SQLException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
+        DbTableMetaData meta = JdbcUtils.getMetaData(rs);
+        UserInfo user = new UserInfo();
+        Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+        for(Map.Entry<String,String> entry: fieldMap.entrySet()){
+
+            String column = entry.getValue();
+            String field = entry.getKey();
+            Object value = null;
+            if(meta.getColumnsMap().containsKey(column) && !field.equals("status") && !field.equals("password")){
+                value = rs.getObject(column);
+                if(value!=null){
+                    setFieldValue(user,field,value);
+                }
+            }
+
+        }
+        user.setUserType("EMPLOYEE");
+        user.setUserState("RESIDENT");
+        //password的获取和user的其他属性相关,如果在遍历过程中进行属性映射,需要在password映射之前,先完成其他属性的映射
+        if (meta.getColumnsMap().containsKey("status")) {
+            user.setStatus(rs.getInt("status"));
+        } else {
+            user.setStatus(ConstsStatus.ACTIVE);
+        }
+        user.setInstId(synchronizer.getInstId());
+        // password
+        if (meta.getColumnsMap().containsKey("password")) {
+            user.setPassword(rs.getString("password"));
+        } else {
+            String last4Char = "6666";
+            if (StringUtils.isNotBlank(user.getIdCardNo())) {
+                last4Char = user.getIdCardNo().substring(user.getIdCardNo().length() - 4);
+            } else if (StringUtils.isNotBlank(user.getMobile())) {
+                last4Char = user.getMobile().substring(user.getMobile().length() - 4);
+            } else if (StringUtils.isNotBlank(user.getEmployeeNumber())) {
+                last4Char = user.getEmployeeNumber().substring(user.getEmployeeNumber().length() - 4);
+            }
+            user.setPassword(user.getUsername() + "@M" + last4Char);
+        }
+
+        HistorySynchronizer historySynchronizer = new HistorySynchronizer();
+        historySynchronizer.setId(historySynchronizer.generateId());
+        historySynchronizer.setSyncId(synchronizer.getId());
+        historySynchronizer.setSyncName(synchronizer.getName());
+        historySynchronizer.setObjectId(user.getId());
+        historySynchronizer.setObjectName(user.getUsername());
+        historySynchronizer.setObjectType(UserInfo.class.getSimpleName());
+        historySynchronizer.setInstId(synchronizer.getInstId());
+        historySynchronizer.setResult("success");
+        historySynchronizerService.insert(historySynchronizer);
+        _logger.debug("User {} ", user);
+
+        return user;
+    }
+
+    public Map<String,String> getFieldMap(Long jobId){
+        Map<String,String> fieldMap = new HashMap<>();
+        //根据job id查询属性映射表
+        List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+        //获取用户属性映射
+        for(SyncJobConfigField element:syncJobConfigFieldList){
+            if(Integer.parseInt(element.getObjectType()) == USER_TYPE.intValue()){
+                fieldMap.put(element.getTargetField(), element.getSourceField());
+            }
+        }
+        return fieldMap;
+    }
+
+    public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+        return syncJobConfigFieldService;
+    }
+
+    public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+        this.syncJobConfigFieldService = syncJobConfigFieldService;
+    }
+
     static {
         mapperList.add(new ColumnFieldMapper("id", "id", "String"));
         mapperList.add(new ColumnFieldMapper("username", "username", "String"));
@@ -191,7 +394,7 @@ public class JdbcUsersService extends AbstractSynchronizerService implements ISy
         mapperList.add(new ColumnFieldMapper("homestreetaddress", "homeStreetAddress", "String"));
         mapperList.add(new ColumnFieldMapper("homeaddressformatted", "homeAddressFormatted", "String"));
         mapperList.add(new ColumnFieldMapper("homeemail", "homeEmail", "String"));
-        mapperList.add(new ColumnFieldMapper("homephonenumber", "homePhonenumber", "String"));
+        mapperList.add(new ColumnFieldMapper("homephoneNumber", "homePhonenumber", "String"));
         mapperList.add(new ColumnFieldMapper("homepostalcode", "homePostalCode", "String"));
         mapperList.add(new ColumnFieldMapper("homefax", "homeFax", "String"));
         //company
@@ -207,8 +410,8 @@ public class JdbcUsersService extends AbstractSynchronizerService implements ISy
         mapperList.add(new ColumnFieldMapper("manager", "manager", "String"));
         mapperList.add(new ColumnFieldMapper("assistantid", "assistantId", "String"));
         mapperList.add(new ColumnFieldMapper("assistant", "assistant", "String"));
-        mapperList.add(new ColumnFieldMapper("entrydate", "entrydate", "String"));
-        mapperList.add(new ColumnFieldMapper("quitdate", "quitdate", "String"));
+        mapperList.add(new ColumnFieldMapper("entryDate", "entrydate", "String"));
+        mapperList.add(new ColumnFieldMapper("quitDate", "quitdate", "String"));
         mapperList.add(new ColumnFieldMapper("ldapdn", "ldapDn", "String"));
 
         mapperList.add(new ColumnFieldMapper("description", "description", "String"));

+ 84 - 2
maxkey-synchronizers/maxkey-synchronizer-ldap/src/main/java/org/dromara/maxkey/synchronizer/ldap/LdapOrganizationService.java

@@ -17,8 +17,11 @@
 
 package org.dromara.maxkey.synchronizer.ldap;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
@@ -35,14 +38,23 @@ import org.dromara.maxkey.ldap.LdapUtils;
 import org.dromara.maxkey.ldap.constants.OrganizationalUnit;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.getFieldValue;
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.setFieldValue;
+
 @Service
 public class LdapOrganizationService extends AbstractSynchronizerService  implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(LdapOrganizationService.class);
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
 
+	private static final Integer ORG_TYPE = 2;
 	LdapUtils ldapUtils;
 	
 	public void sync() {
@@ -212,6 +224,70 @@ public class LdapOrganizationService extends AbstractSynchronizerService  implem
 		}
 		return null;
 	}
+
+
+	public Organizations buildOrgByFieldMap(HashMap<String,Attribute> attributeMap,String name,String nameInNamespace){
+		Organizations org = new Organizations();
+		String []namePaths = name.replaceAll(",OU=" , "/")
+				.replaceAll("OU="  , "/")
+				.replaceAll(",ou=" , "/")
+				.replaceAll("ou="  , "/")
+				.split("/");
+
+		String namePah= "/"+rootOrganization.getOrgName();
+		for(int i = namePaths.length -1 ; i >= 0 ; i --) {
+			namePah = namePah + "/" + namePaths[i];
+		}
+
+		namePah = namePah.substring(0, namePah.length() - 1);
+		org.setLdapDn(nameInNamespace);
+		org.setId(org.generateId());
+		org.setNamePath(namePah);
+		org.setLevel(namePaths.length);
+		org.setType("department");
+
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+		for(Map.Entry<String,String> entry:fieldMap.entrySet()){
+			String orgProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+			try {
+				String fieldValue = null;
+				if(!attributeMap.keySet().contains(sourceProperty.toLowerCase())){
+					fieldValue = (String) getFieldValue(org, sourceProperty);
+				}else {
+					fieldValue = LdapUtils.getAttributeStringValue(sourceProperty,attributeMap);
+				}
+				if(fieldValue!=null){
+					setFieldValue(org,orgProperty,fieldValue);
+				}
+			} catch (InvocationTargetException e) {
+				throw new RuntimeException(e);
+			} catch (NoSuchMethodException e) {
+				throw new RuntimeException(e);
+			} catch (IllegalAccessException e) {
+				throw new RuntimeException(e);
+			} catch (NamingException e) {
+				throw new RuntimeException(e);
+			}
+			org.setInstId(this.synchronizer.getInstId());
+			org.setStatus(ConstsStatus.ACTIVE);
+
+		}
+		return org;
+	}
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> userFiledMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
+				userFiledMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return userFiledMap;
+	}
 	
 
 	public LdapUtils getLdapUtils() {
@@ -221,6 +297,12 @@ public class LdapOrganizationService extends AbstractSynchronizerService  implem
 	public void setLdapUtils(LdapUtils ldapUtils) {
 		this.ldapUtils = ldapUtils;
 	}
-	
-	
+
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 }

+ 143 - 7
maxkey-synchronizers/maxkey-synchronizer-ldap/src/main/java/org/dromara/maxkey/synchronizer/ldap/LdapUsersService.java

@@ -17,7 +17,10 @@
 
 package org.dromara.maxkey.synchronizer.ldap;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.Attribute;
@@ -26,7 +29,6 @@ import javax.naming.directory.SearchResult;
 
 import org.apache.commons.lang3.StringUtils;
 import org.dromara.maxkey.crypto.DigestUtils;
-import org.dromara.maxkey.entity.HistorySynchronizer;
 import org.dromara.maxkey.entity.Organizations;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.entity.UserInfo;
@@ -34,14 +36,23 @@ import org.dromara.maxkey.ldap.LdapUtils;
 import org.dromara.maxkey.ldap.constants.InetOrgPerson;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.getFieldValue;
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.setFieldValue;
+
 @Service
 public class LdapUsersService extends AbstractSynchronizerService  implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(LdapUsersService.class);
+	@Autowired
+	public SyncJobConfigFieldService syncJobConfigFieldService;
 
+	private static final Integer USER_TYPE = 1;
 	LdapUtils ldapUtils;
 	
 	public void sync() {
@@ -124,9 +135,10 @@ public class LdapUsersService extends AbstractSynchronizerService  implements IS
         //namePah = namePah.substring(0, namePah.length());
         String deptNamePath= namePah.substring(0, namePah.lastIndexOf("/"));
         _logger.info("deptNamePath  " + deptNamePath);
-        Organizations  deptOrg = orgsNamePathMap.get(deptNamePath);
+		//和属性映射无关暂时注释
+/*        Organizations  deptOrg = orgsNamePathMap.get(deptNamePath);
         userInfo.setDepartment(deptOrg.getOrgName());
-        userInfo.setDepartmentId(deptOrg.getId());
+        userInfo.setDepartmentId(deptOrg.getId());*/
         
 		try {
 		    userInfo.setId(userInfo.generateId());
@@ -189,8 +201,8 @@ public class LdapUsersService extends AbstractSynchronizerService  implements IS
 			userInfo.setTimeZone("Asia/Shanghai");
 			userInfo.setStatus(1);
 			userInfo.setInstId(this.synchronizer.getInstId());
-
-            HistorySynchronizer historySynchronizer =new HistorySynchronizer();
+			//和属性映射无关暂时注释
+/*            HistorySynchronizer historySynchronizer =new HistorySynchronizer();
             historySynchronizer.setId(historySynchronizer.generateId());
             historySynchronizer.setSyncId(this.synchronizer.getId());
             historySynchronizer.setSyncName(this.synchronizer.getName());
@@ -199,7 +211,7 @@ public class LdapUsersService extends AbstractSynchronizerService  implements IS
             historySynchronizer.setObjectType(Organizations.class.getSimpleName());
             historySynchronizer.setInstId(synchronizer.getInstId());
             historySynchronizer.setResult("success");
-            this.historySynchronizerService.insert(historySynchronizer);
+            this.historySynchronizerService.insert(historySynchronizer);*/
            
 		} catch (NamingException e) {
 			e.printStackTrace();
@@ -207,6 +219,123 @@ public class LdapUsersService extends AbstractSynchronizerService  implements IS
 		return userInfo;
 	}
 
+
+	public UserInfo buildUserInfoByFieldMap(HashMap<String,Attribute> attributeMap,String name,String nameInNamespace){
+		UserInfo userInfo = new  UserInfo();
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+		String []namePaths = name.replaceAll(",OU=" , "/")
+				.replaceAll("OU="  , "/")
+				.replaceAll(",ou=" , "/")
+
+				.replaceAll("ou="  , "/")
+				.split("/");
+		String namePah= "/"+rootOrganization.getOrgName();
+		for(int i = namePaths.length -1 ; i >= 0 ; i --) {
+			namePah = namePah + "/" + namePaths[i];
+		}
+
+		//namePah = namePah.substring(0, namePah.length());
+		String deptNamePath= namePah.substring(0, namePah.lastIndexOf("/"));
+		_logger.info("deptNamePath  " + deptNamePath);
+		//和属性映射无关暂时注释
+		/*Organizations  deptOrg = orgsNamePathMap.get(deptNamePath);*/
+
+		userInfo.setLdapDn(nameInNamespace);
+		userInfo.setId(userInfo.generateId());
+		userInfo.setUserState("RESIDENT");
+		userInfo.setUserType("EMPLOYEE");
+		userInfo.setTimeZone("Asia/Shanghai");
+		userInfo.setStatus(1);
+		userInfo.setInstId(this.synchronizer.getInstId());
+
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+			String  targetAttr = entry.getKey();
+			String sourceAttr = entry.getValue();
+			String value = null;
+			try {
+				//暂时注释
+				/*if(!attributeMap.keySet().contains(sourceAttr.toLowerCase())){
+					value = (String) getFieldValue(deptOrg, sourceAttr);
+					if(value!=null){
+						setFieldValue(userInfo,targetAttr,value);
+						continue;
+					}
+				}*/
+				value = LdapUtils.getAttributeStringValue(sourceAttr,attributeMap);
+				if(targetAttr.equals("formattedName")){
+					userInfo.setFormattedName(LdapUtils.getAttributeStringValue(InetOrgPerson.SN,attributeMap)+
+							LdapUtils.getAttributeStringValue(InetOrgPerson.GIVENNAME,attributeMap));
+					continue;
+				}
+				//只配置 username 到 uid 的映射关系
+				///只配置 windowsAccount 到 uid 的映射关系
+				if (targetAttr.equals("username") || targetAttr.equals("windowsAccount")) {
+					if (sourceAttr.equals("uid") && StringUtils.isBlank(value)) {
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.CN,attributeMap);
+					}else{
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.UID,attributeMap);
+					}
+					//只配置 nickName 到 initials 的映射关系
+					//只配置 nameZhShortSpell 到 initials 的映射关系
+				} else if (targetAttr.equals("nickName") || targetAttr.equals("nameZhShortSpell")) {
+					if (sourceAttr.equals("initials") && StringUtils.isBlank(value)) {
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.SN,attributeMap) +
+								LdapUtils.getAttributeStringValue(InetOrgPerson.GIVENNAME,attributeMap);
+					}else{
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.INITIALS,attributeMap);
+					}
+
+					//只配置 displayName 到 displayName 的映射关系
+				} else if (targetAttr.equals("displayName")) {
+					if (sourceAttr.equals("displayName") && StringUtils.isBlank(value)) {
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.SN,attributeMap) +
+								LdapUtils.getAttributeStringValue(InetOrgPerson.GIVENNAME,attributeMap);
+					}else {
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.DISPLAYNAME,attributeMap);
+					}
+				} else if (targetAttr.equals("mobile")) {
+					if (sourceAttr.equals("mobile") && StringUtils.isBlank(value)) {
+						value = (String) getFieldValue(userInfo,"id");
+					}else {
+						value = LdapUtils.getAttributeStringValue(InetOrgPerson.MOBILE,attributeMap);
+					}
+				}
+
+				setFieldValue(userInfo, targetAttr,value);
+			}catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			} catch (NamingException e) {
+				throw new RuntimeException(e);
+			}
+
+		}
+/*		HistorySynchronizer historySynchronizer =new HistorySynchronizer();
+		historySynchronizer.setId(historySynchronizer.generateId());
+		historySynchronizer.setSyncId(this.synchronizer.getId());
+		historySynchronizer.setSyncName(this.synchronizer.getName());
+		historySynchronizer.setObjectId(userInfo.getId());
+		historySynchronizer.setObjectName(userInfo.getUsername());
+		historySynchronizer.setObjectType(Organizations.class.getSimpleName());
+		historySynchronizer.setInstId(synchronizer.getInstId());
+		historySynchronizer.setResult("success");
+		this.historySynchronizerService.insert(historySynchronizer);*/
+
+		return userInfo;
+	}
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> userFieldMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == USER_TYPE.intValue()){
+				userFieldMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return userFieldMap;
+	}
+
 	
 	public LdapUtils getLdapUtils() {
 		return ldapUtils;
@@ -215,5 +344,12 @@ public class LdapUsersService extends AbstractSynchronizerService  implements IS
 	public void setLdapUtils(LdapUtils ldapUtils) {
 		this.ldapUtils = ldapUtils;
 	}
-	
+
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 }

+ 78 - 8
maxkey-synchronizers/maxkey-synchronizer-workweixin/src/main/java/org/dromara/maxkey/synchronizer/workweixin/WorkweixinOrganizationService.java

@@ -22,20 +22,32 @@ import org.dromara.maxkey.entity.Organizations;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.dromara.maxkey.synchronizer.workweixin.entity.WorkWeixinDepts;
 import org.dromara.maxkey.synchronizer.workweixin.entity.WorkWeixinDeptsResponse;
 import org.dromara.maxkey.util.JsonUtils;
 import org.dromara.maxkey.web.HttpRequestAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.*;
+
 @Service
 public class WorkweixinOrganizationService extends AbstractSynchronizerService implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(WorkweixinOrganizationService.class);
 	
 	String access_token;
-	
+	@Autowired
+	private SyncJobConfigFieldService syncJobConfigFieldService;
+	private static final Integer ORG_TYPE = 2;
 	static String DEPTS_URL="https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s";
 	static long ROOT_DEPT_ID = 1;
 	
@@ -58,8 +70,11 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
 					SynchroRelated synchroRelated = 
 							synchroRelatedService.findByOriginId(
 									this.synchronizer,dept.getId() + "",Organizations.CLASS_TYPE );
-					
-					Organizations organization = buildOrganization(dept);
+					//Parent
+					SynchroRelated synchroRelatedParent =
+							synchroRelatedService.findByOriginId(
+									this.synchronizer,dept.getParentid() + "",Organizations.CLASS_TYPE);
+					Organizations organization = buildOrgByFiledMap(dept,synchroRelatedParent);
 					if(synchroRelated == null) {
 						organization.setId(organization.generateId());
 						organizationsService.insert(organization);
@@ -109,11 +124,8 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
 		return deptsResponse;
 	}
 	
-	public Organizations buildOrganization(WorkWeixinDepts dept) {
-		//Parent
-		SynchroRelated synchroRelatedParent = 
-				synchroRelatedService.findByOriginId(
-				this.synchronizer,dept.getParentid() + "",Organizations.CLASS_TYPE);
+	public Organizations buildOrganization(WorkWeixinDepts dept,SynchroRelated synchroRelatedParent) {
+
 		Organizations org = new Organizations();
 		org.setOrgName(dept.getName());
 		org.setOrgCode(dept.getId()+"");
@@ -126,6 +138,57 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
 		return org;
 	}
 
+
+	public Organizations buildOrgByFiledMap(WorkWeixinDepts dept, SynchroRelated synchroRelatedParent){
+		Organizations org = new Organizations();
+		//fieldMap
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+
+
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+			String orgProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+			try {
+				Object sourceValue = null;
+
+				if (hasField(dept.getClass(), sourceProperty)) {
+					sourceValue = getFieldValue(dept, sourceProperty);
+				}
+				else if (synchroRelatedParent != null && hasField(SynchroRelated.class, sourceProperty)) {
+					sourceValue = getFieldValue(synchroRelatedParent, sourceProperty);
+				}
+				if (sourceValue != null) {
+					setFieldValue(org, orgProperty, sourceValue);
+				}
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+		org.setInstId(this.synchronizer.getInstId());
+		org.setStatus(ConstsStatus.ACTIVE);
+		org.setDescription("WorkWeixin");
+		org.setType("department");
+		return org;
+
+	}
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> filedMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取组织属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
+				filedMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return filedMap;
+	}
+
+
+
+
+
 	public String getAccess_token() {
 		return access_token;
 	}
@@ -134,4 +197,11 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
 		this.access_token = access_token;
 	}
 
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 }

+ 67 - 2
maxkey-synchronizers/maxkey-synchronizer-workweixin/src/main/java/org/dromara/maxkey/synchronizer/workweixin/WorkweixinUsersService.java

@@ -17,25 +17,38 @@
 
 package org.dromara.maxkey.synchronizer.workweixin;
 
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.dromara.maxkey.constants.ConstsStatus;
 import org.dromara.maxkey.entity.SynchroRelated;
 import org.dromara.maxkey.entity.UserInfo;
 import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.dromara.maxkey.synchronizer.workweixin.entity.WorkWeixinUsers;
 import org.dromara.maxkey.synchronizer.workweixin.entity.WorkWeixinUsersResponse;
 import org.dromara.maxkey.util.JsonUtils;
 import org.dromara.maxkey.web.HttpRequestAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static org.dromara.maxkey.synchronizer.utils.FieldUtil.*;
+
 @Service
 public class WorkweixinUsersService extends AbstractSynchronizerService implements ISynchronizerService{
 	final static Logger _logger = LoggerFactory.getLogger(WorkweixinUsersService.class);
-	
+
+
+
+	@Autowired
+	public SyncJobConfigFieldService syncJobConfigFieldService;
+	private static final Integer USER_TYPE = 1;
 	String access_token;
 	
 	static String USERS_URL="https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=%s&department_id=%s&fetch_child=0";
@@ -53,7 +66,7 @@ public class WorkweixinUsersService extends AbstractSynchronizerService implemen
 				_logger.trace("response : " + responseBody);
 				
 				for(WorkWeixinUsers user : usersResponse.getUserlist()) {
-					UserInfo userInfo  = buildUserInfo(user);
+					UserInfo userInfo  = buildUserInfoByFiledMap(user);
 					_logger.debug("userInfo : " + userInfo);
 					userInfo.setPassword(userInfo.getUsername() + UserInfo.DEFAULT_PASSWORD_SUFFIX);
 					userInfoService.saveOrUpdate(userInfo);
@@ -113,7 +126,59 @@ public class WorkweixinUsersService extends AbstractSynchronizerService implemen
 		return userInfo;
 	}
 
+	public UserInfo buildUserInfoByFiledMap(WorkWeixinUsers user){
+		UserInfo userInfo = new UserInfo();
+		Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
+		for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
+
+			String userInfoProperty = entry.getKey();
+			String sourceProperty = entry.getValue();
+
+			try {
+				Object sourceValue = null;
+				if(sourceProperty.equals("status")){
+					userInfo.setStatus(user.getStatus() == 1?ConstsStatus.ACTIVE:ConstsStatus.INACTIVE);
+					continue;
+				}
+				if (hasField(user.getClass(), sourceProperty)) {
+					sourceValue = getFieldValue(user, sourceProperty);
+				}
+				if (sourceValue != null) {
+					setFieldValue(userInfo, userInfoProperty, sourceValue);
+				}
+
+			} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+				e.printStackTrace();
+			}
+		}
+
+		userInfo.setUserType("EMPLOYEE");
+		userInfo.setUserState("RESIDENT");
+		userInfo.setInstId(this.synchronizer.getInstId());
+		return userInfo;
+	}
+
+	public Map<String,String> getFieldMap(Long jobId){
+		Map<String,String> userFieldMap = new HashMap<>();
+		//根据job id查询属性映射表
+		List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
+		//获取用户属性映射
+		for(SyncJobConfigField element:syncJobConfigFieldList){
+			if(Integer.parseInt(element.getObjectType()) == USER_TYPE.intValue()){
+				userFieldMap.put(element.getTargetField(), element.getSourceField());
+			}
+		}
+		return userFieldMap;
+	}
+
 	public void setAccess_token(String access_token) {
 		this.access_token = access_token;
 	}
+	public SyncJobConfigFieldService getSyncJobConfigFieldService() {
+		return syncJobConfigFieldService;
+	}
+
+	public void setSyncJobConfigFieldService(SyncJobConfigFieldService syncJobConfigFieldService) {
+		this.syncJobConfigFieldService = syncJobConfigFieldService;
+	}
 }

+ 40 - 0
maxkey-synchronizers/maxkey-synchronizer/src/main/java/org/dromara/maxkey/synchronizer/service/SyncJobConfigFieldService.java

@@ -0,0 +1,40 @@
+package org.dromara.maxkey.synchronizer.service;
+
+import org.dromara.maxkey.entity.SyncJobConfigField;
+import org.dromara.maxkey.entity.UserInfo;
+import org.dromara.maxkey.persistence.mapper.SyncJobConfigFieldMapper;
+import org.dromara.mybatis.jpa.JpaService;
+import org.springframework.stereotype.Service;
+
+import java.sql.Types;
+import java.util.List;
+
+@Service
+public class SyncJobConfigFieldService extends JpaService<SyncJobConfigField> {
+    public SyncJobConfigFieldService() {
+        super(SyncJobConfigFieldMapper.class);
+    }
+    @Override
+    public SyncJobConfigFieldMapper getMapper() {
+        return (SyncJobConfigFieldMapper)super.getMapper();
+    }
+
+    public List<SyncJobConfigField> findByJobIdAndObjectType(Long jobId, String objectType) {
+
+        return getMapper().findByJobIdAndObjectType(jobId,objectType);
+    }
+
+    public void deleteFieldMapById(Long id){
+       super.deleteBatch(String.valueOf(id));
+    }
+
+    public List<SyncJobConfigField> findByJobId(Long jobId) {
+        List<SyncJobConfigField> list = find(" jobid = ?",
+                new Object[]{jobId},
+                new int[]{Types.BIGINT});
+        return list;
+
+    }
+
+
+}

+ 108 - 0
maxkey-synchronizers/maxkey-synchronizer/src/main/java/org/dromara/maxkey/synchronizer/utils/FieldUtil.java

@@ -0,0 +1,108 @@
+package org.dromara.maxkey.synchronizer.utils;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+public class FieldUtil {
+
+    public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Class<?> clazz = obj.getClass();
+
+        String setterMethodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
+        String getterMethodName = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
+        Method getterMethod = clazz.getMethod(getterMethodName);
+        Class<?> fieldType = getterMethod.getReturnType();
+
+        Object convertedValue = convertValueToFieldType(value, fieldType);
+
+        Method setterMethod = clazz.getMethod(setterMethodName, fieldType);
+        setterMethod.invoke(obj, convertedValue);
+    }
+
+    public static Object getFieldValue(Object obj, String fieldName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Class<?> clazz = obj.getClass();
+
+        String getterMethodName = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
+        Method method = clazz.getMethod(getterMethodName);
+
+        return method.invoke(obj);
+    }
+
+    public static boolean hasField(Class<?> clazz, String fieldName) {
+        Field[] declaredFields = clazz.getDeclaredFields();
+        for (Field field : declaredFields) {
+            if (field.getName().equals(fieldName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static Object convertValueToFieldType(Object value, Class<?> fieldType) {
+        if (fieldType.isInstance(value)) {
+            return value;
+        }
+
+        if (fieldType == Integer.class || fieldType == int.class) {
+            if (value instanceof Boolean) {
+                return (Boolean) value ? 1 : 0;
+            }
+            return Integer.parseInt(value.toString());
+        } else if (fieldType == Long.class || fieldType == long.class) {
+            return Long.parseLong(value.toString());
+        } else if (fieldType == Double.class || fieldType == double.class) {
+            return Double.parseDouble(value.toString());
+        } else if (fieldType == Boolean.class || fieldType == boolean.class) {
+            return "1".equals(value.toString()) || Boolean.parseBoolean(value.toString());
+        } else if (fieldType == String.class) {
+            return value.toString();
+        } else if (fieldType == DateTime.class) {
+            DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+            DateTime dateTime;
+            if (value instanceof Long) {
+                dateTime = new DateTime((Long) value);
+            } else if (value instanceof String) {
+                dateTime = DateTime.parse((String) value);
+            }else {
+                dateTime = new DateTime(value);
+            }
+            return dateTime.toString(formatter);
+        }
+        throw new IllegalArgumentException("Unsupported field type: " + fieldType);
+    }
+
+    public static boolean areFieldsEqual(Object obj1, Object obj2) {
+        if (obj1 == null || obj2 == null) {
+            return false;
+        }
+        if (!obj1.getClass().equals(obj2.getClass())) {
+            return false;
+        }
+
+        Field[] fields = obj1.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            try {
+                Object value1 = field.get(obj1);
+                Object value2 = field.get(obj2);
+                if (value1 == null) {
+                    if (value2 != null) {
+                        return false;
+                    }
+                } else if (!value1.equals(value2)) {
+                    return false;
+                }
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 2 - 0
maxkey-web-apis/maxkey-web-api-rest/src/main/java/org/dromara/maxkey/web/apis/identity/rest/RestOrganizationController.java

@@ -112,4 +112,6 @@ public class RestOrganizationController {
 		return new Message<JpaPageResults<Organizations>>(
 				organizationsService.fetchPageResults(org)).buildResponse();
 	}
+
+
 }

File diff suppressed because it is too large
+ 1681 - 1762
maxkey-web-frontend/maxkey-web-app/package-lock.json


+ 1 - 0
maxkey-web-frontend/maxkey-web-app/package.json

@@ -66,6 +66,7 @@
     "ngx-cookie-service": "^13.2.0",
     "ngx-tinymce": "^13.0.0",
     "ngx-ueditor": "^13.0.0",
+    "npm": "^8.5.5",
     "rxjs": "~7.5.0",
     "screenfull": "^6.0.1",
     "tslib": "^2.3.0",

+ 46 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/entity/JobConfigFeild.ts

@@ -0,0 +1,46 @@
+
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import { BaseEntity } from './BaseEntity';
+
+export class JobConfigFeild extends BaseEntity {
+  jobId!: String;
+  name!: String;
+  objectType!: String;
+  targetField!: String;
+  sourceField!: String;
+
+  constructor() {
+    super();
+  }
+
+  override init(data: any): void {
+    Object.assign(this, data);
+    if (this.status == 1) {
+      this.switch_status = true;
+    }
+
+  }
+  override trans(): void {
+    if (this.switch_status) {
+      this.status = 1;
+    } else {
+      this.status = 0;
+    }
+  }
+}

+ 7 - 1
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/config.module.ts

@@ -45,6 +45,10 @@ import { SocialsProviderEditerComponent } from './socials-provider/socials-provi
 import { SocialsProviderComponent } from './socials-provider/socials-provider.component';
 import { SynchronizerEditerComponent } from './synchronizers/synchronizer-editer/synchronizer-editer.component';
 import { SynchronizersComponent } from './synchronizers/synchronizers.component';
+import { SynchronizerConfigFieldComponent } from './synchronizers/synchronizer-config-field/synchronizer-config-field.component';
+import {
+  SynchronizerConfigFieldEditComponent
+} from "./synchronizers/synchronizer-config-field/editer/synchronizer-config-field-edit.component";
 
 const routes: Routes = [
   {
@@ -115,7 +119,9 @@ const COMPONENTS = [PasswordPolicyComponent, EmailSendersComponent, LdapContextC
     SelectAccountsStrategyComponent,
     SelectAdaptersComponent,
     ConnectorsComponent,
-    ConnectorEditerComponent
+    ConnectorEditerComponent,
+    SynchronizerConfigFieldComponent,
+    SynchronizerConfigFieldEditComponent
   ],
   imports: [SharedModule, CommonModule, RouterModule.forChild(routes)],
   exports: [RouterModule]

+ 41 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/editer/synchronizer-config-field-edit.component.html

@@ -0,0 +1,41 @@
+<div *nzModalTitle> {{ isEdit ? ('mxk.text.edit' | i18n) : ('mxk.text.add' | i18n) }} </div>
+<div>
+  <form nz-form [formGroup]="formGroup" (ngSubmit)="onSubmit($event)" se-container="1">
+    <nz-form-item>
+      <nz-form-label [nzMd]="6" nzFor="sourceField">{{ 'mxk.job.mapping.sourceField' | i18n }}</nz-form-label>
+      <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
+        <input [(ngModel)]="form.model.sourceField" [ngModelOptions]="{ standalone: true }" nz-input
+               name="sourceField" id="sourceField" />
+      </nz-form-control>
+    </nz-form-item>
+
+    <nz-form-item>
+      <nz-form-label [nzMd]="6" nzFor="targetField">{{ 'mxk.job.mapping.targetField' | i18n }}</nz-form-label>
+      <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
+        <input [(ngModel)]="form.model.targetField" [ngModelOptions]="{ standalone: true }" nz-input
+               name="targetField" id="targetField" />
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-label [nzMd]="6" nzFor="description">{{ 'mxk.job.mapping.description' | i18n }}</nz-form-label>
+      <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
+        <input [(ngModel)]="form.model.description" [ngModelOptions]="{ standalone: true }" nz-input
+               name="description" id="description" />
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-label [nzMd]="6" nzFor="objectType">{{ 'mxk.job.mapping.objectType' | i18n }}</nz-form-label>
+      <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
+        <nz-select [(ngModel)]="form.model.objectType" [ngModelOptions]="{ standalone: true }" name="objectType" id="objectType">
+          <nz-option nzValue="1" nzLabel="{{ 'mxk.menu.identities.users' | i18n }}"></nz-option>
+          <nz-option nzValue="2" nzLabel="{{ 'mxk.menu.identities.organizations' | i18n }}"></nz-option>
+        </nz-select>
+      </nz-form-control>
+    </nz-form-item>
+  </form>
+</div>
+
+<div *nzModalFooter>
+  <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+  <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 92 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/editer/synchronizer-config-field-edit.component.ts

@@ -0,0 +1,92 @@
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, ChangeDetectorRef, Input, OnInit, Inject } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { I18NService } from '@core';
+import { _HttpClient, ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef } from 'ng-zorro-antd/modal';
+
+import { JobConfigFeild } from '../../../../../entity/JobConfigFeild';
+import { SynchronizersService } from '../../../../../service/synchronizers.service';
+
+
+@Component({
+  selector: 'app-synchronizer-config-field-edit',
+  templateUrl: './synchronizer-config-field-edit.component.html',
+  styles: [
+    `
+      nz-form-item {
+        width: 100%;
+      }
+    `
+  ],
+})
+export class SynchronizerConfigFieldEditComponent implements OnInit {
+  @Input() id?: String;
+  @Input() jobId?: String;
+  @Input() isEdit?: boolean;
+
+  form: {
+    submitting: boolean;
+    model:  JobConfigFeild
+  } = {
+    submitting: false,
+    model: new JobConfigFeild(),
+  };
+  formGroup: FormGroup = new FormGroup({});
+  constructor(
+    private modalRef: NzModalRef,
+    private synchronizersService: SynchronizersService,
+    private fb: FormBuilder,
+    private msg: NzMessageService,
+    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
+    private cdr: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+   this.get()
+  }
+  get(){
+    if (this.isEdit) {
+      this.synchronizersService.getField(`${this.id}`).subscribe(res => {
+        this.form.model.init(res.data);
+        this.cdr.detectChanges();
+      });
+    }
+  }
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+    this.modalRef.destroy({ refresh: false });
+  }
+
+  onSubmit(e: MouseEvent): void {
+    e.preventDefault();
+    this.form.submitting = true;
+    this.form.model.jobId = this.jobId +"";
+   (this.isEdit ? this.synchronizersService.mappingUpdate(this.form.model) : this.synchronizersService.mappingAdd(this.form.model)).subscribe(res => {
+      if (res.code == 0) {
+        this.msg.success(this.i18n.fanyi(this.isEdit ? 'mxk.alert.update.success' : 'mxk.alert.add.success'));
+      } else {
+        this.msg.error(this.i18n.fanyi(this.isEdit ? 'mxk.alert.update.error' : 'mxk.alert.add.error'));
+      }
+      this.form.submitting = false;
+      this.modalRef.destroy({ refresh: true });
+      this.cdr.detectChanges();
+    });
+  }
+}

+ 41 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.html

@@ -0,0 +1,41 @@
+<div *nzModalTitle> {{ ('mxk.text.mapping' | i18n) }} </div>
+<div>
+
+  <div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 8, lg: 24, xl: 48, xxl: 48 }">
+    <div nz-col [nzSpan]="24" class="table-list-toolbar">
+      <button nz-button type="button" [nzType]="'primary'" (click)="onAdd($event)">{{ 'mxk.text.add' | i18n }}</button>
+    </div>
+    <div nz-col nzMd="24" nzSm="24">
+      <nz-table [nzShowPagination]="false" [nzData]="form.rows">
+        <thead>
+        <tr>
+          <th nzAlign="center">{{ 'mxk.job.mapping.sourceField' | i18n }}</th>
+          <th nzAlign="center">{{ 'mxk.job.mapping.targetField' | i18n }}</th>
+          <th nzAlign="center">{{ 'mxk.job.mapping.description' | i18n }}</th>
+          <th nzAlign="center">{{ 'mxk.job.mapping.objectType' | i18n }}</th>
+          <th nzAlign="center">{{ 'mxk.text.action' | i18n }}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr *ngFor="let data of form.rows">
+          <td nzAlign="left">{{ data.sourceField }} </td>
+          <td nzAlign="left">{{ data.targetField }} </td>
+          <td nzAlign="left">{{ data.description }} </td>
+          <td nzAlign="center">
+           {{ data.objectType == '1' ? ('mxk.menu.identities.users' | i18n) : ('mxk.menu.identities.organizations' | i18n) }}
+          </td>
+          <td nzAlign="center">
+            <button nz-button type="button" (click)="onEdit($event, data.id)">{{ 'mxk.text.edit' | i18n }}</button>
+            <button nz-button type="button" nzDanger (click)="onDelete($event, data.id)">{{ 'mxk.text.delete' | i18n }}</button>
+          </td>
+        </tr>
+        </tbody>
+      </nz-table>
+    </div>
+  </div>
+</div>
+
+<div *nzModalFooter>
+  <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+  <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 0 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.less


+ 42 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.spec.ts

@@ -0,0 +1,42 @@
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SynchronizerConfigFieldComponent } from './synchronizer-config-field.component';
+
+describe('SynchronizerConfigFieldComponent', () => {
+  let component: SynchronizerConfigFieldComponent;
+  let fixture: ComponentFixture<SynchronizerConfigFieldComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ SynchronizerConfigFieldComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SynchronizerConfigFieldComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 148 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizer-config-field/synchronizer-config-field.component.ts

@@ -0,0 +1,148 @@
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Component, ChangeDetectorRef, Input, OnInit, Inject, ViewContainerRef} from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { I18NService } from '@core';
+import { _HttpClient, ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
+import format from 'date-fns/format';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
+
+import { JobConfigFeild } from '../../../../entity/JobConfigFeild';
+import { SynchronizersService } from '../../../../service/synchronizers.service';
+import {NzSafeAny} from "ng-zorro-antd/core/types";
+import { SynchronizerConfigFieldEditComponent } from './editer/synchronizer-config-field-edit.component';
+
+@Component({
+  selector: 'app-synchronizer-config-field',
+  templateUrl: './synchronizer-config-field.component.html',
+  styles: [
+    `
+      nz-form-item {
+        width: 100%;
+      }
+    `
+  ],
+  styleUrls: ['./synchronizer-config-field.component.less']
+})
+export class SynchronizerConfigFieldComponent implements OnInit {
+  @Input() jobId?: String;
+  @Input() isEdit?: boolean;
+
+  form: {
+    submitting: boolean;
+    rows: NzSafeAny[]
+  } = {
+    submitting: false,
+    rows: []
+  };
+
+  formGroup: FormGroup = new FormGroup({});
+
+  constructor(
+    private modalRef: NzModalRef,
+    private modalService: NzModalService,
+    private synchronizersService: SynchronizersService,
+    private viewContainerRef: ViewContainerRef,
+    private fb: FormBuilder,
+    private msg: NzMessageService,
+    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
+    private cdr: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+   this.fetch()
+  }
+  fetch(){
+    this.synchronizersService.getMapping(`${this.jobId}`).subscribe(res => {
+      this.form.rows = res.data
+      this.cdr.detectChanges();
+    });
+  }
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+    this.modalRef.destroy({ refresh: false });
+  }
+
+  onDelete(e: MouseEvent, deleteId: String): void {
+    e.preventDefault();
+    this.synchronizersService.deleteMapping(deleteId).subscribe(res => {
+      if (res.code == 0) {
+        this.msg.success(this.i18n.fanyi('mxk.alert.delete.success'));
+        this.fetch();
+      } else {
+        this.msg.error(this.i18n.fanyi('mxk.alert.delete.error'));
+      }
+      this.cdr.detectChanges();
+    });
+  }
+  onEdit(e: MouseEvent, id: String): void {
+    e.preventDefault();
+    const modal = this.modalService.create({
+      nzContent: SynchronizerConfigFieldEditComponent,
+      nzViewContainerRef: this.viewContainerRef,
+      nzComponentParams: {
+        isEdit: true,
+        id: id,
+        jobId: this.jobId,
+      },
+      nzWidth: 1200,
+      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+    });
+    // Return a result when closed
+    modal.afterClose.subscribe(result => {
+      if (result.refresh) {
+        this.fetch();
+      }
+    });
+  }
+  onAdd(e: MouseEvent): void {
+    e.preventDefault();
+    const modal = this.modalService.create({
+      nzContent: SynchronizerConfigFieldEditComponent,
+      nzViewContainerRef: this.viewContainerRef,
+      nzComponentParams: {
+        isEdit: false,
+        jobId: this.jobId,
+      },
+      nzWidth: 1200,
+      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+    });
+    // Return a result when closed
+    modal.afterClose.subscribe(result => {
+      if (result.refresh) {
+        this.fetch();
+      }
+    });
+  }
+
+  onSubmit(e: MouseEvent): void {
+    e.preventDefault();
+    this.form.submitting = true;
+
+   /* (this.isEdit ? this.synchronizersService.update(this.form.model) : this.synchronizersService.add(this.form.model)).subscribe(res => {
+      if (res.code == 0) {
+        this.msg.success(this.i18n.fanyi(this.isEdit ? 'mxk.alert.update.success' : 'mxk.alert.add.success'));
+      } else {
+        this.msg.error(this.i18n.fanyi(this.isEdit ? 'mxk.alert.update.error' : 'mxk.alert.add.error'));
+      }
+      this.form.submitting = false;
+      this.modalRef.destroy({ refresh: true });
+      this.cdr.detectChanges();
+    });*/
+  }
+}

+ 5 - 1
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizers.component.html

@@ -43,7 +43,7 @@
       <th nzAlign="center">{{ 'mxk.synchronizers.name' | i18n }}</th>
       <th nzAlign="center">{{ 'mxk.synchronizers.scheduler' | i18n }}</th>
       <th nzAlign="center">{{ 'mxk.text.status' | i18n }}</th>
-      <th nzAlign="center" class="table_cell_action_3">{{ 'mxk.text.action' | i18n }}</th>
+      <th nzAlign="center" class="table_cell_action_5">{{ 'mxk.text.action' | i18n }}</th>
     </tr>
     </thead>
     <tbody>
@@ -62,10 +62,14 @@
         <div nz-col>
           <button nz-button type="button" (click)="onSynchr($event, data.id)">{{ 'mxk.text.synchr'
             | i18n }}</button>
+          <button nz-button type="button" (click)="onConfigFeild($event, data.id)">{{ 'mxk.text.mapping'
+            | i18n }}</button>
           <button nz-button type="button" (click)="onEdit($event, data.id)">{{ 'mxk.text.edit' |
             i18n }}</button>
           <button nz-button type="button" (click)="onDelete($event, data.id)" nzDanger>{{ 'mxk.text.delete' | i18n
             }}</button>
+
+
         </div>
       </td>
     </tr>

+ 23 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/config/synchronizers/synchronizers.component.ts

@@ -27,6 +27,7 @@ import { NzTableQueryParams } from 'ng-zorro-antd/table';
 import { SynchronizersService } from '../../../service/synchronizers.service';
 import { set2String } from '../../../shared/index';
 import { SynchronizerEditerComponent } from './synchronizer-editer/synchronizer-editer.component';
+import { SynchronizerConfigFieldComponent } from './synchronizer-config-field/synchronizer-config-field.component';
 @Component({
   selector: 'app-synchronizers',
   templateUrl: './synchronizers.component.html',
@@ -150,6 +151,7 @@ export class SynchronizersComponent implements OnInit {
     });
   }
 
+
   onEdit(e: MouseEvent, editId: String): void {
     e.preventDefault();
     const modal = this.modalService.create({
@@ -159,6 +161,26 @@ export class SynchronizersComponent implements OnInit {
         isEdit: true,
         id: editId
       },
+
+      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+    });
+    // Return a result when closed
+    modal.afterClose.subscribe(result => {
+      if (result.refresh) {
+        this.fetch();
+      }
+    });
+  }
+  onConfigFeild(e: MouseEvent, jobId: String): void {
+    e.preventDefault();
+    const modal = this.modalService.create({
+      nzContent: SynchronizerConfigFieldComponent,
+      nzViewContainerRef: this.viewContainerRef,
+      nzComponentParams: {
+        isEdit: true,
+        jobId: jobId
+      },
+      nzWidth: 1200,
       nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
     });
     // Return a result when closed
@@ -169,6 +191,7 @@ export class SynchronizersComponent implements OnInit {
     });
   }
 
+
   onDelete(e: MouseEvent, deleteId: String): void {
     e.preventDefault();
     this.synchronizersService.delete(deleteId).subscribe(res => {

+ 21 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/synchronizers.service.ts

@@ -22,6 +22,7 @@ import { Observable } from 'rxjs';
 import { Message } from '../entity/Message';
 import { Synchronizers } from '../entity/Synchronizers';
 import { BaseService } from './base.service';
+import {JobConfigFeild} from "../entity/JobConfigFeild";
 
 @Injectable({
   providedIn: 'root'
@@ -34,4 +35,24 @@ export class SynchronizersService extends BaseService<Synchronizers> {
   synchr(synchrId: String): Observable<Message<Synchronizers>> {
     return this.http.get<Message<Synchronizers>>(`${`${this.server.urls.base}/synchr`}?id=${synchrId}`);
   }
+  getMapping(synchrId: String): Observable<Message<Synchronizers>> {
+    return this.http.get<Message<JobConfigFeild>>(`${`${this.server.urls.base}/mapping-list`}/${synchrId}`);
+  }
+
+  getField(id: String): Observable<Message<Synchronizers>> {
+    return this.http.get<Message<JobConfigFeild>>(`${`${this.server.urls.base}/mapping-get`}/${id}`);
+  }
+
+  mappingAdd(body: any): Observable<Message<JobConfigFeild>> {
+    return this.http.post<Message<JobConfigFeild>>(`${this.server.urls.base}/mapping-add`, body);
+  }
+
+  mappingUpdate(body: any): Observable<Message<JobConfigFeild>> {
+    return this.http.put<Message<JobConfigFeild>>(`${this.server.urls.base}/mapping-update`, body);
+  }
+
+  deleteMapping(id: String): Observable<Message<JobConfigFeild>> {
+    return this.http.get<Message<JobConfigFeild>>(`${`${this.server.urls.base}/mapping-delete`}/${id}`);
+  }
+
 }

+ 4 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/theme/layout-default/style/_layout.less

@@ -119,6 +119,10 @@ body {
   margin-bottom: 2px !important;
 }
 
+.table_cell_action_5 {
+  width: 360px;
+}
+
 .table_cell_action_3 {
   width: 240px;
 }

+ 8 - 3
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json

@@ -720,7 +720,7 @@
 				"success":"Operate Success!",
 				"error":"Operate Error!"
 			}
-			
+
 		},
 		"text": {
 			"action": "Action",
@@ -935,5 +935,10 @@
 	"validation.title.required": "Please enter a title",
 	"validation.date.required": "Please select the start and end date",
 	"validation.goal.required": "Please enter a description of the goal",
-	"validation.standard.required": "Please enter a metric"
-}
+	"validation.standard.required": "Please enter a metric",
+  "mxk.text.mapping": "Feild Mapping",
+  "mxk.job.mapping.targetField": "TargetField",
+  "mxk.job.mapping.sourceField": "SourceField",
+  "mxk.job.mapping.objectType": "ObjectType",
+  "mxk.job.mapping.description": "Description"
+}

+ 9 - 2
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json

@@ -927,5 +927,12 @@
 	"validation.title.required": "请输入标题",
 	"validation.date.required": "请选择起止日期",
 	"validation.goal.required": "请输入目标描述",
-	"validation.standard.required": "请输入衡量标准"
-}
+	"validation.standard.required": "请输入衡量标准",
+  "mxk.text.mapping": "属性映射",
+  "mxk.job.mapping.targetField": "目标字段",
+  "mxk.job.mapping.sourceField": "来源字段",
+  "mxk.job.mapping.objectType": "映射类型",
+  "mxk.job.mapping.description": "规则说明"
+
+
+}

+ 7 - 2
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-TW.json

@@ -928,5 +928,10 @@
 	"validation.title.required": "請輸入標題",
 	"validation.date.required": "請選擇起止日期",
 	"validation.goal.required": "請輸入目標描述",
-	"validation.standard.required": "請輸入衡量標準"
-}
+	"validation.standard.required": "請輸入衡量標準",
+  "mxk.text.mapping": "属性映射",
+  "mxk.job.mapping.targetField": "目标字段",
+  "mxk.job.mapping.sourceField": "来源字段",
+  "mxk.job.mapping.objectType": "映射类型",
+  "mxk.job.mapping.description": "规则说明"
+}

+ 3 - 3
maxkey-webs/maxkey-web-maxkey/src/main/resources/application-maxkey.properties

@@ -103,9 +103,9 @@ maxkey.notices.visible                          =false
 spring.datasource.type                          =com.alibaba.druid.pool.DruidDataSource
 #mysql
 spring.datasource.driver-class-name             =com.mysql.cj.jdbc.Driver
-spring.datasource.username                      =${DATABASE_USER:root}
-spring.datasource.password                      =${DATABASE_PWD:maxkey}
-spring.datasource.url                           =jdbc:mysql://${DATABASE_HOST:localhost}:${DATABASE_PORT:3306}/${DATABASE_NAME:maxkey}?autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
+spring.datasource.username                      =root
+spring.datasource.password                      =root
+spring.datasource.url                           =jdbc:mysql://127.0.0.1:3306/maxkey?useSSL=false&serverTimezone=UTC
 #highgo
 #spring.datasource.driver-class-name=com.highgo.jdbc.Driver
 #spring.datasource.username=highgo

+ 10 - 0
maxkey-webs/maxkey-web-mgt/src/main/java/org/dromara/maxkey/autoconfigure/MaxKeyMgtConfig.java

@@ -25,6 +25,8 @@ import org.dromara.maxkey.persistence.repository.LoginHistoryRepository;
 import org.dromara.maxkey.persistence.repository.LoginRepository;
 import org.dromara.maxkey.persistence.repository.PasswordPolicyValidator;
 import org.dromara.maxkey.persistence.service.UserInfoService;
+import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.synchronizer.ldap.LdapSynchronizerService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -69,4 +71,12 @@ public class MaxKeyMgtConfig  {
         return tfaOtpAuthn;
     }
 
+	/*@Bean
+	public ISynchronizerService ldapSynchronizerService() {
+		LdapSynchronizerService ldapSynchronizerService = new LdapSynchronizerService();
+		ldapSynchronizerService.setId("LDAP_11122");
+		ldapSynchronizerService.syncOrg();
+		return ldapSynchronizerService;
+	}*/
+
 }

+ 55 - 4
maxkey-webs/maxkey-web-mgt/src/main/java/org/dromara/maxkey/web/config/contorller/SynchronizersController.java

@@ -19,12 +19,10 @@ package org.dromara.maxkey.web.config.contorller;
 
 import org.dromara.maxkey.authn.annotation.CurrentUser;
 import org.dromara.maxkey.crypto.password.PasswordReciprocal;
-import org.dromara.maxkey.entity.Connectors;
-import org.dromara.maxkey.entity.Message;
-import org.dromara.maxkey.entity.Synchronizers;
-import org.dromara.maxkey.entity.UserInfo;
+import org.dromara.maxkey.entity.*;
 import org.dromara.maxkey.persistence.service.SynchronizersService;
 import org.dromara.maxkey.synchronizer.ISynchronizerService;
+import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
 import org.dromara.maxkey.util.StringUtils;
 import org.dromara.maxkey.web.WebContext;
 import org.slf4j.Logger;
@@ -35,6 +33,7 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Date;
 import java.util.List;
 
 @Controller
@@ -45,6 +44,9 @@ public class SynchronizersController {
     @Autowired
     SynchronizersService synchronizersService;
 
+    @Autowired
+    SyncJobConfigFieldService syncJobConfigFieldService;
+
     @RequestMapping(value = {"/fetch"}, produces = {MediaType.APPLICATION_JSON_VALUE})
     @ResponseBody
     public ResponseEntity<?> fetch(Synchronizers synchronizers, @CurrentUser UserInfo currentUser) {
@@ -127,4 +129,53 @@ public class SynchronizersController {
         return new Message<Synchronizers>(Message.SUCCESS).buildResponse();
     }
 
+
+    @RequestMapping(value = {"/mapping-list/{jobId}"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+    @ResponseBody
+    public ResponseEntity<?> mapping(@PathVariable Long jobId) {
+        logger.debug("mapping {}", jobId);
+        List<SyncJobConfigField> syncJobConfigFields = syncJobConfigFieldService.findByJobId(jobId);
+        return new Message<>(syncJobConfigFields).buildResponse();
+    }
+
+    @RequestMapping(value = {"/mapping-get/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+    @ResponseBody
+    public ResponseEntity<?> mappingGet(@PathVariable Long id) {
+        logger.debug("mapping get {}", id);
+        SyncJobConfigField syncJobConfigFields = syncJobConfigFieldService.get(String.valueOf(id));
+        return new Message<>(syncJobConfigFields).buildResponse();
+    }
+
+    @RequestMapping(value = {"/mapping-delete/{id}"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+    @ResponseBody
+    public ResponseEntity<?> mappingDelete(@PathVariable Long id) {
+        logger.debug("mappingDelete {}", id);
+        syncJobConfigFieldService.deleteFieldMapById(id);
+        return new Message<SyncJobConfigField>(Message.SUCCESS).buildResponse();
+    }
+
+    @ResponseBody
+    @PostMapping(value = {"/mapping-add"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+    public ResponseEntity<?> mappingadd(@RequestBody SyncJobConfigField syncJobConfigField, @CurrentUser UserInfo currentUser) {
+        logger.debug("-mapping add  : {}", syncJobConfigField);
+        syncJobConfigField.setCreateTime(new Date());
+        if (syncJobConfigFieldService.insert(syncJobConfigField)) {
+            return new Message<Synchronizers>(Message.SUCCESS).buildResponse();
+        } else {
+            return new Message<Synchronizers>(Message.FAIL).buildResponse();
+        }
+    }
+
+    @ResponseBody
+    @PutMapping(value = {"/mapping-update"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+    public ResponseEntity<?> mappingupdate(@RequestBody SyncJobConfigField syncJobConfigField, @CurrentUser UserInfo currentUser) {
+        logger.debug("-mapping update  : {}", syncJobConfigField);
+        syncJobConfigField.setUpdateTime(new Date());
+        if (syncJobConfigFieldService.update(syncJobConfigField)) {
+            return new Message<Synchronizers>(Message.SUCCESS).buildResponse();
+        } else {
+            return new Message<Synchronizers>(Message.FAIL).buildResponse();
+        }
+    }
+
 }

+ 3 - 3
maxkey-webs/maxkey-web-mgt/src/main/resources/application-maxkey-mgt.properties

@@ -76,9 +76,9 @@ maxkey.login.jwt.issuer                         =${LOGIN_JWT_ISSUER:${maxkey.ser
 spring.datasource.type                          =com.alibaba.druid.pool.DruidDataSource
 #mysql
 spring.datasource.driver-class-name             =com.mysql.cj.jdbc.Driver
-spring.datasource.username                      =${DATABASE_USER:root}
-spring.datasource.password                      =${DATABASE_PWD:maxkey}
-spring.datasource.url                           =jdbc:mysql://${DATABASE_HOST:localhost}:${DATABASE_PORT:3306}/${DATABASE_NAME:maxkey}?autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
+spring.datasource.username                      =root
+spring.datasource.password                      =root
+spring.datasource.url                           =jdbc:mysql://localhost/maxkey?autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 #highgo
 #spring.datasource.driver-class-name=com.highgo.jdbc.Driver
 #spring.datasource.username=highgo

Some files were not shown because too many files changed in this diff