pymysql和MySQLdb

MySQLdb创建mysql数据库表

其实mysqldb创建表是有限制的,要求是5.1-5.5版本

pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb几乎相同。

1.下载

首先要下载下载:请到官方网站http://sourceforge.net/projects/mysql-python/或者点击链接下载http://downloads.sourceforge.net/project/mysql-python/mysql-python-test/1.2.3c1/MySQL-python-1.2.3c1.tar.gz?use_mirror=nchc

2.解压安装

解压:tar zxvf MySQL-python*

进入文件目录,运行以下命令:
python setup.py install 

3. 用法

'''
# 打开数据库连接
db = MySQLdb.connect("192.168.72.131","root","123456","test" )
# 使用cursor()方法获取操作游标
cursor = db.cursor()
# 如果数据表已经存在使用 execute() 方法删除表。
cursor.execute("DROP TABLE IF EXISTS EMPLOYEE")
# 创建数据表SQL语句
sql = \"\"\"CREATE TABLE EMPLOYEE (
         FIRST_NAME  CHAR(20) NOT NULL,
         LAST_NAME  CHAR(20),
         AGE INT,
         SEX CHAR(1),
         INCOME FLOAT )\"\"\"
cursor.execute(sql)
# 关闭数据库连接
db.close()
'''

 

pymsql

一、下载安装:

pip3 install pymysql

二、使用

1、执行SQL

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pymysql
 
# 创建连接
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1')
# 创建游标
cursor = conn.cursor()
 
# 执行SQL,并返回收影响行数
effect_row = cursor.execute("update hosts set host = '1.1.1.2'")
 
# 执行SQL,并返回受影响行数
#effect_row = cursor.execute("update hosts set host = '1.1.1.2' where nid > %s", (1,))
 
# 执行SQL,并返回受影响行数
#effect_row = cursor.executemany("insert into hosts(host,color_id)values(%s,%s)", [("1.1.1.11",1),("1.1.1.11",2)])
 
 
# 提交,不然无法保存新建或者修改的数据
conn.commit()
 
# 关闭游标
cursor.close()
# 关闭连接
conn.close()
View Code

2、获取新创建数据自增ID

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pymysql
 
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1')
cursor = conn.cursor()
cursor.executemany("insert into hosts(host,color_id)values(%s,%s)", [("1.1.1.11",1),("1.1.1.11",2)])
conn.commit()
cursor.close()
conn.close()
 
# 获取最新自增ID
new_id = cursor.lastrowid
View Code

3、获取查询数据

cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)---》 这么写可以让返回的值为字典
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pymysql
 
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1')
cursor = conn.cursor()
cursor.execute("select * from hosts")
 
# 获取第一行数据
row_1 = cursor.fetchone()
 
# 获取前n行数据
# row_2 = cursor.fetchmany(3)
# 获取所有数据
# row_3 = cursor.fetchall()
 
conn.commit()
cursor.close()
conn.close()
View Code

注:在fetch数据时按照顺序进行,可以使用cursor.scroll(num,mode)来移动游标位置,如:

  • cursor.scroll(1,mode='relative')  # 相对当前位置移动
  • cursor.scroll(2,mode='absolute') # 相对绝对位置移动

4、fetch数据类型

  关于默认获取的数据是元祖类型,如果想要或者字典类型的数据,即:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pymysql
 
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1')
 
# 游标设置为字典类型
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
r = cursor.execute("call p1()")
 
result = cursor.fetchone()
 
conn.commit()
cursor.close()
conn.close()
View Code

5、插入数据注意的地方

    li = tuple(['hrYjT71474436254', '2016-09-21 13:37:34']) #这句是重点
    print li
    sql = "insert INTO `222` VALUES {}".format(li)
    print sql
    cursor.execute(sql)
    conn.commit()

自己造轮子, 二次封装pymysql

class base_pymysql(object):
    def __init__(self, host, port, user, password, db_name=None):
        self.db_host = host
        self.db_port = int(port)
        self.user = user
        self.password = str(password)
        self.db = db_name
        self.conn = None
        self.cursor = None

    def connect(self):
        self.conn = pymysql.connect(host=self.db_host, port=self.db_port, user=self.user,
                                    passwd=self.password, db=self.db, charset="utf8")
        self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)


class MyPymysql(base_pymysql):
    """
    Basic Usage:

        ret = My_Pymysql('test1')
        res = ret.selectone_sql("select * from aaa")
        print(res)
        ret.close()
        --------------
        class writer_information_tables():
            def __init__(self, libname="metadata"):
                self.libname = libname
                self.res = MyPymysql('metadata')

            def insert_sql(self, data):
                sql = '''insert INTO `meta_variable` SET DataTableID={}, VarValues="%s";'''.format(
                    data["DataTableID"])
                value = (data["VarValues"])
                # print(sql)
                # self.res.idu_sql(sql)
                self.res.insert_sql(sql, value=value)
            def commit(self):
                self.res.commit()
                
            def close(self):
                self.res.close()
        ----------------
        def CreateDataTableInfor(data):
            sql = "insert into `meta_data_table` SET DataTableID='{}';".format(
                data["DataTableID"])
            ret = MyPymysql('metadata')
            ret.idu_sql(sql)
            ret.close()
        ---------------

    Precautions:
        Config.__init__(self, config_filename="my.cnf")

    """

    def __init__(self, conf_name):
        self.conf = Config().get_content(conf_name)
        super(MyPymysql, self).__init__(**self.conf)
        self.connect()

    def idu_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 考虑到多语句循环, try就不写在这里了
        self.cursor.execute(sql, value)
        self.conn.commit()

    def commit(self):
        self.conn.commit()

    def rollback(self):
        self.conn.rollback()

    def insert_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)
    def update_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)
    def delete_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)

    def selectone_sql(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()
        return self.cursor.fetchone()

    def selectall_sql(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()
        return self.cursor.fetchall()

    def select_sql(self, sql, value=None):
        # 防止sql注入
        self.cursor.execute(sql, value)
        self.conn.commit()
        return self.cursor.fetchall()

    def close(self):
        self.conn.close()
        self.conn = None
        self.cursor = None
自己造的轮子代码

 

class Config(object):
    """
    # Config().get_content("user_information")
    """

    def __init__(self, config_filename="zk_css.cnf"):
        file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), config_filename)
        self.cf = configparser.ConfigParser()
        self.cf.read(file_path)

    def get_sections(self):
        return self.cf.sections()

    def get_options(self, section):
        return self.cf.options(section)

    def get_content(self, section):
        result = {}
        for option in self.get_options(section):
            value = self.cf.get(section, option)
            result[option] = int(value) if value.isdigit() else value
        return result
Config类

 

class base_pymysql(object):
    def __init__(self, host, port, user, password, db_name=None,  cursor=pymysql.cursors.DictCursor):
        self.db_host = host
        self.db_port = int(port)
        self.user = user
        self.password = str(password)
        self.db = db_name
        self.conn = None
        self.cursor = cursor

    def connect(self):
        self.conn = pymysql.connect(host=self.db_host, port=self.db_port, user=self.user,
                                    passwd=self.password, db=self.db, charset="utf8")
        self.cursor = self.conn.cursor(cursor=self.cursor)


class MyPymysql(base_pymysql):
    """
    Basic Usage:

        ret = My_Pymysql('test1')
        res = ret.selectone_sql("select * from aaa")
        print(res)
        ret.close()
        --------------
        class writer_information_tables():
            def __init__(self, libname="metadata"):
                self.libname = libname
                self.res = MyPymysql('metadata')

            def insert_sql(self, data):
                sql = '''insert INTO `meta_variable` SET DataTableID={}, VarValues="%s";'''.format(
                    data["DataTableID"])
                value = (data["VarValues"])
                # print(sql)
                # self.res.idu_sql(sql)
                self.res.insert_sql(sql, value=value)
            def commit(self):
                self.res.commit()
                
            def close(self):
                self.res.close()
        ----------------
        def CreateDataTableInfor(data):
            sql = "insert into `meta_data_table` SET DataTableID='{}';".format(
                data["DataTableID"])
            ret = MyPymysql('metadata')  或者 MyPymysql('metadata', None), 返回结果将是两种不同的形态 {} 和 ()
            ret.idu_sql(sql)
            ret.close()
        ---------------

    Precautions:
        Config.__init__(self, config_filename="my.cnf")

    """

    def __init__(self, conf_name, cursor=pymysql.cursors.DictCursor):
        self.conf = Config().get_content(conf_name)
        super(MyPymysql, self).__init__(**self.conf, cursor=cursor)
        self.connect()

    def idu_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 考虑到多语句循环, try就不写在这里了
        self.cursor.execute(sql, value)
        self.conn.commit()

    def commit(self):
        self.conn.commit()

    def rollback(self):
        self.conn.rollback()

    def insert_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)
    def update_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)
    def delete_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)

    def selectone_sql(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()
        return self.cursor.fetchone()

    def selectall_sql(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()
        return self.cursor.fetchall()

    def select_sql(self, sql, value=None):
        # 防止sql注入
        self.cursor.execute(sql, value)
        self.conn.commit()
        return self.cursor.fetchall()

    def close(self):
        self.conn.close()
        self.conn = None
        self.cursor = None
升级版, 改变返回值形态

注意:少写了一句: def close(self):里面需要增加一句: self.cursor.close() ------>忘了 

 

 

 

class A():

    def bb(self):
        sql = \
            """
            select * from db_metadata.meta_project limit 1;
            """
        data = self.pymysql.selectall_sql(sql)
        return data




class DSF(A):
    def __init__(self, libname="notdbMysql"):
        self.libname = libname
        self.pymysql = MyPymysql(self.libname)


    def aa(self):
        sql = \
            """
            select * from db_metadata.meta_project limit 1;
        
            """
        data = self.pymysql.selectall_sql(sql)
        return data

    def close(self):
        self.pymysql.close()

if __name__ == '__main__':
    ret = DSF()
    print(ret.aa())
    print(ret.bb())
    ret.close()

 

model 类使用方法

class CreateProjectProduce(SelectInfo):
    def __init__(self, libname="notdbMysql"):
        self.libname = libname
        self.pymysql = MyPymysql(self.libname)

    def __enter__(self):
        return self

    def check_the_data(self, ProjectID):
        '''
        explain:
            检查核对数据是否存在
        :return:
        '''
     # 调用继承类方法
data = self.CatSimpleProjectProduceModel(ProjectID) return data def insert_sql(self, data): sql = '''insert INTO `meta_variable` SET DataTableID={}, VarValues="%s";'''.format( data["DataTableID"]) value = (data["VarValues"]) self.pymysql.insert_sql(sql, value=value) def close(self): self.pymysql.close() def __exit__(self, exc_type, exc_val, exc_tb): self.close()

 

 

 

Use Method

#!/usr/bin/env python
# -*- coding:utf-8 -*-


import os, configparser, pymysql

'''
1. Config 类的 my.cnf 需要指定路径
2. my.cnf 的文件详情
    my.cnf:
    
        # -----<notdbMysql>----- #配置中没有写库的名字,这样更加灵活
        [notdbMysql]
        host = 192.168.2.137
        port = 3306
        user = root
        password = python123
3. 返回值形态设置
    class writer_information_tables():
    def __init__(self, libname="notdbMysql"):
        self.libname = libname
        self.res = MyPymysql(self.libname) # --> 返回值形态是{'aid': 5, 'varlabel': 'adadsf', 'vid': 0}
        # self.res = MyPymysql(self.libname, None)  # --> 返回值形态是(0, 5, 'adadsf')
'''


class Config(object):
    """
    # Config().get_content("user_information")
    """


    def __init__(self, config_filename="my.cnf"):
        file_path = "/opt/code/my_code/tornado_uedit/my.cnf"
        self.cf = configparser.ConfigParser()
        self.cf.read(file_path)

    def get_sections(self):
        return self.cf.sections()

    def get_options(self, section):
        return self.cf.options(section)

    def get_content(self, section):
        result = {}
        for option in self.get_options(section):
            value = self.cf.get(section, option)
            result[option] = int(value) if value.isdigit() else value
        return result

class base_pymysql(object):
    def __init__(self, host, port, user, password, db_name=None, cursor=pymysql.cursors.DictCursor):
        self.db_host = host
        self.db_port = int(port)
        self.user = user
        self.password = str(password)
        self.db = db_name
        self.conn = None
        self.cursor = cursor

    def connect(self):
        self.conn = pymysql.connect(host=self.db_host, port=self.db_port, user=self.user,
                                    passwd=self.password, db=self.db, charset="utf8")
        self.cursor = self.conn.cursor(cursor=self.cursor)


class MyPymysql(base_pymysql):
    """
    Basic Usage:

        ret = My_Pymysql('test1')
        res = ret.selectone_sql("select * from aaa")
        print(res)
        ret.close()
        --------------
        class writer_information_tables():
            def __init__(self, libname="metadata"):
                self.libname = libname
                self.res = MyPymysql('metadata')

            def insert_sql(self, data):
                sql = '''insert INTO `meta_variable` SET DataTableID={}, VarValues="%s";'''.format(
                    data["DataTableID"])
                value = (data["VarValues"])
                # print(sql)
                # self.res.idu_sql(sql)
                self.res.insert_sql(sql, value=value)
            def commit(self):
                self.res.commit()

            def close(self):
                self.res.close()
        ----------------
        def CreateDataTableInfor(data):
            sql = "insert into `meta_data_table` SET DataTableID='{}';".format(
                data["DataTableID"])
            ret = MyPymysql('metadata')  或者 MyPymysql('metadata', None), 返回结果将是两种不同的形态 {} 和 ()
            ret.idu_sql(sql)
            ret.close()
        ---------------

    Precautions:
        Config.__init__(self, config_filename="my.cnf")

    """

    def __init__(self, conf_name, cursor=pymysql.cursors.DictCursor):
        self.conf = Config().get_content(conf_name)
        super(MyPymysql, self).__init__(**self.conf, cursor=cursor)
        self.connect()

    def idu_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 考虑到多语句循环, try就不写在这里了
        self.cursor.execute(sql, value)
        self.conn.commit()

    def commit(self):
        self.conn.commit()

    def rollback(self):
        self.conn.rollback()

    def insert_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)

    def update_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)

    def delete_sql(self, sql, value=None):
        # adu: insert, delete, update的简写
        # 防止sql注入
        self.cursor.execute(sql, value)

    def selectone_sql(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()
        return self.cursor.fetchone()

    def selectall_sql(self, sql):
        self.cursor.execute(sql)
        self.conn.commit()
        return self.cursor.fetchall()

    def select_sql(self, sql, value=None):
        # 防止sql注入
        self.cursor.execute(sql, value)
        self.conn.commit()
        return self.cursor.fetchall()

    def close(self):
        self.conn.close()
        self.conn = None
        self.cursor = None


class writer_information_tables():
    def __init__(self, libname="notdbMysql"):
        self.libname = libname
        self.res = MyPymysql(self.libname) # --> 返回值形态是{'aid': 5, 'varlabel': 'adadsf', 'vid': 0}
        # self.res = MyPymysql(self.libname, None)  # --> 返回值形态是(0, 5, 'adadsf')

    def insert_sql(self):
        sql = "select * from myTest.a1"
        data = self.res.selectone_sql(sql)
        return data

    def commit(self):
        self.res.commit()

    def close(self):
        self.res.close()


if __name__ == '__main__':
    pym_obj = writer_information_tables()
    print(pym_obj.insert_sql())
    pym_obj.close()
Use Method

 

Pymysql 进行事务回滚

#!/usr/bin/env python
import MySQLdb

def connect_mysql():
    db_config = {
        'host': '127.0.0.1',
        'port': 3306,
        'user': 'root',
        'passwd': 'pzk123'
    }
    c = MySQLdb.connect(**db_config)
    return c

if __name__ == '__main__':
    c = connect_mysql()              # 首先连接数据库
    cus = c.cursor()                 # 生成游标对象
    sql = 'drop database test;'      # 定义要执行的SQL语句
    try:
        cus.execute(sql)             # 执行SQL语句
        c.commit()                   # 如果执行成功就提交事务
    except Exception as e: 
        c.rollback()                 # 如果执行失败就回滚事务
        raise e
    finally:
        c.close()                    # 最后记得关闭数据库连接
mysqldb的

 

 

PYMYSQL防止sql注入问题

1.寻找到SQL注入的位置

2.判断服务器类型和后台数据库类型

3.针对不通的服务器和数据库特点进行SQL注入攻击

案例:

1、字符串拼接查询,造成注入

import pymysql

conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='User')
cursor = conn.cursor()
username=input()
password =input()
# 正常构造语句的情况
sql = "select user,pwd from User where user='%s' and pwd='%s'" % (username,password)

row_count = cursor.execute(sql)
row_1 = cursor.fetchone()
print(row_count, row_1)
conn.commit()
cursor.close()
conn.close()
案例

其实用户可以这样输入实现免帐号登录:

username: ‘or 1 = 1 –-

password:

如若没有做特殊处理,那么这个非法用户直接登陆进去了.

当输入了上面的用户名和密码,服务端的sql就变成:

sql = "select user,pwd from User where user=‘'or 1 = 1 –-' and pwd='%s'"

因为条件后面username=”or 1=1 用户名等于 ” 或1=1 那么这个条件一定会成功;然后后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都能正确执行,用户轻易骗过系统,获取合法身份。

解决方法:

注意:一定要用"%s" 而不是‘%s’

#! /usr/bin/env python
# -*- coding:utf-8 -*-
import pymysql
 
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='User')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
username= input()
password =input()

#执行参数化查询
row_count=cursor.execute("select user,pwd from User where user='%s' and pwd='%s'" ,(username,password))
#execute()函数本身就有接受SQL语句变量的参数位,只要正确的使用(直白一点就是:使用”逗号”,而不是”百分号”)就可以对传入的值进行correctly转义,从而避免SQL注入的发生。


#内部执行参数化生成的SQL语句,对特殊字符进行了加\转义,避免注入语句生成。
# sql=cursor.mogrify("select user,pwd from User where user='%s' and pwd='%s'" ,(username,password))
# print (sql)
 
row_1 = cursor.fetchone()
print(row_count,row_1)
 
conn.commit()
cursor.close()
conn.close()

 

 防sql注入问题2

 报错:TypeError: %d format: a number is required, not str"

cursor.execute("""
                    insert into tree (id,parent_id,level,description,code,start,end)
                    values (%d,%d,%d,%s,%s,%f,%f)
                    """, (1,1,1,'abc','def',1,1)
                    )

如果这么写报错

对应mysql表类型

id int(255),
parent_id int(255),
level int(11),
description varchar(255),
code varchar(255),
start decimal(25,4),
end decimal(25,4)
" File "/usr/lib/pymodules/python2.6/MySQLdb/cursors.py", line 151, in execute query = query % db.literal(args)

TypeError: %d format: a number is required, not str"

解决办法:

The format string is not really a normal Python format string. You must always use %s for all fields.

 

防sql注入问题3

pymysql防sql注入必须是"%s", 可以这种%s形式的
from collections import OrderedDict
from common.Base import MyPymysql


d = {'is_top': '0', 'input_data_text': 'adfadf', 'is_comment': '1', 'title': 'adsf', 'tag': ';分离', 'input_data_html': 55158, 'is_release': '1', 'input_data_content': '<p>adfadf<br/></p>', 'is_homepage': '1', 'Password': '', 'edit_category': '2', 'is_password': '0'}

def AddArticleModel(info):
    sql = """
            INSERT INTO blog.text
            SET
                ArticleTitle="%s",

                Release=%s  ----> 这样是错的, pymysql防sql注入必须是"%s", 不能用这种%s形式的,包括数据是int都不行《---可以%s,留着这句话,看看当时因为release关键字让自己写的傻逼笔记

    """
    ret = MyPymysql('notdbMysql')
    value = (
        info["title"],  # ArticleTitle

        int(info["is_release"]),  # Release

    )
    ret.idu_sql(sql, value=value)
    ret.close()

if __name__ == '__main__':
    AddArticleModel(d)

 

 

三、错误集锦

1.

查看mysql单个库大小容量的命令(单位为字节,得到多少M需要除以1048576)

SELECT sum(DATA_LENGTH)+sum(INDEX_LENGTH) FROM information_schema.TABLES where TABLE_SCHEMA='数据库名';

2.

变量名为关键字(尤其前朝余孽留下的坑,特别注意了需要加``)

update `test` set a='' where `index`=0;  这个index耗费了我2个小时,下午5点头晕沉沉的

3.

那么转换一下,上次是测试的语句,下面就直接换正式的了, %s也加引号,视情况而定

sql2 = "update `%s` set a10b1j='%s' where `index`='%s'" % (table_name, li[j], j)

4. 

2.7存入mysql以后unicode的转义解决办法

"""
import pymysql
conn = pymysql.connect(host='112.126.70.69', port=3306, user='datapl', passwd='Rome78Uj', db='data_index_test')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select cid FROM data_bak")
row_3 = cursor.fetchall()
li = [i[0] for i in row_3]
err_li = []
for i in range(len(li)):
    try:
        print i
        a, json_sumdata = VSummary().do_(0, li[i])
        sql = "INSERT INTO data_cid_json SET cid='%s', json_sumdata='%s'" % (li[i], pymysql.escape_string(json_sumdata))
        cursor.execute(sql)
        conn.commit()
    except Exception as e:
        err_li.append(li[i])
        continue
cursor.close()

conn.close()

"""

5.

调用oracle的时候需要注意的问题

他说一直是编码的问题,其实是oracle的问题

6.

解决办法

在创建连接的时候设置一下编码,如:

conn = MySQLdb.connect(host="localhost", user="root", passwd="root", db="db",  charset="utf8")

 

 7. 注意一定要用"%s", 而不能用'%s'

8. 在pymysql里面防sql要注意, 例如: update `%s`  这个时候会把表名也转义, 所以报错

会变成update `\'meta_project\' set ....这样就找不到表名

 之前这么写的

 

 

posted @ 2016-08-06 18:05  我当道士那儿些年  阅读(1637)  评论(3编辑  收藏  举报