阿里云kafka使用记录(python版本)

kafka端   consumer vpc版代码  
import socket
from kafka import KafkaConsumer
from kafka.errors import KafkaError

# context.check_hostname = True

consumer = KafkaConsumer(bootstrap_servers=['192.168.xx.xx:9092'],
                        group_id='xx',
                        api_version = (0,10)
                        )

print('consumer start to consuming...')
consumer.subscribe(('xx',))
for message in consumer:
    print(message.topic)
    print(message.offset)
    print(message.key)
    print(message.value)
    print(message.partition)

 

producer vpc版代码

#!/usr/bin/env python
# encoding: utf-8

import socket
from kafka import KafkaProducer
from kafka.errors import KafkaError

producer = KafkaProducer(bootstrap_servers=['192.168.xx.xx:9092'],
                        api_version = (0,10),
                        retries=5)

partitions = producer.partitions_for('xx')
print('Topic下分区: %s' % partitions)

try:
    future = producer.send(topic='xx', value=b'hello aliyun-kafka!')
    future.get()
    print('send message succeed.')
except KafkaError as e:
    print('send message failed.')
    print(e)

consumer公网版代码

import ssl
import socket
from kafka import KafkaConsumer
from kafka.errors import KafkaError


context = ssl.create_default_context()
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_REQUIRED
# context.check_hostname = True
context.load_verify_locations("/tmp/ca-cert")

consumer = KafkaConsumer(bootstrap_servers=['kafka-ons-internet.aliyun.com:8080'],
                        group_id='xxx',
                        sasl_mechanism="PLAIN",
                        ssl_context=context,
                        security_protocol='SASL_SSL',
                        api_version = (0,10),
                        sasl_plain_username='xxx',
                        sasl_plain_password='1234567890')

print('consumer start to consuming...')
consumer.subscribe(('xxx', ))
for message in consumer:
    print(message.topic)
    print(message.offset)
    print(message.value)
    break

 

  producer 公网版代码
#!/usr/bin/env python
# encoding: utf-8

import ssl
import socket
from kafka import KafkaProducer
from kafka.errors import KafkaError

context = ssl.create_default_context()
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_REQUIRED
# context.check_hostname = True
context.load_verify_locations("/tmp/ca-cert")
#这个文件参考http://github.com/AliwareMQ/aliware-kafka-demos/tree/master/kafka-python-demo

producer = KafkaProducer(bootstrap_servers=['kafka-ons-internet.aliyun.com:8080'],
                        sasl_mechanism="PLAIN",
                        ssl_context=context,
                        security_protocol='SASL_SSL',
                        api_version = (0,10),
                        retries=5,
                        sasl_plain_username='xx',
                        sasl_plain_password='1234567890'#注意是access-key的最后十位)

partitions = producer.partitions_for('xxx')
print ('Topic下分区: %s' % partitions)

try:
    future = producer.send('xxx', b'hello aliyun-kafka!')
    future.get()
    print('send message succeed.')
except KafkaError as e:
    print('send message failed.')
    print(e)

 

 

 

 

从阿里云控台获得连接信息

 

 

本文转载于网络 如有侵权请联系删除

相关文章

  • 状态模式

    最近在看状态机,先逐步从状态模式学起 1.状态模式(StatePattern)允许对象在内部状态改变时改变它的行为,对象看起来好象改了它的类。在传统中编程中使用if-else对所有情况判断并作出对应的行为,这不仅违背了开闭原则,而且会显得臃肿难于阅读和维护,状态模式是用来解决这个问题的状态模式的组成:State:状态接口ConcreteState:状态实现类Context:环境2.流程灯有打开和关闭两种状态,我们用灯的开关状态来说明状态模式:(下面没有使用状态模式)publicclassLight{ privateStringstate; publicStringgetState(){ returnstate; } publicvoidsetState(Stringstate){ this.state=state; } //这里状态只有两种,若情况较多则会臃肿难以维护 publicvoidhandle(){ if("TurnOn".equals(state)){ System.out.println("灯亮了"); }elseif(&qu

  • 竹间科技以认知智能为基础,赋能企业业务发展

    竹间科技创始人兼CEO简仁贤曾在世界人工智能大会上发表了一篇题为《认知智能赋能企业转型》的演讲,重点探讨了认知智能的基石——知识图谱,阐述了知识图谱的定义、优势等,接着围绕产业界极重视的大规模落地问题,结合具体例证及经验心得,描绘了跨越众多行业的不同应用。以下为竹间科技创始人简仁贤先生演讲内容的精彩节选。1、认知智能的重要性对于企业来说,随着时代的发展,互联网已经变成了一个很常见的模型,也是大家生活在这个世界的一种表现方式。认知智能可以帮助企业在外来的数字化变革潮流中加速业务的发展,如果企业开始积极采用认知智能,那么,企业在三五年后获得的红利将远远超过那些没有采用认知智能的企业。2、知识图谱的突出优势知识图谱在认知智能领域中是非常重要的技术,可以协助企业迎接形形色色的业务挑战:第一,它可以帮助企业采集知识,包括业务、商业在内的任何知识;第二,知识图谱有助于发现、整合和使用数据;第三,知识图谱能简洁快速地回答复杂的业务问题;第四,知识图谱可以让AI更加高效。3、认知智能的实际应用认知技术该怎么落地?如何将它应用到企业中?首先要看在当前的业务流程里,是否可以找出一个清晰的四元组模型,包括人

  • 讲得最明白的Elasticsearch源码调试环境搭建教程

    写在前面使用elasticsearch(以下简称ES)也有挺长时间了,一直想找机会深入源码研究下。我看源码有个习惯,就是一定要运行起来。不是只把源码下载下来看看就行的。搭建整个调试环境几乎花了一整天的功夫,这里记录下,希望能帮到需要的人。环境介绍MACMojave系统idea2019.2JDK版本:jdk12elasticsearch版本:7.1.0gradle版本:5.2.1源码下载可以直接去github下载指定release版本的源码,也可以直接clone目前的master分支,然后checkout到一个特定的版本。gitclonehttps://github.com/elastic/elasticsearch.git 复制切换到指定发布版本gitcheckoutv7.x 复制我一般会fork一份到自己的仓库,这样看代码的时候写的注释可以提交到自己的仓库。配置gradle和jdk的环境具体的SDK和gradle的安装步骤不在这里展开了,自行搜索吧。我自己的电脑是通过sdkman管理不同的JDK版本,可以同时有多个JDK版本存在一台主机随时切换使用。这里需要重点说下ES对JDK和Gr

  • 03Python List不得不知的操作之改、查

    上一篇文章我们讲过,Python的List是一种内建的序列型数据结构,由一串元素组成。对于一个List型的对象,它由多个元素构成,对于这一组元素我们必须有方便的方法对它们进行各种增删改查操作才能更好使用它们。我们已经讲过增、删两种操作了,这篇文章再来跟大家一起捋一捋改、查两种操作。List操作之——改其实上一篇文章里讲的增、删也算是对List的更改。我们在这里是借用数据库中常用的操作来学习List的操作,所以也借用了增、删、改、查这种说法,数据库中的改指的是对数据库中已有的记录进行更改。(1)那如何更改List中已有的元素呢?有一种最简单、直接的办法:>>>list1=[0,1,2,3,4]>>>list1[0]=1>>>list1[1,1,2,3,4]复制上面的代码直接拿一个值粗暴的覆盖到List的某个索引处,将原来的值给挤开。(2)除此之外呢,我们还可以很轻易的把一整个List给更改掉,像下面这样:>>>list1[1,1,2,3,4]>>>list1=[1,2,3,4,5]>>&

  • 【2020HBU天梯赛训练】7-31 家庭房产

    7-31家庭房产并查集因为之前没看过书,所以今天卡死了,看了一下知识点才发现原来合并的时候是更改根节点的父亲不是当前节点的父亲。说明不但刷题很重要,看书也很重要,两者缺一不可需要同时进行。给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。输入格式:输入第一行给出一个正整数N(≤1000),随后N行,每行按下列格式给出一个人的房产:编号父母k孩子1...孩子k房产套数总面积复制其中编号是每个人独有的一个4位数的编号;父和母分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1);k(0≤k≤5)是该人的子女的个数;孩子i是其子女的编号。输出格式:首先在第一行输出家庭个数(所有有亲属关系的人都属于同一个家庭)。随后按下列格式输出每个家庭的信息:家庭成员的最小编号家庭人口数人均房产套数人均房产面积复制其中人均值要求保留小数点后3位。家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。输入样例:10 666655515552177771100 123456789012100022300 8888-1-1011000 2468000

  • Python 基础 - 3 常用数值类型

    参考:Python基础-0前言Built-inTypes整型(int)定义参考:classint(x=0)int类型定义如下:创建int值有两种方式:直接赋予变量整数值使用构造器int()创建int类型实例针对第二种方式,如果没有任何输入参数,那么创建int实例值为0如果仅输入单个对象,可以输入一个数字,或者一个数字字符串可选参数base表示第一个参数值所属进制,默认为10,表示输入值为十进制数取值范围为0和[2,36],示例如下:Note:当需要定义输入值的进制时,输入值类型应该为字符串str在所有的进制中,2-进制,8-进制和16-进制可以通过添加前缀0b/0B,0o/0O,or0x/0X的方式进行转换,示例如下:浮点型(float)定义参考:classfloat([x])float类型定义如下:Note:浮点型(float)等同于C语言中的double类型创建float值有两种方式:直接赋予变量整数值使用构造器float()创建float类型实例使用第一种方式,如果该数值没有小数,需要添加后缀.0,否则,解释器会认为这是int类型数值,示例如下:使用第二种方式,如果没有任何输入参

  • BRAM的用量为什么会出现小数

    通过report_utilization可查看设计的资源利用率,而在资源利用率报告中,有时会发现BRAM的Utilization为小数,如下图中的503.50,这是什么原因呢?实际上,BRAM的利用率是以36Kb的BRAM为计算单位的,而一个36Kb的BRAM是由两个相对独立的18KbBRAM构成的。从而,一个36Kb的BRAM可以配置为4种模式:2个18KbRAM;1个18KbRAM+1个18KbFIFO;1个36KbRAM;1个36KbFIFO(点击这里复习BlockRAM的基本结构)另外,资源利用率是通过Tcl脚本生成的。我们也可以自己写Tcl脚本生成资源利用率。这里就以BRAM为例。BRAM的4种配置方式对应的REF_NAME分别为RAMB18E2、FIFO18E2、RAMB36E2和FIFO36E2。从而,可通过如下命令找到相应的资源用量:setram36_num[llength[get_cells-hier\-filter"REF_NAME==RAMB36E2”]]setram18_num[llength[get_cells-hier\-filter"R

  • 完整剖析SpringAOP的自调用

    摘要spring全家桶帮助javaweb开发者节省了很多开发量,提升了效率。但是因为屏蔽了很多细节,导致很多开发者只知其然,不知其所以然,本文就是分析下使用spring的一些注解,不能够自调用的问题。因为本身这类文章很多,所以有些地方不会详述,直接引用其他文章。问题使用了Spring中哪些注解不能进行自调用为什么代理了就不能自调用Spring常用的@Cache,@Async,@Transaction这三种原理上有什么区别吗如何解自调用的问题使用不同的解法各自有什么坑AOP的概述首先需要澄清几个需要区分的名词AOPSpringAOPAspectJAOPAspect-orientedprogramming,面向切面编程,一种解决问题的思想,将一些重复性的编码问题通过切面来实现。很多人了解切面是通过Spring来了解的,所以会有种误解将SpringAOP和AOP划等号,其实不然。SpringAOPSpringAOP算是一种简单的AOP的落地实现方式,它主要提供在Spring容器内的一种AOP实现方式,脱离了Spring就不work了。SpringAOP并不是一套完整的AOP解决方案。Spri

  • 揭秘简单请求与复杂请求

    开发网站时经常会用到跨域资源共享(简称cors,后面使用简称)来解决跨域问题,但是在使用cors的时候,http请求会被划分为两类,简单请求和复杂请求,而这两种请求的区别主要在于是否会触发cors预检请求。 首先我们要明白cors的原理(引自MDN):跨域资源共享标准新增了一组HTTP首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的HTTP请求方法(特别是GET以外的HTTP请求,或者搭配某些MIME类型的POST请求),浏览器必须首先使用OPTIONS方法发起一个预检请求(preflightrequest),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的HTTP请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括Cookies和HTTP认证相关数据)从上面的文字中我们得到如下信息:1、跨域资源共享标准新增了一组HTTP首部字段,服务器通过这些字段来控制浏览器有权访问哪些资源。2、为了安全起见请求方式分为两类,一类不会预先发送options请求,一些会预先发送options请求

  • 数据结构 | 每日一练(41)

    数据结构合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下——老子1每日一练1.有线性表(a1,a2,…,an),采用单链表存储,头指针为H,每个结点中存放线性表中一个元素,现查找某个元素值等于X的结点。分别写出下面三种情况的查找语句。要求时间尽量少。(1)线性表中元素无序。(2)线性表中元素按递增有序。(3)线性表中元素按递减有序。正确答案PS:如果有||,则表示后面的是注释1.设单链表带头结点,工作指针p初始化为p=H->next;(1)while(p!=null&&p->data!=X)p=p->next;if(p==null)return(null);∥查找失败elsereturn(p);∥查找成功(2)while(p!=null&&p->data<X)p=p->next;if(p==null||p->data>X)return(null);∥查找失败elsereturn(p);(3)while(p!=null&&p->data>X)p=p->next;if

  • [gcc][glibc]va_start嵌套导致的问题

    前言使用tgt-1.0.75创建好target之后,在initiator端执行login操作大约卡3s~5s左右。同时观察tgt,CPU消耗到达100%。一度怀疑是glibc的版本问题,在多个发行版上测试,有的发行版上会coredump。分析backtrace用gdb分析coredump文件,拿到backtrace:ProgramterminatedwithsignalSIGSEGV,Segmentationfault. #00x00007f58a410ccc0in_IO_vfprintf_internal(s=s@entry=0x7ffdec98fdc0,format=<optimizedout>, format@entry=0x432bc8"dModepage%d(0x%02x),index:%d\n",ap=ap@entry=0x7ffdec98ff38)atvfprintf.c:1632 #10x00007f58a41d4896in___vsnprintf_chk(s=0x7ffdec990010"",maxlen=<o

  • 李沐老师在伯克利开新课了,深度学习教材已经开源,视频也会有的

    栗子发自凹非寺 量子位报道|公众号QbitAI李沐老师的新课,这个月就要在伯克利开讲了。这是一门深度学习基础课,一周两节;每节课后,都会有课堂视频放出。课程内容,大致是按照李沐老师的开源新书《动手学深度学习》来安排的(但和去年放出的同名课程并不一样)。 除了沐神,另外一位讲师是这本书的共同作者、亚马逊的同事AlexSmola。从基础到高难 课程会从基础概念开始讲起。比如,数学和统计学范畴的链式法则、贝叶斯定理、逻辑回归等等。比如,深度学习入门需要的批尺寸(BatchSize)、学习率(LearningRate)、多层感知器(MultiLayerPerceptrons)、反向传播、随机梯度下降等等。在那之后,便会走进神经网络的世界。先是卷积网络(CNN),从简单的LeNet开始,到ResNet这类用来做高精度模型的架构。再来就是序列模型(SequenceModels)和循环网络(RNN)。LSTM、GRU以及注意力机制都会在这里登场。△5月,就要搭建大规模视觉模型了作为一门注重实践的课程,课程表里有四节课标注了MakingitWork,那便是老师向同学们传授实现大法的时候。以及,这门课完

  • 抽象类 VS 接口

    引言接口和抽象类是面向对象编程(OOP,ObjectOrientedprogramming)中两个绕不开的概念,二者相似而又有所不同。接下来,我们来了解二者的概念并比较它们的异同。什么是抽象类型?抽象类是一种特殊的类,该类不能被实例化。抽象类的存在就是为了被继承,即抽象类可以被其它类继承但不能被实例化。那么,我们为什么需要一个无法被实例化的类呢?这样做的优点是,通过抽象类我们制定了一份强制所有子类必须遵守的合约,使所有子类有着一致的层次结构。抽象类提供了一种规范用于规定子类如何进行工作,子类可根据自身情况来重写抽象类中的抽象成员(及其它可被重写的成员)以满足自身需求。抽象类作为一个基类,可以包含已实现的成员,同时应至少包含一个抽象成员,否则就没必要使用抽象类了。如果一个抽象类中仅仅包含抽象方法,那么这时抽象类就和接口很像了。什么是接口?接口中不能包含任何被实现的成员,即接口中只能包含成员的签名。如,没有方法体的方法、只包含访问器关键字(set、get)的属性等。和抽象类类似,接口也是一份合约。C#中,接口和抽象类的主要区别是,类可以实现多个接口,但只能继承一个(抽象)类。比较异同特征接

  • 入职第三天:vue-loader在项目中是如何配置的

    什么是vue-loader这是我入职第三天的故事,在写这篇文章之前,先来看看咱们今天要讲的主角——vue-loader,你对它了解多少?这是我今天的回答,确实,vue-loader是webpack的一个loader,用于处理.vue文件。.vue文件是一个自定义的文件类型,用类HTML语法描述一个Vue组件。每个.vue文件包含三种类型的顶级语言块<template>、<script>和<style>。vue-loader会解析文件,提取每个语言块,如有必要会通过其它loader处理(比如<script>默认用babel-loader处理,<style>默认用style-loader处理),最后将他们组装成一个CommonJS模块,module.exports出一个Vue.js组件对象。vue-loader支持使用非默认语言,比如CSS预处理器,预编译的HTML模版语言,通过设置语言块的lang属性。例如,你可以像下面这样使用Sass语法编写样式:<stylelang="sass"> /*wri

  • 图扑软件 3D 组态编辑器,低代码零代码构建数字孪生工厂

    行业背景 随着中国制造2025计划的提出,新一轮的工业改革拉开序幕。大数据积累的指数级增长为智能商业爆发奠定了良好的基础,传统制造业高污染、高能耗、低效率的生产模式已不符合现代工业要求。 图扑拖拽式智慧工厂编辑器,0代码搭建2D和3DWeb工业组态,打造可视化大屏,助力制造企业持续改进流程、预防问题发生、优化运营效率,跳出空间限制彻底改造价值链,重塑企业核心竞争力!它不仅能提高工厂生产效率,还能帮助积累和分析大数据,构建高效、节能、绿色、环保、舒适的人性化工厂。     传统工厂缺点 机器设备相互孤立,彼此脱节; 厂内库存物品太多,仓库与生产脱节; 产品无法实现全面可追溯管理; 工序流程无法实现防呆管控; 企业人力成本较高; 设备故障无法即时分析、处理; ERP无法管控生产过程。 传统工厂的诸多缺点严重影响生产效率,利用UI组态和三维组态搭建no-code的智能化工厂能将生产缺陷检测率提升50%,产量提高20%。工厂需综合利用自动化、人工智能(AI)、IoT、边缘计算、云、5G、增材制造和数字孪生等技术,推动工厂运营转型。 效果展示 智慧工厂 工厂从“制造”向“

  • Spring Data Jpa 分表处理

    SpringDataJpa动态表处理 Jpa分表问题 现在有一张学生表t_stu按年份进行了处理,物理表分别是t_stu_2020、t_stu_2021、t_stu_2022这样 如果是mybatis,可以直接把表后缀传入sql,然后使用t_stu_${year}对表名进行拼接 但是对于Jpa这个全自动化ORM框架来说不太好处理,因为Jpa的表名是写在实体类的注解上的,在运行时不能修改: //实体类 @Entity @Table(name="t_stu")//这里的表名定义是无法在运行时修改的 @Getter @Setter publicclassTVisionStu{ privatestaticfinallongserialVersionUID=1L; @Id @Column(name="id") privateStringid; @Column(name="name") privateStringname; } //dao层 @Repository publicinterfaceTVisionStuDaoextendsJpaRepository<TVisionStu,S

  • SpringBoot @Transactional声明事务无效问题

    今天有个同事遇到一个问题,由于业务需求要求,在一个Service的一个方法A中有一个for循环,每次循环里面的业务逻辑有可能发生异常,这个时候就需要将这个循环内的所有数据库操作给回滚掉,但是又不能影响到之前循环里数据的更改,并且后面的循环里不发生异常的情况下也需要正常操作数据库。同事尝试了很久结果还是不能满足业务需求。 大概业务逻辑需求如下: for(inti=1;i<5;i++){ /*** **业务逻辑:例如:每循环一次向数据库user表插入一条记录 **/ } 复制 如果第一次循环没有发生异常,第二次循环发生异常,第三、四次循环都没有发生异常,那么最后数据表里有134三条记录,第二次插入数据没有写入,所以不能单单的在Service上加上事务。 按照这个业务需求我的想法在Service上加上@Transactional事务,将可能发生异常的业务代码提到另外一个方法B中,在B方法上面加上一个@Transactional注解,并且将B方法上事务传播行为改成PROPAGATION_REQUIRES_NEW(创建新事务,无论当前存不存在事务,都创建新事务),并且在第二次循环手动设

  • InverseTransformPoint 函数问题

    unity这个函数是获取世界坐标对于当前Transform的相对位置,这个相对位置是指 当前RectTransformanchormin0.50.5max0.50.5,和当前pivot计算出来的。如果要计算anchor需要自己额外加一下。  

  • iOS 初探代码混淆(OC)

    iOS初探代码混淆(OC) 前言 自己做iOS开发也有几年的时间了,平时做完项目基本就直接打包上传到Appstore上,然后做上架操作了。但是最近,客户方面提出了代码安全的要求。说是要做代码混淆,这方面的工作之前从来没有接触过。然后就上网查了一下,原来有很多应用程序都做了代码混淆。看来是我固步自封了...... 起因 使用classdump对原程序进行dump,可以dump出所有源程序的函数所有信息:源程序所有函数类型,变量全部泄露。这样的话,让攻击者,也就是黑客们了解了程序结构方便逆向。因为在工程中,我们这些变量或函数命名都是有一定可读性的,例如跟用户名相关的,那一般里面会有userName,跟密码相关的一般会有passWord,这样定义也是为了我们自己代码可读性更强,我们修改的时候才更加的方便。但是我们相信,这么个定义法,我们只是希望方便我们自己,我们可不希望方便黑客们去破解我们的APP。总结出来就是一句话:“会把你项目中的所有方法和变量都罗列出来”。 开始混淆: 1. 在进行代码混淆之前,我们需要在我们的项目中增加两个文件:confuse.sh&func.list我们打

  • Java学习笔记109——StringBuffer类的练习—对称字符串

      StringBuffer类的练习——对称字符串 判断一个键盘录入的字符串是否是对称字符串例如"abc"不是对称字符串,"aba"、"abba"、"aaa"、"mnanm"是对称字符串 分析:1、第1个字符与最后一个字符进行比较2、第2个字符与倒数第2个字符进行比较3、... 比较的次数:字符串的长度(length())/2 importjava.util.Scanner; publicclassStringBufferDemo10{   publicstaticvoidmain(String[]args){     //创建键盘录入对象     Scannersc=newScanner(System.in);     System.out.println("请输入您想要判断的字符串:");     StringstringLine=sc.next(); ​    &

  • http-server让你在任何目录下都可以创建web服务

    在做前端页面开发,或者预览时,如果借助于Apache、Tomcat、nginx等预览页面,每次需要将所需预览的页面移动到对应的文件夹下,且还需要考虑是否删除相关目录原有的文件,显然比较麻烦。 那么有没有什么更快的方法,最好是想以哪个目录为web服务的根目录就直接一键即可。 http-server就是这样一款,让你在任何目录下都可以创建web服务的npm工具。   使用方法如下: 1.首先确保是全局安装,命令如下   npminstallhttp-server-g 2.在存在网页的文件夹下,按住Shift键,再右击鼠标右键,选择“在此处打开命令框”,然后在DOS界面下输入"hs"或者“http-server”即可。 3.按照DOS界面下的地址,访问网页即可。  复制 复制 作者:imsoft Email:imsofter#163.com 出处:http://www.cnblogs.com/imsoft/ 本文版权归作者和博客园共有,欢迎转载、交流,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如果觉得本文对您有益,欢迎点赞、欢迎探讨。

相关推荐

推荐阅读