Repository: chinapnr/fishbase (language: python)

Issue:
<v1.4> fish_logger，修改 set_log_file 方法：自动创建日志目录

fish_date 代码优化
2021.5.27 
针对fish_date 包中的代码进行优化，发现原来一些代码写法不够优雅和简洁，单元测试容易产生潜在问题，函数命名和文档等也存在一些问题。
fish_date 中 GetRandomTime 中函数优化和修改
1.  date_time_this_month() 函数，获取当前月的随机时间，修改名称为 random_datetime_this_month() 

docstring 和 代码都需要修改；

```python
@staticmethod
    def date_time_this_month():
        """
        获取当前月的随机时间

        :return:
            * date_this_month: (datetime) 当前月份的随机时间

        举例如下::

            print('--- GetRandomTime.date_time_this_month demo ---')
            print(GetRandomTime.date_time_this_month())
            print('---')

        执行结果::
    
            --- GetRandomTime.date_time_this_month demo demo ---
            2018-07-01 12:47:20
            ---

       """
        now = datetime.now()
        this_month_start = now.replace(
            day=1, hour=0, minute=0, second=0, microsecond=0)
        this_month_days = calendar.monthrange(now.year, now.month)
        random_seconds = random.randint(0, this_month_days[1]*A_DAY_SECONDS)

        return this_month_start + timedelta(seconds=random_seconds)
```

2.  date_time_this_year() 函数，获取当前年的随机时间字符串，修改名称为 random_datetime_this_year() 

docstring 和 代码都需要修改；

```python
    @staticmethod
    def date_time_this_year():
        """
        获取当前年的随机时间字符串
        
        :return:
            * date_this_year: (datetime) 当前月份的随机时间

        举例如下::

            print('--- GetRandomTime.date_time_this_year demo ---')
            print(GetRandomTime.date_time_this_year())
            print('---')

        执行结果::
    
            --- GetRandomTime.date_time_this_year demo demo ---
            2018-02-08 17:16:09
            ---
       """
        now = datetime.now()
        this_year_start = now.replace(
            month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
        this_year_days = sum(calendar.mdays)
        random_seconds = random.randint(0, this_year_days*A_DAY_SECONDS)

        return this_year_start + timedelta(seconds=random_seconds)
```
fish_date 中 GetRandomTime 中函数优化和修改
1.  date_time_this_month() 函数，获取当前月的随机时间，修改名称为 random_datetime_this_month() 

docstring 和 代码都需要修改；

```python
@staticmethod
    def date_time_this_month():
        """
        获取当前月的随机时间

        :return:
            * date_this_month: (datetime) 当前月份的随机时间

        举例如下::

            print('--- GetRandomTime.date_time_this_month demo ---')
            print(GetRandomTime.date_time_this_month())
            print('---')

        执行结果::
    
            --- GetRandomTime.date_time_this_month demo demo ---
            2018-07-01 12:47:20
            ---

       """
        now = datetime.now()
        this_month_start = now.replace(
            day=1, hour=0, minute=0, second=0, microsecond=0)
        this_month_days = calendar.monthrange(now.year, now.month)
        random_seconds = random.randint(0, this_month_days[1]*A_DAY_SECONDS)

        return this_month_start + timedelta(seconds=random_seconds)
```

2.  date_time_this_year() 函数，获取当前年的随机时间字符串，修改名称为 random_datetime_this_year() 

docstring 和 代码都需要修改；

```python
    @staticmethod
    def date_time_this_year():
        """
        获取当前年的随机时间字符串
        
        :return:
            * date_this_year: (datetime) 当前月份的随机时间

        举例如下::

            print('--- GetRandomTime.date_time_this_year demo ---')
            print(GetRandomTime.date_time_this_year())
            print('---')

        执行结果::
    
            --- GetRandomTime.date_time_this_year demo demo ---
            2018-02-08 17:16:09
            ---
       """
        now = datetime.now()
        this_year_start = now.replace(
            month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
        this_year_days = sum(calendar.mdays)
        random_seconds = random.randint(0, this_year_days*A_DAY_SECONDS)

        return this_year_start + timedelta(seconds=random_seconds)
```


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 1ba77c6..df3c57f 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -1,6 +1,7 @@
 # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
 # 2020.3.25 create by David Yi;
+# 2021.5.26 edit by David Yi, add python 3.9 support
 
 name: Python package
 
@@ -12,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.5, 3.6, 3.7, 3.8]
+        python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v2
diff --git a/LICENSE.txt b/LICENSE.txt
index c2d7ccf..b99df47 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) [2016] [fish_base]
+Copyright (c) [2016-2021] [fish_base]
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.rst b/README.rst
index d5c7829..f46d71c 100644
--- a/README.rst
+++ b/README.rst
@@ -12,11 +12,11 @@
 fishbase 简介
 =================
 
-fishbase 是由我们自主开发和整理的一套 Python 基础函数库，将我们平时在开发 Python 项目时候的各类工具函数汇聚到一起，方便集中管理和使用。
+fishbase 是由我们开发和整理的一套 Python基础函数库，将我们平时在开发 Python项目时候的各类工具函数汇聚到一起，方便集中管理和使用。希望对你也有所帮助，也适合初学 Python 的朋友通过代码来学习。
 
-fishbase 当前版本为 v1.2，支持 Python 3.5-3.8，绝大部分函数也能工作在 Python 2.7 下，但是我们不推荐使用 Python 2.7 .
+fishbase 最新版本为 v1.4，支持 Python 3.5-3.9，绝大部分函数也能工作在 Python 2.7下，但是我们不推荐使用 Python 2.7。
 
-自 2016/3 初次发布以来，我们坚持不断更新，先后发布了 20 余个版本。近一年来，我们逐步形成每月更新 1 到 2 个版本的频率，抽象出了很多通用的方法，主要分为以下模块：
+fishbase 主要有以下功能模块：
 
 +----------------------------------------------------------------------------------+----------------------------------------+
 |       模块                                                                       | 功能函数                               |
@@ -25,9 +25,9 @@ fishbase 当前版本为 v1.2，支持 Python 3.5-3.8，绝大部分函数也能
 +----------------------------------------------------------------------------------+----------------------------------------+
 | `fish_crypt <https://fishbase.readthedocs.io/en/latest/fish_crypt.html>`_        | 加密数据函数包                         |
 +----------------------------------------------------------------------------------+----------------------------------------+
-| `fish_csv <https://fishbase.readthedocs.io/en/latest/fish_csv.html>`_            | csv 处理增强函数包                     |
+| `fish_csv <https://fishbase.readthedocs.io/en/latest/fish_csv.html>`_            | csv处理增强函数包                     |
 +----------------------------------------------------------------------------------+----------------------------------------+
-| `fish_data <https://fishbase.readthedocs.io/en/latest/fish_data.html>`_          | 数据信息处理函数包，含银行卡、身份证等 |
+| `fish_data <https://fishbase.readthedocs.io/en/latest/fish_data.html>`_          | 数据处理函数包，包括银行卡、身份证等 |
 +----------------------------------------------------------------------------------+----------------------------------------+
 | `fish_date <https://fishbase.readthedocs.io/en/latest/fish_date.html>`_          | 日期处理增强函数包                     |
 +----------------------------------------------------------------------------------+----------------------------------------+
@@ -35,9 +35,9 @@ fishbase 当前版本为 v1.2，支持 Python 3.5-3.8，绝大部分函数也能
 +----------------------------------------------------------------------------------+----------------------------------------+
 | `fish_logger <https://fishbase.readthedocs.io/en/latest/fish_logger.html>`_      | 日志记录增强函数包                     |
 +----------------------------------------------------------------------------------+----------------------------------------+
-| `fish_project <https://fishbase.readthedocs.io/en/latest/fish_project.html>`_    | project 目录结构生成函数包             |
+| `fish_project <https://fishbase.readthedocs.io/en/latest/fish_project.html>`_    | 项目目录结构生成函数包             |
 +----------------------------------------------------------------------------------+----------------------------------------+
-| `fish_random <https://fishbase.readthedocs.io/en/latest/fish_random.html>`_      | 随机数据生成函数包                     |
+| `fish_random <https://fishbase.readthedocs.io/en/latest/fish_random.html>`_      | 随机数据生成增强函数包                     |
 +----------------------------------------------------------------------------------+----------------------------------------+
 | `fish_system <https://fishbase.readthedocs.io/en/latest/fish_system.html>`_      | 系统增强函数包                         |
 +----------------------------------------------------------------------------------+----------------------------------------+
@@ -52,17 +52,15 @@ fishbase 当前版本为 v1.2，支持 Python 3.5-3.8，绝大部分函数也能
    pip install -U fishbase
 
 
-fishbase 能干什么？
+fishbase 功能举例
 ===================
 
-举例：
-
 获取文件的绝对路径
 ------------------------------
 
 .. code:: python
 
-   >>> from fishbase.fish_common import find_files
+   >>> from fishbase.fish_files import get_abs_filename_with_sub_path
    >>> print(get_abs_filename_with_sub_path('/etc', 'hosts'))
    (True, '/etc/hosts')
 
@@ -77,7 +75,7 @@ fishbase 能干什么？
    {'days': 1, 'hours': 3, 'minutes': 28, 'seconds': 28}
 
 
-生成随机数据
+生成随机符合校验规则的身份证和银行卡数据
 ----------------------
 
 .. code:: python
@@ -112,15 +110,21 @@ fishbase 能干什么？
    ['requirements.txt', 'README.md', 'setup.py']
 
 
-最近更新
+更新记录
 ==========
 
+2021.6.23 v1.4
+------------------
+- `#288 <https://github.com/chinapnr/fishbase/issues/288>`_ , fish_date 中的 GetRandomTime 修改为 RandomTime，其中函数名称修改为 get_random_datetime_this_month() get_random_datetime_this_year() get_random_date_by_year() get_random_date_by_range();
+- `#292 <https://github.com/chinapnr/fishbase/issues/292>`_ , fish_data 增加敏感数据掩码显示类 SensitiveMask，增加函数 get_idcard_number() get_bankcard_number() get_mobile_number() get_email() ;
+- 修改小错误
+- 修改文档描述
+
 2020.4.25 v1.3
 ------------------
 - `#273 <https://github.com/chinapnr/fishbase/issues/273>`_ , 随机数包的文档举例中的函数名称错误修正;
 - `#275 <https://github.com/chinapnr/fishbase/issues/275>`_ , 随机数，gen_random_id_card() 函数优化;
 
-
 2020.3.28 v1.2
 ------------------
 - `#255 <https://github.com/chinapnr/fishbase/issues/255>`_ `#266 <https://github.com/chinapnr/fishbase/issues/266>`_ , 开始使用 github 的 Actions 进行 CI 集成;
@@ -131,10 +135,9 @@ fishbase 能干什么？
 - `#263 <https://github.com/chinapnr/fishbase/issues/263>`_ , common 包，删除一些为了向前兼容的函数;
 - `#263 <https://github.com/chinapnr/fishbase/issues/263>`_ , 删除 flask swagger 支持;
 
-
 2019.12.5 v1.1.16
 ------------------
-- 为 flask 应用添加 swagger 模块 `#249 <https://github.com/chinapnr/fishbase/issues/249>`_
+- 为 flask应用添加 swagger模块 `#249 <https://github.com/chinapnr/fishbase/issues/249>`_
 
 2019.7.17 v1.1.15
 ------------------
@@ -153,7 +156,7 @@ fishbase 能干什么？
 - 数字金额和中文大写相互转换 `#235 <https://github.com/chinapnr/fishbase/issues/235>`_
 
 
-更多
+详细帮助
 ====
 
 更多详细文档，请参见：http://fishbase.readthedocs.io/
@@ -164,7 +167,9 @@ fishbase 能干什么？
 感谢
 ====
 
-非常感谢所有在 fishbase 函数包发展过程中做出共享的朋友们：
+自 2016年3月初次发布以来，先后发布了 20 余个版本，非常感谢所有在 fishbase函数包发展过程中做出贡献的朋友们：
+
+Yi Jun
 
 Leo
 
@@ -180,6 +185,7 @@ Miao Tianshi
 
 Jin Xiongwei
 
-Yi Jun
+Wang Xiaolong
+
 
 
diff --git a/appveyor.yml b/appveyor.yml
index 6095e9f..190a0cb 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -6,6 +6,8 @@ environment:
     - PYTHON: "C:/Python35"
     - PYTHON: "C:/Python36"
     - PYTHON: "C:/Python37"
+    - PYTHON: "C:/Python38"
+    - PYTHON: "C:/Python39"
 
 init:
   - "ECHO %PYTHON%"
diff --git a/fishbase/fish_data.py b/fishbase/fish_data.py
index 6839357..3677c25 100644
--- a/fishbase/fish_data.py
+++ b/fishbase/fish_data.py
@@ -35,6 +35,8 @@ def sqlite_query(db, sql, params):
     return values
 
 
+# 身份证，身份证号等相关计算；
+# 2018，2019， create by David Yi
 class IdCard(object):
     """
     校验身份证号、获取身份证校验位，获取随机生成身份证号所需身份代码等函数；
@@ -136,10 +138,10 @@ def get_checkcode(cls, id_number_str):
     
     # 检查身份证号码是否能通过校验规则
     # ---
+    # some original source: https://zhuanlan.zhihu.com/p/24449773
     # 2018.12.9 create by David Yi, add in v1.1.3, github issue #137
     # 2018.12.13 edit, v1.1.4 github issue #145
     # 2019.1.5 edit, v1.1.6 github issue #187, 修改函数名称
-    # some original source: https://zhuanlan.zhihu.com/p/24449773
     @classmethod
     def check_number(cls, id_number):
         """
@@ -352,12 +354,12 @@ def get_province_info(cls):
 
     # 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243
     @classmethod
-    def get_number_detail(cls, id_num):
+    def get_number_detail(cls, id_number):
         """
         根据身份证号获取性别、省份、出生年月日信息
 
         :param:
-            * id_num: (string) 要查询的身份证号
+            * id_number: (string) 要查询的身份证号
 
         :returns:
             * flag: (bool) 是否查询成功
@@ -368,7 +370,7 @@ def get_number_detail(cls, id_num):
             from fishbase.fish_data import *
 
             print('--- fish_data get_number_detail demo ---')
-            print(IdCard.get_id_num_detail('130522198407316471'))
+            print(IdCard.get_id_number_detail('130522198407316471'))
             print('---')
 
         输出结果::
@@ -377,17 +379,18 @@ def get_number_detail(cls, id_num):
         (True, {'province': '130000', 'gender': '男', 'birth_date': '19840731'})
         ---
         """
-        if len(str(id_num)) != 18:
+        if len(str(id_number)) != 18:
             return False, {}
-        province = id_num[:2] + '0' * 4
-        gender = '男' if int(id_num[-2]) % 2 == 1 else '女'
-        birth_date = id_num[6:14]
+        province = id_number[:2] + '0' * 4
+        gender = '男' if int(id_number[-2]) % 2 == 1 else '女'
+        birth_date = id_number[6:14]
     
         return True, {'province': province,
                       'gender': gender,
                       'birth_date': birth_date}
 
 
+# 银行卡，卡号卡bin等计算；
 # 2019.1.6 create by David Yi, #188 用 class CardBin 方法实现
 class CardBin(object):
     """
@@ -672,3 +675,98 @@ def get_bank_name_by_code(cls, bank_code):
                              'select bankname from cn_bank where bankcode=:bank_code ',
                              {"bank_code": bank_code})
         return value[0][0]
+
+
+# v1.4 edit by David Yi, 根据 @jacsonking #292 建议和代码，增加敏感数据掩码类
+class SensitiveMask(object):
+    """
+    获取敏感数据的掩码表示
+
+    举例如下::
+
+        print('--- GetSensitiveMask demo ---')
+        print(SensitiveMask.identity_number('429004199205270758'))
+        print(SensitiveMask.bankcard_number('9558800200136454752'))
+        print(SensitiveMask.mobile_number('13958462541'))
+        print(SensitiveMask.email('xiaolongli@163.com'))
+        print('---')
+
+    执行结果::
+
+        --- GetSensitiveMask demo ---
+        429************758
+        955880*********4752
+        139****2541
+        x********i@163.com
+        ---
+
+    """
+
+    @staticmethod
+    def get_idcard_number(data):
+        """
+        身份证号掩码表示（前3位后3位显示，其他用掩码*表示）
+        :param:
+            * data(string): 身份证号明文
+        :return:
+            * mask_data(string): 身份证号掩码表示
+        """
+        # 如果不是字符串，报错误
+        if not isinstance(data, str):
+            raise TypeError
+
+        mask_data = data[:3] + '*' * (len(data) - 6) + data[-3:]
+        return mask_data
+
+    @staticmethod
+    def get_bankcard_number(data):
+        """
+        银行卡号掩码表示（前6位后4位显示，其他用掩码*表示）
+        :param:
+            * data(string): 银行卡号明文
+        :return:
+            * mask_data(string): 银行卡号掩码表示
+        """
+        # 如果不是字符串，报错误
+        if not isinstance(data, str):
+            raise TypeError
+
+        mask_data = data[:6] + '*' * (len(data) - 10) + data[-4:]
+        return mask_data
+
+    @staticmethod
+    def get_mobile_number(data):
+        """
+        手机号掩码表示（前3位后4位显示，其他用掩码*表示）
+        :param:
+            * data(string): 手机号明文
+        :return:
+            * mask_data(string): 手机号掩码表示
+        """
+        # 如果不是字符串，报错误
+        if not isinstance(data, str):
+            raise TypeError
+
+        mask_data = data[:3] + '*' * 4 + data[-4:]
+        return mask_data
+
+    @staticmethod
+    def get_email(data):
+        """
+        邮箱账号掩码表示（@之前首末位显示，其他用掩码*表示）
+        :param:
+            * data(string): 邮箱账号明文
+        :return:
+            * mask_data(string): 邮箱账号掩码表示
+        """
+        # 如果不是字符串，报错误
+        if not isinstance(data, str):
+            raise TypeError
+
+        need_mask = data.split('@')[0][1:-1]
+        mask_data = data.replace(need_mask, '*' * len(need_mask))
+        return mask_data
+
+
+
+
diff --git a/fishbase/fish_date.py b/fishbase/fish_date.py
index 707cca9..07ec983 100644
--- a/fishbase/fish_date.py
+++ b/fishbase/fish_date.py
@@ -1,460 +1,446 @@
-# coding=utf-8
-
-import time
-from datetime import datetime, timedelta
-import calendar
-import random
-
-A_DAY_SECONDS = 24 * 60 * 60
-
-
-# 2016.4.26
-# 输入: date_kind, eg 'last month', 'this month'
-# 输出: tuple, type datetime.date eg '2016-03-01' '2016-03-31'
-# v1.0.14 edit by Hu Jun #61
-def get_date_range(dates, separator='-'):
-    """
-    获取某个月的日期范围，返回该月第一天和最后一天的字符串表示
-    
-    :param:
-        * dates: (string 或者 datetime obj) 月份信息
-        * separator: (string) 分隔符，默认为 '-'
-    :return:
-        * first_day: (string) 该月份的第一天
-        * last_day: (string) 该月份的最后一天
-
-    举例如下::
-
-        print('--- get_date_range demo ---')
-        now_time = datetime.now()
-        print(get_date_range(now_time))
-        print(get_date_range('201802',separator='/'))
-        print('---')
-
-    执行结果::
-
-        --- get_years demo ---
-        ('2018-06-1', '2018-06-30')
-        ('2018/02/1', '2018/02/28')
-        ---
-
-    """
-    if isinstance(dates, str) and dates.isdigit():
-        y = dates[:4]
-        m = dates[4:]
-        if (len(y) != 4) or (not 1 <= int(m) <= 12):
-            raise (ValueError("date must be a date string like '201806', but get {}".format(dates)))
-    elif hasattr(dates, 'year') and hasattr(dates, 'month'):
-        y = str(dates.year)
-        m = str(dates.month)
-    else:
-        raise (TypeError("date except a years string like '201806' or a object has 'year' "
-                         "and 'month' attribute, but get a {}".format(type(dates))))
-    
-    # set month to length 2 if month less than 10
-    m = '0'+m if len(m) != 2 else m
-    
-    mr = calendar.monthrange(int(y), int(m))
-    
-    first_day = separator.join([y, m, '1'])
-    last_day = separator.join([y, m, str(mr[1])])
-
-    return first_day, last_day
-
-
-# v1.0.14 edit by Hu Jun #37
-def get_years(months=0, refer=None):
-    """
-    获取基准时月份增量的年月
-
-    :param:
-        * months: (int) 月份增量，正数为往后年月，整数为往前年月
-        * refer: (datetime obj) datetime 对象，或者有 month 和 year 属性的实例，默认为当前时间
-    :return:
-        * result: (string) 年月字符串
-
-    举例如下::
-
-        print('--- get_years demo ---')
-        print(get_years(-5))
-        print(get_years(7, datetime.now()))
-        print('---')
-
-    执行结果::
-
-        --- get_years demo ---
-        201801
-        201901
-        ---
-
-    """
-    if refer is None:
-        refer = datetime.now()
-    # 计算当前总月份数
-    try:
-        months_count = refer.year * 12 + refer.month
-    except Exception:
-        raise TypeError('refer except {}, got an {}'.format(type(datetime.now()), type(refer)))
-    
-    # 计算结果总月分数
-    months_count += months
-    
-    y, m = divmod(months_count, 12)
-    
-    # 将m的值转换为1-12
-    if m == 0:
-        y -= 1
-        m = 12
-    
-    return ''.join(['%04d' % y, '%02d' % m])
-
-
-# v1.1.15 edit by Jia Chunying #142 #164
-# v1.1.14 edit by Hu Jun #142
-# v1.0.16 edit by Hu Jun #87
-class GetRandomTime(object):
-    """
-    获取随机时间
-
-    举例如下::
-
-        print('--- GetRandomTime demo ---')
-        print(GetRandomTime.date_time_this_month())
-        print(GetRandomTime.date_time_this_year())
-        print('---')
-
-    执行结果::
-
-        --- Base64 demo ---
-        2018-07-01 12:47:20
-        2018-02-08 17:16:09
-        ---
-
-    """
-    @staticmethod
-    def date_time_this_month():
-        """
-        获取当前月的随机时间
-
-        :return:
-            * date_this_month: (datetime) 当前月份的随机时间
-
-        举例如下::
-
-            print('--- GetRandomTime.date_time_this_month demo ---')
-            print(GetRandomTime.date_time_this_month())
-            print('---')
-
-        执行结果::
-    
-            --- GetRandomTime.date_time_this_month demo demo ---
-            2018-07-01 12:47:20
-            ---
-
-       """
-        now = datetime.now()
-        this_month_start = now.replace(
-            day=1, hour=0, minute=0, second=0, microsecond=0)
-        this_month_days = calendar.monthrange(now.year, now.month)
-        random_seconds = random.randint(0, this_month_days[1]*A_DAY_SECONDS)
-
-        return this_month_start + timedelta(seconds=random_seconds)
-
-    @staticmethod
-    def date_time_this_year():
-        """
-        获取当前年的随机时间字符串
-        
-        :return:
-            * date_this_year: (datetime) 当前月份的随机时间
-
-        举例如下::
-
-            print('--- GetRandomTime.date_time_this_year demo ---')
-            print(GetRandomTime.date_time_this_year())
-            print('---')
-
-        执行结果::
-    
-            --- GetRandomTime.date_time_this_year demo demo ---
-            2018-02-08 17:16:09
-            ---
-       """
-        now = datetime.now()
-        this_year_start = now.replace(
-            month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
-        this_year_days = sum(calendar.mdays)
-        random_seconds = random.randint(0, this_year_days*A_DAY_SECONDS)
-
-        return this_year_start + timedelta(seconds=random_seconds)
-
-    @staticmethod
-    def gen_date_by_year(year):
-        """
-        获取当前年的随机时间字符串
-
-        :param:
-            * year: (string) 长度为 4 位的年份字符串
-
-        :return:
-            * date_str: (string) 传入年份的随机合法的日期
-        
-        举例如下::
-        
-            print('--- GetRandomTime.gen_date_by_year demo ---')
-            print(GetRandomTime.gen_date_by_year("2010"))
-            print('---')
-        
-        执行结果::
-        
-            --- GetRandomTime.gen_date_by_year demo ---
-            20100505
-            ---
-        """
-        if isinstance(year, int) and len(str(year)) != 4:
-            raise ValueError("year should be int year like 2018, but we got {}, {}".
-                             format(year, type(year)))
-        if isinstance(year, str) and len(year) != 4:
-            raise ValueError("year should be string year like '2018', but we got {}, {}".
-                             format(year, type(year)))
-        if isinstance(year, int):
-            year = str(year)
-
-        date_str = GetRandomTime.gen_date_by_range(year + "-01-01", year + "-12-31", "%Y%m%d")
-
-        return date_str
-
-    @staticmethod
-    def gen_date_by_range(begin_date, end_date, date_format="%Y-%m-%d"):
-        """
-
-        指定一个日期范围，随机生成区间内的某一个日期，该区间为闭区间
-
-        :param:
-            * begin_date: (string) 范围的起始日期，字符串 yyyy-MM-dd eg. 2018-01-01
-            * end_date: (string) 范围的结束日期，字符串 yyyy-MM-dd eg. 2018-12-31
-            * date_format: 返回的日期格式，字符串：默认格式yyyyMMdd default: "%Y%m%d"
-
-        :return:
-            * date_str 日期区间内的一个指定格式的合法的随机日期
-
-        举例如下::
-
-            print('--- GetRandomTime.gen_date_by_range demo ---')
-            print(GetRandomTime.gen_date_by_range("2010-01-01","2010-12-31"))
-            print('---')
-
-        执行结果::
-
-            --- GetRandomTime.gen_date_by_range demo ---
-            20100124
-            ---
-        """
-        # 设置开始日期
-        begin_date_info = begin_date.split("-")
-        begin_date_info = [int(x) for x in begin_date_info]
-        begin_date_info.extend([0, 0, 0, 0, 0, 0])
-        begin_date_tuple = tuple(begin_date_info)
-        # 设置结束日期
-        end_date_info = end_date.split("-")
-        end_date_info = [int(x) for x in end_date_info]
-        end_date_info.extend([23, 59, 59, 59, 0, 0])
-        end_date_tuple = tuple(end_date_info)
-
-        try:
-            # 生成开始时间戳
-            start_timestamp = time.mktime(begin_date_tuple)
-            # 生成结束时间戳
-            end_timestamp = time.mktime(end_date_tuple)
-        except TypeError as e:
-            raise TypeError(e, "begin_date/end_date format error")
-
-        # 在开始和结束时间戳中随机取出一个
-        rand_timedelta = random.randint(start_timestamp, end_timestamp)
-        # 将时间戳生成时间元组
-        date_tuple = time.localtime(rand_timedelta)
-
-        # 将时间元组转成格式化字符串
-        date_str = time.strftime(date_format, date_tuple)
-        return date_str
-
-
-# v1.1.0 edit by Hu Jun #90
-def get_time_interval(start_time, end_time):
-    """
-    获取两个unix时间戳之间的时间间隔
-
-    :param:
-        * start_time: (int) 开始时间，unix 时间戳
-        * end_time: (int) 结束时间，unix 时间戳
-    :return:
-        * interval_dict: (dict) 时间间隔字典
-
-    举例如下::
-
-        print('--- get_time_interval demo ---')
-        import time
-        start = int(time.time())
-        end = start - 98908
-        print(get_time_interval(end, start))
-        print('---')
-
-    执行结果::
-
-        --- get_time_interval demo ---
-        {'days': 1, 'hours': 3, 'minutes': 28, 'seconds': 28}
-        ---
-
-    """
-    if not isinstance(start_time, int) or not isinstance(end_time, int):
-        raise TypeError('start_time and end_time should be int, bu we got {0} and {1}'.
-                        format(type(start_time), type(end_time)))
-        
-    # 计算天数
-    time_diff = abs(end_time - start_time)
-    days = (time_diff // (60*60*24))
-
-    # 计算小时数
-    remain = time_diff % (60*60*24)
-    hours = (remain // (60*60))
-
-    # 计算分钟数
-    remain = remain % (60*60)
-    minutes = (remain // 60)
-
-    # 计算秒数
-    seconds = remain % 60
-    interval_dict = {"days": days, "hours": hours, "minutes": minutes, "seconds": seconds}
-
-    return interval_dict
-
-
-# v1.1.0 edit by Hu Jun #93
-def transform_unix_to_datetime(timestamp):
-    """
-    将 unix 时间戳转换成 datetime 类型
-
-    :param:
-        * timestamp: (int) unix 时间戳
-    :return:
-        * data_type: (datetime) datetime 类型实例
-
-    举例如下::
-
-        print('--- transform_unix_to_datetime demo ---')
-        import time
-        timestamp = int(time.time())
-        date_type = transform_unix_to_datetime(timestamp)
-        print(type(date_type))
-        print(date_type)
-        
-        print('---')
-
-    执行结果::
-
-        --- transform_unix_to_datetime demo ---
-        <class 'datetime.datetime'>
-        2018-08-22 19:48:03
-        ---
-
-    """
-    if not isinstance(timestamp, float) and not isinstance(timestamp, int):
-        raise TypeError('timestamp should be a float or int, but we got {}'.format(type(timestamp)))
-
-    date_type = datetime.fromtimestamp(timestamp)
-    return date_type
-
-
-# v1.1.1 edit by Hu Jun #101
-def transform_datetime_to_unix(dtime=None):
-    """
-    将 datetime 类型转换成 unix 时间戳
-
-    :param:
-        * dtime: (datetime) datetime 类型实例,默认为当前时间
-    :return:
-        * data_type: (datetime) datetime 类型实例
-
-    举例如下::
-
-        print('--- transform_datetime_to_unix demo ---')
-        dtime = datetime.datetime.now()
-        ans_time = transform_datetime_to_unix(dtime)
-        print(ans_time)
-        print('---')
-
-    执行结果::
-
-        --- transform_datetime_to_unix demo ---
-        1535108620.0
-        ---
-
-    """
-    if not dtime:
-        dtime = datetime.now()
-
-    if not isinstance(dtime, datetime):
-        raise TypeError('dtime should be datetime, but we got {}'.format(type(dtime)))
-
-    return time.mktime(dtime.timetuple())
-
-
-# v1.1.3 edit by Hu Jun #116
-class FishDateTimeFormat(object):
-    """
-    实现 datetime 和 str 之间相互转换，基于 Python 的 datetime.datetime 进行封装和扩展；
-
-    举例如下::
-
-        print('--- FishDateTimeFormat demo ---')
-        datetime_obj = date(year=2018, month=11, day=23)
-        print(FishDateTimeFormat.strftime(datetime_obj, '%Y-%m-%d'))
-        date_time_str = '2018-11-23 23:17:20'
-        time_format = '%Y-%m-%d %H:%M:%S'
-        print(FishDateTimeFormat.strptime(date_time_str, time_format))
-        print('---')
-
-    执行结果::
-
-        --- FishDateTimeFormat demo ---
-        2018-11-23
-        2018-11-23 23:17:20
-        <class 'datetime.datetime'>
-        ---
-
-        """
-    @staticmethod
-    def strftime(date_time=None, time_format=None):
-        """
-        将 datetime 对象转换为 str
-
-        :param:
-            * date_time: (obj) datetime 对象
-            * time_format: (sting) 日期格式字符串
-        :return:
-            * date_time_str: (string) 日期字符串
-        """
-        if not date_time:
-            datetime_now = datetime.now()
-        else:
-            datetime_now = date_time
-        if not time_format:
-            time_format = '%Y/%m/%d %H:%M:%S'
-        return datetime.strftime(datetime_now, time_format)
-
-    @staticmethod
-    def strptime(time_str, time_format):
-        """
-        将 str 转换为 datetime 对象
-
-        :param:
-            * time_str: (string) 日期字符串
-            * time_format: (sting) 日期格式字符串
-        :return:
-            * datetime_obj: (obj) datetime 对象
-        """
-        try:
-            datetime_obj = datetime.strptime(time_str, time_format)
-            return datetime_obj
-        except ValueError as ex:
-            raise ValueError(ex)
+# coding=utf-8
+
+import time
+from datetime import datetime, timedelta
+import calendar
+import random
+
+A_DAY_SECONDS = 24 * 60 * 60
+
+
+# 2016.4.26
+# 输入: date_kind, eg 'last month', 'this month'
+# 输出: tuple, type datetime.date eg '2016-03-01' '2016-03-31'
+# v1.0.14 edit by Hu Jun #61
+def get_date_range(dates, separator='-'):
+    """
+    获取某个月的日期范围，返回该月第一天和最后一天的字符串表示
+    
+    :param:
+        * dates: (string 或者 datetime obj) 月份信息
+        * separator: (string) 分隔符，默认为 '-'
+    :return:
+        * first_day: (string) 该月份的第一天
+        * last_day: (string) 该月份的最后一天
+
+    举例如下::
+
+        print('--- get_date_range demo ---')
+        now_time = datetime.now()
+        print(get_date_range(now_time))
+        print(get_date_range('201802',separator='/'))
+        print('---')
+
+    执行结果::
+
+        --- get_years demo ---
+        ('2018-06-1', '2018-06-30')
+        ('2018/02/1', '2018/02/28')
+        ---
+
+    """
+    if isinstance(dates, str) and dates.isdigit():
+        y = dates[:4]
+        m = dates[4:]
+        if (len(y) != 4) or (not 1 <= int(m) <= 12):
+            raise (ValueError("date must be a date string like '201806', but get {}".format(dates)))
+    elif hasattr(dates, 'year') and hasattr(dates, 'month'):
+        y = str(dates.year)
+        m = str(dates.month)
+    else:
+        raise (TypeError("date except a years string like '201806' or a object has 'year' "
+                         "and 'month' attribute, but get a {}".format(type(dates))))
+    
+    # set month to length 2 if month less than 10
+    m = '0'+m if len(m) != 2 else m
+    
+    mr = calendar.monthrange(int(y), int(m))
+    
+    first_day = separator.join([y, m, '1'])
+    last_day = separator.join([y, m, str(mr[1])])
+
+    return first_day, last_day
+
+
+# v1.0.14 edit by Hu Jun #37
+def get_years(months=0, refer=None):
+    """
+    获取基准时月份增量的年月
+
+    :param:
+        * months: (int) 月份增量，正数为往后年月，整数为往前年月
+        * refer: (datetime obj) datetime 对象，或者有 month 和 year 属性的实例，默认为当前时间
+    :return:
+        * result: (string) 年月字符串
+
+    举例如下::
+
+        print('--- get_years demo ---')
+        print(get_years(-5))
+        print(get_years(7, datetime.now()))
+        print('---')
+
+    执行结果::
+
+        --- get_years demo ---
+        201801
+        201901
+        ---
+
+    """
+    if refer is None:
+        refer = datetime.now()
+    # 计算当前总月份数
+    try:
+        months_count = refer.year * 12 + refer.month
+    except Exception:
+        raise TypeError('refer except {}, got an {}'.format(type(datetime.now()), type(refer)))
+    
+    # 计算结果总月分数
+    months_count += months
+    
+    y, m = divmod(months_count, 12)
+    
+    # 将m的值转换为1-12
+    if m == 0:
+        y -= 1
+        m = 12
+    
+    return ''.join(['%04d' % y, '%02d' % m])
+
+
+# v1.0.16 edit by Hu Jun #87
+# v1.1.14 edit by Hu Jun #142
+# v1.1.15 edit by Jia Chunying #142 #164
+# v1.4 edit by David Yi, #287, 修改 函数 gen_date_by_range()
+# 2021.6.2, #287, 修改 gen_date_by_range() 为 random_date_by_range；gen_date_by_range() 为 random_date_by_range()
+# 2021.6.4, #288, 修改 date_time_this_month() 为 random_datetime_this_month()
+# 2021.6.7, #288, 修改 date_time_this_year() 为 random_datetime_this_year()
+# 2021.6.23, #288, 修改类名称和函数名，更加符合标准
+class RandomTime(object):
+    """
+    获取随机时间
+
+    举例如下::
+
+        print('--- RandomTime demo ---')
+        print(RandomTime.get_date_time_this_month())
+        print(RandomTime.get_date_time_this_year())
+        print('---')
+
+    执行结果::
+
+        --- Base64 demo ---
+        2018-07-01 12:47:20
+        2018-02-08 17:16:09
+        ---
+
+    """
+    @staticmethod
+    def get_random_datetime_this_month():
+        """
+        生成一个属于当前月的随机日期时间
+
+        :return:
+            * random_date: (datetime) 个属于当前月的随机日期时间
+
+        举例如下::
+
+            print('--- RandomTime.get_random_datetime_this_month demo ---')
+            print(RandomTime.get_random_datetime_this_month)
+            print('---')
+
+        执行结果::
+    
+            --- RandomTime.get_date_time_this_month demo demo ---
+            2018-07-01 12:47:20
+            ---
+
+       """
+        now = datetime.now()
+        begin_this_month = now.replace(
+            day=1, hour=0, minute=0, second=0, microsecond=0)
+        this_month_days = calendar.monthrange(now.year, now.month)
+        random_seconds = random.randint(0, this_month_days[1]*A_DAY_SECONDS)
+
+        random_date = begin_this_month + timedelta(seconds=random_seconds)
+
+        return random_date
+
+    @staticmethod
+    def get_random_datetime_this_year():
+        """
+        获取当前年的随机时间字符串
+        
+        :return:
+            * date_this_year: (datetime) 当前月份的随机时间
+
+        举例如下::
+
+            print('--- RandomTime.get_date_time_this_year demo ---')
+            print(RandomTime.get_date_time_this_year())
+            print('---')
+
+        执行结果::
+    
+            --- RandomTime.get_date_time_this_year demo demo ---
+            2018-02-08 17:16:09
+            ---
+       """
+        now = datetime.now()
+        this_year_start = now.replace(
+            month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
+        this_year_days = sum(calendar.mdays)
+        random_seconds = random.randint(0, this_year_days*A_DAY_SECONDS)
+
+        return this_year_start + timedelta(seconds=random_seconds)
+
+    @staticmethod
+    def get_random_date_by_year(year):
+        """
+        获取当前年的随机日期字符串
+
+        :param:
+            * year: (string) 长度为 4 位的年份字符串，eg. 2018
+
+        :return:
+            * date_str: (string) 传入年份内的随机合法日期字符串，eg. 20180201
+        
+        举例如下::
+        
+            print('--- RandomTime.get_random_date_by_year demo ---')
+            print(RandomTime.get_random_date_by_year("2010"))
+            print('---')
+        
+        执行结果::
+        
+            --- RandomTime.gen_date_by_year demo ---
+            20100505
+            ---
+        """
+        if isinstance(year, int) and len(str(year)) != 4:
+            raise ValueError("year should be int year like 2018, but we got {}, {}".
+                             format(year, type(year)))
+        if isinstance(year, str) and len(year) != 4:
+            raise ValueError("year should be string year like '2018', but we got {}, {}".
+                             format(year, type(year)))
+        if isinstance(year, int):
+            year = str(year)
+
+        date_str = RandomTime.get_random_date_by_range(year + "-01-01", year + "-12-31", "%Y-%m-%d")
+
+        return date_str
+
+    @staticmethod
+    def get_random_date_by_range(begin_date, end_date, date_format="%Y-%m-%d"):
+        """
+
+        生成指定日期范围内的一个随机日期
+
+        :param:
+            * begin_date: (string) 起始日期，yyyy-MM-dd，eg. 2018-01-01
+            * end_date: (string) 结束日期，yyyy-MM-dd，eg. 2018-12-31
+            * date_format: (string) 输入的日期格式，默认格式 "%Y-%m-%d"
+
+        :return:
+            * date_str: (string) 日期范围内的一个指定格式的随机日期，指定格式默认为 "%Y%m%d"，eg. 20180530
+
+        举例如下::
+
+            print('--- RandomTime.get_random_date_by_range demo ---')
+            print(RandomTime.get_random_date_by_range("2010-01-01","2010-12-31"))
+            print('---')
+
+        执行结果::
+
+            --- RandomTime.get_random_date_by_range demo ---
+            20100124
+            ---
+        """
+
+        d0 = datetime.strptime(begin_date, date_format)
+        d1 = datetime.strptime(end_date, date_format)
+        random_date = d0 + (d1-d0)*random.random()
+
+        return datetime.strftime(random_date, "%Y%m%d")
+
+
+# v1.1.0 edit by Hu Jun #90
+def get_time_interval(start_time, end_time):
+    """
+    获取两个unix时间戳之间的时间间隔
+
+    :param:
+        * start_time: (int) 开始时间，unix 时间戳
+        * end_time: (int) 结束时间，unix 时间戳
+    :return:
+        * interval_dict: (dict) 时间间隔字典
+
+    举例如下::
+
+        print('--- get_time_interval demo ---')
+        import time
+        start = int(time.time())
+        end = start - 98908
+        print(get_time_interval(end, start))
+        print('---')
+
+    执行结果::
+
+        --- get_time_interval demo ---
+        {'days': 1, 'hours': 3, 'minutes': 28, 'seconds': 28}
+        ---
+
+    """
+    if not isinstance(start_time, int) or not isinstance(end_time, int):
+        raise TypeError('start_time and end_time should be int, bu we got {0} and {1}'.
+                        format(type(start_time), type(end_time)))
+        
+    # 计算天数
+    time_diff = abs(end_time - start_time)
+    days = (time_diff // (60*60*24))
+
+    # 计算小时数
+    remain = time_diff % (60*60*24)
+    hours = (remain // (60*60))
+
+    # 计算分钟数
+    remain = remain % (60*60)
+    minutes = (remain // 60)
+
+    # 计算秒数
+    seconds = remain % 60
+    interval_dict = {"days": days, "hours": hours, "minutes": minutes, "seconds": seconds}
+
+    return interval_dict
+
+
+# v1.1.0 edit by Hu Jun #93
+def transform_unix_to_datetime(timestamp):
+    """
+    将 unix 时间戳转换成 datetime 类型
+
+    :param:
+        * timestamp: (int) unix 时间戳
+    :return:
+        * data_type: (datetime) datetime 类型实例
+
+    举例如下::
+
+        print('--- transform_unix_to_datetime demo ---')
+        import time
+        timestamp = int(time.time())
+        date_type = transform_unix_to_datetime(timestamp)
+        print(type(date_type))
+        print(date_type)
+        
+        print('---')
+
+    执行结果::
+
+        --- transform_unix_to_datetime demo ---
+        <class 'datetime.datetime'>
+        2018-08-22 19:48:03
+        ---
+
+    """
+    if not isinstance(timestamp, float) and not isinstance(timestamp, int):
+        raise TypeError('timestamp should be a float or int, but we got {}'.format(type(timestamp)))
+
+    date_type = datetime.fromtimestamp(timestamp)
+    return date_type
+
+
+# v1.1.1 edit by Hu Jun #101
+def transform_datetime_to_unix(dtime=None):
+    """
+    将 datetime 类型转换成 unix 时间戳
+
+    :param:
+        * dtime: (datetime) datetime 类型实例,默认为当前时间
+    :return:
+        * data_type: (datetime) datetime 类型实例
+
+    举例如下::
+
+        print('--- transform_datetime_to_unix demo ---')
+        dtime = datetime.datetime.now()
+        ans_time = transform_datetime_to_unix(dtime)
+        print(ans_time)
+        print('---')
+
+    执行结果::
+
+        --- transform_datetime_to_unix demo ---
+        1535108620.0
+        ---
+
+    """
+    if not dtime:
+        dtime = datetime.now()
+
+    if not isinstance(dtime, datetime):
+        raise TypeError('dtime should be datetime, but we got {}'.format(type(dtime)))
+
+    return time.mktime(dtime.timetuple())
+
+
+# v1.1.3 edit by Hu Jun #116
+class FishDateTimeFormat(object):
+    """
+    实现 datetime 和 str 之间相互转换，基于 Python 的 datetime.datetime 进行封装和扩展；
+
+    举例如下::
+
+        print('--- FishDateTimeFormat demo ---')
+        datetime_obj = date(year=2018, month=11, day=23)
+        print(FishDateTimeFormat.strftime(datetime_obj, '%Y-%m-%d'))
+        date_time_str = '2018-11-23 23:17:20'
+        time_format = '%Y-%m-%d %H:%M:%S'
+        print(FishDateTimeFormat.strptime(date_time_str, time_format))
+        print('---')
+
+    执行结果::
+
+        --- FishDateTimeFormat demo ---
+        2018-11-23
+        2018-11-23 23:17:20
+        <class 'datetime.datetime'>
+        ---
+
+        """
+    @staticmethod
+    def strftime(date_time=None, time_format=None):
+        """
+        将 datetime 对象转换为 str
+
+        :param:
+            * date_time: (obj) datetime 对象
+            * time_format: (sting) 日期格式字符串
+        :return:
+            * date_time_str: (string) 日期字符串
+        """
+        if not date_time:
+            datetime_now = datetime.now()
+        else:
+            datetime_now = date_time
+        if not time_format:
+            time_format = '%Y/%m/%d %H:%M:%S'
+        return datetime.strftime(datetime_now, time_format)
+
+    @staticmethod
+    def strptime(time_str, time_format):
+        """
+        将 str 转换为 datetime 对象
+
+        :param:
+            * time_str: (string) 日期字符串
+            * time_format: (sting) 日期格式字符串
+        :return:
+            * datetime_obj: (obj) datetime 对象
+        """
+        try:
+            datetime_obj = datetime.strptime(time_str, time_format)
+            return datetime_obj
+        except ValueError as ex:
+            raise ValueError(ex)
diff --git a/fishbase/fish_http_status.py b/fishbase/fish_http_status.py
new file mode 100644
index 0000000..1a4b069
--- /dev/null
+++ b/fishbase/fish_http_status.py
@@ -0,0 +1,163 @@
+"""
+
+``fish_http_status`` 包含最通用的一些网络状态码
+
+https://github.com/openstack/swift/blob/master/swift/common/http.py
+
+"""
+
+
+def is_informational(status):
+    """
+    检查状态码是否信息提示
+
+    :param:
+        * status: http 状态码
+    :return:
+        * result: True or False
+    """
+    return 100 <= status <= 199
+
+
+def is_success(status):
+    """
+    检查状态码是否成功
+
+    :param:
+        * status: http 状态码
+    :return:
+        * result: True or False
+    """
+    return 200 <= status <= 299
+
+
+def is_redirection(status):
+    """
+    检查状态码是否重定向
+
+    :param:
+        * status: http 状态码
+    :return:
+        * result: True or False
+    """
+    return 300 <= status <= 399
+
+
+def is_client_error(status):
+    """
+    检查状态码是否客户端错误
+
+    :param:
+        * status: http 状态码
+    :return:
+        * result: True or False
+    """
+    return 400 <= status <= 499
+
+
+def is_server_error(status):
+    """
+    检查状态码是否服务端错误
+
+    :param:
+        * status: http 状态码
+    :return:
+        * result: True or False
+    """
+    return 500 <= status <= 599
+
+
+# List of HTTP status codes
+
+###############################################################################
+# 1xx Informational
+###############################################################################
+
+
+HTTP_CONTINUE = 100
+HTTP_SWITCHING_PROTOCOLS = 101
+HTTP_PROCESSING = 102  # WebDAV
+HTTP_CHECKPOINT = 103
+HTTP_REQUEST_URI_TOO_LONG = 122
+
+###############################################################################
+# 2xx Success
+###############################################################################
+
+HTTP_OK = 200
+HTTP_CREATED = 201
+HTTP_ACCEPTED = 202
+HTTP_NON_AUTHORITATIVE_INFORMATION = 203
+HTTP_NO_CONTENT = 204
+HTTP_RESET_CONTENT = 205
+HTTP_PARTIAL_CONTENT = 206
+HTTP_MULTI_STATUS = 207  # WebDAV
+HTTP_IM_USED = 226
+
+###############################################################################
+# 3xx Redirection
+###############################################################################
+
+HTTP_MULTIPLE_CHOICES = 300
+HTTP_MOVED_PERMANENTLY = 301
+HTTP_FOUND = 302
+HTTP_SEE_OTHER = 303
+HTTP_NOT_MODIFIED = 304
+HTTP_USE_PROXY = 305
+HTTP_SWITCH_PROXY = 306
+HTTP_TEMPORARY_REDIRECT = 307
+HTTP_RESUME_INCOMPLETE = 308
+
+###############################################################################
+# 4xx Client Error
+###############################################################################
+
+HTTP_BAD_REQUEST = 400
+HTTP_UNAUTHORIZED = 401
+HTTP_PAYMENT_REQUIRED = 402
+HTTP_FORBIDDEN = 403
+HTTP_NOT_FOUND = 404
+HTTP_METHOD_NOT_ALLOWED = 405
+HTTP_NOT_ACCEPTABLE = 406
+HTTP_PROXY_AUTHENTICATION_REQUIRED = 407
+HTTP_REQUEST_TIMEOUT = 408
+HTTP_CONFLICT = 409
+HTTP_GONE = 410
+HTTP_LENGTH_REQUIRED = 411
+HTTP_PRECONDITION_FAILED = 412
+HTTP_REQUEST_ENTITY_TOO_LARGE = 413
+HTTP_REQUEST_URI_TOO_LONG = 414
+HTTP_UNSUPPORTED_MEDIA_TYPE = 415
+HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416
+HTTP_EXPECTATION_FAILED = 417
+HTTP_IM_A_TEAPOT = 418
+HTTP_UNPROCESSABLE_ENTITY = 422  # WebDAV
+HTTP_LOCKED = 423  # WebDAV
+HTTP_FAILED_DEPENDENCY = 424  # WebDAV
+HTTP_UNORDERED_COLLECTION = 425
+HTTP_UPGRADE_REQUIED = 426
+HTTP_PRECONDITION_REQUIRED = 428
+HTTP_TOO_MANY_REQUESTS = 429
+HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
+HTTP_NO_RESPONSE = 444
+HTTP_RETRY_WITH = 449
+HTTP_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS = 450
+HTTP_CLIENT_CLOSED_REQUEST = 499
+
+###############################################################################
+# 5xx Server Error
+###############################################################################
+
+HTTP_INTERNAL_SERVER_ERROR = 500
+HTTP_NOT_IMPLEMENTED = 501
+HTTP_BAD_GATEWAY = 502
+HTTP_SERVICE_UNAVAILABLE = 503
+HTTP_GATEWAY_TIMEOUT = 504
+HTTP_VERSION_NOT_SUPPORTED = 505
+HTTP_VARIANT_ALSO_NEGOTIATES = 506
+HTTP_INSUFFICIENT_STORAGE = 507  # WebDAV
+HTTP_BANDWIDTH_LIMIT_EXCEEDED = 509
+HTTP_NOT_EXTENDED = 510
+HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511
+HTTP_NETWORK_READ_TIMEOUT_ERROR = 598     # not used in RFC
+HTTP_NETWORK_CONNECT_TIMEOUT_ERROR = 599  # not used in RFC
diff --git a/fishbase/fish_logger.py b/fishbase/fish_logger.py
index 9301265..cf6c302 100644
--- a/fishbase/fish_logger.py
+++ b/fishbase/fish_logger.py
@@ -157,13 +157,14 @@ def set_log_file(local_file=None, file_name_format='%project_name-%log-%date'):
     """
 
     default_log_file = 'default.log'
-
-    _formatter = logging.Formatter(
-        '%(asctime)s %(levelname)s %(filename)s[ln:%(lineno)d] %(message)s')
-
     if local_file is not None:
         default_log_file = local_file
 
+    # 创建日志目录
+    parent_dir = os.path.dirname(default_log_file)
+    if not os.path.exists(parent_dir):
+        os.makedirs(parent_dir)
+
     support_split = ['%project_name', '%log', '%date']
 
     file_format_split = file_name_format.split('-')
@@ -174,6 +175,9 @@ def set_log_file(local_file=None, file_name_format='%project_name-%log-%date'):
     # _tfh = TimedRotatingFileHandler(default_log_file, when="midnight")
     _tfh = SafeFileHandler(filename=default_log_file, file_name_format=file_name_format)
     _tfh.setLevel(logging.INFO)
+
+    _formatter = logging.Formatter(
+        '%(asctime)s %(levelname)s %(filename)s[ln:%(lineno)d] %(message)s')
     _tfh.setFormatter(_formatter)
 
     logger.setLevel(logging.INFO)
diff --git a/fishbase/fish_project.py b/fishbase/fish_project.py
index 8d4c54b..2117286 100644
--- a/fishbase/fish_project.py
+++ b/fishbase/fish_project.py
@@ -10,6 +10,10 @@
 import yaml
 import os
 
+try:
+    from yaml import CLoader as Loader, CDumper as Dumper
+except ImportError:
+    from yaml import Loader, Dumper
 
 package_yml = """
 project: hellopackage
@@ -102,7 +106,9 @@ def init_project_by_yml(project_config=None, dist=None):
     if os.path.isfile(project_config):
         project_config = open(project_config)
     try:
-        yml_data = yaml.load(project_config)
+        yml_data = yaml.load(project_config, Loader=Loader)
+        if not yml_data or type(yml_data) != dict:
+            raise RuntimeError('fail to load')
         project_name = yml_data['project']
         project_tree = yml_data['tree']
     except Exception as e:
diff --git a/fishbase/fish_random.py b/fishbase/fish_random.py
index baa4076..72b0461 100644
--- a/fishbase/fish_random.py
+++ b/fishbase/fish_random.py
@@ -1,644 +1,648 @@
-# coding=utf-8
-"""
-
-``fish_random`` 包含的是一些生成随机值的函数。
-
-"""
-
-# 2019.1.6 edit by David Yi, #187 #188 修改 IdCard 和 CardBin 两个类，对这里有修改
-# 2019.1.13 edit by David Yi, #202 优化 class CardBin(), class IdCard()
-# 2019.1.18 edit by Hu Jun, #204 优化函数名称, #200 remove fish_common.get_random_str to gen_random_str
-
-# 2018.12.26 v1.1.5 created
-import string
-import random
-from fishbase.fish_date import GetRandomTime, FishDateTimeFormat
-from fishbase.fish_data import CardBin, IdCard
-
-
-# v1.1.6 edit by Hu Jun #200 合并 fish_common.get_random_str 为 gen_random_str
-# v1.1.5 edit by Hu Jun #163
-def gen_random_str(min_length, max_length, prefix=None, suffix=None,
-                   has_letter=True, has_digit=False, has_punctuation=False):
-    """
-    指定一个前后缀、字符串长度以及字符串包含字符类型，返回随机生成带有前后缀及指定长度的字符串
-
-    :param:
-        * min_length: (int) 字符串最小长度
-        * max_length: (int) 字符串最小长度
-        * prefix: (string) 字符串前缀
-        * suffix: (string) 字符串后缀
-        * has_letter: (bool) 字符串时候包含字母，默认为 True
-        * has_digit: (bool) 字符串是否包含数字，默认为 False
-        * has_punctuation: (bool) 字符串是否包含标点符号，默认为 False
-
-    :return:
-        * random_str: (string) 指定规则的随机字符串
-
-    举例如下::
-
-        print('--- gen_random_str demo ---')
-        print(gen_random_str(5, 7))
-        print(gen_random_str(5, 7, prefix='FISHBASE_'))
-        print(gen_random_str(5, 7, prefix='FISHBASE_', suffix='.py'))
-        print(gen_random_str(5, 7, has_digit=True, has_punctuation=True))
-        print(gen_random_str(5, 7, prefix='FISHBASE_', has_digit=True, has_punctuation=True))
-        print('---')
-
-    执行结果::
-
-        --- gen_string_by_range demo ---
-        q4uo6E8
-
-        FISHBASE_8uCBEUH
-
-        FISHBASE_D4wRX2.py
-
-        FISHBASE_65nqlNs
-
-        FISHBASE_3"uFm$s
-        ---
-    """
-    if not all([isinstance(min_length, int), isinstance(max_length, int)]):
-        raise ValueError('min_length and max_length should be int, but we got {} and {}'.
-                         format(type(min_length), type(max_length)))
-
-    if min_length > max_length:
-        raise ValueError('min_length should less than or equal to max_length')
-
-    # 避免随机源为空
-    if not any([has_letter, has_digit, has_punctuation]):
-        raise ValueError('At least one value is True in has_letter, has_digit and has_punctuation')
-
-    random_str_len = random.randint(min_length, max_length)
-
-    random_source = ''
-    random_source += string.ascii_letters if has_letter else ''
-    random_source += string.digits if has_digit else ''
-    random_source += string.punctuation if has_punctuation else ''
-
-    # 避免出现 ValueError: Sample larger than population or is negative
-    if random_str_len > len(random_source):
-        random_source *= (random_str_len // len(random_source) + 1)
-
-    mid_random_str = ''.join(random.sample(random_source, random_str_len))
-
-    prefix = prefix if prefix else ''
-    suffix = suffix if suffix else ''
-
-    random_str = ''.join([prefix, mid_random_str, suffix])
-
-    return random_str
-
-
-# v1.1.6 add by Hu Jun #204
-# v1.1.5 add by Jia Chunying #171
-# v1.3 edit by David Yi, #273
-def gen_random_name(family_name=None, gender=None, length=None):
-    """
-    指定姓氏、性别、长度，返回随机人名，也可不指定生成随机人名
-
-    :param:
-        * family_name: (string) 姓
-        * gender: (string) 性别 "01" 男性， "00" 女性, 默认 None: 随机
-        * length: (int) 大于等于 2 小于等于 10 的整数, 默认 None: 随机 2 或者 3
-
-    :return:
-        * full_name: (string) 随机人名
-
-    举例如下::
-
-        print('--- gen_random_name demo ---')
-        print(gen_random_name())
-        print(gen_random_name("赵","01", 3))
-        print('---')
-
-    执行结果::
-
-        --- gen_random_name demo ---
-        师艺
-        赵群腾
-        ---
-
-    """
-    family_word = ("赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛"
-                   "奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康"
-                   "伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵"
-                   "席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯咎管卢莫经房裘缪干解应宗"
-                   "宣丁贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣翁荀羊於惠甄魏加封芮羿储靳汲邴糜松井段富巫"
-                   "乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭厉戎祖武符刘姜詹束龙叶幸司韶郜黎蓟薄"
-                   "印宿白怀蒲台从鄂索咸籍赖卓蔺屠蒙池乔阴郁胥能苍双闻莘党翟谭贡劳逄姬申扶堵冉宰郦雍却璩桑桂"
-                   "濮牛寿通边扈燕冀郏浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庚终暨居衡步都耿满弘"
-                   "匡国文寇广禄阙东殴殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相"
-                   "查后江红游竺权逯盖益桓公万俟司马上官欧阳夏侯诸葛闻人东方赫连皇甫尉迟公羊澹台公冶宗政濮阳"
-                   "淳于仲孙太叔申屠公孙乐正轩辕令狐钟离闾丘长孙慕容鲜于宇文司徒司空亓官司寇仉督子车颛孙端木"
-                   "巫马公西漆雕乐正壤驷公良拓拔夹谷宰父谷粱晋楚阎法汝鄢涂钦段干百里东郭南门呼延归海羊舌微生"
-                   "岳帅缑亢况后有琴梁丘左丘东门西门商牟佘佴伯赏南宫墨哈谯笪年爱阳佟第五言福百家姓续")
-
-    name_dict = {"00": ("秀娟英华慧巧美娜静淑惠珠翠雅芝玉萍红娥玲芬芳燕彩春菊兰凤洁梅琳素云莲真环雪荣爱妹霞"
-                        "香月莺媛艳瑞凡佳嘉琼勤珍贞莉桂娣叶璧璐娅琦晶妍茜秋珊莎锦黛青倩婷姣婉娴瑾颖露瑶怡婵"
-                        "雁蓓纨仪荷丹蓉眉君琴蕊薇菁梦岚苑婕馨瑗琰韵融园艺咏卿聪澜纯毓悦昭冰爽琬茗羽希宁欣飘"
-                        "育滢馥筠柔竹霭凝晓欢霄枫芸菲寒伊亚宜可姬舒影荔枝思丽"),
-                 "01": ("伟刚勇毅俊峰强军平保东文辉力明永健世广志义兴良海山仁波宁贵福生龙元全国胜学祥才发武"
-                        "新利清飞彬富顺信子杰涛昌成康星光天达安岩中茂进林有坚和彪博诚先敬震振壮会思群豪心邦"
-                        "承乐绍功松善厚庆磊民友裕河哲江超浩亮政谦亨奇固之轮翰朗伯宏言若鸣朋斌梁栋维启克伦翔"
-                        "旭鹏泽晨辰士以建家致树炎德行时泰盛雄琛钧冠策腾楠榕风航弘")}
-
-    if family_name is None:
-        family_name = random.choice(family_word)
-    if gender is None or gender not in ['00', '01']:
-        gender = random.choice(['00', '01'])
-    if length is None or length not in [2, 3, 4, 5, 6, 7, 8, 9, 10]:
-        length = random.choice([2, 3])
-    name = "".join([random.choice(name_dict[gender]) for _ in range(length - 1)])
-    full_name = "{family_name}{name}".format(family_name=family_name, name=name)
-    return full_name
-
-
-# v1.1.6 add by Hu Jun #204
-# v1.1.5 add by Jia Chunying #166
-def gen_random_mobile():
-    """
-    随机生成一个手机号
-
-    :return:
-        * str: (string) 手机号
-
-    举例如下::
-
-        print('--- gen_random_mobile demo ---')
-        print(gen_random_mobile())
-        print(gen_random_mobile())
-        print('---')
-
-    执行结果::
-
-        --- gen_random_mobile demo ---
-        16706146773
-        14402633925
-        ---
-
-    """
-    prefix_list = ["13",
-                   "1400", "1410", "1440", "145", "146", "147", "148",
-                   "15",
-                   "162", "165", "166", "167",
-                   "170", "171", "172", "173", "175", "176", "177", "178", "1740",
-                   "18",
-                   "191", "198", "199"]
-    prefix_str = random.choice(prefix_list)
-    return prefix_str + "".join(random.choice("0123456789") for _ in range(11 - len(prefix_str)))
-
-
-# v1.1.6 edit by Hu Jun #204
-# v1.1.6 edit by Hu Jun #190
-# v1.1.5 edit by Hu Jun #162
-def gen_random_float(minimum, maximum, decimals=2):
-    """
-    指定一个浮点数范围，随机生成并返回区间内的一个浮点数，区间为闭区间
-    受限于 random.random 精度限制，支持最大 15 位精度
-
-    :param:
-        * minimum: (float) 浮点数最小取值
-        * maximum: (float) 浮点数最大取值
-        * decimals: (int) 小数位数，默认为 2 位
-
-    :return:
-        * random_float: (float) 随机浮点数
-
-    举例如下::
-
-        print('--- gen_random_float demo ---')
-        print(gen_random_float(1.0, 9.0))
-        print(gen_random_float(1.0, 9.0, decimals=10))
-        print(gen_random_float(1.0, 9.0, decimals=20))
-        print('---')
-
-    执行结果::
-
-        --- gen_random_float demo ---
-        6.08
-        6.8187342239
-        2.137902497554043
-        ---
-
-    """
-    if not (isinstance(minimum, float) and isinstance(maximum, float)):
-        raise ValueError('param minimum, maximum should be float, but got minimum: {} maximum: {}'.
-                         format(type(minimum), type(maximum)))
-    if not isinstance(decimals, int):
-        raise ValueError('param decimals should be a int, but we got {}'.format(type(decimals)))
-    # 精度目前只支持最大 15 位
-    decimals = 15 if decimals > 15 else decimals
-    # 存在 round 之后四舍五入之后，精度不匹配的情况，新加判断
-    while True:
-        random_float = random.uniform(minimum, maximum)
-        random_float = round(random_float, decimals)
-        if len(str(random_float).split('.')[-1]) == decimals:
-            return random_float
-
-
-# v1.1.6 edit by Hu Jun #204
-# v1.1.5 edit by Hu Jun #173
-# v1.1.12 edit by Hu Jun #232
-# v1.3 edit by David Yi, #273
-def get_random_areanote(zone):
-    """
-    省份行政区划代码，返回下辖的随机地区名称
-
-    :param:
-        * zone: (string) 省份行政区划代码 比如 '310000'
-
-    :returns:
-        * random_areanote: (string) 省份下辖随机地区名称
-
-    举例如下::
-
-        print('--- fish_data get_random_areanote demo ---')
-        print(get_random_areanote(310000))
-        print('---')
-
-    输出结果::
-
-        --- fish_data get_random_areanote demo ---
-        徐汇区
-        ---
-
-    """
-    # 获取省份下的地区信息
-    province = str(zone)[:2]
-    areanote_list = IdCard.get_areanote_info(province)
-    # 选出省份名称
-    province_name_list = [item for item in areanote_list if item[0] == str(zone)]
-
-    if not (areanote_list and province_name_list):
-        raise ValueError('zone error, please check and try again')
-
-    province_name = province_name_list[0][-1]
-    while True:
-        random_areanote = random.choice(areanote_list)
-        # 只选取下辖区域
-        if random_areanote != province_name_list[0]:
-            break
-    full_areanote = random_areanote[-1]
-    return full_areanote.split(province_name)[-1]
-
-
-# v1.1.6 edit by Hu Jun #204
-# v1.1.5 edit by Hu Jun #170
-# v1.3 edit by David Yi, #273
-def gen_random_address(zone):
-    """
-    通过省份行政区划代码，返回该省份的随机地址
-
-    :param:
-        * zone: (string) 省份行政区划代码 比如 '310000'
-
-    :returns:
-        * random_addr: (string) 省份下辖随机地区名称
-
-    举例如下::
-
-        print('--- gen_address demo ---')
-        print(gen_random_address('310000'))
-        print('---')
-
-    输出结果::
-
-        --- gen_address demo ---
-        上海市卢湾区陵县支街918号
-        ---
-
-    """
-    # 获取省份下的地区信息
-    province = str(zone)[:2]
-    areanote = IdCard.get_areanote_info(province)
-    if not areanote:
-        raise ValueError('zone error, please check and try again')
-    # 第一项是省份名称
-    province_name = areanote[0][-1]
-    areanote_info = get_random_areanote(zone)
-    address_word = ("重庆大厦,黑龙江路,十梅庵街,遵义路,湘潭街,瑞金广场,仙山街,仙山东路,仙山西大厦,白沙河路,"
-                    "赵红广场,机场路,民航街,长城南路,流亭立交桥,虹桥广场,长城大厦,礼阳路,风岗街,中川路,"
-                    "白塔广场,兴阳路,文阳街,绣城路,河城大厦,锦城广场,崇阳街,华城路,康城街,正阳路,和阳广场,"
-                    "中城路,江城大厦,顺城路,安城街,山城广场,春城街,国城路,泰城街,德阳路,明阳大厦,春阳路,"
-                    "艳阳街,秋阳路,硕阳街,青威高速,瑞阳街,丰海路,双元大厦,惜福镇街道,夏庄街道,古庙工业园,"
-                    "中山街,太平路,广西街,潍县广场,博山大厦,湖南路,济宁街,芝罘路,易州广场,荷泽四路,"
-                    "荷泽二街,荷泽一路,荷泽三大厦,观海二广场,广西支街,观海一路,济宁支街,莒县路,平度广场,"
-                    "明水路,蒙阴大厦,青岛路,湖北街,江宁广场,郯城街,天津路,保定街,安徽路,河北大厦,黄岛路,"
-                    "北京街,莘县路,济南街,宁阳广场,日照街,德县路,新泰大厦,荷泽路,山西广场,沂水路,肥城街,"
-                    "兰山路,四方街,平原广场,泗水大厦,浙江路,曲阜街,寿康路,河南广场,泰安路,大沽街,红山峡支路,"
-                    "西陵峡一大厦,台西纬一广场,台西纬四街,台西纬二路,西陵峡二街,西陵峡三路,台西纬三广场,"
-                    "台西纬五路,明月峡大厦,青铜峡路,台西二街,观音峡广场,瞿塘峡街,团岛二路,团岛一街,台西三路,"
-                    "台西一大厦,郓城南路,团岛三街,刘家峡路,西藏二街,西藏一广场,台西四街,三门峡路,城武支大厦,"
-                    "红山峡路,郓城北广场,龙羊峡路,西陵峡街,台西五路,团岛四街,石村广场,巫峡大厦,四川路,寿张街,"
-                    "嘉祥路,南村广场,范县路,西康街,云南路,巨野大厦,西江广场,鱼台街,单县路,定陶街,滕县路,"
-                    "钜野广场,观城路,汶上大厦,朝城路,滋阳街,邹县广场,濮县街,磁山路,汶水街,西藏路,城武大厦,"
-                    "团岛路,南阳街,广州路,东平街,枣庄广场,贵州街,费县路,南海大厦,登州路,文登广场,信号山支路,"
-                    "延安一街,信号山路,兴安支街,福山支广场,红岛支大厦,莱芜二路,吴县一街,金口三路,金口一广场,"
-                    "伏龙山路,鱼山支街,观象二路,吴县二大厦,莱芜一广场,金口二街,海阳路,龙口街,恒山路,鱼山广场,"
-                    "掖县路,福山大厦,红岛路,常州街,大学广场,龙华街,齐河路,莱阳街,黄县路,张店大厦,祚山路,苏州街,"
-                    "华山路,伏龙街,江苏广场,龙江街,王村路,琴屿大厦,齐东路,京山广场,龙山路,牟平街,延安三路,"
-                    "延吉街,南京广场,东海东大厦,银川西路,海口街,山东路,绍兴广场,芝泉路,东海中街,宁夏路,香港西大厦,"
-                    "隆德广场,扬州街,郧阳路,太平角一街,宁国二支路,太平角二广场,天台东一路,太平角三大厦,漳州路一路,"
-                    "漳州街二街,宁国一支广场,太平角六街,太平角四路,天台东二街,太平角五路,宁国三大厦,澳门三路,"
-                    "江西支街,澳门二路,宁国四街,大尧一广场,咸阳支街,洪泽湖路,吴兴二大厦,澄海三路,天台一广场,"
-                    "新湛二路,三明北街,新湛支路,湛山五街,泰州三广场,湛山四大厦,闽江三路,澳门四街,南海支路,"
-                    "吴兴三广场,三明南路,湛山二街,二轻新村镇,江南大厦,吴兴一广场,珠海二街,嘉峪关路,高邮湖街,"
-                    "湛山三路,澳门六广场,泰州二路,东海一大厦,天台二路,微山湖街,洞庭湖广场,珠海支街,福州南路,"
-                    "澄海二街,泰州四路,香港中大厦,澳门五路,新湛三街,澳门一路,正阳关街,宁武关广场,闽江四街,"
-                    "新湛一路,宁国一大厦,王家麦岛,澳门七广场,泰州一路,泰州六街,大尧二路,青大一街,闽江二广场,"
-                    "闽江一大厦,屏东支路,湛山一街,东海西路,徐家麦岛函谷关广场,大尧三路,晓望支街,秀湛二路,"
-                    "逍遥三大厦,澳门九广场,泰州五街,澄海一路,澳门八街,福州北路,珠海一广场,宁国二路,临淮关大厦,"
-                    "燕儿岛路,紫荆关街,武胜关广场,逍遥一街,秀湛四路,居庸关街,山海关路,鄱阳湖大厦,新湛路,漳州街,"
-                    "仙游路,花莲街,乐清广场,巢湖街,台南路,吴兴大厦,新田路,福清广场,澄海路,莆田街,海游路,镇江街,"
-                    "石岛广场,宜兴大厦,三明路,仰口街,沛县路,漳浦广场,大麦岛,台湾街,天台路,金湖大厦,高雄广场,海江街,"
-                    "岳阳路,善化街,荣成路,澳门广场,武昌路,闽江大厦,台北路,龙岩街,咸阳广场,宁德街,龙泉路,丽水街,"
-                    "海川路,彰化大厦,金田路,泰州街,太湖路,江西街,泰兴广场,青大街,金门路,南通大厦,旌德路,汇泉广场,"
-                    "宁国路,泉州街,如东路,奉化街,鹊山广场,莲岛大厦,华严路,嘉义街,古田路,南平广场,秀湛路,长汀街,"
-                    "湛山路,徐州大厦,丰县广场,汕头街,新竹路,黄海街,安庆路,基隆广场,韶关路,云霄大厦,新安路,仙居街,"
-                    "屏东广场,晓望街,海门路,珠海街,上杭路,永嘉大厦,漳平路,盐城街,新浦路,新昌街,高田广场,市场三街,"
-                    "金乡东路,市场二大厦,上海支路,李村支广场,惠民南路,市场纬街,长安南路,陵县支街,冠县支广场,"
-                    "小港一大厦,市场一路,小港二街,清平路,广东广场,新疆路,博平街,港通路,小港沿,福建广场,高唐街,"
-                    "茌平路,港青街,高密路,阳谷广场,平阴路,夏津大厦,邱县路,渤海街,恩县广场,旅顺街,堂邑路,李村街,"
-                    "即墨路,港华大厦,港环路,馆陶街,普集路,朝阳街,甘肃广场,港夏街,港联路,陵县大厦,上海路,宝山广场,"
-                    "武定路,长清街,长安路,惠民街,武城广场,聊城大厦,海泊路,沧口街,宁波路,胶州广场,莱州路,招远街,"
-                    "冠县路,六码头,金乡广场,禹城街,临清路,东阿街,吴淞路,大港沿,辽宁路,棣纬二大厦,大港纬一路,贮水山支街,"
-                    "无棣纬一广场,大港纬三街,大港纬五路,大港纬四街,大港纬二路,无棣二大厦,吉林支路,大港四街,普集支路,"
-                    "无棣三街,黄台支广场,大港三街,无棣一路,贮水山大厦,泰山支路,大港一广场,无棣四路,大连支街,大港二路,"
-                    "锦州支街,德平广场,高苑大厦,长山路,乐陵街,临邑路,嫩江广场,合江路,大连街,博兴路,蒲台大厦,黄台广场,"
-                    "城阳街,临淄路,安邱街,临朐路,青城广场,商河路,热河大厦,济阳路,承德街,淄川广场,辽北街,阳信路,益都街,"
-                    "松江路,流亭大厦,吉林路,恒台街,包头路,无棣街,铁山广场,锦州街,桓台路,兴安大厦,邹平路,胶东广场,章丘路,"
-                    "丹东街,华阳路,青海街,泰山广场,周村大厦,四平路,台东西七街,台东东二路,台东东七广场,台东西二路,东五街,"
-                    "云门二路,芙蓉山村,延安二广场,云门一街,台东四路,台东一街,台东二路,杭州支广场,内蒙古路,台东七大厦,"
-                    "台东六路,广饶支街,台东八广场,台东三街,四平支路,郭口东街,青海支路,沈阳支大厦,菜市二路,菜市一街,"
-                    "北仲三路,瑞云街,滨县广场,庆祥街,万寿路,大成大厦,芙蓉路,历城广场,大名路,昌平街,平定路,长兴街,"
-                    "浦口广场,诸城大厦,和兴路,德盛街,宁海路,威海广场,东山路,清和街,姜沟路,雒口大厦,松山广场,长春街,"
-                    "昆明路,顺兴街,利津路,阳明广场,人和路,郭口大厦,营口路,昌邑街,孟庄广场,丰盛街,埕口路,丹阳街,汉口路,"
-                    "洮南大厦,桑梓路,沾化街,山口路,沈阳街,南口广场,振兴街,通化路,福寺大厦,峄县路,寿光广场,曹县路,昌乐街,"
-                    "道口路,南九水街,台湛广场,东光大厦,驼峰路,太平山,标山路,云溪广场,太清路")
-    random_addr = random.choice(address_word.split(','))
-    if random_addr.endswith('路') or random_addr.endswith('街'):
-        random_addr = ''.join([random_addr, str(random.randint(1, 1000)), '号'])
-    address_pattern = '{province_name}{areanote_info}{random_addr}'
-    return address_pattern.format(province_name=province_name,
-                                  areanote_info=areanote_info,
-                                  random_addr=random_addr)
-
-
-# v1.1.5 edit by Hu Jun #172
-# v1.1.11 edit by Hu Jun #229
-# v1.3 edit by David Yi, #273
-def gen_random_bank_card(bank_name=None, card_type=None):
-    """
-    通过指定的银行名称，随机生成该银行的卡号
-
-    :param:
-        * bank_name: (string) 银行名称 eg. 中国银行
-        * card_type：(string) 卡种类，可选 CC(信用卡)、DC(借记卡)
-
-    :returns:
-        * random_bank_card: (string) 随机生成的银行卡卡号
-
-    举例如下::
-
-        print('--- gen_random_bank_card demo ---')
-        print(gen_random_bank_card())
-        print(gen_random_bank_card('中国银行', 'CC'))
-        print(gen_random_bank_card('中国银行', 'DC'))
-        print('---')
-
-    输出结果::
-
-        --- gen_random_bank_card demo ---
-        6282689914390956
-        6259073791134721
-        6212836989522229131
-        ---
-
-    """
-    if not bank_name:
-        # 不指定银行名称时，银行名称从 中国工商银行、中国农业银行、中国银行、中国建设银行 中选取
-        bank_name = random.choice(['中国工商银行', '中国农业银行', '中国银行', '中国建设银行'])
-    if not card_type:
-        card_type = random.choice(['DC', 'CC'])
-
-    bank_info = CardBin.get_bank_info(bank_name)
-    if not bank_info:
-        raise ValueError('bankname {} error, check and try again'.format(bank_name))
-
-    # 获取银行代码
-    bank = bank_info[0][0]
-
-    # 获取 cardbin
-    cardbin_info = CardBin.get_cardbin_info(bank, card_type)
-    if not cardbin_info:
-        raise ValueError('card_type {} error, check and try again'.format(card_type))
-
-    random_cardbin = random.choice(cardbin_info)
-
-    cardbin = random_cardbin[0]
-    card_len = random_cardbin[-1]
-
-    # 银行卡前缀
-    card_number_str = cardbin
-
-    # 随机生成前N-1位
-    while len(card_number_str) < card_len - 1:
-        card_number_str += str(random.randint(0, 9))
-
-    # 获取校验位
-    check_code = CardBin.get_checkcode(card_number_str)
-    return card_number_str + check_code
-
-
-# v1.1.5 edit by Hu Jun #165
-# v1.1.12 edit by Hu Jun #232
-# v1.3 edit by David Yi, #273
-def gen_random_id_card(zone=None, gender=None, age=None, birth_year=None, result_type='SINGLE_STR'):
-    """
-    根据指定的省份编号、性别或年龄，随机生成一个身份证号
-
-    :param:
-        * zone: (string) 省份编号 eg. 310000, 默认 None: 随机
-        * gender：(string) 性别 "01" 男性， "00" 女性, 默认 None: 随机
-        * age：(int) 年龄 默认 None：随机最大年龄100岁
-        * result_type: (string) 返回结果数量类型，默认值 'SINGLE_STR'，表示随机返回一个身份证号，可选 'LIST'，返回一个随机身份证列表
-
-    :returns:
-        * id_num_list: (list) 随机生成的身份证号组成的列表
-
-    举例如下::
-
-        print('--- gen_random_id_card demo ---')
-        print(gen_random_id_card('310000'))
-        print(gen_random_id_card('310000', age=100))
-        print(gen_random_id_card('310000', age=100, birth_year=1990))
-        print(gen_random_id_card('310000', age=30, gender='00'))
-        print(gen_random_id_card(age=30, gender='01', result_type='LIST'))
-        print('---')
-
-    输出结果::
-
-        --- gen_random_id_card demo ---
-        ['310108194005039404']
-        ['310225192009222639']
-        ['310114199505252156']
-        ['310102199001126142']
-        ['412822199003256855', '411330199011239176', '410102199011165210', '410328199012105337', ...
-        ---
-
-    """
-    if zone:
-        province = zone[:2]
-    else:
-        province_list = IdCard.get_province_info()
-        province = random.choice(province_list)[0]
-        zone = province + '0000'
-    areanote_list = IdCard.get_areanote_info(province)
-
-    # 判断 zone 是否合法
-    if zone[:2] != '0000' and zone not in [item[0] for item in areanote_list]:
-        raise ValueError('zone {} error, check and try again'.format(zone))
-
-    # 删除省份编号，身份证前缀不包含省份编号
-    # areanote_list.remove([item for item in areanote_list if item[0] == zone][0])
-
-    zone_list = [item[0] for item in areanote_list]
-
-    total_num = 1 if result_type == 'SINGLE_STR' else 20
-
-    id_num_list = list()
-
-    for _ in range(total_num):
-        # 顺序码的奇数分配给男性，偶数分配给女性
-        gender = gender if gender else random.choice(['00', '01'])
-        gender_dict = {'00': [0, 2, 4, 6, 8],
-                       '01': [1, 3, 5, 7, 9]}
-
-        now_date_str = FishDateTimeFormat.strftime(time_format='%Y-%m-%d')
-        year, month, day = now_date_str.split('-')
-        # 年龄
-        if not age:
-            # 随机年龄最大100岁
-            age = random.randint(0, 100)
-        if birth_year:
-            age = int(year) - int(birth_year)
-
-        start_date_str = '{year}-{month}-{day}'.format(year=int(year) - age, month=month, day=day)
-        birth = GetRandomTime.gen_date_by_range(start_date_str, now_date_str, date_format="%Y%m%d")
-        birth = str(int(year) - age) + birth[4:]
-
-        while True:
-            zone = random.choice(zone_list)
-            # 身份证前缀不包含省份编号
-            if zone[2:] != '0000':
-                break
-
-        random_str = str(random.randint(10, 99))
-        gender_str = str(random.choice(gender_dict.get(gender)))
-        _, check_code = IdCard.get_checkcode(zone + birth + random_str + gender_str)
-        random_id = ("{zone}{birth_date}{random_str}{gender}{check_code}".
-                     format(zone=zone,
-                            birth_date=birth,
-                            random_str=random_str,
-                            gender=gender_str,
-                            check_code=check_code))
-        id_num_list.append(random_id)
-    return id_num_list
-
-
-# v1.1.6 edit by Hu Jun #204
-# v1.1.5 edit by Hu Jun #171
-def gen_random_company_name():
-    """
-    随机生成一个公司名称
-
-    :returns:
-        * company_name: (string) 银行名称
-
-    举例如下::
-
-        print('--- gen_random_company_name demo ---')
-        print(gen_random_company_name())
-        print('---')
-
-    输出结果::
-
-        --- gen_random_company_name demo ---
-        上海大升旅游质询有限责任公司
-        ---
-
-    """
-    region_info = ("北京,上海,广州,深圳,天津,成都,杭州,苏州,重庆,武汉,南京,大连,沈阳,长沙,郑州,西安,青岛,"
-                   "无锡,济南,宁波,佛山,南通,哈尔滨,东莞,福州,长春,石家庄,烟台,合肥,唐山,常州,太原,昆明,"
-                   "潍坊,南昌,泉州,温州,绍兴,嘉兴,厦门,贵阳,淄博,徐州,南宁,扬州,呼和浩特,鄂尔多斯,乌鲁木齐,"
-                   "金华,台州,镇江,威海,珠海,东营,大庆,中山,盐城,包头,保定,济宁,泰州,廊坊,兰州,洛阳,宜昌,"
-                   "沧州,临沂,泰安,鞍山,邯郸,惠州,江门,襄阳,湖州,吉林,芜湖,德州,聊城,漳州,株洲,淮安,榆林,"
-                   "常德,咸阳,衡阳,滨州,柳州,遵义,菏泽,南阳,新乡,湛江,岳阳,郴州,许昌,连云港,枣庄,茂名,周口,"
-                   "宿迁")
-    middle_word = ("泰宏本晶辉昌昌本同永康洪皇贵久圣正裕如恒长佳协义晶合优荣汇洪千东祥复昌皇久丰兴昌国裕亚大"
-                   "荣康通仁元裕厚瑞如弘升久隆旺吉德谦长贵百久汇百伟升隆复飞佳隆浩发丰亨公荣复光福美禄欣丰大"
-                   "祥晶宏中仁宏华隆盈旺仁顺春满美中谦瑞和圣多信合盛千亚晶祥鑫隆飞鑫优合本旺发久国汇百恒佳东"
-                   "洪通恒大公中优广宝盈泰如合丰捷本伟华春元亚广中晶如浩仁汇亚永凯富富裕茂华中飞浩台美佳圣仁"
-                   "成全润金庆百贵康仁茂皇东广荣宏荣新元康公升亨洪福伟永义巨国升进合耀巨润巨元发洪源寿仁发光"
-                   "顺升凯全全辉欣成公裕康合禄兴汇顺浩贵晶捷东飞益福宏国禄元昌弘和满发巨宝生耀隆大欣昌佳本兴"
-                   "吉生宝凯润新高和元亨巨久光益旺春巨鑫进东晶中飞兴中美丰同晶复耀进洪全兴汇宝捷伟仁安宏多庆"
-                   "益生和干干福亚新复吉亚恒亚春德飞伟利庆华丰宏合德瑞进顺祥信合康富益全巨茂台谦厚台成福捷浩"
-                   "信长飞长金利美昌满丰干佳美金洪昌富千和美旺旺晶春仁华中凯浩鼎泰辉新干高进辉同欣广庆吉益德"
-                   "浩中润和春元生高进皇茂利同盈复复晶多巨圣弘捷公宝汇鑫成高新正和和巨祥光宏大丰欣恒昌昌厚合"
-                   "庆泰丰干益和金洪复元顺捷金万辉全吉庆德瑞优长鼎顺汇顺欣飞浩荣祥光泰多春凯信进公优飞昌协美"
-                   "多发中盈协成祥益昌汇泰春满千鼎东光优谦仁中飞生恒伟福晶宝信辉金皇升飞亨鑫安伟华元旺益大寿"
-                   "皇元康耀久荣满协信凯谦宝巨丰正光发康康捷中源国多多康公利顺光辉如茂晶永大高成生裕裕和万干"
-                   "飞全洪伟同发禄升欣盈高欣谦亨裕康宝复庆光皇源凯凯圣发东本辉寿捷茂和庆丰多宏亚万益公福捷升"
-                   "福茂宝捷同复合隆中汇禄鑫中新德昌新大皇安东信瑞元皇皇洪瑞弘捷本鑫中亨亚广昌永宏润同成高利"
-                   "台中生如百康旺巨福德春元通国成浩永康泰盛泰利生茂巨久昌佳复富隆通盈同庆皇顺如辉全旺捷皇长"
-                   "全富广源恒鼎顺汇本百洪鼎进欣吉凯汇欣义东长禄捷浩益旺复弘昌生发伟荣高亨元聚广新复多富千中"
-                   "兴佳升康成同贵宝生捷晶全泰全永旺发富康仁兴谦利茂亨洪佳洪元鼎全国本丰亨鑫弘富干寿春贵国成"
-                   "盛大发久弘国大金生久高久益浩晶盈益瑞正丰百浩泰台合德昌昌美皇合隆裕东广亚国升益福旺高贵信"
-                   "生汇多泰元厚瑞飞千顺盛如大德润新新顺润飞瑞优源宏千盛吉高大耀进信欣信利瑞荣升亨盈盛千合复"
-                   "隆贵丰义公优荣宏广福华洪洪捷吉进盛盛裕国洪浩祥晶弘吉欣鼎德佳成和满台光复汇佳通浩昌欣康瑞"
-                   "万亚谦兴福利千元皇瑞润禄信合长润捷中旺成金益公隆宏康亚禄隆通光广国义中优多富复盛庆千长永"
-                   "国源安永千中正康发复协利皇亚协鑫义巨源中润旺高进巨新高协兴生福恒富国协捷盛同复巨千益长洪"
-                   "亚欣美复康洪全高安进千汇通益美耀美台耀万康合洪禄中宏百凯华优鑫协泰兴裕欣进安茂丰光飞全飞"
-                   "高康进同大洪永祥飞美满兴丰谦和鑫贵百洪通裕升干永升亨光德盛永金东鼎永裕佳和德仁荣辉同瑞恒"
-                   "聚谦长广鑫金久庆国吉禄弘顺汇恒汇瑞隆洪光鼎复公鼎泰盛佳恒鼎中飞聚亚宏盈光安谦成合巨洪飞庆"
-                   "久瑞正茂信协百生盛合国圣盛同同盈信宏禄仁大中皇宝德金台优长成成亚盛公美荣成昌久禄泰亚进台"
-                   "辉佳凯安久本荣飞晶隆晶弘同丰辉华高光兴庆贵如耀飞仁宏欣皇洪宏金满鼎耀巨义德昌源中洪裕祥晶"
-                   "本国金洪昌金源恒福万义久多谦高佳欣和凯本泰春贵大浩永寿昌禄金弘仁美久升亨辉久茂皇弘泰德成"
-                   "宏美辉辉禄仁华晶春干圣长同耀光庆华晶生新辉鑫金满中千谦瑞祥昌茂复长新祥祥福同优佳恒千如兴"
-                   "裕华凯康全贵巨旺祥捷厚贵富宏义盛谦同盛同益谦润东广千进辉升复昌聚吉飞飞元公台本华升美久长"
-                   "庆亚升东正高弘亚庆和寿宏满万优伟浩新合聚庆万广寿东恒光圣润同高谦昌兴义仁安本捷公进康益金"
-                   "庆正进正千辉和升本益高广中百新庆金同如鼎寿茂鼎庆茂瑞全禄辉美贵优丰益同信兴聚浩新协宝耀圣"
-                   "晶盈飞安荣富千祥成源裕合兴佳裕旺金长禄亨本大德成亨皇通全华贵弘成福聚信福光盛丰满宏福益国"
-                   "弘生弘源新万泰成生伟兴兴辉和大元和协通千宝协伟荣长禄晶盛欣隆新本复正盛和皇升万益高盈义裕"
-                   "成仁巨弘千亚耀吉庆厚国新高利和润中捷亚信百合亨佳佳多信鑫永复公千佳捷元东宝协大贵本满泰长"
-                   "协耀圣仁旺生干盛恒义多宏益协润长皇伟晶茂大辉谦多台高恒巨兴辉台华升满公升成元利利厚隆裕厚"
-                   "高公通浩凯金皇庆新发宏大本谦升欣升华益巨益百辉亨辉成欣庆同晶瑞义久成佳利优进满康信盈东盛"
-                   "华义公贵美宝信丰正谦旺华皇吉如鑫泰协全优福寿中生厚成生亚公弘顺千信祥和圣金华康德台顺全厚"
-                   "协亨美万瑞美东飞万飞如长仁高全汇升宏利吉泰益发谦亚汇亚恒耀恒飞浩益通捷亨新恒百佳中成公圣"
-                   "宏满鑫成旺禄元福凯百永东源庆耀万鼎公春昌广润全聚德旺洪隆宝伟亨合满隆进升盛东正新多进浩康"
-                   "长合大耀和美厚如寿鑫禄德仁发庆光通义荣盈昌升荣优华国成欣大宏丰光亚复万光春鼎汇旺和辉辉伟"
-                   "捷汇通寿耀益皇盛晶隆义同合益春通万飞弘如安信本利安复协庆吉新永久公鑫广同富源公宏台长辉耀"
-                   "光千佳宝康祥盛富升顺亚吉皇美润仁广仁台瑞干隆美信优伟安生如成耀盛润升正升新公荣宏恒洪圣泰"
-                   "弘升美益顺隆大生新茂复丰亚华恒仁弘富公美昌干永满汇如洪昌荣飞新谦万百丰进宝禄贵千生进大润"
-                   "禄祥公金祥聚兴和旺盈晶百义协巨顺裕中发千辉亨美本元丰金盈盛新全国源和协富谦发万耀福大发浩"
-                   "隆正宏升弘旺长德百发鼎金满春新成新台正弘润晶大盈茂厚富泰通厚协百源复广恒欣合圣本巨复多正"
-                   "伟润高满凯仁凯高禄万本复信满德升茂金如富谦旺佳美盈千发宝禄进兴鼎丰圣广公进昌东润进优祥生"
-                   "辉茂安顺正伟圣宝优庆厚新益亚鑫皇浩兴顺多生寿金益千丰旺义东光庆泰全协吉兴千瑞丰兴茂泰庆捷"
-                   "丰升弘茂鼎润复永发多成美聚福贵合光亚聚庆大大万顺贵进光国顺飞耀佳合巨洪源祥聚百汇兴本洪荣"
-                   "利春庆协成昌瑞同厚春百光国如升同仁佳合成复凯佳汇升鼎宝宝进洪和信昌康润源圣巨康同欣浩辉正"
-                   "永汇泰禄弘鼎多厚和佳进荣如茂全贵祥飞祥祥汇禄合源盈如和庆利寿旺汇春盈荣洪宏凯宝润如洪金鼎"
-                   "聚安和吉宏捷亚伟美洪元吉厚谦吉凯汇晶中义升协吉大益祥中鑫成正盛福满辉成亨福富益洪厚禄佳益"
-                   "亨巨圣辉厚皇")
-    service_type = ("咨询,中介,科技服务,文化交流服务,技术服务,信息服务,零售贸易,制造,批发贸易,集团,餐饮服务,"
-                    "餐饮管理,旅游质询,人事服务")
-    company_type = "股份有限公司,有限责任公司"
-
-    company_pattern = '{region_info}{middle_word}{service_type}{company_type}'
-
-    return company_pattern.format(region_info=random.choice(region_info.split(',')),
-                                  middle_word=''.join([random.choice(middle_word)
-                                                       for _ in range(random.randint(2, 5))]),
-                                  service_type=random.choice(service_type.split(',')),
-                                  company_type=random.choice(company_type.split(',')))
+# coding=utf-8
+"""
+
+``fish_random`` 包含的是一些生成随机值的函数。
+
+"""
+
+# 2019.1.6 edit by David Yi, #187 #188 修改 IdCard 和 CardBin 两个类，对这里有修改
+# 2019.1.13 edit by David Yi, #202 优化 class CardBin(), class IdCard()
+# 2019.1.18 edit by Hu Jun, #204 优化函数名称, #200 remove fish_common.get_random_str to gen_random_str
+
+# 2018.12.26 v1.1.5 created
+import string
+import random
+from fishbase.fish_date import RandomTime, FishDateTimeFormat
+from fishbase.fish_data import CardBin, IdCard
+
+
+# v1.1.6 edit by Hu Jun #200 合并 fish_common.get_random_str 为 gen_random_str
+# v1.1.5 edit by Hu Jun #163
+def gen_random_str(min_length, max_length, prefix=None, suffix=None,
+                   has_letter=True, has_digit=False, has_punctuation=False):
+    """
+    指定一个前后缀、字符串长度以及字符串包含字符类型，返回随机生成带有前后缀及指定长度的字符串
+
+    :param:
+        * min_length: (int) 字符串最小长度
+        * max_length: (int) 字符串最小长度
+        * prefix: (string) 字符串前缀
+        * suffix: (string) 字符串后缀
+        * has_letter: (bool) 字符串时候包含字母，默认为 True
+        * has_digit: (bool) 字符串是否包含数字，默认为 False
+        * has_punctuation: (bool) 字符串是否包含标点符号，默认为 False
+
+    :return:
+        * random_str: (string) 指定规则的随机字符串
+
+    举例如下::
+
+        print('--- gen_random_str demo ---')
+        print(gen_random_str(5, 7))
+        print(gen_random_str(5, 7, prefix='FISHBASE_'))
+        print(gen_random_str(5, 7, prefix='FISHBASE_', suffix='.py'))
+        print(gen_random_str(5, 7, has_digit=True, has_punctuation=True))
+        print(gen_random_str(5, 7, prefix='FISHBASE_', has_digit=True, has_punctuation=True))
+        print('---')
+
+    执行结果::
+
+        --- gen_string_by_range demo ---
+        q4uo6E8
+
+        FISHBASE_8uCBEUH
+
+        FISHBASE_D4wRX2.py
+
+        FISHBASE_65nqlNs
+
+        FISHBASE_3"uFm$s
+        ---
+    """
+    if not all([isinstance(min_length, int), isinstance(max_length, int)]):
+        raise ValueError('min_length and max_length should be int, but we got {} and {}'.
+                         format(type(min_length), type(max_length)))
+
+    if min_length > max_length:
+        raise ValueError('min_length should less than or equal to max_length')
+
+    # 避免随机源为空
+    if not any([has_letter, has_digit, has_punctuation]):
+        raise ValueError('At least one value is True in has_letter, has_digit and has_punctuation')
+
+    random_str_len = random.randint(min_length, max_length)
+
+    random_source = ''
+    random_source += string.ascii_letters if has_letter else ''
+    random_source += string.digits if has_digit else ''
+    random_source += string.punctuation if has_punctuation else ''
+
+    # 避免出现 ValueError: Sample larger than population or is negative
+    if random_str_len > len(random_source):
+        random_source *= (random_str_len // len(random_source) + 1)
+
+    mid_random_str = ''.join(random.sample(random_source, random_str_len))
+
+    prefix = prefix if prefix else ''
+    suffix = suffix if suffix else ''
+
+    random_str = ''.join([prefix, mid_random_str, suffix])
+
+    return random_str
+
+
+# v1.1.6 add by Hu Jun #204
+# v1.1.5 add by Jia Chunying #171
+# v1.3 edit by David Yi, #273
+def gen_random_name(family_name=None, gender=None, length=None):
+    """
+    指定姓氏、性别、长度，返回随机人名，也可不指定生成随机人名
+
+    :param:
+        * family_name: (string) 姓
+        * gender: (string) 性别 "01" 男性， "00" 女性, 默认 None: 随机
+        * length: (int) 大于等于 2 小于等于 10 的整数, 默认 None: 随机 2 或者 3
+
+    :return:
+        * full_name: (string) 随机人名
+
+    举例如下::
+
+        print('--- gen_random_name demo ---')
+        print(gen_random_name())
+        print(gen_random_name("赵","01", 3))
+        print('---')
+
+    执行结果::
+
+        --- gen_random_name demo ---
+        师艺
+        赵群腾
+        ---
+
+    """
+    family_word = ("赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛"
+                   "奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康"
+                   "伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵"
+                   "席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯咎管卢莫经房裘缪干解应宗"
+                   "宣丁贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣翁荀羊於惠甄魏加封芮羿储靳汲邴糜松井段富巫"
+                   "乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭厉戎祖武符刘姜詹束龙叶幸司韶郜黎蓟薄"
+                   "印宿白怀蒲台从鄂索咸籍赖卓蔺屠蒙池乔阴郁胥能苍双闻莘党翟谭贡劳逄姬申扶堵冉宰郦雍却璩桑桂"
+                   "濮牛寿通边扈燕冀郏浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庚终暨居衡步都耿满弘"
+                   "匡国文寇广禄阙东殴殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相"
+                   "查后江红游竺权逯盖益桓公万俟司马上官欧阳夏侯诸葛闻人东方赫连皇甫尉迟公羊澹台公冶宗政濮阳"
+                   "淳于仲孙太叔申屠公孙乐正轩辕令狐钟离闾丘长孙慕容鲜于宇文司徒司空亓官司寇仉督子车颛孙端木"
+                   "巫马公西漆雕乐正壤驷公良拓拔夹谷宰父谷粱晋楚阎法汝鄢涂钦段干百里东郭南门呼延归海羊舌微生"
+                   "岳帅缑亢况后有琴梁丘左丘东门西门商牟佘佴伯赏南宫墨哈谯笪年爱阳佟第五言福百家姓续")
+
+    name_dict = {"00": ("秀娟英华慧巧美娜静淑惠珠翠雅芝玉萍红娥玲芬芳燕彩春菊兰凤洁梅琳素云莲真环雪荣爱妹霞"
+                        "香月莺媛艳瑞凡佳嘉琼勤珍贞莉桂娣叶璧璐娅琦晶妍茜秋珊莎锦黛青倩婷姣婉娴瑾颖露瑶怡婵"
+                        "雁蓓纨仪荷丹蓉眉君琴蕊薇菁梦岚苑婕馨瑗琰韵融园艺咏卿聪澜纯毓悦昭冰爽琬茗羽希宁欣飘"
+                        "育滢馥筠柔竹霭凝晓欢霄枫芸菲寒伊亚宜可姬舒影荔枝思丽"),
+                 "01": ("伟刚勇毅俊峰强军平保东文辉力明永健世广志义兴良海山仁波宁贵福生龙元全国胜学祥才发武"
+                        "新利清飞彬富顺信子杰涛昌成康星光天达安岩中茂进林有坚和彪博诚先敬震振壮会思群豪心邦"
+                        "承乐绍功松善厚庆磊民友裕河哲江超浩亮政谦亨奇固之轮翰朗伯宏言若鸣朋斌梁栋维启克伦翔"
+                        "旭鹏泽晨辰士以建家致树炎德行时泰盛雄琛钧冠策腾楠榕风航弘")}
+
+    if family_name is None:
+        family_name = random.choice(family_word)
+    if gender is None or gender not in ['00', '01']:
+        gender = random.choice(['00', '01'])
+    if length is None or length not in [2, 3, 4, 5, 6, 7, 8, 9, 10]:
+        length = random.choice([2, 3])
+    name = "".join([random.choice(name_dict[gender]) for _ in range(length - 1)])
+    full_name = "{family_name}{name}".format(family_name=family_name, name=name)
+    return full_name
+
+
+# v1.1.6 add by Hu Jun #204
+# v1.1.5 add by Jia Chunying #166
+def gen_random_mobile():
+    """
+    随机生成一个手机号
+
+    :return:
+        * str: (string) 手机号
+
+    举例如下::
+
+        print('--- gen_random_mobile demo ---')
+        print(gen_random_mobile())
+        print(gen_random_mobile())
+        print('---')
+
+    执行结果::
+
+        --- gen_random_mobile demo ---
+        16706146773
+        14402633925
+        ---
+
+    """
+    prefix_list = ["13",
+                   "1400", "1410", "1440", "145", "146", "147", "148",
+                   "15",
+                   "162", "165", "166", "167",
+                   "170", "171", "172", "173", "175", "176", "177", "178", "1740",
+                   "18",
+                   "191", "198", "199"]
+    prefix_str = random.choice(prefix_list)
+    return prefix_str + "".join(random.choice("0123456789") for _ in range(11 - len(prefix_str)))
+
+
+# v1.1.6 edit by Hu Jun #204
+# v1.1.6 edit by Hu Jun #190
+# v1.1.5 edit by Hu Jun #162
+def gen_random_float(minimum, maximum, decimals=2):
+    """
+    指定一个浮点数范围，随机生成并返回区间内的一个浮点数，区间为闭区间
+    受限于 random.random 精度限制，支持最大 15 位精度
+
+    :param:
+        * minimum: (float) 浮点数最小取值
+        * maximum: (float) 浮点数最大取值
+        * decimals: (int) 小数位数，默认为 2 位
+
+    :return:
+        * random_float: (float) 随机浮点数
+
+    举例如下::
+
+        print('--- gen_random_float demo ---')
+        print(gen_random_float(1.0, 9.0))
+        print(gen_random_float(1.0, 9.0, decimals=10))
+        print(gen_random_float(1.0, 9.0, decimals=20))
+        print('---')
+
+    执行结果::
+
+        --- gen_random_float demo ---
+        6.08
+        6.8187342239
+        2.137902497554043
+        ---
+
+    """
+    if not (isinstance(minimum, float) and isinstance(maximum, float)):
+        raise ValueError('param minimum, maximum should be float, but got minimum: {} maximum: {}'.
+                         format(type(minimum), type(maximum)))
+    if not isinstance(decimals, int):
+        raise ValueError('param decimals should be a int, but we got {}'.format(type(decimals)))
+    # 精度目前只支持最大 15 位
+    decimals = 15 if decimals > 15 else decimals
+    # 存在 round 之后四舍五入之后，精度不匹配的情况，新加判断
+    while True:
+        random_float = random.uniform(minimum, maximum)
+        random_float = round(random_float, decimals)
+        if len(str(random_float).split('.')[-1]) == decimals:
+            return random_float
+
+
+# v1.1.6 edit by Hu Jun #204
+# v1.1.5 edit by Hu Jun #173
+# v1.1.12 edit by Hu Jun #232
+# v1.3 edit by David Yi, #273
+def get_random_areanote(zone):
+    """
+    省份行政区划代码，返回下辖的随机地区名称
+
+    :param:
+        * zone: (string) 省份行政区划代码 比如 '310000'
+
+    :returns:
+        * random_areanote: (string) 省份下辖随机地区名称
+
+    举例如下::
+
+        print('--- fish_data get_random_areanote demo ---')
+        print(get_random_areanote(310000))
+        print('---')
+
+    输出结果::
+
+        --- fish_data get_random_areanote demo ---
+        徐汇区
+        ---
+
+    """
+    # 获取省份下的地区信息
+    province = str(zone)[:2]
+    areanote_list = IdCard.get_areanote_info(province)
+    # 选出省份名称
+    province_name_list = [item for item in areanote_list if item[0] == str(zone)]
+
+    if not (areanote_list and province_name_list):
+        raise ValueError('zone error, please check and try again')
+
+    province_name = province_name_list[0][-1]
+    while True:
+        random_areanote = random.choice(areanote_list)
+        # 只选取下辖区域
+        if random_areanote != province_name_list[0]:
+            break
+    full_areanote = random_areanote[-1]
+    return full_areanote.split(province_name)[-1]
+
+
+# v1.1.6 edit by Hu Jun #204
+# v1.1.5 edit by Hu Jun #170
+# v1.3 edit by David Yi, #273
+def gen_random_address(zone):
+    """
+    通过省份行政区划代码，返回该省份的随机地址
+
+    :param:
+        * zone: (string) 省份行政区划代码 比如 '310000'
+
+    :returns:
+        * random_addr: (string) 省份下辖随机地区名称
+
+    举例如下::
+
+        print('--- gen_address demo ---')
+        print(gen_random_address('310000'))
+        print('---')
+
+    输出结果::
+
+        --- gen_address demo ---
+        上海市卢湾区陵县支街918号
+        ---
+
+    """
+    # 获取省份下的地区信息
+    province = str(zone)[:2]
+    areanote = IdCard.get_areanote_info(province)
+    if not areanote:
+        raise ValueError('zone error, please check and try again')
+    # 第一项是省份名称
+    province_name = areanote[0][-1]
+    areanote_info = get_random_areanote(zone)
+    address_word = ("重庆大厦,黑龙江路,十梅庵街,遵义路,湘潭街,瑞金广场,仙山街,仙山东路,仙山西大厦,白沙河路,"
+                    "赵红广场,机场路,民航街,长城南路,流亭立交桥,虹桥广场,长城大厦,礼阳路,风岗街,中川路,"
+                    "白塔广场,兴阳路,文阳街,绣城路,河城大厦,锦城广场,崇阳街,华城路,康城街,正阳路,和阳广场,"
+                    "中城路,江城大厦,顺城路,安城街,山城广场,春城街,国城路,泰城街,德阳路,明阳大厦,春阳路,"
+                    "艳阳街,秋阳路,硕阳街,青威高速,瑞阳街,丰海路,双元大厦,惜福镇街道,夏庄街道,古庙工业园,"
+                    "中山街,太平路,广西街,潍县广场,博山大厦,湖南路,济宁街,芝罘路,易州广场,荷泽四路,"
+                    "荷泽二街,荷泽一路,荷泽三大厦,观海二广场,广西支街,观海一路,济宁支街,莒县路,平度广场,"
+                    "明水路,蒙阴大厦,青岛路,湖北街,江宁广场,郯城街,天津路,保定街,安徽路,河北大厦,黄岛路,"
+                    "北京街,莘县路,济南街,宁阳广场,日照街,德县路,新泰大厦,荷泽路,山西广场,沂水路,肥城街,"
+                    "兰山路,四方街,平原广场,泗水大厦,浙江路,曲阜街,寿康路,河南广场,泰安路,大沽街,红山峡支路,"
+                    "西陵峡一大厦,台西纬一广场,台西纬四街,台西纬二路,西陵峡二街,西陵峡三路,台西纬三广场,"
+                    "台西纬五路,明月峡大厦,青铜峡路,台西二街,观音峡广场,瞿塘峡街,团岛二路,团岛一街,台西三路,"
+                    "台西一大厦,郓城南路,团岛三街,刘家峡路,西藏二街,西藏一广场,台西四街,三门峡路,城武支大厦,"
+                    "红山峡路,郓城北广场,龙羊峡路,西陵峡街,台西五路,团岛四街,石村广场,巫峡大厦,四川路,寿张街,"
+                    "嘉祥路,南村广场,范县路,西康街,云南路,巨野大厦,西江广场,鱼台街,单县路,定陶街,滕县路,"
+                    "钜野广场,观城路,汶上大厦,朝城路,滋阳街,邹县广场,濮县街,磁山路,汶水街,西藏路,城武大厦,"
+                    "团岛路,南阳街,广州路,东平街,枣庄广场,贵州街,费县路,南海大厦,登州路,文登广场,信号山支路,"
+                    "延安一街,信号山路,兴安支街,福山支广场,红岛支大厦,莱芜二路,吴县一街,金口三路,金口一广场,"
+                    "伏龙山路,鱼山支街,观象二路,吴县二大厦,莱芜一广场,金口二街,海阳路,龙口街,恒山路,鱼山广场,"
+                    "掖县路,福山大厦,红岛路,常州街,大学广场,龙华街,齐河路,莱阳街,黄县路,张店大厦,祚山路,苏州街,"
+                    "华山路,伏龙街,江苏广场,龙江街,王村路,琴屿大厦,齐东路,京山广场,龙山路,牟平街,延安三路,"
+                    "延吉街,南京广场,东海东大厦,银川西路,海口街,山东路,绍兴广场,芝泉路,东海中街,宁夏路,香港西大厦,"
+                    "隆德广场,扬州街,郧阳路,太平角一街,宁国二支路,太平角二广场,天台东一路,太平角三大厦,漳州路一路,"
+                    "漳州街二街,宁国一支广场,太平角六街,太平角四路,天台东二街,太平角五路,宁国三大厦,澳门三路,"
+                    "江西支街,澳门二路,宁国四街,大尧一广场,咸阳支街,洪泽湖路,吴兴二大厦,澄海三路,天台一广场,"
+                    "新湛二路,三明北街,新湛支路,湛山五街,泰州三广场,湛山四大厦,闽江三路,澳门四街,南海支路,"
+                    "吴兴三广场,三明南路,湛山二街,二轻新村镇,江南大厦,吴兴一广场,珠海二街,嘉峪关路,高邮湖街,"
+                    "湛山三路,澳门六广场,泰州二路,东海一大厦,天台二路,微山湖街,洞庭湖广场,珠海支街,福州南路,"
+                    "澄海二街,泰州四路,香港中大厦,澳门五路,新湛三街,澳门一路,正阳关街,宁武关广场,闽江四街,"
+                    "新湛一路,宁国一大厦,王家麦岛,澳门七广场,泰州一路,泰州六街,大尧二路,青大一街,闽江二广场,"
+                    "闽江一大厦,屏东支路,湛山一街,东海西路,徐家麦岛函谷关广场,大尧三路,晓望支街,秀湛二路,"
+                    "逍遥三大厦,澳门九广场,泰州五街,澄海一路,澳门八街,福州北路,珠海一广场,宁国二路,临淮关大厦,"
+                    "燕儿岛路,紫荆关街,武胜关广场,逍遥一街,秀湛四路,居庸关街,山海关路,鄱阳湖大厦,新湛路,漳州街,"
+                    "仙游路,花莲街,乐清广场,巢湖街,台南路,吴兴大厦,新田路,福清广场,澄海路,莆田街,海游路,镇江街,"
+                    "石岛广场,宜兴大厦,三明路,仰口街,沛县路,漳浦广场,大麦岛,台湾街,天台路,金湖大厦,高雄广场,海江街,"
+                    "岳阳路,善化街,荣成路,澳门广场,武昌路,闽江大厦,台北路,龙岩街,咸阳广场,宁德街,龙泉路,丽水街,"
+                    "海川路,彰化大厦,金田路,泰州街,太湖路,江西街,泰兴广场,青大街,金门路,南通大厦,旌德路,汇泉广场,"
+                    "宁国路,泉州街,如东路,奉化街,鹊山广场,莲岛大厦,华严路,嘉义街,古田路,南平广场,秀湛路,长汀街,"
+                    "湛山路,徐州大厦,丰县广场,汕头街,新竹路,黄海街,安庆路,基隆广场,韶关路,云霄大厦,新安路,仙居街,"
+                    "屏东广场,晓望街,海门路,珠海街,上杭路,永嘉大厦,漳平路,盐城街,新浦路,新昌街,高田广场,市场三街,"
+                    "金乡东路,市场二大厦,上海支路,李村支广场,惠民南路,市场纬街,长安南路,陵县支街,冠县支广场,"
+                    "小港一大厦,市场一路,小港二街,清平路,广东广场,新疆路,博平街,港通路,小港沿,福建广场,高唐街,"
+                    "茌平路,港青街,高密路,阳谷广场,平阴路,夏津大厦,邱县路,渤海街,恩县广场,旅顺街,堂邑路,李村街,"
+                    "即墨路,港华大厦,港环路,馆陶街,普集路,朝阳街,甘肃广场,港夏街,港联路,陵县大厦,上海路,宝山广场,"
+                    "武定路,长清街,长安路,惠民街,武城广场,聊城大厦,海泊路,沧口街,宁波路,胶州广场,莱州路,招远街,"
+                    "冠县路,六码头,金乡广场,禹城街,临清路,东阿街,吴淞路,大港沿,辽宁路,棣纬二大厦,大港纬一路,贮水山支街,"
+                    "无棣纬一广场,大港纬三街,大港纬五路,大港纬四街,大港纬二路,无棣二大厦,吉林支路,大港四街,普集支路,"
+                    "无棣三街,黄台支广场,大港三街,无棣一路,贮水山大厦,泰山支路,大港一广场,无棣四路,大连支街,大港二路,"
+                    "锦州支街,德平广场,高苑大厦,长山路,乐陵街,临邑路,嫩江广场,合江路,大连街,博兴路,蒲台大厦,黄台广场,"
+                    "城阳街,临淄路,安邱街,临朐路,青城广场,商河路,热河大厦,济阳路,承德街,淄川广场,辽北街,阳信路,益都街,"
+                    "松江路,流亭大厦,吉林路,恒台街,包头路,无棣街,铁山广场,锦州街,桓台路,兴安大厦,邹平路,胶东广场,章丘路,"
+                    "丹东街,华阳路,青海街,泰山广场,周村大厦,四平路,台东西七街,台东东二路,台东东七广场,台东西二路,东五街,"
+                    "云门二路,芙蓉山村,延安二广场,云门一街,台东四路,台东一街,台东二路,杭州支广场,内蒙古路,台东七大厦,"
+                    "台东六路,广饶支街,台东八广场,台东三街,四平支路,郭口东街,青海支路,沈阳支大厦,菜市二路,菜市一街,"
+                    "北仲三路,瑞云街,滨县广场,庆祥街,万寿路,大成大厦,芙蓉路,历城广场,大名路,昌平街,平定路,长兴街,"
+                    "浦口广场,诸城大厦,和兴路,德盛街,宁海路,威海广场,东山路,清和街,姜沟路,雒口大厦,松山广场,长春街,"
+                    "昆明路,顺兴街,利津路,阳明广场,人和路,郭口大厦,营口路,昌邑街,孟庄广场,丰盛街,埕口路,丹阳街,汉口路,"
+                    "洮南大厦,桑梓路,沾化街,山口路,沈阳街,南口广场,振兴街,通化路,福寺大厦,峄县路,寿光广场,曹县路,昌乐街,"
+                    "道口路,南九水街,台湛广场,东光大厦,驼峰路,太平山,标山路,云溪广场,太清路")
+    random_addr = random.choice(address_word.split(','))
+    if random_addr.endswith('路') or random_addr.endswith('街'):
+        random_addr = ''.join([random_addr, str(random.randint(1, 1000)), '号'])
+    address_pattern = '{province_name}{areanote_info}{random_addr}'
+    return address_pattern.format(province_name=province_name,
+                                  areanote_info=areanote_info,
+                                  random_addr=random_addr)
+
+
+# v1.1.5 edit by Hu Jun #172
+# v1.1.11 edit by Hu Jun #229
+# v1.3 edit by David Yi, #273
+# v1.4 edit by David Yi, #293
+def gen_random_bankcard(bank_name=None, card_type=None):
+    """
+    通过指定的银行名称，随机生成该银行的卡号
+
+    :param:
+        * bank_name: (string) 银行名称 eg. 中国银行
+        * card_type：(string) 卡种类，可选 CC(信用卡)、DC(借记卡)
+
+    :returns:
+        * bankcard_number: (string) 随机生成的银行卡卡号
+
+    举例如下::
+
+        print('--- gen_random_bankcard demo ---')
+        print(gen_random_bankcard())
+        print(gen_random_bankcard('中国银行', 'CC'))
+        print(gen_random_bankcard('中国银行', 'DC'))
+        print('---')
+
+    输出结果::
+
+        --- gen_random_bankcard demo ---
+        6282689914390956
+        6259073791134721
+        6212836989522229131
+        ---
+
+    """
+    if not bank_name:
+        # 不指定银行名称时，银行名称从 中国工商银行、中国农业银行、中国银行、中国建设银行 中选取
+        bank_name = random.choice(['中国工商银行', '中国农业银行', '中国银行', '中国建设银行'])
+    if not card_type:
+        card_type = random.choice(['DC', 'CC'])
+
+    bank_info = CardBin.get_bank_info(bank_name)
+    if not bank_info:
+        raise ValueError('bank name {} error, check and try again'.format(bank_name))
+
+    # 获取银行代码
+    bank = bank_info[0][0]
+
+    # 获取 cardbin
+    cardbin_info = CardBin.get_cardbin_info(bank, card_type)
+    if not cardbin_info:
+        raise ValueError('card_type {} error, check and try again'.format(card_type))
+
+    random_cardbin = random.choice(cardbin_info)
+
+    # 银行卡前缀
+    bankcard_number = random_cardbin[0]
+    card_len = random_cardbin[-1]
+
+    # 随机生成前N-1位
+    while len(bankcard_number) < card_len - 1:
+        bankcard_number += str(random.randint(0, 9))
+
+    # 获取校验位
+    check_code = CardBin.get_checkcode(bankcard_number)
+
+    bankcard_number = bankcard_number + check_code
+
+    return bankcard_number
+
+
+# v1.1.5 edit by Hu Jun #165
+# v1.1.12 edit by Hu Jun #232
+# v1.3 edit by David Yi, #273
+# v1.4 edit by David Yi, #293
+def gen_random_idcard(zone=None, gender=None, age=None, birth_year=None, result_type='SINGLE_STR'):
+    """
+    根据指定的省份编号、性别或年龄，随机生成一个身份证号
+
+    :param:
+        * zone: (string) 省份编号 eg. 310000, 默认 None: 随机
+        * gender：(string) 性别 "01" 男性， "00" 女性, 默认 None: 随机
+        * age：(int) 年龄 默认 None：随机最大年龄100岁
+        * result_type: (string) 返回结果数量类型，默认值 'SINGLE_STR'，表示随机返回一个身份证号，可选 'LIST'，返回一个随机身份证列表，包含 20 个随机身份证号
+
+    :returns:
+        * idcard_list: (list) 随机生成的身份证号组成的列表
+
+    举例如下::
+
+        print('--- gen_random_idcard demo ---')
+        print(gen_random_id_card('310000'))
+        print(gen_random_id_card('310000', age=100))
+        print(gen_random_id_card('310000', age=100, birth_year=1990))
+        print(gen_random_id_card('310000', age=30, gender='00'))
+        print(gen_random_id_card(age=30, gender='01', result_type='LIST'))
+        print('---')
+
+    输出结果::
+
+        --- gen_random_idcard demo ---
+        ['310108194005039404']
+        ['310225192009222639']
+        ['310114199505252156']
+        ['310102199001126142']
+        ['412822199003256855', '411330199011239176', '410102199011165210', '410328199012105337', ...
+        ---
+
+    """
+    if zone:
+        province = zone[:2]
+    else:
+        province_list = IdCard.get_province_info()
+        province = random.choice(province_list)[0]
+        zone = province + '0000'
+
+    areanote_list = IdCard.get_areanote_info(province)
+
+    # 判断 zone 是否合法
+    if zone[:2] != '0000' and zone not in [item[0] for item in areanote_list]:
+        raise ValueError('zone {} error, check and try again'.format(zone))
+
+    # 删除省份编号，身份证前缀不包含省份编号
+    # areanote_list.remove([item for item in areanote_list if item[0] == zone][0])
+
+    zone_list = [item[0] for item in areanote_list]
+
+    total_num = 1 if result_type == 'SINGLE_STR' else 20
+
+    idcard_list = list()
+
+    for _ in range(total_num):
+        # 顺序码的奇数分配给男性，偶数分配给女性
+        gender = gender if gender else random.choice(['00', '01'])
+        gender_dict = {'00': [0, 2, 4, 6, 8],
+                       '01': [1, 3, 5, 7, 9]}
+
+        now_date_str = FishDateTimeFormat.strftime(time_format='%Y-%m-%d')
+        year, month, day = now_date_str.split('-')
+        # 年龄
+        if not age:
+            # 随机年龄最大100岁
+            age = random.randint(0, 100)
+        if birth_year:
+            age = int(year) - int(birth_year)
+
+        start_date_str = '{year}-{month}-{day}'.format(year=int(year) - age, month=month, day=day)
+        birth = RandomTime.get_random_date_by_range(start_date_str, now_date_str, date_format="%Y-%m-%d")
+        birth = str(int(year) - age) + birth[4:]
+
+        while True:
+            zone = random.choice(zone_list)
+            # 身份证前缀不包含省份编号
+            if zone[2:] != '0000':
+                break
+
+        random_str = str(random.randint(10, 99))
+        gender_str = str(random.choice(gender_dict.get(gender)))
+        _, check_code = IdCard.get_checkcode(zone + birth + random_str + gender_str)
+        random_id = ("{zone}{birth_date}{random_str}{gender}{check_code}".
+                     format(zone=zone,
+                            birth_date=birth,
+                            random_str=random_str,
+                            gender=gender_str,
+                            check_code=check_code))
+        idcard_list.append(random_id)
+    return idcard_list
+
+
+# v1.1.6 edit by Hu Jun #204
+# v1.1.5 edit by Hu Jun #171
+def gen_random_company_name():
+    """
+    随机生成一个公司名称
+
+    :returns:
+        * company_name: (string) 银行名称
+
+    举例如下::
+
+        print('--- gen_random_company_name demo ---')
+        print(gen_random_company_name())
+        print('---')
+
+    输出结果::
+
+        --- gen_random_company_name demo ---
+        上海大升旅游质询有限责任公司
+        ---
+
+    """
+    region_info = ("北京,上海,广州,深圳,天津,成都,杭州,苏州,重庆,武汉,南京,大连,沈阳,长沙,郑州,西安,青岛,"
+                   "无锡,济南,宁波,佛山,南通,哈尔滨,东莞,福州,长春,石家庄,烟台,合肥,唐山,常州,太原,昆明,"
+                   "潍坊,南昌,泉州,温州,绍兴,嘉兴,厦门,贵阳,淄博,徐州,南宁,扬州,呼和浩特,鄂尔多斯,乌鲁木齐,"
+                   "金华,台州,镇江,威海,珠海,东营,大庆,中山,盐城,包头,保定,济宁,泰州,廊坊,兰州,洛阳,宜昌,"
+                   "沧州,临沂,泰安,鞍山,邯郸,惠州,江门,襄阳,湖州,吉林,芜湖,德州,聊城,漳州,株洲,淮安,榆林,"
+                   "常德,咸阳,衡阳,滨州,柳州,遵义,菏泽,南阳,新乡,湛江,岳阳,郴州,许昌,连云港,枣庄,茂名,周口,"
+                   "宿迁")
+    middle_word = ("泰宏本晶辉昌昌本同永康洪皇贵久圣正裕如恒长佳协义晶合优荣汇洪千东祥复昌皇久丰兴昌国裕亚大"
+                   "荣康通仁元裕厚瑞如弘升久隆旺吉德谦长贵百久汇百伟升隆复飞佳隆浩发丰亨公荣复光福美禄欣丰大"
+                   "祥晶宏中仁宏华隆盈旺仁顺春满美中谦瑞和圣多信合盛千亚晶祥鑫隆飞鑫优合本旺发久国汇百恒佳东"
+                   "洪通恒大公中优广宝盈泰如合丰捷本伟华春元亚广中晶如浩仁汇亚永凯富富裕茂华中飞浩台美佳圣仁"
+                   "成全润金庆百贵康仁茂皇东广荣宏荣新元康公升亨洪福伟永义巨国升进合耀巨润巨元发洪源寿仁发光"
+                   "顺升凯全全辉欣成公裕康合禄兴汇顺浩贵晶捷东飞益福宏国禄元昌弘和满发巨宝生耀隆大欣昌佳本兴"
+                   "吉生宝凯润新高和元亨巨久光益旺春巨鑫进东晶中飞兴中美丰同晶复耀进洪全兴汇宝捷伟仁安宏多庆"
+                   "益生和干干福亚新复吉亚恒亚春德飞伟利庆华丰宏合德瑞进顺祥信合康富益全巨茂台谦厚台成福捷浩"
+                   "信长飞长金利美昌满丰干佳美金洪昌富千和美旺旺晶春仁华中凯浩鼎泰辉新干高进辉同欣广庆吉益德"
+                   "浩中润和春元生高进皇茂利同盈复复晶多巨圣弘捷公宝汇鑫成高新正和和巨祥光宏大丰欣恒昌昌厚合"
+                   "庆泰丰干益和金洪复元顺捷金万辉全吉庆德瑞优长鼎顺汇顺欣飞浩荣祥光泰多春凯信进公优飞昌协美"
+                   "多发中盈协成祥益昌汇泰春满千鼎东光优谦仁中飞生恒伟福晶宝信辉金皇升飞亨鑫安伟华元旺益大寿"
+                   "皇元康耀久荣满协信凯谦宝巨丰正光发康康捷中源国多多康公利顺光辉如茂晶永大高成生裕裕和万干"
+                   "飞全洪伟同发禄升欣盈高欣谦亨裕康宝复庆光皇源凯凯圣发东本辉寿捷茂和庆丰多宏亚万益公福捷升"
+                   "福茂宝捷同复合隆中汇禄鑫中新德昌新大皇安东信瑞元皇皇洪瑞弘捷本鑫中亨亚广昌永宏润同成高利"
+                   "台中生如百康旺巨福德春元通国成浩永康泰盛泰利生茂巨久昌佳复富隆通盈同庆皇顺如辉全旺捷皇长"
+                   "全富广源恒鼎顺汇本百洪鼎进欣吉凯汇欣义东长禄捷浩益旺复弘昌生发伟荣高亨元聚广新复多富千中"
+                   "兴佳升康成同贵宝生捷晶全泰全永旺发富康仁兴谦利茂亨洪佳洪元鼎全国本丰亨鑫弘富干寿春贵国成"
+                   "盛大发久弘国大金生久高久益浩晶盈益瑞正丰百浩泰台合德昌昌美皇合隆裕东广亚国升益福旺高贵信"
+                   "生汇多泰元厚瑞飞千顺盛如大德润新新顺润飞瑞优源宏千盛吉高大耀进信欣信利瑞荣升亨盈盛千合复"
+                   "隆贵丰义公优荣宏广福华洪洪捷吉进盛盛裕国洪浩祥晶弘吉欣鼎德佳成和满台光复汇佳通浩昌欣康瑞"
+                   "万亚谦兴福利千元皇瑞润禄信合长润捷中旺成金益公隆宏康亚禄隆通光广国义中优多富复盛庆千长永"
+                   "国源安永千中正康发复协利皇亚协鑫义巨源中润旺高进巨新高协兴生福恒富国协捷盛同复巨千益长洪"
+                   "亚欣美复康洪全高安进千汇通益美耀美台耀万康合洪禄中宏百凯华优鑫协泰兴裕欣进安茂丰光飞全飞"
+                   "高康进同大洪永祥飞美满兴丰谦和鑫贵百洪通裕升干永升亨光德盛永金东鼎永裕佳和德仁荣辉同瑞恒"
+                   "聚谦长广鑫金久庆国吉禄弘顺汇恒汇瑞隆洪光鼎复公鼎泰盛佳恒鼎中飞聚亚宏盈光安谦成合巨洪飞庆"
+                   "久瑞正茂信协百生盛合国圣盛同同盈信宏禄仁大中皇宝德金台优长成成亚盛公美荣成昌久禄泰亚进台"
+                   "辉佳凯安久本荣飞晶隆晶弘同丰辉华高光兴庆贵如耀飞仁宏欣皇洪宏金满鼎耀巨义德昌源中洪裕祥晶"
+                   "本国金洪昌金源恒福万义久多谦高佳欣和凯本泰春贵大浩永寿昌禄金弘仁美久升亨辉久茂皇弘泰德成"
+                   "宏美辉辉禄仁华晶春干圣长同耀光庆华晶生新辉鑫金满中千谦瑞祥昌茂复长新祥祥福同优佳恒千如兴"
+                   "裕华凯康全贵巨旺祥捷厚贵富宏义盛谦同盛同益谦润东广千进辉升复昌聚吉飞飞元公台本华升美久长"
+                   "庆亚升东正高弘亚庆和寿宏满万优伟浩新合聚庆万广寿东恒光圣润同高谦昌兴义仁安本捷公进康益金"
+                   "庆正进正千辉和升本益高广中百新庆金同如鼎寿茂鼎庆茂瑞全禄辉美贵优丰益同信兴聚浩新协宝耀圣"
+                   "晶盈飞安荣富千祥成源裕合兴佳裕旺金长禄亨本大德成亨皇通全华贵弘成福聚信福光盛丰满宏福益国"
+                   "弘生弘源新万泰成生伟兴兴辉和大元和协通千宝协伟荣长禄晶盛欣隆新本复正盛和皇升万益高盈义裕"
+                   "成仁巨弘千亚耀吉庆厚国新高利和润中捷亚信百合亨佳佳多信鑫永复公千佳捷元东宝协大贵本满泰长"
+                   "协耀圣仁旺生干盛恒义多宏益协润长皇伟晶茂大辉谦多台高恒巨兴辉台华升满公升成元利利厚隆裕厚"
+                   "高公通浩凯金皇庆新发宏大本谦升欣升华益巨益百辉亨辉成欣庆同晶瑞义久成佳利优进满康信盈东盛"
+                   "华义公贵美宝信丰正谦旺华皇吉如鑫泰协全优福寿中生厚成生亚公弘顺千信祥和圣金华康德台顺全厚"
+                   "协亨美万瑞美东飞万飞如长仁高全汇升宏利吉泰益发谦亚汇亚恒耀恒飞浩益通捷亨新恒百佳中成公圣"
+                   "宏满鑫成旺禄元福凯百永东源庆耀万鼎公春昌广润全聚德旺洪隆宝伟亨合满隆进升盛东正新多进浩康"
+                   "长合大耀和美厚如寿鑫禄德仁发庆光通义荣盈昌升荣优华国成欣大宏丰光亚复万光春鼎汇旺和辉辉伟"
+                   "捷汇通寿耀益皇盛晶隆义同合益春通万飞弘如安信本利安复协庆吉新永久公鑫广同富源公宏台长辉耀"
+                   "光千佳宝康祥盛富升顺亚吉皇美润仁广仁台瑞干隆美信优伟安生如成耀盛润升正升新公荣宏恒洪圣泰"
+                   "弘升美益顺隆大生新茂复丰亚华恒仁弘富公美昌干永满汇如洪昌荣飞新谦万百丰进宝禄贵千生进大润"
+                   "禄祥公金祥聚兴和旺盈晶百义协巨顺裕中发千辉亨美本元丰金盈盛新全国源和协富谦发万耀福大发浩"
+                   "隆正宏升弘旺长德百发鼎金满春新成新台正弘润晶大盈茂厚富泰通厚协百源复广恒欣合圣本巨复多正"
+                   "伟润高满凯仁凯高禄万本复信满德升茂金如富谦旺佳美盈千发宝禄进兴鼎丰圣广公进昌东润进优祥生"
+                   "辉茂安顺正伟圣宝优庆厚新益亚鑫皇浩兴顺多生寿金益千丰旺义东光庆泰全协吉兴千瑞丰兴茂泰庆捷"
+                   "丰升弘茂鼎润复永发多成美聚福贵合光亚聚庆大大万顺贵进光国顺飞耀佳合巨洪源祥聚百汇兴本洪荣"
+                   "利春庆协成昌瑞同厚春百光国如升同仁佳合成复凯佳汇升鼎宝宝进洪和信昌康润源圣巨康同欣浩辉正"
+                   "永汇泰禄弘鼎多厚和佳进荣如茂全贵祥飞祥祥汇禄合源盈如和庆利寿旺汇春盈荣洪宏凯宝润如洪金鼎"
+                   "聚安和吉宏捷亚伟美洪元吉厚谦吉凯汇晶中义升协吉大益祥中鑫成正盛福满辉成亨福富益洪厚禄佳益"
+                   "亨巨圣辉厚皇")
+    service_type = ("咨询,中介,科技服务,文化交流服务,技术服务,信息服务,零售贸易,制造,批发贸易,集团,餐饮服务,"
+                    "餐饮管理,旅游质询,人事服务")
+    company_type = "股份有限公司,有限责任公司"
+
+    company_pattern = '{region_info}{middle_word}{service_type}{company_type}'
+
+    return company_pattern.format(region_info=random.choice(region_info.split(',')),
+                                  middle_word=''.join([random.choice(middle_word)
+                                                       for _ in range(random.randint(2, 5))]),
+                                  service_type=random.choice(service_type.split(',')),
+                                  company_type=random.choice(company_type.split(',')))
diff --git a/fishbase/fish_system.py b/fishbase/fish_system.py
index 67017c8..30acbe1 100644
--- a/fishbase/fish_system.py
+++ b/fishbase/fish_system.py
@@ -69,6 +69,7 @@ def optionxform(self, optionstr):
 # v1.0.17 edit by Hu Jun, #212
 # v1.1.9 edit by Hu Jun, #222
 # v1.2 edit by David Yi, #257
+# v1.4 edit by wxl
 def conf_as_dict(conf_filename, encoding=None, case_sensitive=False):
     """
     读入 ini 配置文件，返回根据配置文件内容生成的字典类型变量；
@@ -131,7 +132,7 @@ def conf_as_dict(conf_filename, encoding=None, case_sensitive=False):
 
     # 检查文件是否存在
     if not pathlib.Path(conf_filename).is_file():
-        return flag,
+        return flag, {}, 'path {} is not a regular file'.format(conf_filename)
 
     # 判断是否对大小写敏感
     cf = configparser.ConfigParser() if not case_sensitive else MyConfigParser()
@@ -142,9 +143,8 @@ def conf_as_dict(conf_filename, encoding=None, case_sensitive=False):
             cf.read(conf_filename, encoding=encoding)
         else:
             cf.read(conf_filename)
-    except Exception:
-        flag = False
-        return flag,
+    except Exception as e:
+        return flag, {}, str(e)
 
     d = OrderedDict(cf._sections)
     for k in d:
diff --git a/setup.py b/setup.py
index 206245d..3011c4c 100644
--- a/setup.py
+++ b/setup.py
@@ -50,6 +50,8 @@
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
     ]
 
 )


Test patch:
diff --git a/test/test_conf_bad_data.ini b/test/test_conf_bad_data.ini
new file mode 100644
index 0000000..116006a
--- /dev/null
+++ b/test/test_conf_bad_data.ini
@@ -0,0 +1,2 @@
+[show_opt]
+Short_Opt
\ No newline at end of file
diff --git a/test/test_conf_empty.ini b/test/test_conf_empty.ini
new file mode 100644
index 0000000..2fab060
--- /dev/null
+++ b/test/test_conf_empty.ini
@@ -0,0 +1,2 @@
+#[show_opt]
+#Short_Opt=b:d:v:p:f:
\ No newline at end of file
diff --git a/test/test_data.py b/test/test_data.py
index 03faf01..6f0ebce 100644
--- a/test/test_data.py
+++ b/test/test_data.py
@@ -1,4 +1,5 @@
 # coding=utf-8
+import pytest
 from fishbase.fish_data import *
 
 
@@ -172,7 +173,7 @@ def test_get_bank_name_by_code(self):
         assert result == '中国农业银行'
 
     # 2019.07.17 edit by Hu Jun
-    def test_get_number_detail(self):
+    def test_get_number_detail_01(self):
         values = IdCard.get_number_detail('130522198407316471')
         assert values[0]
         assert values[1].get('province') == '130000'
@@ -180,6 +181,63 @@ def test_get_number_detail(self):
         assert values[1].get('birth_date') == '19840731'
 
     # 2019.07.17 edit by Hu Jun
-    def test_get_number_detail_01(self):
+    def test_get_number_detail_02(self):
         values = IdCard.get_number_detail('130522198407316')
         assert not values[0]
+
+    # 2021.6.22 edit by David Yi, #292, 敏感数据掩码unittest
+    def test_get_idcard_number_01(self):
+        values = SensitiveMask.get_idcard_number('620105199412201639')
+        assert values == '620************639'
+
+    def test_get_idcard_number_02(self):
+        values = SensitiveMask.get_idcard_number('220681198701034560')
+        assert values != '2206***********560'
+
+    def test_get_idcard_number_01_raises(self):
+        with pytest.raises(TypeError) as e:
+            values = SensitiveMask.get_idcard_number(123)
+            # print(values)
+        assert True
+
+    def test_get_bankcard_number_01(self):
+        values = SensitiveMask.get_bankcard_number('4391880006990109')
+        assert values == '439188******0109'
+
+    def test_get_bankcard_number_02(self):
+        values = SensitiveMask.get_bankcard_number('4391880006990109')
+        assert values != '43918*******0109'
+
+    def test_get_bankcard_number_01_raises(self):
+        with pytest.raises(TypeError) as e:
+            values = SensitiveMask.get_bankcard_number(123)
+            # print(values)
+        assert True
+
+    def test_get_mobile_number_01(self):
+        values = SensitiveMask.get_mobile_number('13801108286')
+        assert values == '138****8286'
+
+    def test_get_mobile_number_02(self):
+        values = SensitiveMask.get_mobile_number('13801108286')
+        assert values != '1380***8286'
+
+    def test_get_mobile_number_01_raises(self):
+        with pytest.raises(TypeError) as e:
+            values = SensitiveMask.get_mobile_number(123)
+            # print(values)
+        assert True
+
+    def test_get_email_01(self):
+        values = SensitiveMask.get_email('david@gmail.com')
+        assert values == 'd***d@gmail.com'
+
+    def test_get_email_02(self):
+        values = SensitiveMask.get_email('david@gmail.com')
+        assert values == 'd***d@gmail.com'
+
+    def test_get_email_01_raises(self):
+        with pytest.raises(TypeError) as e:
+            values = SensitiveMask.get_email(123)
+            # print(values)
+        assert True
diff --git a/test/test_date.py b/test/test_date.py
index 7b459ee..dd9cb27 100644
--- a/test/test_date.py
+++ b/test/test_date.py
@@ -9,6 +9,7 @@
 
 
 # 2018.6.11 v1.0.14 #37 create by Hu Jun
+# 2021.6.2 6.11, edit by David Yi
 class TestFishDate(object):
     # test get_years() tc
     def test_get_years_01(self):
@@ -36,8 +37,8 @@ def test_get_date_range_01(self):
             get_date_range('asdafsd')
 
     # 测试 GetRandomTime()  tc
-    def test_date_time_this_month_01(self):
-        this_month = GetRandomTime.date_time_this_month()
+    def test_random_datetime_this_month_01(self):
+        this_month = RandomTime.get_random_datetime_this_month()
         now = datetime.datetime.now()
         this_month_days = calendar.monthrange(now.year, now.month)
         assert now.year == this_month.year
@@ -45,26 +46,26 @@ def test_date_time_this_month_01(self):
         assert now.day <= this_month_days[1]
 
     # 测试 GetRandomTime()  tc
-    def test_date_time_this_year_01(self):
-        this_month = GetRandomTime.date_time_this_year()
+    def test_random_datetime_this_year_01(self):
+        this_month = RandomTime.get_random_datetime_this_year()
         now = datetime.datetime.now()
         this_year_days = sum(calendar.mdays)
         assert now.year == this_month.year
         assert now.day <= this_year_days
 
     # 测试 GetRandomTime()  tc
-    def test_random_date_str_01(self):
-        date_str = GetRandomTime.gen_date_by_year(2018)
+    def test_random_date_by_year_01(self):
+        date_str = RandomTime.get_random_date_by_year(2018)
 
         assert date_str[:4] == '2018'
 
     # 测试 GetRandomTime()  tc
-    def test_random_date_str_02(self):
+    def test_random_date_by_year_02(self):
         with pytest.raises(ValueError):
-            GetRandomTime.gen_date_by_year('201812')
+            RandomTime.get_random_date_by_year('201812')
 
         with pytest.raises(ValueError):
-            GetRandomTime.gen_date_by_year(18)
+            RandomTime.get_random_date_by_year(18)
 
     #  测试 get_time_interval()  tc
     def test_get_time_interval_01(self):
diff --git a/test/test_http_status.py b/test/test_http_status.py
new file mode 100644
index 0000000..bbeb75c
--- /dev/null
+++ b/test/test_http_status.py
@@ -0,0 +1,30 @@
+from fishbase.fish_http_status import *
+
+
+class TestHttpStatus(object):
+
+    def setup_method(self):
+        self.BAD_HTTP_STATUS = -1
+
+    def teardown_method(self):
+        self.BAD_HTTP_STATUS = None
+
+    def test_status_informational(self):
+        assert is_informational(HTTP_CONTINUE) is True
+        assert is_informational(self.BAD_HTTP_STATUS) is False
+
+    def test_status_success(self):
+        assert is_success(HTTP_OK) is True
+        assert is_success(self.BAD_HTTP_STATUS) is False
+
+    def test_status_redirection(self):
+        assert is_redirection(HTTP_MULTIPLE_CHOICES) is True
+        assert is_redirection(self.BAD_HTTP_STATUS) is False
+
+    def test_status_client_error(self):
+        assert is_client_error(HTTP_BAD_REQUEST) is True
+        assert is_client_error(self.BAD_HTTP_STATUS) is False
+
+    def test_status_server_error(self):
+        assert is_server_error(HTTP_INTERNAL_SERVER_ERROR) is True
+        assert is_server_error(self.BAD_HTTP_STATUS) is False
diff --git a/test/test_logger.py b/test/test_logger.py
index fb61c4f..73d9ec0 100644
--- a/test/test_logger.py
+++ b/test/test_logger.py
@@ -37,21 +37,29 @@ def teardown_class(self):
         except Exception as _:
             pass
         
-    def test_format1(self):
+    def test_format_01(self):
         set_log_file(self.log_filename)
         log.info('test_format1')
         assert 'unittest.log.{}'.format(self.suffix_time) in os.listdir(self.log_path)
 
-    def test_format2(self):
+    def test_format_02(self):
         set_log_file(self.log_filename, file_name_format='%project_name-%date-%log')
         log.info('test_format2')
         assert 'unittest.{}.log'.format(self.suffix_time) in os.listdir(self.log_path)
 
-    def test_format3(self):
+    def test_format_03(self):
         set_log_file(self.log_filename, file_name_format='%date-%project_name-%log')
         log.info('test_format3')
         assert 'unittest.log.{}'.format(self.suffix_time) in os.listdir(self.log_path)
 
-    def test_format4(self):
+    def test_format_04(self):
         with pytest.raises(ValueError):
             set_log_file(self.log_filename, file_name_format='%date-%project_name-%log1')
+
+    # 2021.6.22, #294, 修复小错误
+    def test_without_dir(self):
+        for h in log.handlers:
+            h.close()
+        shutil.rmtree(self.log_path)
+        set_log_file(self.log_filename)
+        assert os.path.exists(self.log_path)
diff --git a/test/test_project.py b/test/test_project.py
index ea52fe3..89589ed 100644
--- a/test/test_project.py
+++ b/test/test_project.py
@@ -1,12 +1,30 @@
 # coding=utf-8
 import os
 import shutil
+
+import pytest
+
 from fishbase.fish_project import init_project_by_yml
 
 
 # 2018.6.27 v1.0.14 #73 create by Jia ChunYing
 class TestProject(object):
 
+    # 2021.6.22, #294, 修复小错误
+    def test_load_bad_01(self):
+        """
+        empty file
+        """
+        base_dir = os.path.dirname(os.path.abspath(__file__))
+        target_file = base_dir + os.sep + 'test_project_with_empty_file.yaml'
+        with open(target_file, 'wb') as f:
+            f.close()
+        with pytest.raises(KeyError) as e:
+            init_project_by_yml(target_file, '.')
+        exec_msg = e.value.args[0]
+        assert exec_msg == 'project config format Error: fail to load'
+        # os.remove(target_file)
+
     def test_init_project_by_yml(self):
         # define yml string
         package_yml = '''
diff --git a/test/test_project_with_empty_file.yaml b/test/test_project_with_empty_file.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/test/test_random.py b/test/test_random.py
index 61d0517..9f2a18f 100644
--- a/test/test_random.py
+++ b/test/test_random.py
@@ -88,35 +88,35 @@ def test_gen_random_address_02(self):
             gen_random_address('aa1234')
 
     # test gen_random_bank_card() tc
-    def test_gen_random_bank_card_01(self):
-        random_bank_card = gen_random_bank_card('中国银行', 'CC')
+    def test_gen_random_bankcard_01(self):
+        random_bank_card = gen_random_bankcard('中国银行', 'CC')
         assert CardBin.check_bankcard(random_bank_card)
 
     # test gen_random_bank_card() tc
-    def test_gen_random_bank_card_02(self):
+    def test_gen_random_bankcard_02(self):
         with pytest.raises(ValueError):
-            gen_random_bank_card('fishbase银行', 'CC')
+            gen_random_bankcard('fishbase银行', 'CC')
         with pytest.raises(ValueError):
-            gen_random_bank_card('中国银行', 'AA')
+            gen_random_bankcard('中国银行', 'AA')
 
     # test gen_random_bank_card() tc
-    def test_gen_random_bank_card_03(self):
-        random_bank_card = gen_random_bank_card()
+    def test_gen_random_bankcard_03(self):
+        random_bank_card = gen_random_bankcard()
         assert CardBin.check_bankcard(random_bank_card)
 
     # test gen_random_id_card() tc
-    def test_gen_random_id_card_01(self):
-        random_id_list = gen_random_id_card()
+    def test_gen_random_idcard_01(self):
+        random_id_list = gen_random_idcard()
         assert len(random_id_list[0]) == 18
         # 测试身份证是否合法
         assert IdCard.check_number(random_id_list[0])
 
-        random_id_list_1 = gen_random_id_card('310000')
+        random_id_list_1 = gen_random_idcard('310000')
         assert (random_id_list_1[0]).startswith('310')
         # 测试身份证是否合法
         assert IdCard.check_number(random_id_list_1[0])
 
-        random_id_list_2 = gen_random_id_card('310000', age=30)
+        random_id_list_2 = gen_random_idcard('310000', age=30)
         year_now = datetime.datetime.now().year
         id_card_year = int(year_now) - 30
         # 测试身份证是否合法
@@ -124,7 +124,7 @@ def test_gen_random_id_card_01(self):
         assert (random_id_list_2[0]).startswith('310')
         assert int((random_id_list_2[0][6:10])) == id_card_year
 
-        random_id_list_3 = gen_random_id_card('110000', age=20, gender='01', result_type='LIST')
+        random_id_list_3 = gen_random_idcard('110000', age=20, gender='01', result_type='LIST')
         # 测试身份证是否合法
         check_valid_list = list(map(IdCard.check_number, random_id_list_3))
         assert all(check_valid_list)
@@ -144,9 +144,9 @@ def test_gen_random_id_card_01(self):
         assert all(check_age_list)
 
     # test gen_random_id_card() tc
-    def test_gen_random_id_card_02(self):
+    def test_gen_random_idcard_02(self):
         with pytest.raises(ValueError):
-            gen_random_id_card('123456')
+            gen_random_idcard('123456')
 
     # test gen_random_company_name() tc
     def test_gen_random_company_name_01(self):
diff --git a/test/test_system.py b/test/test_system.py
index 2bf309f..e869dee 100644
--- a/test/test_system.py
+++ b/test/test_system.py
@@ -13,8 +13,9 @@
 current_path = os.path.dirname(os.path.abspath(__file__))
 # 定义配置文件名
 conf_filename = os.path.join(current_path, 'test_conf.ini')
-error_conf_filename = os.path.join(current_path, 'test_conf1.ini')
-
+conf_filename_not_exist = os.path.join(current_path, 'test_conf_not_exist.ini')
+conf_filename_empty = os.path.join(current_path, 'test_conf_empty.ini')
+conf_filename_bad_data = os.path.join(current_path, 'test_conf_bad_data.ini')
 
 # 2018.5.26 v1.0.13 create by David Yi, fish_system unittest
 class TestFishSystem(object):
@@ -58,14 +59,11 @@ def test_config_dict_01(self):
     # 测试 conf_as_dict()  tc
     def test_config_dict_02(self):
         # 读取不存在的配置文件
-        ds = conf_as_dict(error_conf_filename)
+        ds = conf_as_dict(conf_filename_not_exist)
 
         # 返回结果
         assert ds[0] is False
-
-        # 应该读不到返回的 dict 内容
-        with pytest.raises(IndexError):
-            d = ds[1]
+        assert ds[1] == {}
 
     def test_config_dict_03(self):
         # 读取配置文件
@@ -83,11 +81,8 @@ def test_config_dict_04(self):
         ds = conf_as_dict(conf_filename, encoding='utf-8')
         d = ds[1]
 
-        list1 = ['show_opt', 'show_opt_common', 'show_opt_common2', 'get_args', 'show_rule_pattern',
-                 'show_pattern', 'get_extra_rules']
-
-        # 断言是否保序
-        assert list(d.keys()) == list1
+        # 断言正常读取中文
+        assert d['get_extra_rules']['zh_item'] == '中文'
 
     def test_config_dict_05(self):
         # 读取配置文件, 大小写敏感
@@ -96,3 +91,23 @@ def test_config_dict_05(self):
 
         for item in ['Short_Opt', 'Long_Opt']:
             assert item in d.get('show_opt')
+
+    # 测试 conf_as_dict()  tc
+    def test_config_dict_06(self):
+        # 读取空配置文件
+        ds = conf_as_dict(conf_filename_empty)
+
+        # 返回结果
+        assert ds[0] is True
+        assert isinstance(ds[1], OrderedDict)
+        assert ds[2] == 0
+
+    # 测试 conf_as_dict()  tc
+    def test_config_dict_07(self):
+        # 读取坏的配置文件
+        ds = conf_as_dict(conf_filename_bad_data)
+
+        # 返回结果
+        assert ds[0] is False
+        assert 'errors' in ds[2]
+