python3对系统增量进行取包处理

定义引用方法:

 

点击查看代码
# _*_ utf-8 _*_
import os, re, zipfile
from openpyxl import load_workbook


def zip_folder(dataList, zip_full_name):
    """
    压缩dataList下所有文件夹下所有子文件夹和文件
    :param zip_full_name: zip文件绝对路径
            dataList:路径相对路径
    """
    with zipfile.ZipFile(zip_full_name, 'w') as zf:
        for yunyan_num in dataList:
            for dir_full_path, dir_names, file_names in os.walk(yunyan_num):
                # zip压缩包内的相对路径
                print('--', dir_full_path, file_names)
                for dir_name in dir_names:
                    zf.write(os.path.join(dir_full_path, dir_name), os.path.join(dir_full_path, dir_name))
                for file_name in file_names:
                    zf.write(os.path.join(dir_full_path, file_name), os.path.join(dir_full_path, file_name))

    zf.close()


def is_onlyString(char):
    '''判断是否是包含中文字符'''
    checkWord = []
    if type(char) == str:
        for word in char:
            if type(word) == str:
                if b'\xb0\xa1' <= word.encode('gb2312') <= b'\xd7\xf9':
                    checkWord.append(1)

    if len(checkWord) == 0:
        return True
    else:
        return False


def removeQiCode(string):
    '''对一个str进行成员去重'''
    if string:
        '''去除前后空格,去除" '等符号,去除可能存在的\字符'''
        string = string.strip().replace("\"", '').replace("\'", '').replace('\\', '')
        return string
    else:
        # 不为真的不做任何操作
        return None


def removeDumplict(List):
    '''对一个List进行去重'''
    if List:
        for item in List:
            if List.count(item) == 2:
                List.remove(item)
        # print(List)  # 测试完删除
        return List
    else:
        return None


def checkKongGe(string):
    '''检查字符中是否包含\n,返回一个列表'''
    result = []
    if re.findall('\n', string):
        return string.split('\n')
    else:
        result.append(string)
        return result


def dealwith_service(dataList):
    '''
    将给定的列表,按照字符要求去掉不需要的字符和空格等
    :return: dictionary = {wen: [], haoren: []}
    '''
    result_dict = {'wen4sx': [], 'haoren': [], 'nginx': [], 'number': []}

    if dataList:
        for num, item in enumerate(dataList):
            # 筛选不包含中文字符的成员
            if is_onlyString(item):
                # 对\n进行处理
                for code_path in checkKongGe(item):
                    # print(str(num) + ':' + code_path)  # 测试完成后删除
                    if code_path.startswith('/xiaoweiba-wen/'):
                        # 处理一个空格中有多行内容的情况
                        # 去除/xiaoweiba-wen/字符
                        result_dict['wen4sx'].append(removeQiCode(code_path[len('/xiaoweiba-wen/'):]))

                    elif code_path.startswith('/xiaoweiba-haoren-huairen/'):
                        # 去除/xiaoweiba-haoren/字符
                        result_dict['haoren'].append(removeQiCode(code_path[len('/xiaoweiba-haoren-huairen/'):]))

                    elif code_path.startswith('/xiaoweiba-haoren-basiccommon/'):
                        # 去除/xiaoweiba-haoren-basiccommon/字符
                        result_dict['haoren'].append(removeQiCode(code_path[len('/xiaoweiba-haoren-basiccommon/'):]))

                    elif code_path.startswith('/xiaoweiba-jadp-static/'):
                        # 去除/xiaoweiba-jadp-static/
                        result_dict['nginx'].append(removeQiCode(code_path[len('/xiaoweiba-jadp-static/'):]))

                    elif code_path.startswith('/xiaoweiba-haoren-static/'):
                        # 去除/xiaoweiba-haoren-static/
                        result_dict['nginx'].append(removeQiCode(code_path[len('/xiaoweiba-haoren-static/'):]))

                    elif code_path.startswith('xiaoweiba-Q'):
                        # 获取云烟但号
                        result_dict['number'].append(removeQiCode(code_path))

                    else:
                        # 待扩展模块
                        pass

    if result_dict['wen4sx']:
        result_dict['wen4sx'] = removeDumplict(result_dict['wen4sx'])
    if result_dict['haoren']:
        result_dict['haoren'] = removeDumplict(result_dict['haoren'])
    if result_dict['nginx']:
        result_dict['nginx'] = removeDumplict(result_dict['nginx'])

    return result_dict


def dealwith_data(fileName, columnName, sheetNum=0):
    '''
        从一个给定的xlsx文件(绝对路径)
        读取一个sheet
        读取第n列
    '''
    RELEASE = []

    def single_file(filename):
        # 加载Excel文件
        wb = load_workbook(filename=filename, read_only=True, data_only=True)
        # 问题列表
        ws1 = wb[wb.sheetnames[sheetNum]]
        # 最大行数
        row_max = ws1.max_row
        word = ws1.rows
        row_num = 0

        sheet = wb.active
        for num in range(1, 30):
            row_value = sheet.cell(row=2, column=num).value
            if row_value == columnName:
                num_col = num - 1

        for row in word:
            # 这里可以对num_col service_col static_col判断,为true则执行,false则输出字段不存在
            col_value = row[num_col].value
            if col_value:
                RELEASE.append(col_value)

            row_num += 1

        wb.close()

    # 以上内容为define变量和识别目录
    # print(fileName)
    single_file(fileName)

    if RELEASE:
        return RELEASE
    else:
        return None


def printListInfo(str1, dataList):
    print('==从表格中获取的{0}为:{1}'.format(str1, dataList))


def printInfo(txt):
    print("============================={0}=============================".format(txt))


def printMember(Dict, char):
    '''
    按照关键字char输出dataList中有关char的成员清单
    :param Dict: 字典数据
    :param char: 关键字字符
    :return: None
    '''
    # 显示增量内容

    if char == 'service':
        if Dict['wen4sx']:
            for item in Dict['wen4sx']:
                print(item)
        if Dict['haoren']:
            for item in Dict['haoren']:
                print(item)

    elif char == 'nginx':
        for item in Dict[char]:
            print(item)


if __name__ == '__main__':
    pass

取包脚本主体如下:

点击查看代码
# _*_ utf-8 _*_
import os, sys, re, glob, time, shutil
from zipfile import ZipFile
from os.path import join, exists, dirname
from svn.remote import RemoteClient
from autoApiDeamo import zip_folder, dealwith_service
from autoApiDeamo import printInfo, printMember, dealwith_data, printListInfo

# 定义部署包路径和取包路径
APPS_HOME = '/data/aa/cc/dd/apps'
# RELEASE_HOME = '/data/aa/cc/dd/xiaoweiba-release'
RELEASE_HOME = os.getcwd()
# 获取当前时间
NOW_TIME = time.strftime("%Y%m%d%H%M")  # 202306071508
# 获取xlsx清单
file_list = re.findall('小尾巴想看雪\S+.xlsx',
                       str(glob.glob(join(RELEASE_HOME,"/*.*"))))
XLSX_FILE = file_list[0]
# 增量版本号
RELEASE_NAME = str(XLSX_FILE)[15:][:-5]
# 镜像版本号
RELEASE_NUM = str(XLSX_FILE)[18:][:-5]
IMAGES_TARNAME = 'xiaoweiba-images-' + RELEASE_NAME + '.tar.gz'
SQL_ZIPNAME = 'xiaoweiba-sql-' + RELEASE_NAME + '.zip'
# 取包镜像备份目录
RELEASE_BAK = join(RELEASE_HOME, 'release_bak')
# 基准包备份目录(初始备份+每次增量完后备份)
PACKAGE_BASE_BAK = join(RELEASE_HOME, join('package_base_bak',RELEASE_NAME))
# 取包存放目录
PACKAGE_HOME = join(RELEASE_HOME, RELEASE_NAME)
BACKUP_IMAGES = "true"  # 是否打包镜像
IMAGE_NAMES = ''  # 定义镜像名,在使用时需要去重
# 脚本SVN库本地关联目录
DOC_HOME = join(RELEASE_HOME, 'releasedoc')
# 脚本SVN库地址
DOC_SVN_PATH = 'http://192.168.xx.xx/SC/xiaoweiba/aa/ee/releasedoc'
svn_username = 'xiaoweiba'
svn_password = 'xiaoweiba@522'


def autoCreateDir():
    if len(file_list) != 1:
        print('==当前目录下出现了{0}个xlsx文件,请确认并清理不是本次发布得xlsx文件'.format(len(file_list)))
        sys.exit()

    """检查取包目录是否存在,不存在则创建"""
    if exists(PACKAGE_HOME):
        print('=={0}已存在'.format(PACKAGE_HOME))
    else:
        print('==创建目录{0}'.format(PACKAGE_HOME))
        os.mkdir(PACKAGE_HOME)


def getServicePackage():
    """提取后端发布包"""
    # 从excel文件获取代码路径列数据,返回数据为字典
    global IMAGE_NAMES
    global SERVICE_CODE_LIST
    SERVICE_CODE_LIST = dealwith_service(dealwith_data(XLSX_FILE,
                                                       '代码路径', sheetNum=0))
    if SERVICE_CODE_LIST['wen4sx']:
        printListInfo('wen代码路径', SERVICE_CODE_LIST['wen4sx'])
    if SERVICE_CODE_LIST['huairen']:
        printListInfo('huairen后端代码路径', SERVICE_CODE_LIST['huairen'])

    # 获取后端代码并移动到基础包目录
    for key in SERVICE_CODE_LIST.keys():
        if SERVICE_CODE_LIST[key]:
            # 处理需要打包的镜像名
            if key == 'wen4sx':
                IMAGE_NAMES = IMAGE_NAMES + ' ' + 'wen:' + RELEASE_NUM
            else:
                IMAGE_NAMES = IMAGE_NAMES + ' ' + key + ':' + RELEASE_NUM

            for item in SERVICE_CODE_LIST[key]:
                apps_path = APPS_HOME + '/' + key + '/' + item
                release_path = RELEASE_HOME + '/' + key + '/' + item
                # 从测试环境目录移动后端代码到发布基准目录
                shutil.copy(apps_path, release_path)


def getStaticPackage():
    global IMAGE_NAMES
    global STATIC_CODE_LIST
    STATIC_CODE_LIST = dealwith_service(dealwith_data(XLSX_FILE,
                                                      '设计方案', sheetNum=0))
    # 测试完成后视情况判断是否删除
    printListInfo('前端路径', SERVICE_CODE_LIST['nginx'])

    # # # 获取前端代码并移动到基础包目录
    for key in STATIC_CODE_LIST.keys():
        if STATIC_CODE_LIST[key]:
            # 处理需要打包的镜像名
            if key == 'nginx':
                IMAGE_NAMES = IMAGE_NAMES + ' ' + key + ':' + RELEASE_NUM

            for item in STATIC_CODE_LIST[key]:
                apps_path = APPS_HOME + '/' + key + '/' + item
                release_path = RELEASE_HOME + '/' + key + '/' + item
                # 从测试环境目录移动前端代码到发布基准目录
                shutil.copy(apps_path, release_path)


def getSqlPackage():
    # 获取单号
    global NUMBER_CODE_LIST
    NUMBER_CODE_LIST = dealwith_service(dealwith_data(XLSX_FILE,
                                                      '问题编号', sheetNum=0))
    if NUMBER_CODE_LIST['number']:
        # 测试完成后删除
        printListInfo('单号', SERVICE_CODE_LIST['number'])
        patterns = ''
        for item in NUMBER_CODE_LIST['number']:
            if patterns:
                patterns = patterns + '|' + item
            else:
                patterns = item

        if not exists(DOC_HOME):
            os.mkdir(DOC_HOME)

        # checkout svn库
        svnClient = RemoteClient(DOC_SVN_PATH,
                                 username=svn_username, password=svn_password)
        svnClient.checkout(DOC_HOME)

        os.chdir(DOC_HOME)
        num_list = []
        for root, dirs, files in os.walk(DOC_HOME):
            # 测试完成后删除
            if not re.findall('已发现场', root):
                # 测试完成后删除
                # print('{0} -- {1}'.format(root, files))
                if re.findall(patterns, root):
                    num_list.append(root.replace(DOC_HOME + '/', ''))

        if num_list:
            print('==本次匹配单号:{0}'.format(num_list))
            # 打包zip压缩包文件
            zip_folder(num_list, join(PACKAGE_HOME, SQL_ZIPNAME))

            # 移动到正式已执行
            svn_command_mv = 'mv'
            for item in num_list:
                dir_path = join(DOC_HOME, item)
                target_path = join(DOC_SVN_PATH, dirname(item))
                tar_path = join(target_path, join(target_path, '已发正式'))
                command_args_mv = [dir_path, tar_path]
                svnClient.run_command(svn_command_mv, command_args_mv)

        else:
            print('==svn库中未匹配到相关单号内容')

    else:
        print('==从excel中未获取单号')


def tarImages():
    # 执行脚本
    os.chdir(RELEASE_HOME)
    os.system('sh build_image.sh')

    # 镜像
    if IMAGE_NAMES:
        os.system('docker save {0} -o {1}'.format(IMAGE_NAMES,
                                                  join(PACKAGE_HOME, IMAGES_TARNAME)))

    # 移动清单文件到package_home
    shutil.move(XLSX_FILE, PACKAGE_HOME)
    # 将增量文件名写入txt文件
    with open(join(PACKAGE_HOME, RELEASE_NAME + '_增量清单.txt'), mode='w+') as file:
        for item in os.listdir(PACKAGE_HOME):
            file.write(item)
        file.close()

    # 备份增量包和备份基准包
    if not exists(PACKAGE_BASE_BAK):
        os.mkdir(PACKAGE_BASE_BAK)
    for item in IMAGE_NAMES:
        service = item.split(':')[0]
        if service == 'wen':
            service = 'wen4sx'
        shutil.copy(service, join(PACKAGE_BASE_BAK,
                                  service + '-' + NOW_TIME))
    if not exists(RELEASE_BAK):
        os.mkdir(RELEASE_BAK)
    shutil.copy(PACKAGE_HOME, RELEASE_BAK)


def detailedList():
    # 显示增量内容
    printInfo('增量取包清单')
    printInfo('前端文件清单')
    printMember(STATIC_CODE_LIST, 'service')
    printInfo('后端文件清单清单')
    printMember(SERVICE_CODE_LIST, 'service')
    printInfo('容器镜像清单')
    image_list = IMAGE_NAMES.replace(' ', '\n')
    print(image_list)
    sql_num, doc_num = 0, 0
    zip_name_path = join(PACKAGE_HOME, SQL_ZIPNAME)
    if zip_name_path:
        for item in ZipFile(zip_name_path).namelist():
            if re.findall('\S+.sql$', item):
                if sql_num == 0:
                    printInfo('业务脚本清单')
                sql_num += 1
                print(item)

        for item in ZipFile(zip_name_path).namelist():
            if re.findall('\S+.docx$|\S+.doc$|\S+.xlsx$|\S+.xls$|\S*.csv$', item):
                if doc_num == 0:
                    printInfo('操作票文档')
                doc_num += 1
                print(item)

    else:
        printInfo('业务脚本清单')
        printInfo('操作票文档')


if __name__ == '__main__':
    autoCreateDir()
    getServicePackage()
    getStaticPackage()
    getSqlPackage()
    tarImages()
    detailedList()

取包后自动下发工单:

点击查看代码
# coding: utf-8

import time,sys
import requests
import jsonpath as jp
import json


# 类的实例化

class ApiDemo():
    '''
        定义函数进行引用
    '''

    # 登录url
    def do_get(self, url, params=None, **kwargs):
        return requests.get(url=url, params=params, **kwargs)

    # 切换项目
    def do_post(self, url, data=None, json=None, **kwargs):
        return requests.post(url=url, data=data, json=json, **kwargs)

    # 数据读取
    def get_data(self, res, keys, part=2):
        '''res需要是一个字典'''
        if res is not None:
            try:
                if part == 2:
                    value = jp.jsonpath(json.loads(res), '$..{0}'.format(keys))
                else:
                    value = jp.jsonpath(json.loads(res), '$.{0}'.format(keys))
                    # 判断值
                if value:
                    if len(value) == 1:
                        return value[0]
                return value
            except Exception as e:
                return e

    def get_data1(self, res, keys):
        '''res需要是一个字典'''
        if res is not None:
            try:
                value = jp.jsonpath(json.loads(res), keys)
                if value:
                    if len(value) == 1:
                        return value[0]
                return value
            except Exception as e:
                return e

    # file_name = 'data.yaml'
    # current_path = os.path.abspath("../data")
    # file = os.path.join(current_path, file_name)

    # def get_yaml(self, file_name, keys):
    #     '''
    #         从yaml文件中读取参数,返回一个字典
    #         将结果处理为一个json对象
    #     '''
    #     with open(file_name, 'r', encoding="utf-8") as file:
    #         file_read = file.read()
    #         file.close()
    #         file_read = yaml.load_all(file_read, Loader=yaml.FullLoader)
    #         while True:
    #             try:
    #                 values = json.dumps(next(file_read))  # 这个生成器一次就可以输出来
    #                 return self.get_data(values, keys)
    #             except StopIteration:
    #                 break


api = ApiDemo()

# 定义参数
base_url = 'http://weiba.haoren.com'
login_url = base_url + '/web/login.html'
session_url = base_url + '/get/auth/session'
# 登录用户信息,未用到
params = {
    'account': "hauiren_user",
    'password': "xZzyjv/6zet7/7Uxq+L3ig=="
}
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27',
    'Cookie': 'JSESSIONID=9FA2039AE5B37CEA49B6A016DE81BC39; OWASP_CSRFTOKEN=20UR-7LDT-UD5J-SFK1-WY3R-VL8Y-AWED-9P1X; _account=%7B%22account%22%3A%22dwglscm%22%2C%22password%22%3A%22xZzyjv%2F6zet7%2F7Uxq%2BL3ig%3D%3D%22%2C%22inputPassword%22%3A%22POWER_scm%232020%22%7D; province_flag=7F079D562F9A8360BE8CA1292A27AF28; _isLogout=false; JSESSIONID=CD4115BAFBAD89CD02F4050B278632E0; access-token=eyJhbGciOiJIUzUxMiJ9.eyJhY2NvdW50IjoiZHdnbHNjbSIsInVzZXJJZCI6IkJDQTEwQkJEQTUzOTRDN0RCNTRENkE4QkNDNDFGMEYxIiwiZW1wbG95ZWVJZCI6IkJFREYwM0E5NDdBNDQ3NDk5OUI0QjNCNjQ1ODdFQjI4IiwiZW1wbG95ZWVOYW1lIjoi6YWN572u566h55CG5ZGYIiwib3JnSWQiOiJGNURDNEUzRUZFQUE0RDA4OTBCQjIzRjM0RkNBM0M5RCIsIm9yZ0NvZGUiOiIwMzExNzYiLCJvcmdOYW1lIjoi6YWN572u566h55CG57uEIiwiYmFzZU9yZ0NvZGUiOiIiLCJ0aGlyZFN5c3RlbU5hbWUiOiJKQURQIiwic2FwSHJPcmdJZCI6IiIsInN5c3RlbU5hbWUiOiJudWxsIiwiYmVsb25nUHJvdmluY2UiOiIwMyIsInN1YiI6IumFjee9rueuoeeQhuWRmCIsImlhdCI6MTY2MjYwMDUxOCwiZXhwIjoxNjYyNjAyNDA5LCJyZWZyZXNoSW50ZXJ2YWwiOjMwLCJqdGkiOiI4MTkzM2RiNS0xNzY0LTRmNmItYThiOS02ZmJhZDZkNDkxYmEiLCJyZWZyZXNoRGF0ZSI6MTY2MjYwMDYwOTAwMH0.xO78U6K55jCtnqhfhwqKucagJuZ9y3BXpkFfrZ7ZNtmkg5OJQEAMxk-onc8ikoeoXkZZu6zhd-ADMa9jSETszg'
}

payload = {
    "endPlanReleaseTime": "2023-04-14",
    'operatorIdList': [],
    'pageNo': 1,
    'pageSize': 25,
    'projectId': "AAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    'releaseTypeList': [],
    "startPlanReleaseTime": "2023-01-14",
    'statusList': [5, 6]
}

if len(sys.argv) == 2:
    release_name = sys.argv[1]
else:
    print('请输入完整的单号')
    sys.exit()

#
versionlist_id = []


def get_versionlist():
    url = base_url + '/get/testcase/version/queryVersionListByPagePagination'
    data = payload
    # print("测试待删除data值为%s" %(data))
    VersionList_res = json.loads(api.do_post(url=url, json=data, headers=headers).text)
    VersionList_list = jp.jsonpath(VersionList_res, '$..list')[0]
    # print(VersionList_list)
    for single_version in VersionList_list:
        single_id = single_version["id"]
        if release_name == single_version["versionName"]:
            versionlist_id.append(single_id)
    print('单号为:%s'.format(versionlist_id))


def dealwith_sc():
    if versionlist_id:
        for value in versionlist_id:
            loadById_url = base_url + '/get/testcase/version/loadById/' + value
            loadById_info = requests.get(loadById_url)
            # 获取工单号的operator,我们的节点为:管理员
            operator_scm = jp.jsonpath(json.loads(loadById_info.text), '$..operator')[0]

            id = jp.jsonpath(json.loads(loadById_info.text), '$..id')[0]
            # print("测试待删除--这里的ID输出%s" % (id))
            versionName = jp.jsonpath(json.loads(loadById_info.text), '$..versionName')[0]
            projectId = jp.jsonpath(json.loads(loadById_info.text), '$..projectId')[0]
            operatorId = jp.jsonpath(json.loads(loadById_info.text), '$..operatorId')[0]
            payload_data = jp.jsonpath(json.loads(loadById_info.text), '$.data')[0]
            planReleaseCount = jp.jsonpath(json.loads(loadById_info.text), '$..planReleaseCount')[0]  # 工单数量
            # print('测试待删除,sleep时间为%d' %(2*planReleaseCount))

            if operator_scm == '管理员':
                print('---- 待内部发布-->点击提交 ----')
                # http://weiba.huairen.com/get/testcase/version/waitVersionRelease 重要请求
                url = base_url + '/get/testcase/version/waitVersionRelease'
                payload_data['releasePackageName'] = versionName
                payload_data['remark'] = ''
                payload_data['webSocketId'] = '9934da5e94def6357b57569b5c4a9a4a'  # 目前看来所有工单都可以使用
                payload = payload_data
                res = requests.post(url=url, json=payload, headers=headers)
                print('{0}({1})的步骤{2}处理结果为: {3}'.format(versionName, id, 'waitVersionRelease', res.text))
                time.sleep(4 * planReleaseCount)  # 下发工单需要时间 == 缺陷单条数*2
            else:
                print('---- 待内部发布-->点击提交 ----')
                print('{0}({1})的状态不为待内部发布状态'.format(versionName, id))

            res_data = jp.jsonpath(json.loads(requests.get(loadById_url).text), '$.data')[0]
            len_num = len(jp.jsonpath(res_data, '$.versionConfirmerRecordList')[0])
            operator_fz = jp.jsonpath(res_data, '$.operator')[0]


            # 存在部分工单长度不一致的情况,目前处理方式为所有的工单都过一次接口,看其响应状态判断处理结果
            if operator_fz == '人员名':
                print('---- 环境发布-->点击提交 ----')
                # 获取object_id
                url = base_url + '/get/testcase/sceneReleaseTask/queryVersionReleaseRecordByVersonIdPagination'
                payload = {'releaseVersionId': id, 'pageNo': 1, 'pageSize': 10}
                res = requests.post(url=url, json=payload, headers=headers)
                objectId = jp.jsonpath(json.loads(res.text), '$..id')[0]
                # print('获取objectId为: %s' % (objectId))

            # waitSimulationEnvRelease
            url = base_url + '/get/testcase/sceneReleaseTask/waitSimulationEnvRelease'
            payload = {'id': objectId, 'remark': ''}
            # print(payload)
            res = requests.post(url=url, json=payload, headers=headers)
            if res.status_code == 200:
                print('{0}({1})的步骤{2}处理结果为: {3}'.format(versionName,
                                                                id, 'waitSimulationEnvRelease', res.text))
            elif res.status_code == 500:
                print('{0}({1})的状态不为仿真环境发布状态'.format(versionName, id))
            else:
                # 暂时应该还无其它
                print('{0}({1})的步骤{2}处理结果为: {3}'.format(versionName,
                                                                id, 'waitSimulationEnvRelease', res.text))

            print('')


if __name__ == '__main__':
    get_versionlist()
    dealwith_sc()

posted @ 2023-07-03 10:15  小尾巴想看雪  阅读(27)  评论(0编辑  收藏  举报