概述
本文档记录了 JEECG 系统与 Maxkey 系统间组织与用户数据的同步方案,以及集成 LDAP 域账号认证的登录功能改造。系统通过定时任务实现从 Maxkey 数据库同步组织架构和用户信息至 JEECG 本地数据库,并支持用户通过域账号进行身份验证。
代码目录结构说明
jeecg-system-biz/src/main/java/org/jeecg/modules/system/
├── entity/
│ ├── MxkOrganizations.java
│ └── MxkUserinfo.java
├── job/
│ ├── MaxkeyDepartmentJob.java
│ └── MaxkeyUserJob.java
├── mapper/
│ ├── MxkOrganizationsMapper.java
│ └── MxkUserinfoMapper.java
└── service/
├── impl/
│ ├── MxkOrganizationsServiceImpl.java
│ └── MxkUserinfoServiceImpl.java
├── IMxkOrganizationsService.java
└── IMxkUserinfoService.java效果图

一、同步用户
功能说明
该任务定时从 Maxkey 系统拉取用户数据,同步至 JEECG 系统的 sys_user 及相关附属表(部门关联、租户绑定、职务字典等),实现用户信息的统一维护。
核心逻辑
查询 Maxkey 中所有非
admin用户。遍历用户列表,逐条处理:
基础信息映射(用户名、姓名、邮箱、手机号等)。
关联用户与部门。
处理用户职务信息,并同步至职务字典。
绑定用户至指定租户(ID: 1002)。
同步完成后,更新系统职务字典项。
package org.jeecg.modules.system.job;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.SysDictItemMapper;
import org.jeecg.modules.system.mapper.SysPositionMapper;
import org.jeecg.modules.system.mapper.SysUserDepartMapper;
import org.jeecg.modules.system.mapper.SysUserTenantMapper;
import org.jeecg.modules.system.service.IMxkUserinfoService;
import org.jeecg.modules.system.service.ISysDictItemService;
import org.jeecg.modules.system.service.ISysDictService;
import org.jeecg.modules.system.service.ISysUserService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @Description: Maxkey用户同步任务
*
* @author: liaogui
* @date: 2026年01月14日
*/
@Slf4j
public class MaxkeyUserJob implements Job {
@Autowired
private IMxkUserinfoService mxkUserinfoService;
@Autowired
private ISysUserService sysUserService;
@Autowired
private SysUserDepartMapper sysUserDepartMapper;
@Autowired
private SysPositionMapper sysPositionMapper;
@Autowired
private ISysDictItemService sysDictItemService;
@Autowired
private SysDictItemMapper sysDictItemMapper;
@Autowired
private SysUserTenantMapper sysUserTenantMapper;
private final static Integer TENANT_ID = 1002;
// 默认:123456
private final static String PASSWORD = "cb362cfeefbf3d8d";
private final static String SALT = "RCGTeGiH";
private final static String ADMIN = "admin";
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("--------同步Maxkey用户信息 start--------");
List<MxkUserinfo> mxkUserinfos = mxkUserinfoService.getBaseMapper()
.selectList(new LambdaQueryWrapper<MxkUserinfo>()
.notIn(MxkUserinfo::getUsername, List.of(ADMIN))
);
AtomicInteger successCount = new AtomicInteger();
AtomicInteger failCount = new AtomicInteger();
mxkUserinfos.forEach(mxkUserinfo -> {
log.info("==============当前用户信息: mxkUserinfo{} ==============", mxkUserinfo);
if (StringUtils.isNotBlank(mxkUserinfo.getNickname())) {
try {
SysUser sysUser = new SysUser();
sysUser.setUsername(mxkUserinfo.getUsername());
sysUser.setRealname(mxkUserinfo.getNickname());
sysUser.setPassword(PASSWORD);
sysUser.setSalt(SALT);
sysUser.setEmail(mxkUserinfo.getEmail());
sysUser.setPhone(mxkUserinfo.getMobile());
sysUser.setLoginTenantId(TENANT_ID);
sysUser.setOrgCode(mxkUserinfo.getDepartmentid());
sysUser.setBelongDepIds(mxkUserinfo.getDepartmentid());
sysUser.setStatus(mxkUserinfo.getUserstate().equals("RESIDENT") ? 1 : 3 );
sysUser.setDelFlag(0);
sysUser.setActivitiSync(1);
sysUser.setWorkNo(mxkUserinfo.getEmployeenumber());
sysUser.setUserIdentity(1);
sysUser.setSort(1000);
sysUser.setCreateBy(ADMIN);
sysUser.setSex(mxkUserinfo.getGender() == 2 ? 1 : 2);
SysUser one = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, mxkUserinfo.getUsername()));
if (one != null ){
sysUser.setId(one.getId());
sysUser.setUpdateBy(ADMIN);
}
try {
if (StringUtils.isNotBlank(mxkUserinfo.getDepartmentid())) {
// 先删除原来的关联关系
sysUserDepartMapper.delete(new LambdaQueryWrapper<SysUserDepart>().eq(SysUserDepart::getUserId, sysUser.getId()));
sysUserDepartMapper.insert(new SysUserDepart(sysUser.getId(), mxkUserinfo.getDepartmentid()));
}
} catch (Exception e) {
log.error("当前已存在用户与部门的关联关系");
}
// 更新职务信息表
if (StringUtils.isNotBlank(mxkUserinfo.getJobtitle())) {
SysPosition sysPosition = sysPositionMapper.selectOne(
new LambdaQueryWrapper<SysPosition>()
.eq(SysPosition::getName, mxkUserinfo.getJobtitle()));
if (sysPosition == null) {
sysPosition = new SysPosition();
sysPosition.setName(mxkUserinfo.getJobtitle());
Long count = sysPositionMapper.selectCount(new LambdaQueryWrapper<>());
sysPosition.setPostLevel(count.intValue() + 1);
sysPositionMapper.insertOrUpdate(sysPosition);
}
sysUser.setPositionType(String.valueOf(sysPosition.getPostLevel()));
}
// 更新用户对应租户ID
Long tenantCount = sysUserTenantMapper.selectCount(new LambdaQueryWrapper<SysUserTenant>()
.eq(SysUserTenant::getUserId, sysUser.getId()));
if (tenantCount == 0L) {
// 绑定租户
SysUserTenant sysUserTenant = new SysUserTenant();
sysUserTenant.setUserId(sysUser.getId());
sysUserTenant.setTenantId(TENANT_ID);
sysUserTenant.setStatus("1");
sysUserTenant.setCreateBy(ADMIN);
sysUserTenantMapper.insert(sysUserTenant);
}
sysUserService.saveOrUpdate(sysUser);
successCount.getAndIncrement();
}catch (Exception e) {
log.error("同步用户信息失败:{}", e.getMessage());
failCount.getAndIncrement();
}
} else {
log.error("用户信息缺失:{}", mxkUserinfo);
}
});
// 更新用户职务字典项
// 先删除用户职务字典项
List<SysDictItem> sysDictItems = sysDictItemService.selectItemsByMainId("1964944899916697602");
List<String> ItemIds = sysDictItems.stream()
.map(sysDictItem -> sysDictItem.getId()).collect(Collectors.toList());
sysDictItemMapper.deleteByIds(ItemIds);
List<SysPosition> sysPositions = sysPositionMapper.selectList(new LambdaQueryWrapper<>());
List<SysDictItem> items = sysPositions.stream()
.map(sysPosition -> {
SysDictItem sysDictItem = new SysDictItem();
sysDictItem.setDictId("1964944899916697602");
sysDictItem.setItemText(sysPosition.getName());
sysDictItem.setItemValue(String.valueOf(sysPosition.getPostLevel()));
sysDictItem.setDescription(sysPosition.getName());
sysDictItem.setSortOrder(sysPosition.getPostLevel());
return sysDictItem;
})
.collect(Collectors.toList());
sysDictItemMapper.insert(items);
log.info("同步Maxkey用户信息成功:{}条,失败:{}条", successCount, failCount);
}
}/
二、同步组织
功能说明
定时同步 Maxkey 的组织架构至 JEECG 的部门表(sys_depart),保持两边的组织树结构一致。
核心逻辑
查询 Maxkey 所有组织数据。
转换为 JEECG 部门对象,并设置父子关系(根部门 ID 为 "1" 的不设父级)。
批量保存或更新至
sys_depart表。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.system.entity.MxkOrganizations;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.service.IMxkOrganizationsService;
import org.jeecg.modules.system.service.ISysDepartService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: Maxkey部门同步任务
*
* @author: liaogui
* @date: 2026年01月14日
*/
@Slf4j
public class MaxkeyDepartmentJob implements Job {
@Autowired
private IMxkOrganizationsService mxkOrganizationsService;
@Autowired
private ISysDepartService sysDepartService;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
List<MxkOrganizations> list = mxkOrganizationsService.list(new LambdaQueryWrapper<MxkOrganizations>());
List<SysDepart> sysDeparts = new ArrayList<>();
list.forEach(mxkOrganizations -> {
SysDepart depart = new SysDepart();
depart.setId(mxkOrganizations.getId());
if (!mxkOrganizations.getId().equals("1")) {
depart.setParentId(mxkOrganizations.getParentid());
}
depart.setDepartName(mxkOrganizations.getFullname());
depart.setOrgCode(mxkOrganizations.getOrgcode());
depart.setDelFlag("0");
sysDeparts.add(depart);
});
sysDepartService.saveOrUpdateBatch(sysDeparts);
log.info("Maxkey部门同步任务执行成功");
}
}三、接入LDAP域账号登录
功能说明
在原有登录逻辑基础上,增加对 LDAP/AD 域账号的认证支持,实现域账号与本地账号的统一登录入口。
实现要点
工具类
LDAPUtil提供
checkLdap()方法,用于验证域账号密码。封装 LDAP 连接、认证及异常处理。
登录接口改造
在用户登录时,先尝试本地数据库验证。
若本地验证失败(或根据策略),调用
LDAPUtil.checkLdap()进行域认证。根据返回结果判断是否登录成功。
配置项
LDAP_URL:域控制器地址。USER_EMAIL:域名后缀,用于构建用户 Principal。
package org.jeecg.common.system.util;
import lombok.extern.slf4j.Slf4j;
import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.util.Hashtable;
/**
* @Description: 域账号验证工具
*/
@Slf4j
public class LDAPUtil {
private static final String LDAP_URL = "ldap://test.com:389";//域名
private static final String USER_EMAIL = "@test.com";
/**
* 创建LdapContext进行身份验证
*
* @param username 用户名
* @param password 密码
* @return 验证成功的LdapContext对象
* @throws NamingException 如果身份验证过程中发生命名相关异常
*/
public static LdapContext getLdapContext(String username, String password) throws NamingException {
//创建连接
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, LDAP_URL);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, username + USER_EMAIL);
env.put(Context.SECURITY_CREDENTIALS, password);
return new InitialLdapContext(env, null);
}
/**
* 连接并验证LDAP服务
*
* @param username 用户名
* @param password 密码
* @return 错误消息,如果为空则表示验证成功
*/
public static String checkLdap(String username, String password) {
LdapContext lct = null;
try {
lct = getLdapContext(username, password);
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (CommunicationException e) {
log.error("AD域连接失败!", e);
return "AD域连接失败!";
} catch (NamingException e) {
log.error("身份验证未知异常!", e);
return "身份验证未知异常!";
} catch (Exception e) {
log.error("未知异常!", e);
return "未知异常!";
} finally {
if (null != lct) {
try {
lct.close();
} catch (Exception e) {
log.error("关闭LdapContext异常!", e);
}
}
}
return "";
}
}如何使用?
String ck = LDAPUtil.checkLdap(username, password);
boolean ldap = StringUtils.isNotBlank(ck);完整代码
写在结尾
“如果你遇到过类似问题或有不同解法,欢迎在评论区交流。关注 里奥圭,持续获取Java实践笔记。”