背景
LDAP听上去耳熟能详,可真要用Java去写一个通用性高的数据同步服务好像需要了解的东西又得特别多,能查到的东西又多是断章取义。最近断断续续的折腾了3周,基本上搞了一个这玩意,打算陆续分享给大家吧,由于相关东西不是一篇能讲完的,所以打算搞个小专题吧,希望给喜欢抄作业的朋友们有所帮助。
上一篇写了一个LDAP部署教程:小白级CentOS下yum方式安装OpenLDAP(SSL)及phpLDAPadmin部署教程,这次先介绍一下测试时需要使用到的LDAP数据工具。计划下一篇最后为大家介绍一下LDAP同步服务的实现思路。
1. 工具参数介绍
参数名称 | 类型 | 说明 | 备注 |
host | String | LDAP服务器地址 | |
port | Integer | LDAP服务器端口 | |
bindDn | String | 绑定 DN | |
bindPassword | String | 绑定 DN 密码 | |
baseDn | String | 根节点DN | |
ouBreadthPerNode | Integer | 部门广度/父节点 | Must >0 |
ouDepth | Integer | 部门深度 | Must >0 |
personBreadthPerNode | Integer | 用户广度/父节点 | Must >0 |
uidNumberBegin | Integer | 用户ID开始值 | Must >0 |
gidNumber | Integer | LDAP用户组ID | Must >0 |
opType | Integer | 操作类型 | 1:创建数据;2:清除数据; |
2. 思路简介绍
分享这个工具目的主要是2点:
1、如何把循环嵌套改为自递归的写法(因为不能搞一个只能创建固定深度的死的工具,例如说只能构建3级部门,如果要变为4级则需要加一层循环去改代码)
2、如何构造中文姓名、部门名称。
2.1 创建数据
- 根据参数构建一颗LDAP条目数据树,为了构建这棵树,工具中定义了一个简单的树化的数据结构(详见:TreeNode);另外,由于直接把部门的深度定义为外部输入参数,因此在树的构建中需要进行自递归。简单来说,如何把以下循环嵌套代码改为递归(详见:LdapDataToolHelper.buildLdapEntryTree()方法):
void loop()
{
for(int i=1;i<4;i++)
{
System.out.println(i);
for(int j=1;j<4;j++)
{
System.out.println(i + "-" + j);
for(int k=1;k<4;k++)
{
System.out.println(i + "-" + j + "-" + k);
}
}
}
}
- 将构建好的LDAP条目数据树写入LDAP服务器,为了保障父节点一定在子节点之前完成写入,在树的遍历过程中需要保障其“自上而下”的顺序。
- 为了让构造的数据看起来好一点,对于人和部门条目数据的构造,进行了相对真实的人名和部门中文名称的随机生成(详见:ChineseUtils.java),数据生成效果如下(-ouBreadthPerNode 3 -ouDepth 2 -personBreadthPerNode 5 的构建效果):
2.2 清除数据
- 为了删除某个BaseDN下的所有的人和部门数据,一般来说需要从下自上的去遍历LDAP条目树,因为常见DN删除方式,如果该DN下有子节点数据,则删除操作会被拒绝。为了偷懒,我这里采用SubtreeDelete方式,从而只需要找出BaseDN下的一级人和部门的条目,然后进行一次删除即可。
3. 工具代码
代码没多少,直接上代码吧。
3.1 LdapDataTool.java
@Slf4j
public class LdapDataTool {
public static final String OPTION_HOST = "host";
public static final String OPTION_PORT = "port";
public static final String OPTION_BIND_DN = "bindDn";
public static final String OPTION_BIND_PASSWORD = "bindPassword";
public static final String OPTION_BASE_DN = "baseDn";
public static final String OPTION_OU_BREADTH_PER_NODE = "ouBreadthPerNode";
public static final String OPTION_OU_DEPTH = "ouDepth";
public static final String OPTION_PERSON_BREADTH_PER_NODE = "personBreadthPerNode";
public static final String OPTION_UID_NUMBER_BEGIN = "uidNumberBegin";
public static final String OPTION_GID_NUMBER = "gidNumber";
public static final String OPTION_OP_TYPE = "opType";
public static final String OPTION_IS_REQUIRED = "option is required";
public static final String OPTION_IS_INVALID = "option is invalid";
public static final String OPTION_MUST_GT_ZERO = "option must > 0";
public static final Integer OPERATION_TYPE_CREATE_DATA = 1;
public static final Integer OPERATION_TYPE_CLEAN_DATA = 2;
public static final String ORG_PERSON_DEFAULT_PASSWORD = "123456";
protected static final List<String> OPTION_LIST = Arrays.asList(OPTION_HOST,
OPTION_PORT,
OPTION_BIND_DN,
OPTION_BIND_PASSWORD,
OPTION_BASE_DN,
OPTION_OU_BREADTH_PER_NODE,
OPTION_OU_DEPTH,
OPTION_PERSON_BREADTH_PER_NODE,
OPTION_UID_NUMBER_BEGIN,
OPTION_GID_NUMBER,
OPTION_OP_TYPE);
/**
* main
*
* @param args
*/
public static void main(String[] args) {
try {
Options options = new Options();
options.addOption(OPTION_HOST, OPTION_HOST, true, "ldap server host");
options.addOption(OPTION_PORT, OPTION_PORT, true, "ldap server port");
options.addOption(OPTION_BIND_DN, OPTION_BIND_DN, true, "ldap server bind DN");
options.addOption(OPTION_BIND_PASSWORD, OPTION_BIND_PASSWORD, true, "ldap server bind Password");
options.addOption(OPTION_BASE_DN, OPTION_BASE_DN, true, "ldap server base DN");
options.addOption(OPTION_OU_BREADTH_PER_NODE, OPTION_OU_BREADTH_PER_NODE, true, "the breadth per node of OU");
options.addOption(OPTION_OU_DEPTH, OPTION_OU_DEPTH, true, "the depth of OU");
options.addOption(OPTION_PERSON_BREADTH_PER_NODE, OPTION_PERSON_BREADTH_PER_NODE, true, "the breadth per node of person");
options.addOption(OPTION_UID_NUMBER_BEGIN, OPTION_UID_NUMBER_BEGIN, true, "the uid number begin");
options.addOption(OPTION_GID_NUMBER, OPTION_GID_NUMBER, true, "the gid number");
options.addOption(OPTION_OP_TYPE, OPTION_OP_TYPE, true, "the operation type");
CommandLine cmd = new DefaultParser().parse(options, args);
for (String option : OPTION_LIST) {
if (!cmd.hasOption(option)) {
throw new IDaaSException(option + " " + OPTION_IS_REQUIRED);
}
}
LdapDataToolOptions ldapDataToolOptions = LdapDataToolOptions.getOptions(cmd);
log.info("LdapDataTool begin process, " + ldapDataToolOptions.toString());
if (ldapDataToolOptions.getOpType().equals(OPERATION_TYPE_CREATE_DATA)) {
createData(ldapDataToolOptions);
} else if (ldapDataToolOptions.getOpType().equals(OPERATION_TYPE_CLEAN_DATA)) {
cleanData(ldapDataToolOptions);
} else {
throw new IDaaSException("unsupported operation!");
}
} catch (Exception ex) {
log.error("LdapDataTool process error!", ex);
} finally {
log.info("LdapDataTool end process");
}
}
/**
* createData
*
* @param options
*/
private static void createData(LdapDataToolOptions options)
throws BadHanyuPinyinOutputFormatCombination, LDAPException, UnsupportedEncodingException, GeneralSecurityException {
LDAPConnection connection = null;
try {
connection = LdapUtils.getLdapConnection(options.getHost(), options.getPort(), options.getBindDn(), options.getBindPassword(), false);
TreeNode<Entry> ldapEntryTree = LdapDataToolHelper.buildLdapEntryTree(options);
LdapDataToolHelper.setLdapEntryTree(connection, ldapEntryTree);
} finally {
if (connection != null && connection.isConnected()) {
connection.close();
}
}
}
/**
* cleanData
*
* @param options
*/
private static void cleanData(LdapDataToolOptions options) throws LDAPException, GeneralSecurityException {
LDAPConnection connection = null;
try {
connection = LdapUtils.getLdapConnection(options.getHost(), options.getPort(), options.getBindDn(), options.getBindPassword(), false);
String filter = "|(objectClass=inetOrgPerson)(objectClass=organizationalUnit)";
SearchRequest baseSearchRequest = new SearchRequest(options.getBaseDn(), SearchScope.ONE, filter);
SearchResult searchResult = connection.search(baseSearchRequest);
for (SearchResultEntry entry : searchResult.getSearchEntries()) {
String dn = entry.getDN();
SubtreeDeleter subtreeDeleter = new SubtreeDeleter();
try {
subtreeDeleter.delete(connection, dn);
log.info("delete entry done, " + dn);
} catch (Exception ex) {
log.error("delete " + dn + " error!", ex);
}
}
} finally {
if (connection != null && connection.isConnected()) {
connection.close();
}
}
}
}
3.2 LdapDataToolHelper.java
@Slf4j
public class LdapDataToolHelper {
private LdapDataToolHelper() {
}
/**
* buildLdapEntryTree
*
* @param options
* @return
*/
public static TreeNode<Entry> buildLdapEntryTree(LdapDataToolOptions options)
throws LDAPException, BadHanyuPinyinOutputFormatCombination, UnsupportedEncodingException {
AtomicInteger ouIndex = new AtomicInteger(0);
AtomicInteger personIndex = new AtomicInteger(0);
// 构建root节点
DN baseDn = new DN(options.getBaseDn());
Entry baseEntry = new Entry(baseDn);
TreeNode<Entry> root = new TreeNode<>(baseEntry);
// 自递归构建LDAP条目树
recursiveBuildLdapEntryTree(root, options, ouIndex, personIndex);
log.info("buildLdapEntryTree() done, ouIndex=" + ouIndex + ", personIndex=" + personIndex);
return root;
}
/**
* setLdapEntryTree
*
* @param connection
* @param ldapEntryTree
*/
public static void setLdapEntryTree(LDAPConnection connection, TreeNode<Entry> ldapEntryTree) {
// 自递归写入LDAP条目数据树
recursiveSetLdapEntryTree(connection, ldapEntryTree);
}
/**
* 自递归构建LDAP条目树
*
* @param parent
* @param options
* @param ouIndex
* @param personIndex
* @throws LDAPException
*/
private static void recursiveBuildLdapEntryTree(TreeNode<Entry> parent, LdapDataToolOptions options, AtomicInteger ouIndex, AtomicInteger personIndex)
throws LDAPException, BadHanyuPinyinOutputFormatCombination, UnsupportedEncodingException {
int ouDepth = options.getOuDepth();
int ouBreadthPerNode = options.getOuBreadthPerNode();
int personBreadthPerNode = options.getPersonBreadthPerNode();
if (ouDepth <= 0) {
// 末级OU节点构建OrgPerson
for (int i = 0; i < personBreadthPerNode; i++) {
Entry ouEntry = getOrgPersonEntry(options, parent, personIndex);
parent.addChild(ouEntry);
}
return;
}
for (int i = 1; i <= ouBreadthPerNode; i++) {
Entry ouEntry = getOrganizationalUnitEntry(parent, ouIndex);
TreeNode<Entry> childNode = parent.addChild(ouEntry);
// 自递归构建下级OU
options.setOuDepth(ouDepth - 1);
recursiveBuildLdapEntryTree(childNode, options, ouIndex, personIndex);
}
}
/**
* getOrganizationalUnitEntry
*
* @param parent
* @param ouIndex
* @return
*/
private static Entry getOrganizationalUnitEntry(TreeNode<Entry> parent, AtomicInteger ouIndex) throws LDAPException {
String parentDnString = parent.getData().getDN();
String ouName = ChineseUtils.getDepartmentName(ouIndex.getAndIncrement());
String dnString = "ou=" + ouName + "," + parentDnString;
DN dn = new DN(dnString);
Entry entry = new Entry(dn);
entry.addAttribute("ou", ouName);
entry.addAttribute("objectClass", "organizationalUnit", "top");
return entry;
}
/**
* getOrgPersonEntry
*
* @param options
* @param parent
* @param personIndex
* @return
* @throws LDAPException
* @throws UnsupportedEncodingException
* @throws BadHanyuPinyinOutputFormatCombination
*/
private static Entry getOrgPersonEntry(LdapDataToolOptions options, TreeNode<Entry> parent, AtomicInteger personIndex)
throws LDAPException, UnsupportedEncodingException, BadHanyuPinyinOutputFormatCombination {
int currentPersonIndex = personIndex.incrementAndGet();
String parentDnString = parent.getData().getDN();
Combo2<String, String> randomChineseName = ChineseUtils.getRandomChineseName(1);
String lasName = randomChineseName.getV1();
String firstName = randomChineseName.getV2();
String lastNamePy = ChineseUtils.getPinYin(lasName, false, false);
String givenName = randomChineseName.getV2();
String sn = randomChineseName.getV1();
String cn = lasName + firstName + currentPersonIndex;
String uid = lastNamePy + ChineseUtils.getPinYin(firstName, true, false) + currentPersonIndex;
int uidNumber = options.getUidNumberBegin() + currentPersonIndex;
String gidNumber = String.valueOf(options.getGidNumber());
String homeDirectory = "/home/users/" + uid;
String dnString = "cn=" + cn + "," + parentDnString;
DN dn = new DN(dnString);
Entry entry = new Entry(dn);
entry.addAttribute("givenName", givenName);
entry.addAttribute("sn", sn);
entry.addAttribute("cn", cn);
entry.addAttribute("uid", uid);
entry.addAttribute("userPassword", ORG_PERSON_DEFAULT_PASSWORD);
entry.addAttribute("uidNumber", String.valueOf(uidNumber));
entry.addAttribute("gidNumber", String.valueOf(gidNumber));
entry.addAttribute("homeDirectory", homeDirectory);
entry.addAttribute("loginShell", "/bin/bash");
entry.addAttribute("objectClass", "inetOrgPerson", "posixAccount", "top");
return entry;
}
/**
* 自递归写入LDAP条目数据树
*
* @param connection
* @param data
*/
private static void recursiveSetLdapEntryTree(LDAPConnection connection, TreeNode<Entry> data) {
List<TreeNode<Entry>> children = data.getChildren();
if (CollectionUtils.isEmpty(children)) {
return;
}
for (TreeNode<Entry> childNode : children) {
Entry entry = childNode.getData();
try {
connection.add(new AddRequest(entry));
log.info("add entry done, " + entry.getDN());
} catch (Exception ex) {
log.info("add entry error! (" + entry.getDN() + ")", ex);
} finally {
recursiveSetLdapEntryTree(connection, childNode);
}
}
}
}
3.3 LdapDataToolOptions.java
@ToString
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LdapDataToolOptions {
private String host;
private Integer port;
private String bindDn;
private String bindPassword;
private String baseDn;
private Integer ouBreadthPerNode;
private Integer ouDepth;
private Integer personBreadthPerNode;
private Integer uidNumberBegin;
private Integer gidNumber;
private Integer opType;
/**
* getOptions
*
* @param cmd
* @return
*/
public static LdapDataToolOptions getOptions(CommandLine cmd) throws IDaaSException {
Integer port = getPositiveIntegerOptionValue(cmd, OPTION_PORT);
Integer ouBreadth = getPositiveIntegerOptionValue(cmd, OPTION_OU_BREADTH_PER_NODE);
Integer ouDepth = getPositiveIntegerOptionValue(cmd, OPTION_OU_DEPTH);
Integer personBreadth = getPositiveIntegerOptionValue(cmd, OPTION_PERSON_BREADTH_PER_NODE);
Integer uidNumberBegin = getPositiveIntegerOptionValue(cmd, OPTION_UID_NUMBER_BEGIN);
Integer gidNumber = getPositiveIntegerOptionValue(cmd, OPTION_GID_NUMBER);
Integer opType = getPositiveIntegerOptionValue(cmd, OPTION_OP_TYPE);
return LdapDataToolOptions.builder()
.host(cmd.getOptionValue(OPTION_HOST))
.port(port)
.bindDn(cmd.getOptionValue(OPTION_BIND_DN))
.bindPassword(cmd.getOptionValue(OPTION_BIND_PASSWORD))
.baseDn(cmd.getOptionValue(OPTION_BASE_DN))
.ouBreadthPerNode(ouBreadth).ouDepth(ouDepth)
.personBreadthPerNode(personBreadth)
.uidNumberBegin(uidNumberBegin)
.gidNumber(gidNumber)
.opType(opType)
.build();
}
/**
* getPositiveIntOptionValue
*
* @param cmd
* @param optionName
* @return
* @throws IDaaSException
*/
private static Integer getPositiveIntegerOptionValue(CommandLine cmd, String optionName) throws IDaaSException {
Integer value = null;
try {
value = Integer.parseInt(cmd.getOptionValue(optionName));
if (value <= 0) {
throw new IDaaSException(optionName + " " + OPTION_MUST_GT_ZERO);
}
} catch (Exception ex) {
throw new IDaaSException(optionName + " " + OPTION_IS_INVALID);
}
return value;
}
}
3.4 TreeNode.java
@Data
public class TreeNode<T> {
private T data;
private TreeNode<T> parent;
private List<TreeNode<T>> children;
public TreeNode(T data) {
this.data = data;
this.children = new LinkedList<>();
}
/**
* isRoot
*
* @return
*/
public boolean isRoot() {
return this.parent == null;
}
/**
* addChild
*
* @param child
* @return
*/
public TreeNode<T> addChild(T child) {
TreeNode<T> childNode = new TreeNode<>(child);
childNode.parent = this;
this.children.add(childNode);
return childNode;
}
/**
* getDepth
*
* @return
*/
public int getDepth() {
return this.isRoot() ? 0 : this.parent.getDepth() + 1;
}
}
3.5 ChineseUtils.java
public class ChineseUtils {
/**
* 部门集合
*/
private static List<String> departmentList = new ArrayList<>();
private static Random random = new Random();
/**
* 姓氏资源
*/
private static final String[] LAST_NAMES = {"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许",
"何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎",
"鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷",
"罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和",
"穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒",
"屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟",
"徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应",
"宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀",
"羊", "于", "惠", "甄", "曲", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山",
"谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景",
"詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "溥", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠",
"蒙", "池", "乔", "阴", "郁", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "却",
"璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习",
"宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄",
"阙", "东", "欧", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空",
"曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "后", "荆", "红", "游", "郏", "竺", "权", "逯", "盖", "益", "桓", "公", "仉",
"督", "岳", "帅", "缑", "亢", "况", "郈", "有", "琴", "归", "海", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "商", "牟", "佘", "佴", "伯", "赏", "墨",
"哈", "谯", "篁", "年", "爱", "阳", "佟", "言", "福", "南", "火", "铁", "迟", "漆", "官", "冼", "真", "展", "繁", "檀", "祭", "密", "敬", "揭", "舜", "楼",
"疏", "冒", "浑", "挚", "胶", "随", "高", "皋", "原", "种", "练", "弥", "仓", "眭", "蹇", "覃", "阿", "门", "恽", "来", "綦", "召", "仪", "风", "介", "巨",
"木", "京", "狐", "郇", "虎", "枚", "抗", "达", "杞", "苌", "折", "麦", "庆", "过", "竹", "端", "鲜", "皇", "亓", "老", "是", "秘", "畅", "邝", "还", "宾",
"闾", "辜", "纵", "侴", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "羊舌", "尉迟", "公羊", "澹台", "公冶", "宗正",
"濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "兀官", "司寇",
"南门", "呼延", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "车正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "段干", "百里", "东郭", "微生",
"梁丘", "左丘", "东门", "西门", "南宫", "第五", "公仪", "公乘", "太史", "仲长", "叔孙", "屈突", "尔朱", "东乡", "相里", "胡母", "司城", "张廖", "雍门",
"毋丘", "贺兰", "綦毋", "屋庐", "独孤", "南郭", "北宫", "王孙"};
/**
* 部门名称资源part1
*/
private static final String[] PART_1_ARR = {"北京", "上海", "天津", "重庆", "黑龙江", "吉林", "辽宁", "山东", "山西", "陕西", "河北", "河南", "湖北", "湖南", "海南", "江苏", "江西",
"广东", "广西", "云南", "贵州", "四川", "内蒙古", "宁夏", "甘肃", "青海", "安徽", "浙江", "福建", "西藏", "台湾", "香港", "澳门", "东城区", "黄浦区", "和平区", "万州区", "哈尔滨市",
"长春市", "沈阳市", "济南市", "太原市", "西安市", "石家庄市", "郑州市", "武汉市", "长沙市", "海口市", "南京市", "南昌市", "广州市", "南宁市", "昆明市", "贵阳市", "成都市", "呼和浩特市",
"银川市", "兰州市", "西宁市", "合肥市", "杭州市", "福州市", "拉萨市", "台北市", "中西区", "西城区", "卢湾区", "河东区", "涪陵区", "齐齐哈尔市", "吉林市", "大连市", "青岛市",
"大同市", "铜川市", "唐山市", "开封市", "黄石市", "株洲市", "三亚市", "无锡市", "景德镇市", "韶关市", "柳州市", "曲靖市", "六盘水市", "自贡市", "包头市", "石嘴山市", "嘉峪关市",
"海东地区", "芜湖市", "宁波市", "厦门市", "昌都地区", "高雄市", "湾仔区", "崇文区", "徐汇区", "河西区", "渝中区", "鸡西市", "四平市", "鞍山市", "淄博市", "阳泉市", "宝鸡市",
"秦皇岛市", "洛阳市", "十堰市", "湘潭市", "五指山市", "徐州市", "萍乡市", "深圳市", "桂林市", "玉溪市", "遵义市", "攀枝花市", "乌海市", "吴忠市", "金昌市", "海北藏族自治州",
"蚌埠市", "温州市", "莆田市", "山南地区新疆", "基隆市", "东区", "宣武区", "长宁区", "南开区", "大渡口区", "鹤岗市", "辽源市", "抚顺市", "枣庄市", "长治市", "咸阳市", "邯郸市",
"平顶山市", "宜昌市", "衡阳市", "琼海市", "常州市", "九江市", "珠海市", "梧州市", "保山市", "安顺市", "泸州市", "赤峰市", "固原市", "白银市", "黄南藏族自治州", "淮南市", "嘉兴市",
"三明市", "乌鲁木齐市", "台中市", "南区", "朝阳区", "静安区", "河北区", "江北区", "双鸭山市", "通化市", "本溪市", "东营市", "晋城市", "渭南市", "邢台市", "安阳市", "襄樊市", "邵阳市",
"儋州市", "苏州市", "新余市", "汕头市", "北海市", "昭通市", "铜仁地区", "德阳市", "通辽市", "中卫市", "天水市", "海南藏族自治州", "马鞍山市", "湖州市", "泉州市", "克拉玛依市", "台南市",
"油尖旺区", "丰台区", "普陀区", "红桥区", "沙坪坝区", "大庆市", "白山市", "丹东市", "烟台市", "朔州市", "延安市", "保定市", "鹤壁市", "鄂州市", "岳阳市", "文昌市", "南通市", "鹰潭市",
"佛山市", "防城港市", "丽江市", "黔西南布依族苗族自治州", "绵阳市", "鄂尔多斯市", "武威市", "果洛藏族自治州", "淮北市", "绍兴市", "漳州市", "吐鲁番地区", "新竹市", "深水埗区",
"石景山区", "闸北区", "塘沽区", "九龙坡区", "伊春市", "松原市", "锦州市", "潍坊市", "晋中市", "汉中市", "张家口市", "新乡市", "荆门市", "常德市", "万宁市", "连云港市", "赣州市",
"江门市", "钦州市", "思茅市", "毕节地区", "广元市", "呼伦贝尔市", "张掖市", "玉树藏族自治州", "铜陵市", "金华市", "南平市", "哈密地区日喀则地区", "嘉义市", "九龙城区", "海淀区",
"虹口区", "汉沽区", "南岸区", "佳木斯市", "白城市", "营口市", "济宁市", "运城市", "榆林市", "承德市", "焦作市", "孝感市", "张家界市", "东方市", "淮安市", "吉安市", "湛江市",
"贵港市", "临沧市", "黔东南苗族侗族自治州", "遂宁市", "巴彦淖尔市", "平凉市", "海西蒙古族藏族自治州", "安庆市", "衢州市", "龙岩市", "那曲地区", "黄大仙区", "门头沟区", "杨浦区",
"大港区", "北碚区", "七台河市", "延边朝鲜族自治州", "阜新市", "泰安市", "忻州市", "安康市", "沧州市", "济源市", "荆州市", "益阳市", "定安县", "盐城市", "宜春市", "茂名市", "玉林市",
"楚雄彝族自治州", "黔南布依族苗族自治州", "内江市", "乌兰察布市", "酒泉市", "黄山市", "舟山市", "宁德市", "阿里地区", "观塘区", "房山区", "闵行区", "东丽区", "万盛区", "牡丹江市",
"辽阳市", "威海市", "临汾市", "商洛市", "廊坊市", "濮阳市", "黄冈市", "郴州市", "屯昌县", "扬州市", "抚州市", "肇庆市", "百色市", "红河哈尼族彝族自治州", "乐山市", "兴安盟",
"庆阳市", "滁州市", "台州市", "林芝地区", "荃湾区", "通州区", "宝山区", "西青区", "双桥区", "黑河市", "盘锦市", "日照市", "吕梁市", "衡水市", "许昌市", "咸宁市", "永州市", "澄迈县",
"镇江市", "上饶市", "惠州市", "贺州市", "文山壮族苗族自治州", "南充市", "锡林郭勒盟", "定西市", "阜阳市", "丽水市", "昌吉回族自治州", "葵青区", "顺义区", "嘉定区", "津南区", "渝北区",
"绥化市", "铁岭市", "莱芜市", "漯河市", "随州市", "怀化市", "临高县", "泰州市", "梅州市", "河池市", "西双版纳傣族自治州", "眉山市", "阿拉善盟", "陇南市", "宿州市",
"博尔塔拉蒙古自治州", "沙田区", "昌平区", "浦东新区", "北辰区", "巴南区", "大兴安岭地区", "朝阳市", "临沂市", "三门峡市", "恩施土家族苗族自治州", "娄底市", "白沙黎族自治县",
"宿迁市", "汕尾市", "来宾市", "大理白族自治州", "宜宾市", "临夏回族自治州", "巢湖市", "巴音郭楞蒙古自治州", "西贡区", "大兴区", "金山区", "武清区", "黔江区", "葫芦岛市", "德州市",
"南阳市", "仙桃市", "湘西土家族苗族自治州", "昌江黎族自治县", "河源市", "崇左市", "德宏傣族景颇族自治州", "广安市", "甘南藏族自治州", "六安市", "阿克苏地区", "大埔区", "怀柔区",
"松江区", "宝坻区", "长寿区", "聊城市", "商丘市", "潜江市", "乐东黎族自治县", "阳江市", "怒江傈僳族自治州", "达州市", "亳州市", "克孜勒苏柯尔克孜自治州", "北区", "平谷区", "青浦区",
"宁河县", "江津区", "滨州市", "信阳市", "天门市", "陵水黎族自治县", "清远市", "迪庆藏族自治州", "雅安市", "池州市", "喀什地区", "元朗区", "密云县", "南汇区", "静海县", "合川区",
"菏泽市", "周口市", "神农架林区", "保亭黎族苗族自治县", "东莞市", "巴中市", "宣城市", "和田地区", "屯门区", "延庆县", "奉贤区", "蓟县", "永川区", "驻马店市", "琼中黎族苗族自治县",
"中山市", "资阳市", "伊犁哈萨克自治州", "离岛区", "崇明县", "南川区", "潮州市", "阿坝藏族羌族自治州", "塔城地区", "綦江县", "揭阳市", "甘孜藏族自治州", "阿勒泰地区", "潼南县",
"云浮市", "凉山彝族自治州", "石河子市", "铜梁县", "阿拉尔市", "大足县", "图木舒克市", "荣昌县", "五家渠市", "璧山县", "梁平县", "城口县", "丰都县", "垫江县", "武隆县"};
/**
* 部门名称资源part2
*/
private static final List<String> PART_2_LIST = Arrays.asList("研发", "市场", "财务", "法务", "销售", "人力", "管理", "行政", "测试", "运维");
/**
* 部门名称资源part3
*/
private static final List<String> PART_3_LIST = Arrays.asList("1部", "2部", "3部", "4部", "5部", "6部", "7部", "8部", "9部", "10部", "11部", "12部", "13部", "14部", "15部", "16部", "17部", "18部", "19部", "20部");
private static final String CHAR_SET = "GB2312";
static {
for (String par1 : PART_1_ARR) {
for (String part2 : PART_2_LIST) {
for (String par3 : PART_3_LIST) {
departmentList.add(par1 + part2 + par3);
}
}
}
}
private ChineseUtils() {
}
/**
* getRandomChinese(获取随机中文)
*
* @return
* @throws Exception
*/
public static String getRandomChinese() throws UnsupportedEncodingException {
int highPos = (176 + Math.abs(random.nextInt(71)));
random = new Random();
int lowPos = 161 + Math.abs(random.nextInt(94));
byte[] bArr = new byte[2];
bArr[0] = (Integer.valueOf(highPos)).byteValue();
bArr[1] = (Integer.valueOf(lowPos)).byteValue();
return new String(bArr, CHAR_SET);
}
/**
* getRandomLastChineseName(获取随机姓氏)
*
* @return
*/
public static String getRandomLastChineseName() {
return LAST_NAMES[random.nextInt(LAST_NAMES.length)];
}
/**
* getRandomChineseName(获取随机中文名字)
*
* @param firstNameLength
* @return V1:lastName, V2:firstName
* @throws UnsupportedEncodingException
*/
public static Combo2<String, String> getRandomChineseName(int firstNameLength) throws UnsupportedEncodingException {
if (firstNameLength <= 0) {
firstNameLength = 1;
}
String lastName = getRandomLastChineseName();
StringBuilder firstNameBuilder = new StringBuilder();
for (int i = 0; i < firstNameLength; i++) {
firstNameBuilder.append(getRandomChinese());
}
return new Combo2<>(lastName, firstNameBuilder.toString());
}
/**
* getPinYin
*
* @param chinese
* @param firstLetter
* @param upper
* @return
*/
public static String getPinYin(String chinese, boolean firstLetter, boolean upper) throws BadHanyuPinyinOutputFormatCombination {
char[] chineseArr = chinese.toCharArray();
HanyuPinyinOutputFormat outputFormat = new HanyuPinyinOutputFormat();
outputFormat.setCaseType(upper ? HanyuPinyinCaseType.UPPERCASE : HanyuPinyinCaseType.LOWERCASE);
outputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < chineseArr.length; i++) {
// 判断是否为中文
if (!String.valueOf(chineseArr[i]).matches("[\u4e00-\u9fa5]+")) {
continue;
}
String tmp = PinyinHelper.toHanyuPinyinStringArray(chineseArr[i], outputFormat)[0];
if (firstLetter) {
tmp = tmp.substring(0, 1);
}
sb.append(tmp);
}
return sb.toString();
}
/**
* getDepartmentName
*
* @param index
* @return
*/
public static String getDepartmentName(int index) {
if (index < 0) {
index = 0;
}
String result = departmentList.get(index % departmentList.size());
if (index > departmentList.size()) {
result = result + index;
}
return result;
}
}