同步用户数据

This commit is contained in:
luojiaquan
2026-09-29 16:08:27 +08:00
parent b363945e26
commit 61a7aaf096
10 changed files with 686 additions and 1 deletions
@@ -0,0 +1,66 @@
package com.bms.system.orgsync;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.bms.common.annotation.DataSource;
import com.bms.common.enums.DataSourceType;
import com.bms.system.orgsync.mapper.OracleOrgSyncMapper;
/**
* Oracle 组织/用户/角色读取器。
* 走 SLAVE(Oracle 只读账号)数据源;必须独立 Bean,
* 否则 @DataSource 切面在同类自调用时不生效。
*
* @author bms
*/
@Service
public class OracleOrgReader
{
@Autowired
private OracleOrgSyncMapper oracleOrgSyncMapper;
@DataSource(DataSourceType.SLAVE)
public List<Map<String, Object>> readAllDept()
{
try
{
PageHelper.clearPage();
return oracleOrgSyncMapper.selectAllTabDept();
}
finally
{
PageHelper.clearPage();
}
}
@DataSource(DataSourceType.SLAVE)
public List<Map<String, Object>> readAllEmployee()
{
try
{
PageHelper.clearPage();
return oracleOrgSyncMapper.selectAllTabEmployee();
}
finally
{
PageHelper.clearPage();
}
}
@DataSource(DataSourceType.SLAVE)
public List<Map<String, Object>> readAllRole()
{
try
{
PageHelper.clearPage();
return oracleOrgSyncMapper.selectAllSysRole();
}
finally
{
PageHelper.clearPage();
}
}
}
@@ -0,0 +1,350 @@
package com.bms.system.orgsync;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bms.common.core.domain.entity.SysDept;
import com.bms.common.core.domain.entity.SysRole;
import com.bms.common.core.domain.entity.SysUser;
/**
* 老系统组织架构(Oracle)-> BMS(MySQL)覆盖式迁移。
*
* 部门:TAB_DEPT 11 个部门统一挂到根节点“威狮国际”(dept_id=200)下,子部门 201 起顺序编号;
* 角色:T_SYS_ROLE 有效内部角色原样迁入,role_id 沿用 Oracle ID,不带菜单权限;
* 用户:TAB_EMPLOYEE 全量迁入,EMPLOYEE_CODE 作登录账号;user_id 取 Oracle USER_ID,
* USER_ID 为空时取 EMP_ID;默认密码 123456;不分配角色。
* 内置超级管理员 admin(user_id=1)/超级管理员角色(role_id=1)保留,仅将 admin 调到根部门。
*
* @author bms
*/
@Service
public class OrgSyncService
{
private static final Logger log = LoggerFactory.getLogger(OrgSyncService.class);
/** 根部门 ID(威狮国际) */
public static final long ROOT_DEPT_ID = 200L;
private static final long FIRST_CHILD_DEPT_ID = 201L;
private static final String ROOT_DEPT_NAME = "威狮国际";
/** 写库人标记 */
public static final String SYNC_MARKER = "oracle-sync";
/** 新用户默认密码(BCrypt,明文 123456) */
private static final String DEFAULT_PASSWORD =
"$2a$10$gAOzrNJaYLM2m85.9ykkLO0IPvQktxqee8RVpAu1e3.J0Ujiv4Z0G";
/** 老系统内置 admin 账号不迁移,避免覆盖 BMS 超级管理员 */
private static final String BUILTIN_ADMIN = "admin";
@Autowired
private OracleOrgReader oracleOrgReader;
@Autowired
private OrgSyncWriter orgSyncWriter;
/**
* 执行一次全量迁移
*/
public Map<String, Object> syncFromOracle()
{
long t0 = System.currentTimeMillis();
List<Map<String, Object>> deptRows = oracleOrgReader.readAllDept();
List<Map<String, Object>> roleRows = oracleOrgReader.readAllRole();
List<Map<String, Object>> empRows = oracleOrgReader.readAllEmployee();
// ---- 部门 ----
List<SysDept> depts = buildDepts(deptRows);
Map<String, Long> deptIdByCode = new HashMap<>();
for (SysDept d : depts)
{
if (d.getParentId() == ROOT_DEPT_ID)
{
deptIdByCode.put(d.getDeptName(), d.getDeptId());
}
}
// ---- 角色 ----
List<SysRole> roles = buildRoles(roleRows);
// ---- 用户 ----
List<SysUser> users = buildUsers(empRows, deptIdByCode);
orgSyncWriter.writeAll(depts, roles, users, ROOT_DEPT_ID);
long disabled = users.stream().filter(u -> "1".equals(u.getStatus())).count();
long cost = System.currentTimeMillis() - t0;
String summary = String.format(
"老系统组织同步完成:部门 %d 个(含根节点“%s”),角色 %d 个(不含权限),用户 %d 个(启用 %d/停用 %d,默认密码 123456),耗时 %dms",
depts.size(), ROOT_DEPT_NAME, roles.size(), users.size(), users.size() - disabled, disabled, cost);
log.info(summary);
Map<String, Object> result = new LinkedHashMap<>();
result.put("deptCount", depts.size());
result.put("roleCount", roles.size());
result.put("userCount", users.size());
result.put("enabledUserCount", users.size() - disabled);
result.put("disabledUserCount", disabled);
result.put("costMs", cost);
result.put("summary", summary);
return result;
}
private List<SysDept> buildDepts(List<Map<String, Object>> rows)
{
List<SysDept> list = new ArrayList<>();
SysDept root = newDept(ROOT_DEPT_ID, 0L, "0", ROOT_DEPT_NAME, 0);
list.add(root);
long id = FIRST_CHILD_DEPT_ID;
int order = 1;
Set<String> seen = new HashSet<>();
for (Map<String, Object> row : rows)
{
String code = clean(str(row, "DEPT_CODE"));
String name = clean(str(row, "DEPT_NAME"));
String deptName = name != null ? name : code;
if (code == null || !seen.add(code))
{
continue;
}
list.add(newDept(id++, ROOT_DEPT_ID, "0," + ROOT_DEPT_ID, deptName, order++));
}
return list;
}
private SysDept newDept(long id, long parentId, String ancestors, String name, int order)
{
SysDept d = new SysDept();
d.setDeptId(id);
d.setParentId(parentId);
d.setAncestors(ancestors);
d.setDeptName(name);
d.setOrderNum(order);
d.setStatus("0");
d.setDelFlag("0");
d.setCreateBy(SYNC_MARKER);
d.setCreateTime(new Date());
return d;
}
private List<SysRole> buildRoles(List<Map<String, Object>> rows)
{
List<SysRole> list = new ArrayList<>();
Set<Long> seenIds = new HashSet<>();
int sort = 1;
for (Map<String, Object> row : rows)
{
Long rid = lng(row, "ID");
String name = clean(str(row, "ROLE_NAME"));
if (rid == null || name == null || !seenIds.add(rid))
{
continue;
}
SysRole r = new SysRole();
r.setRoleId(rid);
r.setRoleName(truncate(name, 30));
r.setRoleKey("ORA_" + rid);
r.setRoleSort(sort++);
r.setDataScope("1");
r.setMenuCheckStrictly(true);
r.setDeptCheckStrictly(true);
r.setStatus("0");
r.setDelFlag("0");
r.setCreateBy(SYNC_MARKER);
r.setCreateTime(new Date());
r.setRemark(truncate(clean(str(row, "ROLE_DESC")), 500));
list.add(r);
}
return list;
}
private List<SysUser> buildUsers(List<Map<String, Object>> rows, Map<String, Long> deptIdByCode)
{
List<SysUser> list = new ArrayList<>();
Set<Long> seenIds = new HashSet<>();
Set<String> seenNames = new HashSet<>();
int skipped = 0;
for (Map<String, Object> row : rows)
{
String code = clean(str(row, "EMPLOYEE_CODE"));
String name = clean(str(row, "EMPLOYEE_NAME"));
if (code == null || name == null)
{
skipped++;
continue;
}
if (BUILTIN_ADMIN.equalsIgnoreCase(code))
{
// 保留 BMS 内置超级管理员,不被老系统 admin 覆盖
skipped++;
continue;
}
// ID:优先 USER_ID,为空取 EMP_ID
Long uid = lng(row, "USER_ID");
if (uid == null)
{
uid = lng(row, "EMP_ID");
}
if (uid == null || !seenIds.add(uid) || !seenNames.add(code))
{
log.warn("用户同步跳过:{}({}),ID 缺失或重复", code, name);
skipped++;
continue;
}
SysUser u = new SysUser();
u.setUserId(uid);
String deptCode = clean(str(row, "DEPT_CODE"));
Long deptId = deptCode == null ? null : deptIdByCode.get(deptCode);
u.setDeptId(deptId != null ? deptId : ROOT_DEPT_ID);
u.setUserName(code);
u.setNickName(truncate(name, 30));
u.setPassword(DEFAULT_PASSWORD);
u.setStatus("1".equals(clean(str(row, "BL_OPEN"))) ? "0" : "1");
u.setSex(mapSex(clean(str(row, "SEX"))));
u.setEmail(cleanEmail(str(row, "EMAIL")));
u.setPhonenumber(cleanPhone(str(row, "MOBILE_PHONE")));
u.setRemark(truncate(clean(str(row, "REMARK")), 500));
Object createDate = row.get("CREATE_DATE");
if (createDate instanceof Date)
{
u.setCreateTime((Date) createDate);
}
else
{
u.setCreateTime(new Date());
}
u.setCreateBy(SYNC_MARKER);
list.add(u);
}
if (skipped > 0)
{
log.warn("用户同步共跳过 {} 条(内置 admin / 编码或ID重复 / 关键字段为空)", skipped);
}
return list;
}
/** Oracle:1男 0女;BMS:0男 1女 2未知 */
private static String mapSex(String sex)
{
if ("1".equals(sex))
{
return "0";
}
if ("0".equals(sex))
{
return "1";
}
return "2";
}
/** 老系统手机号存在加密脏值,仅接收纯号码/短横线/加号格式且不超过 11 位 */
private static String cleanPhone(String v)
{
String s = clean(v);
if (s == null)
{
return null;
}
s = s.replace(" ", "");
if (s.length() > 11 || !s.matches("[0-9+\\-]{6,11}"))
{
return null;
}
return s;
}
private static String cleanEmail(String v)
{
String s = clean(v);
if (s == null || s.length() > 50 || !s.contains("@"))
{
return null;
}
return s;
}
private static String truncate(String v, int max)
{
if (v == null)
{
return null;
}
return v.length() > max ? v.substring(0, max) : v;
}
private static String clean(String v)
{
if (v == null)
{
return null;
}
String s = v.trim();
return s.isEmpty() ? null : s;
}
private static String str(Map<String, Object> row, String key)
{
Object v = getIgnoreCase(row, key);
return v == null ? null : String.valueOf(v);
}
/** Oracle NUMBER 经 JDBC 多为 BigDecimal */
private static Long lng(Map<String, Object> row, String key)
{
Object v = getIgnoreCase(row, key);
if (v == null)
{
return null;
}
if (v instanceof Number)
{
return ((Number) v).longValue();
}
String s = String.valueOf(v).trim();
if (s.isEmpty())
{
return null;
}
int dot = s.indexOf('.');
if (dot >= 0)
{
s = s.substring(0, dot);
}
try
{
return Long.parseLong(s);
}
catch (NumberFormatException e)
{
return null;
}
}
private static Object getIgnoreCase(Map<String, Object> row, String key)
{
if (row.containsKey(key))
{
return row.get(key);
}
for (Map.Entry<String, Object> e : row.entrySet())
{
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key))
{
return e.getValue();
}
}
return null;
}
}
@@ -0,0 +1,62 @@
package com.bms.system.orgsync;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.core.domain.entity.SysDept;
import com.bms.common.core.domain.entity.SysRole;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.system.orgsync.mapper.SysOrgSyncWriterMapper;
/**
* 组织迁移写库。独立 Bean:保证 @Transactional 经代理生效,且与 SLAVE 读连接隔离。
* 单个事务内覆盖式重建:除内置超级管理员(user_id=1 / role_id=1)外全部替换。
*
* @author bms
*/
@Service
public class OrgSyncWriter
{
/** 用户批量插入每批条数 */
private static final int USER_BATCH = 200;
@Autowired
private SysOrgSyncWriterMapper writerMapper;
@Transactional(rollbackFor = Exception.class)
public void writeAll(List<SysDept> depts, List<SysRole> roles, List<SysUser> users, Long adminDeptId)
{
// 1. 清关联(保留 admin <-> 超级管理员角色)
writerMapper.deleteAllRoleDept();
writerMapper.deleteRoleMenuKeepAdmin();
writerMapper.deleteUserRoleKeepAdmin();
writerMapper.deleteUserPostKeepAdmin();
// 2. 清用户/角色/部门
writerMapper.deleteUsersKeepAdmin();
writerMapper.deleteRolesKeepAdmin();
writerMapper.deleteAllDept();
// 3. 重建部门
for (SysDept d : depts)
{
writerMapper.insertDept(d);
}
// 4. 重建角色(不带任何菜单权限)
for (SysRole r : roles)
{
writerMapper.insertRole(r);
}
// 5. 重建用户(分批,避免单条 SQL 过长)
for (int i = 0; i < users.size(); i += USER_BATCH)
{
writerMapper.batchInsertUser(users.subList(i, Math.min(i + USER_BATCH, users.size())));
}
// 6. 超级管理员挂到根部门
writerMapper.updateUserDept(1L, adminDeptId);
}
}
@@ -0,0 +1,21 @@
package com.bms.system.orgsync.mapper;
import java.util.List;
import java.util.Map;
/**
* 老系统 Oracle 组织/用户/角色只读查询(SLAVE 数据源,账号只读)
*
* @author bms
*/
public interface OracleOrgSyncMapper
{
/** TAB_DEPT 全部部门(扁平结构,DEPT_CODE=DEPT_NAME) */
List<Map<String, Object>> selectAllTabDept();
/** TAB_EMPLOYEE 全部员工 */
List<Map<String, Object>> selectAllTabEmployee();
/** T_SYS_ROLE 有效角色(REC_FLAG=1,内部角色 ROLE_TYPE=100) */
List<Map<String, Object>> selectAllSysRole();
}
@@ -0,0 +1,46 @@
package com.bms.system.orgsync.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.common.core.domain.entity.SysDept;
import com.bms.common.core.domain.entity.SysRole;
import com.bms.common.core.domain.entity.SysUser;
/**
* 组织架构老系统数据迁移写库(MASTER 数据源)。
* 覆盖式迁移:除内置超级管理员(user_id=1 / role_id=1)外,
* 现有演示部门、角色、用户全部清空后按 Oracle 数据重建。
*
* @author bms
*/
public interface SysOrgSyncWriterMapper
{
/** 清空角色-部门(自定义数据范围)关联 */
int deleteAllRoleDept();
/** 清空全部部门 */
int deleteAllDept();
/** 新增部门 */
int insertDept(SysDept dept);
/** 清空除超级管理员角色(role_id=1)以外角色的菜单、用户关联 */
int deleteRoleMenuKeepAdmin();
int deleteUserRoleKeepAdmin();
/** 删除除超级管理员角色外的全部角色 */
int deleteRolesKeepAdmin();
/** 新增角色 */
int insertRole(SysRole role);
/** 删除除超级管理员(user_id=1)以外用户的岗位关联与用户 */
int deleteUserPostKeepAdmin();
int deleteUsersKeepAdmin();
/** 新增用户(批量) */
int batchInsertUser(@Param("list") List<SysUser> list);
/** 将指定用户挂到根部门 */
int updateUserDept(@Param("userId") Long userId, @Param("deptId") Long deptId);
}