文章

用户和组织数据同步方法

用户和组织数据同步方法

框架已实现用户和组织数据同步的通用方法。

支持组织全量、用户全量和增量数据同步功能。

1.表设计

用户和组织数据会先记录在表SECURITY_DATA_SYNC_LOG中。处理过程中,会更新STATUS字段状态。可以在此表中查看每次同步的结果和异常信息。

SECURITY_DATA_SYNC_LOG表设计如下:

28-1

demo示例代码及资源文件所在位置:

28-2

2.组织同步

2.1.继承OrganizationSyncBase类

在项目中新建一个实现类,继承OrganizationSyncBase类,并实现基类的setRootParentId、toString和changeDataToEntityList方法。

demo示例代码中新建了SyncOrg实现类,继承了OrganizationSyncBase类。注意:新建的SyncOrg类上需要加上@Component注解。

SyncOrg实现类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Component
public class SyncOrg extends OrganizationSyncBase {
  @Autowired private ObjectMapper jsonMapper;
 
  @Override
  public String setRootParentId() {
    return "0";
  }
 
  @SneakyThrows
  @Override
  public String toString(Object data) {
    return (String) data;
  }
 
  @SneakyThrows
  @Override
  public List<Organization> changeDataToEntityList(Object data) {
    String content = (String) data;
    SyncModel syncModel = jsonMapper.readValue(content, SyncModel.class);
    List<SyncOrganizationData> syncOrganizationDataList =
        jsonMapper.readValue(
            syncModel.getDatas(), new TypeReference<List<SyncOrganizationData>>() {});
 
    List<Organization> list = new ArrayList<>();
    syncOrganizationDataList.forEach(
        syncOrganizationData -> {
          Organization organization = new Organization();
          organization.setSourceId(syncOrganizationData.getBusinessId());
          organization.setType("unit");
          organization.setParentType("unit");
          organization.setName(syncOrganizationData.getName());
          organization.setParentId(syncOrganizationData.getParentBusinessId());
          organization.setSort(syncOrganizationData.getSorting());
          organization.setUseAlias("no");
          organization.setRank("sbj");
          organization.setStatus("active");
          organization.setSourceUpdateTime(syncOrganizationData.getTimestampSave());
 
          // 删除标识deleted为1时,表示删除。需设置同步状态为delete
          if (syncOrganizationData.getDeleted() == 1) {
            organization.setSyncStatus(deleteFlag);
          }
          list.add(organization);
        });
 
    return list;
  }
}
  • setRootParentId方法
    • 方法描述:方法返回待同步组织的根节点的parentId。
    • 方法作用:此方法用于将同步过来的组织树挂在本系统组织根节点root下,即后续处理会将实际同步过来的根节点的parentId设置为root。
  • toString方法
    • 方法描述:将待同步组织数据转换成String类型数据。
    • 方法作用:方便后续将数据记录在数据同步表SECURITY_DATA_SYNC_LOG的DATA字段中。
  • changeDataToEntityList方法
    • 方法描述:将数据同步表中String类型的数据待同步组织数据转换成数据库组织实体Organization对象集合。
    • 方法作用:方便后续定时任务进行数据处理。

注意:

  • changeDataToEntityList方法转换数据时,如果原始数据有删除标记,需要额外将组织实体的同步状态设置为删除。设置方法如下:
1
organization.setSyncStatus(deleteFlag);

其中deleteFlag为框架内置变量,可以直接使用。此处建议使用deleteFlag传参,避免自己传递字符串时输入错误。

  • 在此处设置删除标记,目的在于后续处理会根据删除标记进行数据删除,而不会删除数据库额外添加的组织。

待同步数据转换成数据库实体Organization对象时,需要处理的字段见下表:

属性 描述 是否必须 备注
SourceId 源数据Id。源数据的唯一标识  
Type 组织类型。对应字典表groupCode为orgType的数据。  
Name 组织名称  
ParentId 组织父节点Id。 此处应传入源数据的原始ParentId。
框架后续会自动将源数据的ParentId处理成数据库对应的ParentId。
Status 组织状态。对应字典表groupCode为status的数据。  
ParentType 组织父节点类型。对应字典表groupCode为orgType的数据。 如果源组织数据在同一张表,SourceId不会重复时,无需设置。如果源组织数据在不同表,且SourceId可能重复,则此项必须设置。
SourceUpdateTime 源数据更新时间。 如果源数据有更新时间,可将更新时间赋值给此字段。无更新时间,无需赋值。
1. SourceUpdateTime字段值存在时,框架会根据SourceId查询对应数据,再根据数据的更新时间是否变化判断是否需要更新。若更新时间变化则更新,否则,不更新。
2. SourceUpdateTime字段值不存在时,框架根据SourceId查询对应数据,数据存在就更新。
Sort 排序 建议同步时赋值排序字段,避免组织展示杂乱。不赋值,不影响组织同步。
UseAlias 是否启用别名。对应字典表groupCode为useAlias的数据。  
Rank 行政级别。对应字典表groupCode为rank的数据。  
SyncStatus 同步标识字段。 只有数据需要删除时,需给此字段赋值删除标识:deleteFlag。新增,更新状态无需赋值。
(其中deleteFlag为框架定义字段,避免状态字符串书写错误,建议使用此字段。)
处理程序在碰到删除标识时,会自动按删除处理。

非必须字段和其他字段,可按实际需求自行调整添加。

2.2.调用syncDataMain方法保存待同步数据

通过2.1中的SyncOrg实现类调用基类syncDataMain方法,将获取到的组织待同步数据保存到数据同步表SECURITY_DATA_SYNC_LOG中。

demo示例代码中,从文件中读取了待同步组织数据,并通过2.1中的SyncOrg实现类调用基类syncDataMain方法,保存待同步组织数据。

DemoSyncController控制器中示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Autowired private SyncOrg syncOrg;
 
@Operation(description = "同步组织数据")
@RequestMapping(value = "/syncOrg", method = RequestMethod.GET)
public Boolean syncOrg() throws IOException {
  // 读取resources目录下sync_org.txt
  ClassPathResource classPathResource = new ClassPathResource("userOrgSync/sync_org.txt");
  InputStreamReader reader = new InputStreamReader(classPathResource.getInputStream());
  BufferedReader bf = new BufferedReader(reader);
  StringBuilder sb = new StringBuilder();
  String text = null;
  while ((text = bf.readLine()) != null) {
    sb.append(text);
  }
  String data = sb.toString();
  syncOrg.syncDataMain(data);
  return true;
}

其中主要方法如下:

syncOrg.syncDataMain(data);

该方法将数据保存到SECURITY_DATA_SYNC_LOG表中,并设置STATUS状态为wait待处理。后续再通过定时任务统一处理。

以上示例只是演示使用方法。实际项目可能需要定时读取数据或者接收推送来的数据,然后再调用syncDataMain方法保存待处理数据。请根据实际项目进行调整。

2.3.定时任务处理组织

通过定时任务同时处理组织和用户,请参考目录 4 中的内容。

3.用户同步

3.1.继承UserSyncBase类

在项目中新建一个实现类,继承UserSyncBase类,并实现基类的toString和changeDataToEntityList方法。

demo示例代码中新建了SyncUser实现类,继承了UserSyncBase类。注意:新建的SyncUser类上需要加上@Component注解。

SyncUser实现类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Component
public class SyncUser extends UserSyncBase {
  @Autowired private ObjectMapper jsonMapper;
 
  @SneakyThrows
  @Override
  public String toString(Object data) {
    return (String) data;
  }
 
  @SneakyThrows
  @Override
  public List<User> changeDataToEntityList(Object data) {
    String content = (String) data;
    SyncModel syncModel = jsonMapper.readValue(content, SyncModel.class);
    List<SyncUserData> syncUserList =
        jsonMapper.readValue(syncModel.getDatas(), new TypeReference<List<SyncUserData>>() {});
 
    List<User> userList = new ArrayList<>();
    syncUserList.forEach(
        syncUserData -> {
          User user = new User();
          user.setSourceId(syncUserData.getId());
          user.setRealName(syncUserData.getName());
          user.setCertificateType("idcard");
          user.setCertificateNo(syncUserData.getIdcarNumber());
          user.setPhone(syncUserData.getPhoneNumber());
          user.setGender(
              "男".equals(syncUserData.getSexName())
                  ? "male"
                  : "女".equals(syncUserData.getSexName()) ? "female" : "");
          user.setAccount(syncUserData.getIdcarNumber());
          user.setPassword("");
          user.setStatus("active");
          user.setSourceUpdateTime(syncUserData.getTimestampSave());
 
          // 需赋值用户的组织(需组织已同步。如不赋值,无法新增关联关系)
          user.setSourceOrgId(syncUserData.getOrganizationOfPersonnelRelationsId());
 
          // deleted为1时,表示删除。人员现状为1,表示在职。(删除的deleted=1,或者人员不在职PersonnelCurrentStatus!=1,设置删除标记。)
          if (syncUserData.getDeleted() == 1
              || !"1".equals(syncUserData.getPersonnelCurrentStatus())) {
            user.setSyncStatus(deleteFlag);
          }
          userList.add(user);
        });
 
    return userList;
  }
}
  • toString方法
    • 方法描述:将待同步用户数据转换成String类型数据。
    • 方法作用:方便后续将数据记录在数据同步表SECURITY_DATA_SYNC_LOG的DATA字段中。
  • changeDataToEntityList方法
    • 方法描述:将数据同步表中String类型的数据待同步用户数据转换成数据库用户实体User对象集合。
    • 方法作用:方便后续定时任务进行数据处理。

注意:

  • changeDataToEntityList方法转换数据时,如果原始数据有删除标记,需要额外将用户实体的同步状态设置为删除。设置方法如下:
1
user.setSyncStatus(deleteFlag);

其中deleteFlag为框架内置变量,可以直接使用。此处建议使用deleteFlag传参,避免自己传递字符串时输入错误。

  • 在此处设置删除标记,目的在于后续处理会根据删除标记进行数据删除,而不会删除数据库已有的用户。(场景:用户可能为增量,或者用户量过大分批推送)

待同步数据转换成数据库实体User对象时,需要处理的字段见下表:

属性 描述 是否必须 备注
SourceId 源数据Id。源数据的唯一标识  
RealName 姓名  
Account 登录账号  
Status 用户状态。对应字典表groupCode为status的数据。  
Password 登录密码 如果不设置密码,需要赋值空字符串。此字段在数据不能为null。
如需设置默认密码,可调用BCryptPasswordEncoder类的encode方法生成密码。示例如下:
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode(“User@6375”));
其中,字符串User@6375为实际密码,按实际需求更改,更改的密码应当符合密码要求,如下:
1. 包含大写字母、小写字母、数字、特殊英文字符,长度8-16位
2. 包含的数字不能连续
SourceUpdateTime 源数据更新时间。 如果源数据有更新时间,可将更新时间赋值给此字段。无更新时间,无需赋值。
SourceUpdateTime字段值存在时,框架会根据SourceId查询对应数据,再根据数据的更新时间是否变化判断是否需要更新。若更新时间变化则更新,否则,不更新。
SourceUpdateTime字段值不存在时,框架根据SourceId查询对应数据,数据存在就更新。
SourceOrgId 源数据组织Id。 源数据对应的原始组织Id。
如不赋值,不影响用户同步,但无法将用户关联到组织。
CertificateType 证件类型。idcard:身份证  
CertificateNo 证件号码  
Phone 电话号码  
Gender 性别。对应字典表groupCode为gender的数据。  
SyncStatus 同步标识字段。 只有数据需要删除时,需给此字段赋值删除标识:deleteFlag。新增,更新状态无需赋值。
(其中deleteFlag为框架定义字段,避免状态字符串书写错误,建议使用此字段。)
处理程序在碰到删除标识时,会自动按删除处理。

非必须字段和其他字段,可按实际需求自行调整添加。

3.2.调用syncDataMain方法保存待同步数据

通过3.1中的SyncUser实现类调用基类syncDataMain方法,将获取到的用户待同步数据保存到数据同步表SECURITY_DATA_SYNC_LOG中。

demo示例代码中,从文件中读取了待同步用户数据,并通过3.1中的SyncUser实现类调用基类syncDataMain方法,保存待同步用户数据。

DemoSyncController控制器中示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Autowired private SyncUser syncUser;
 
@Operation(description = "同步用户数据")
@RequestMapping(value = "/syncUser", method = RequestMethod.GET)
public Boolean syncUser() throws IOException {
  // 读取resources目录下sync_user.txt
  ClassPathResource classPathResource = new ClassPathResource("userOrgSync/sync_user.txt");
  InputStreamReader reader = new InputStreamReader(classPathResource.getInputStream());
  BufferedReader bf = new BufferedReader(reader);
  StringBuilder sb = new StringBuilder();
  String text = null;
  while ((text = bf.readLine()) != null) {
    sb.append(text);
  }
 
  String data = sb.toString();
 
  syncUser.syncDataMain(data);
 
  return true;
}

其中主要方法如下:

syncUser.syncDataMain(data);

该方法将数据保存到SECURITY_DATA_SYNC_LOG表中,并设置STATUS状态为wait待处理。后续再通过定时任务统一处理。

以上示例只是演示使用方法。实际项目可能需要定时读取数据或者接收推送来的数据,然后再调用syncDataMain方法保存待处理数据。请根据实际项目进行调整。

3.3.定时任务处理用户

通过定时任务同时处理组织和用户,请参考目录 4 中的内容。

4.定时任务处理组织和用户

目前仅支持通过定时任务进行数据同步。

4.1.项目启动类中增加配置

项目启动类中,需增加@EnableScheduling配置。

在demo示例代码DemoApplication启动类中增加了@EnableScheduling配置,如下:

1
2
3
4
5
6
7
/** 启用Schedule定时任务 */
@EnableScheduling
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

4.2.项目增加定时任务处理组织和用户

项目中增加定时任务,通过框架中的SyncBaseUtils类调用syncWaitData方法处理组织和用户。

在demo示例中,增加了ScheduleInit类,类中开启了定时任务,每5分钟执行一次。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@S@Slf4j
@Configuration
public class ScheduleInit {
  @Autowired SyncBaseUtils syncBaseUtils;
  @Autowired SyncOrg syncOrg;
  @Autowired SyncUser syncUser;
 
  /** 组织用户同步任务,每5分钟执行一次(0 0/5 * ? * *) */
  @Scheduled(cron = "0 0/5 * ? * *")
  public void start() {
    try {
      log.info("组织用户同步任务开始执行:");
      syncBaseUtils.syncWaitData(syncOrg, syncUser);
      log.info("组织用户同步任务执行完成!");
    } catch (Exception ex) {
      log.error("组织用户同步任务执行失败:" + ex.getMessage() + ex.getStackTrace());
    }
  }
}

syncBaseUtils.syncWaitData方法参数为组织和用户的实现类。

5.组织用户对接支持

框架已提供了组织、用户的数据获取接口以及自动同步代码。开发人员只用配置用户组织同步API地址和同步时间频率,不用写大量代码就能方便的对接单点登录系统自带的用户组织同步功能。

配置文件中增加如下配置:

1
2
3
4
5
6
7
8
9
#-------------------框架用户组织同步功能对接配置-----------------------------
#组织用户同步请求组织的url
base.security.sync.impl.request.orgUrl=
#组织用户同步请求用户的url
base.security.sync.impl.request.userUrl=
#定时通过url获取组织用户数据cron配置(-表示不开启同步。每30分钟执行一次表达式:0 0/30 * ? * *)
base.security.sync.impl.request.cron=
#定时同步组织用户数据cron配置(-表示不开启同步。每30分钟执行一次表达式:0 2/30 * ? * *)
base.security.sync.impl.process.cron=

其中请求组织和用户的url为单点登录系统提供用于对接的url:

  • 请求组织的url路径的固定部分为:/api/open/sync/listOrg

  • 请求用户的url路径的固定部分为:/api/open/sync/listUser

配置的url示例为:

  • http://localhost:9001/services/admin/api/open/sync/listOrg

其中localhost:9001应替换为单点登录的ip和端口。

/services/admin应替换为单点登录系统配置文件中配置的server.servlet.context-path

请求url返回的数据字段如下。

  • 请求组织的url返回的信息字段:
字段名称 字段描述 备注
字段名称 字段描述 备注
id 唯一标识  
name 组织名称  
type 组织类型:公司、子公司、单位、部门等  
parentId 父组织ID  
sort 排序编号  
  • 请求用户的url返回的信息字段:
字段名称 字段描述 备注
id 唯一标识  
account 账号名  
realName 实名  
orgId 用户组织  

base.security.sync.impl.request.cron和base.security.sync.impl.process.cron表达式配置,可根据实际情况进行配置。

建议:定时同步组织用户数据cron配置(base.security.sync.impl.process.cron)比获取组织用户数据cron配置(base.security.sync.impl.request.cron)晚几分钟(2-5分钟,根据实际接口执行时间自行判断)。

等几分钟数据获取完成后,开始执行同步任务,可提高同步效率。

本文由作者按照 CC BY 4.0 进行授权