背景

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 的构建效果):

ldap如何创建组 ldap搭建 软件_算法

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;
    }
}