fabric-sdk-go错误锦集(一)

楔子

本人在使用fabric-sdk-go碰到的一些坑,一些错误总结,希望能帮到有缘人,以下主要是针对go 语言sdk场景,另外文中得一些错误解决方案不一定都有用,因为有些错误报告不是很具体特别得泛,错误原因不一样但是报错内容是一样的,因此本文仅供参考。

正文

一、failed to initialize configuration: unable to initialize cryptosuite using crypto suite config: failed to initialize crypto suite: Could not initialize BCCSP SW: Failed initializing configuration at [256,SM3]: Hash Family not supported [SM3]

错误描述

原因:

1).这是国密套件问题。查看配置中是否配置正确国密就是config.yaml中client.BCCSP模块,比如:

config.yaml

很坑的! 由于fabric官方对秘钥模块也就是BCCSP模块实现的不是特别友好,改造只能侵入式代码进行改造,不能通过实现接口的方式去支持国密,造成中国的社区国密改造改的百花齐放,有的provider中是 "GM" 有的就是 “SW”,想吐槽的地方太多太多。

2). 如果上述没问题,请查看部署的链是否支持国密配置,是否连错了链,以及跟当时改造国密链的相关同事进行沟通

二、SendEnvelope failed: calling orderer 'orderer.example.com:7050' failed: Orderer Client Status Code: (2) CONNECTION_FAILED. Description: dialing connection timed out [orderer.example.com:7050]

在使用fabirc-go-sdk调用执行合约时出现此错误。有一个奇怪的现象,就是我监听方法是正常的,调用交易查询也是正常的,唯独调用合约(合约就是简单的set操作)时出现此错误. 更奇怪的是我在此链的服务器上部署的应用服务调用合约没有问题,而我在其他服务器上部署的应用服务调用就会有问题,着实有些摸不着头脑。通过上面的错误初步分析是order服务相关的错误,首先我是使用了telnet检测了一下order服务是否联通在非链的服务器上,发现telnet是联通的那说明服务是正常的没挂掉,于是我登录了链所在的服务器查看了下order服务日志如下:

可以看到上半部分ERROR红色部分的日志,可分析出,是tls认证相关错误,此错误日志就是我在非链的服务器上的应用调用的日志,然后下面的info日志是所在链服务器上的应用调用成功的结果,ok顺着往下想,然后我排查了下sdk配置文件config.yaml中order配置的证书,比较两个服务的配置文件是否有差异,发现是一样的没有问题,然后我上github上搜了一圈也没发现相关issues,无望,然后借助搜索引擎,但也没找到解决方案,但搜了一些比较有价值的文章,可参考以下博文,其中可能有一些能解决符合你的问题情况

[1] 证书错误问题

[2] 代理的问题

[3] 在通道作用域下添加order

[4] 证书解析工具

中间尝试了各种姿势,都是一样的错误,一顿折腾后,无意间发现我配置文件中的order配置的内容好像有问题,我发现docker ps 命令查看服务相关信息时,发现我配置文件中的order域名和docker ps的不一致

config.yaml中的配置

config.yaml
config.yaml

docker ps 内容

docker ps

对比发现我配置文件中多出了一个0,然后更改了配置文件最终终于好了。

哎~ 心累。

三、Failed to get network: Failed to create new channel client: event service creation failed: could not get chConfig cache reference: QueryBlockConfig failed: QueryBlockConfig failed: queryChaincode failed: Multiple errors occurred: - Transaction processing for endorser [xx.xx.xx.xx:8051]: gRPC Transport Status Code: (2) Unknown. Description: access denied: channel [mychannel] creator org [org1MSP] - Transaction processing for endorser [xx.xx.xx.xx:7051]: gRPC Transport Status Code: (2) Unknown. Description: access denied: channel [mychannel] creator org [org1MSP]

原因是mspid填写错误,如果不知道具体的mspid是多少请查找文件configtx.yaml文件中的值,以及docker-compose文件大概内容入下:

docker-compose.yaml
configtx.yaml

四、failed to initialize configuration: unable to load endpoint config: failed to initialize endpoint config from config backend: network configuration load failed: failed to load channel configs: failed to load orderer configs: Orderer has no certs configured. Make sure TLSCACerts.Pem or TLSCACerts.Path is set for grpcs://xx.xx.xx.xx:9050

 产生的错误意思可能是配置了多个oreder就是在 oreders作用域下面(这个待确定),一般配置为一个就可以了,这个待填坑,我这边是配置了一个是可以的

五、2022-05-28 11:24:45.325 UTC [core.comm] ServerHandshake -> ERRO 0bf TLS handshake failed with error remote error: tls: bad certificate server=Orderer remoteaddress=xx.xx.xx.xx:17528

以上是peer节点日志,产生的错原因有可能:

1. 证书配置的错误不匹配等

2. config.yaml中的entityMatchers作用域中 peer和orderer中的urlSubstitutionExp的服务ip和端口是一样的也会造成此错误

3.其他情况待复现待补充

六、[fabsdk/msp] 2022/06/01 03:00:24 UTC - msp.NewIdentityManager -> WARN Cryptopath not provided for organization [org1], MSP stores not created

此错误为fabric-sdk-go中的错误.错误原因就是配置config.yaml中organization中的org1组织中的cryptoPath没有配置路径

自己的config.yaml

正常来说按照官方的标准,我们配置时是采用文件目录的方式去寻找证书以及私钥相关的内容如下官方示例配置

官方config.yaml示例文件

但是我这里是采用了pem的方式直接把证书私钥等写在配置文件中了,也就是上文user那一段落下(Admin为管理员账户),为什么这么做的原因就是,程序不需要考虑证书文件的绝对路径相对路径的问题,但是这么做不好的一点就是每次更换证书时需要挨个找到对应的证书和私钥等文件,然后copy到相应的位置,而且很容易出错。

回到正题,此错误原因的日志打印在程序打印的位置如下:

github.com/hyperledger/fabric-sdk-go@v1.0.1-0.20220428141930-cbdc79f1c9f2/pkg/msp/identitymgr.go

然后根据代码上下文我们可以证明是因为没有配置cryptoPath导致的,如果你是采用我上述pem方式配置的,同时也不想在服务启动的时候打印WARN日志你可以配置上cryptoPath,至于目录随便给一个空目录就行。

七、failed to initialize configuration: unable to load endpoint config: failed to initialize endpoint config from config backend: network configuration load failed : failed to load channel configs: failed to load TLS cert pool: failed to create cert pool: crypto/x509: system root pool is not available on windows

错误原因是config.yaml中client模块下的tlsCerts配置了系统证书池

config.yaml

最根本的原因是你在windows平台运行的服务,当前此刻go标准库1.17.x版本windows平台还未支持系统证书池功能

crypto/x509/cert_pool.go

详情可在github上go项目查看 issue 16736和18609

八、

[core.com] ServerHandshake -> ERRO [0m TlS handshake failed with error tls: client didn't provide a certificate server=PeerServer remoteaddress=xx.xx.xx.xx:30132

[grpc] handleRawConn -> DEBU [0m grpc: Server.Serve failed to complete security handshake from "172.30.8.1:48032": tls: client didn't provide a certificate

场景:已经配置好了config.yaml文件,并且sdk初始化链接已经成功了,通过debug模式已经看到了DEBU SDK initialized successfully日志输出,但是在初始化完成之后调用合约则发现出现以下错误

could not get chConfig cache reference: QueryBlockConfig failed: QueryBlockConfig failed: queryChaincod failed: Transaction processing for endorser [xx.xx.xx.xx:30901]: Endorser Client Status Code: (2) CONNECTION_FAILED. Description: dialing connection timed out [xx.xx.xx.xx:30901]

然后我们看了下peer节点的日志,会出现这两行日志,就是我们当前问题的标题内容。ok到这基本上可以说出结论了可能是因为链配置了双向tls认证的原因,那么如何确定自己的链是否开启了双向tls连接呢,我们可查看当时部署时使用的docker-compose.yaml以及相关的脚本文件,或者直接进入对应链服务所在的docker容器(k8s也相似雷同只要能查看环境变量即可),使用以下命令查看环境变量值

docker exec -it [此处是正在运行的服务镜像id] bash

进入成功之后在当前终端使用export 命令查看环境变量中是否有以下环境变量值(ps此图摘自网上):

order节点的环境变量
peer节点的环境变量

如果 XXX_TLS_CLIENTAUTHREQUIRED为true那说明服务开启了双向tls那么此时你sdk中config.yaml也需要配置client tls证书,具体你有没有配置可查看你当前sdk config.yaml配置文件中是否包含以下内容,如果没有则进行添加

官方的示例配置模板

ok到这里基本就结束了,其实这个问题也可以换一种问法:

fabrci-sdk-go如何配置双tls认证

为啥我要提出这个词条呢,因为在当时我全网搜索没有搜到相关的信息,只搜到了java相关的。说句闲话这个问题我卡了4 5天,甲方项目赶时间,都到测试要交付的阶段了突然出了这么个幺蛾子天坑,至于为啥我没发现是双tls的问题,是因为甲方行内环境和我们的测试开发的环境不一样,项目方搭建的链也没有说清楚情况,在加上我也没碰到过这个问题(fabirc-simple官方默认不开启双tls的是单tls的,之前测试学习都是用的单tls的所以..... 强行解释一波), 其间协调了咨询了很多我们公司的人,以及甲方的,都没给出个解决问题思路和方案,最后自己静下心来看了下官方文档,以及google,多次尝试发现了此问题。另外其间也看到了一些文档也挺不错的贴在这里自行查看,有些需要梯子。

[5]

[6]

[7]

[8]

[9] 官方的一个社区讨论

[10] 官方文档

九、Multiple errors occurred: - Transaction processing for endorser [xx.xx.xx.xx:9051]: Chaincode status Code: (500) UNKNOWN. Description: failed to execute transaction 16935c80e1a83c9e64865b32b02ccaf525ab1ae62f6d1671ffc4a5a870fdc816: error sending: chaincode stream terminated - Transaction processing for endorser [xx.xx.xx.xx:7051]: Chaincode status Code: (500) UNKNOWN. Description: failed to execute transaction 16935c80e1a83c9e64865b32b02ccaf525ab1ae62f6d1671ffc4a5a870fdc816: error sending: chaincode stream terminated

现象:同一个合约调用其他方法是正常的,唯独调用某个合约方法出现此错误。

产生的原因是合约容器退出,我调用了一个合约由于合约测试不健全,造成数组越界程序panic异常退出所以产生此错误,至于调用其他方式可以是因为,docker k8s有相应的服务重启策略,只需要把合约bug修复调试正确重新部署即可

十、failed to create identity manager provider: failed to initialize identity manager for organization: org1: Either a cryptopath or an embedded list of users is required

原因:组织中没有配置用户证书,或者是配置字段值中内容填错

golang中配置证书有两种方式,以下是其中一种方式,其中我字段值user填错,应该是users

golang配置文件片段

Admin为使用的是Admin账户,如果使用其他用户比如User1则替换为相应的值,另外同时需要替换User1账户对应的公私钥文件。

另外一种方式官方示例,文件截取片段:

官方例子

官方是采用配置文件路径的方式,然后sdk层面配置解析会根据这个路径去寻找解析到对应的账户公钥和私钥,其实就是我上文中图一pem的内容.另外那个certificateAuthorities为ca配置内容,我这链没有没用搭建CA因此我没有配置.

十一、2022-12-23 02:56:41.700 UTC [core.comm] ServerHandshake -> ERRO 066 TLS handshake failed with error tls: first record does not look like a TLS handshake server=Orderer remoteaddress=xx.xx.xx.xx:4635

链版本 :v2.2.0

以下日志来自于order服务节点日志

当使用golang sdk调用合约上链操作时出现此错误。

然后我又调用查询操作sdk打印日志出现以下错误:

2022/12/23 14:43:58 EvaluateTransaction: Failed to evaluate: Transaction processing for endorser [xx.xx.xx.xx:9051]: Endorser Client Status Code: (2) CONNECTION_FAILED. Description: dialing connection on target [xx.xx.xx.xx:9051]: waiting for connection failed: context deadline exceeded

ok到这说结论,大概率是服务端口未开放,检查服务端口是否开放,可使用telnet进行检测。

正常能连接通:

成功

连接失败:

失败

另外当时的背景是我找运维开了服务端口,只探测其中一个正常的服务端口,另外几个端口没有测试,最终定位发现是运维把9051服务端口忘开了造成上述错误。

十二、2022-12-23 08:48:15.090 UTC [chaincode.platform] func1 -> ERRO 0e2 docker build failed: Error returned from build: 1 "go: github.com/hyperledger/fabric-chaincode-go@v0.0.0-20220720122508-9207360bbddd: Get "https://proxy.golang.org/github.com/hyperledger/fabric-chaincode-go/@v/v0.0.0-20220720122508-9207360bbddd.mod": dial tcp 172.217.160.113:443: i/o timeout

链版本:v2.2.0

错误原因是golang proxy代理问题,可以提前下载好依赖包放到golang得vendor中,可执行go mod vendor命令则会当前合约项目目录中生成vendor依赖代码,另外proxy配置在哪里待考究,我是链码安装所在的服务器配置了proxy但是不知为何还没有生效。

ok到这弄完之后再部署合约发现又出现了问题,错误如下:

Error building image: docker build failed: Error executing build: API error (500): failed to create shim task: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:402: getting the final child's pid from pipe caused: EOF: unknown ""

peer节点服务日志

通过资料查询发现是docker问题,更进一步说是系统内核版本问题,可参考docker给出的解决方案:

https://github.com/moby/moby/issues/40835#issuecomment-730981130

按照流程配置相应内容,然后再次通过sdk调用合约,发现又出现了错误如下

从错误描述发现是代码找不到os.ErrDeadlineExceeded对应依赖,原因其实就是安装合约代码依赖版本有问题,需要更换golang中go.mod依赖的代码版本,对应依赖库位golang.org/x/net. 我代码依赖的库版本是golang.org/x/net v0.0.0-20220708220712-1185a9018129 给他替换掉,换到别的版本有os.ErrDeadlineExceeded,另外此库版本有安全漏洞问题可查看: https://github.com/hyperledger/fabric-contract-api-go/issues/75

哎心累,一波未平一波又起。

十三、sending approve transaction proposal failed: Transaction processing for endorser [121.xx.xx.xx:9051]: Chaincode status Code: (500) UNKNOWN. Description: Failed to authorize invocation due to failed ACL check: Failed deserializing proposal creator during channelless check policy with policy [Admins]: [expected MSP ID Org2MSP, received Org1MSP]

链版本:v2.2.0

背景:在部署合约以lifecycle管理合约方式时,调用LifecycleApproveCC方法出现此错误,合约策略是AND策略,有两个组织,每个组织个一个节点。

错误原因:合约策略和实际部署时客户端节点不对应,调用时option指定组织节点,如果不指定时,sdk调用时组织节点会选择错误节点造成错乱

好吧这块说的比较绕很难说清楚。

十四、2022-12-23 11:43:35.719 UTC [lifecycle] Work -> WARN 197 could not launch chaincode 'my-test:996285378f16012d23d80e1e7ab2d847ed311903ade057e4d70885ac2a76a9fa': chaincode registration failed: container exited with 0

以上错误为peer节点数据日志,然后我们再进入已部署的合约容器内查看错误日志:

Cannot use metadata. Metadata did not match schema: 1. components.schemas..required: Array must have at least 1 items

链版本:v2.2.0

背景:lifecycle方式部署完合约之后出现此错误,也就是调用完LifecycleCommitCC()方法之后

原因:其实是合约编写的有问题不符合chaincode的规范,更近一步是因为chaincode 生成的metadata 在做json scheme校验时不符合规则造成此错误

解决方式:检查合约中数据类型是否符合chaincode的规范,修改合约重新部署.规范可参考

https://github.com/hyperledger/fabric-contract-api-go/blob/main/tutorials/getting-started.md#writing-contract-functions.

另外说下,由于fabric v2.0.0以上采用新的合约管理方式lifecycle方式,同时新版本合约golang采用新的v2版本sdk(github.com/hyperledger/fabric-contract-api-go/contractapi),为了简单快速,我在开发过程中把旧版的合约拿来简单改改,换了新的sdk部署之后就出现上述问题,我修改的代码片段如下:

可以看到合约采用的是旧版和v1的方式写法,通过switch case条件调用相应的方法,同时返回值是一个peer.Response对象,但是新版本合约编写有限制规定,返回值只能是限定规则类型列表中的类型。因此解决此问题的方式是,返回值不能返回peer.Response了,改成返回一个golang中error错误类型即可。

另外笔者依赖的合约sdk版本为:github.com/hyperledger/fabric-contract-api-go v1.1.1版本

十五、sending commit transaction proposal failed: Multiple errors occurred: - Transaction processing for endorser [121.xx.xx.xx:7051]: Chaincode status Code: (500) UNKNOWN. Description: failed to invoke backing implementation of 'CommitChaincodeDefinition': chaincode definition not agreed to by this org (Org1MSP) - Transaction processing for endorser [121.xx.xx.xx:9051]: Chaincode status Code: (500) UNKNOWN. Description: failed to invoke backing implementation of 'CommitChaincodeDefinition': chaincode definition not agreed to by this org (Org2MSP)

以上是sdk打印日志,我调用了LifecycleCommitCC()方法出现此问题,peer节点打印日志如下:

2022-12-23 15:27:01.155 UTC [lifecycle] Work -> WARN 11b could not launch chaincode 'jcx-test-5:dad82f082a2fc5158254d53bc8b780c4c4a82aed91cae08fbd157b27276a8072': chaincode registration failed: container exited with 0

链版本:v2.2.0

背景:lifecycle方式部署升级合约之后出现此错误。

原因:出现此错误检查在调用LifecycleCommitCC()方法之前有没有调用LifecycleApproveCC()如果调用了请检查调用参数比如Name Version PackageID Sequence 参数是否正确!

总结

以下是我一个一线开发的程序员的吐槽

fabric作为联盟链的领头羊,相对来说生态比较好。本人是从传统互联网转到区块链行业的,写下本文时间距离入此行业也有个7 8个月了,切身感受到了区块链社区生态真的是差的不行,有些问题出现很容易没有解决方案,容易搁置,甚至搜都搜不到相关答案无解,只能自己去摸索,就感觉你是一个人在战斗。还有一些很多无意义的无用的重复劳作,就比如国密改造问题,社区也没有一个好的解决方案,一个国密改造的五花八门,如果你想国密链升级对不起你只能下载社区版本的代码,然后在从头再改造一遍,不光如此你需要改造sdk fabric ca 命令行以及合约的sdk等模块,然后在把整体组合起来都测试一遍,工作量人力物力麻烦就不说了,而且我认为更重要的是,联盟链诞生的目的就是联盟,现在的情况是个自厂商纷纷按自己的理解以及参考或者博文去改造国密,自测试通过了就认为改造成功了,从长久考虑来说,如果后续厂商之间还想使用fabric想对接联盟都是一个问题,更具体一点来说国密算法套件的实现不同以及改造的完整性等一些问题,因为双方改造的方式都不一样没有一个统一的具体详细的标准,这就造成了孤立性的问题,违背了联盟链的初衷。小确幸的是,github上有一个fabric的中国社区,不知道是民间组织的还是ibm官方的,诞生的目的就是为了解决国密改造的问题,说是fork一个分支等改造差不多了在合并到社区主分支上,详情查看点击此wiki,说实话我感觉推动挺缓慢的,不过差不多每周都有周会以及相关会议纪要什么的,感兴趣的可以参加,我只希望能早日统一了吧。

整个区块链生态还是有待发展的,现在算是务实的一个起步阶段?从炒币的阶段过渡到实际应用价值的一个阶段吧。?

参考

[1] https://learnblockchain.cn/question/124

[2] https://stackoverflow.com/questions/50499746/fabric-sdk-go-connect-failed

[3] https://blog.csdn.net/weixin_40098405/article/details/108200722

[4] https://www.ssleye.com/ssltool/sm2_check.html

[5] https://kctheservant.medium.com/tls-in-hyperledger-fabric-b38fccb8614c

[6] https://stackoverflow.com/questions/62495170/hyperledger-fabric-serverhandshake-tls-handshake-bad-certificate-server-peerser

[7] http://www.lifegoeson.cn/2021/03/14/peer%20channel%20join%20%E6%97%B6%E6%8A%A5%E9%94%99TLS%20handshake%20failed%20with%20error%20remote%20error%20tls%20bad%20certificate%20server=PeerServer%20remoteaddress/

[8] https://stackoverflow.com/questions/66773036/tls-handshake-failed-with-error-remote-error-tls-bad-certificate-server-ordere

[9] https://lists.hyperledger.org/g/fabric/topic/mutual_tls_issue/32571007

[10] https://hyperledger-fabric.readthedocs.io/zh_CN/release-1.4/enable_tls.html

无固定时间更新中。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容