亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Golang 微服務(wù)教程(二)

sevi_stuo / 2797人閱讀

摘要:定義微服務(wù)作為客戶端調(diào)用的函數(shù)實(shí)現(xiàn)中的接口,使作為的服務(wù)端檢查是否有適合的貨輪貨物被承運(yùn)解析命令行參數(shù)作為的客戶端增加貨物并運(yùn)行更新中的貨物,塞入三個(gè)集裝箱,重量和容量都變大。

譯文鏈接:wuYin/blog
原文鏈接:ewanvalentine.io,翻譯已獲作者 Ewan Valentine 授權(quán)。

本節(jié)未細(xì)致介紹 Docker,更多可參考:《第一本Docker書 修訂版》

前言

在上一篇中,我們使用 gRPC 初步實(shí)現(xiàn)了我們的微服務(wù),本節(jié)將 Docker 化該微服務(wù)并引入 go-micro 框架代替 gRPC 簡化服務(wù)的實(shí)現(xiàn)。

Docker 背景

占據(jù)著云計(jì)算的優(yōu)勢,微服務(wù)架構(gòu)越來越流行,同時(shí)它的云端分布式的運(yùn)行環(huán)境也對我們的開發(fā)、測試和部署提出了很高的要求,容器(container)便是一項(xiàng)解決方案。

在傳統(tǒng)軟件開發(fā)中,應(yīng)用直接部署在環(huán)境和依賴都準(zhǔn)備好的系統(tǒng)上,或在一臺物理服務(wù)器上部署在由 Chef 或 Puppet 管理的虛擬集群里。這種部署方案不利于橫向擴(kuò)展,比如要部署多臺物理服務(wù)器,需要都安裝相同的依賴,再部署,很是麻煩。

vagrant 這類管理多個(gè)虛擬機(jī)的工具,雖然使項(xiàng)目的部署更為遍歷,但每個(gè)虛擬機(jī)都運(yùn)行有一個(gè)完整的操作系統(tǒng),十分耗費(fèi)宿主主機(jī)的資源,并不適合微服務(wù)的開發(fā)和部署。

容器 特性

容器 是精簡版的操作系統(tǒng),但并不運(yùn)行一個(gè) kernel 或系統(tǒng)底層相關(guān)的驅(qū)動,它只包含一些 run-time 必需的庫,多個(gè)容器共享宿主主機(jī)的 kernel,多個(gè)容器之間相互隔離,互補(bǔ)影響??蓞⒖迹篟edhat topic

優(yōu)勢

容器的運(yùn)行環(huán)境只包含代碼所需要的依賴,而不是使用完整的操作系統(tǒng)包含一大堆不需要的組件。此外,容器本身的體積相比虛擬機(jī)是比較小的,比如對比 ubuntu 16.04 優(yōu)勢不言而喻:

虛擬機(jī)大小

容器鏡像大小

Docker 與容器

一般人會認(rèn)為容器技術(shù)就是 Docker,實(shí)則不然,Docker 只是容器技術(shù)的一種實(shí)現(xiàn),因?yàn)槠洳僮骱啽闱覍W(xué)習(xí)門檻低,所以如此流行。

Docker 化微服務(wù) Dockerfile

創(chuàng)建微服務(wù)部署的 Dockerfile

# 若運(yùn)行環(huán)境是 Linux 則需把 alpine 換成 debian
# 使用最新版 alpine 作為基礎(chǔ)鏡像
FROM alpine:latest

# 在容器的根目錄下創(chuàng)建 app 目錄
RUN mkdir /app

# 將工作目錄切換到 /app 下
WORKDIR /app

# 將微服務(wù)的服務(wù)端運(yùn)行文件拷貝到 /app 下
ADD consignment-service /app/consignment-service

# 運(yùn)行服務(wù)端
CMD ["./consignment-service"]

alpine 是一個(gè)超輕量級 Linux 發(fā)行版本,專為 Docker 中 Web 應(yīng)用而生。它能保證絕大多數(shù) web 應(yīng)用可以正常運(yùn)行,即使它只包含必要的 run-time 文件和依賴,鏡像大小只有 4 MB,相比上邊 Ubuntu16.4 節(jié)約了 99.7% 的空間:

由于 docker 鏡像的超輕量級,在上邊部署和運(yùn)行微服務(wù)耗費(fèi)的資源是很小的。

編譯項(xiàng)目

為了在 alpine 上運(yùn)行我們的微服務(wù),向 Makefile 追加命令:

build:
    ...
    # 告知 Go 編譯器生成二進(jìn)制文件的目標(biāo)環(huán)境:amd64 CPU 的 Linux 系統(tǒng)
    GOOS=linux GOARCH=amd64 go build    
    # 根據(jù)當(dāng)前目錄下的 Dockerfile 生成名為 consignment-service 的鏡像
    docker build -t consignment-service .

需手動指定 GOOSGOARCH 的值,否則在 macOS 上編譯出的文件是無法在 alpine 容器中運(yùn)行的。

其中 docker build 將程序的執(zhí)行文件 consignment-service 及其所需的 run-time 環(huán)境打包成了一個(gè)鏡像,以后在 docker 中直接 run 鏡像即可啟動該微服務(wù)。

你可以把你的鏡像分享到 DockerHub,二者的關(guān)系類比 npm 與 nodejs、composer 與 PHP,去 DockerHub 瞧一瞧,會發(fā)現(xiàn)很多優(yōu)秀的開源軟件都已 Docker 化,參考演講:Willy Wonka of Containers

關(guān)于 Docker 構(gòu)建鏡像的細(xì)節(jié),請參考書籍《第一本 Docker 書》第四章

運(yùn)行 Docker 化后的微服務(wù)

繼續(xù)在 Makefile 中追加命令:

build:
    ...
run:
    # 在 Docker alpine 容器的 50001 端口上運(yùn)行 consignment-service 服務(wù)
    # 可添加 -d 參數(shù)將微服務(wù)放到后臺運(yùn)行
    docker run -p 50051:50051 consignment-service

由于 Docker 有自己獨(dú)立的網(wǎng)絡(luò)層,所以需要指定將容器的端口映射到本機(jī)的那個(gè)端口,使用 -p 參數(shù)即可指定,比如 -p 8080:50051 是將容器 50051端口映射到本機(jī) 8080 端口,注意順序是反的。更多參考:Docker 文檔

現(xiàn)在運(yùn)行 make build && make run 即可在 docker 中運(yùn)行我們的微服務(wù),此時(shí)在本機(jī)執(zhí)行微服務(wù)的客戶端代碼,將成功調(diào)用 docker 中的微服務(wù):

Go-micro 為什么不繼續(xù)使用 gRPC ? 管理麻煩

在客戶端代碼(consignment-cli/cli.go)中,我們手動指定了服務(wù)端的地址和端口,在本地修改不是很麻煩。但在生產(chǎn)環(huán)境中,各服務(wù)可能不在同一臺主機(jī)上(分布式獨(dú)立運(yùn)行),其中任一服務(wù)重新部署后 IP 或運(yùn)行的端口發(fā)生變化,其他服務(wù)將無法再調(diào)用它。如果你有很多個(gè)服務(wù),彼此指定 IP 和端口來相互調(diào)用,那管理起來很麻煩

服務(wù)發(fā)現(xiàn)

為解決服務(wù)間的調(diào)用問題,服務(wù)發(fā)現(xiàn)(service discovery)出現(xiàn)了,它作為一個(gè)注冊中心會記錄每個(gè)微服務(wù)的 IP 和端口,各微服務(wù)上線時(shí)會在它那注冊,下線時(shí)會注銷,其他服務(wù)可通過名字或 ID 找到該服務(wù)類比門面模式。

為不重復(fù)造輪子,我們直接使用實(shí)現(xiàn)了服務(wù)注冊的 go-micro 框架。

安裝
go get -u github.com/micro/protobuf/proto
go get -u github.com/micro/protobuf/protoc-gen-go

使用 go-micro 自己的編譯器插件,在 Makefile 中修改 protoc 命令:

build:
    # 不再使用 grpc 插件
    protoc -I. --go_out=plugins=micro:$(GOPATH)/src/shippy/consignment-service proto/consignment/consignment.proto
服務(wù)端使用 go-micro

你會發(fā)現(xiàn)重新生成的 consignment.pb.go 大有不同。修改服務(wù)端代碼 main.go 使用 go-micro

package main

import (
    pb "shippy/consignment-service/proto/consignment"
    "context"
    "log"
    "github.com/micro/go-micro"
)

//
// 倉庫接口
//
type IRepository interface {
    Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新貨物
    GetAll() []*pb.Consignment                                   // 獲取倉庫中所有的貨物
}

//
// 我們存放多批貨物的倉庫,實(shí)現(xiàn)了 IRepository 接口
//
type Repository struct {
    consignments []*pb.Consignment
}

func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
    repo.consignments = append(repo.consignments, consignment)
    return consignment, nil
}

func (repo *Repository) GetAll() []*pb.Consignment {
    return repo.consignments
}

//
// 定義微服務(wù)
//
type service struct {
    repo Repository
}

//
// 實(shí)現(xiàn) consignment.pb.go 中的 ShippingServiceHandler 接口
// 使 service 作為 gRPC 的服務(wù)端
//
// 托運(yùn)新的貨物
// func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, resp *pb.Response) error {
    // 接收承運(yùn)的貨物
    consignment, err := s.repo.Create(req)
    if err != nil {
        return err
    }
    resp = &pb.Response{Created: true, Consignment: consignment}
    return nil
}

// 獲取目前所有托運(yùn)的貨物
// func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest) (*pb.Response, error) {
func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest, resp *pb.Response) error {
    allConsignments := s.repo.GetAll()
    resp = &pb.Response{Consignments: allConsignments}
    return nil
}

func main() {
    server := micro.NewService(
        // 必須和 consignment.proto 中的 package 一致
        micro.Name("go.micro.srv.consignment"),
        micro.Version("latest"),
    )

    // 解析命令行參數(shù)
    server.Init()
    repo := Repository{}
    pb.RegisterShippingServiceHandler(server.Server(), &service{repo})

    if err := server.Run(); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

go-micro 的實(shí)現(xiàn)相比 gRPC 有 3 個(gè)主要的變化:

創(chuàng)建 RPC 服務(wù)器的流程

micro.NewService(...Option) 簡化了微服務(wù)的注冊流程, micro.Run() 也簡化了 gRPCServer.Serve(),不再需要手動創(chuàng)建 TCP 連接并監(jiān)聽。

微服務(wù)的 interface

注意看代碼中第 47、59 行,會發(fā)現(xiàn) go-micro 將響應(yīng)參數(shù) Response 提到了入?yún)?,只返?error,整合了 gRPC 的 [四種運(yùn)行模式]()

運(yùn)行地址的管理

服務(wù)的監(jiān)聽端口沒有在代碼中寫死,go-mirco 會自動使用系統(tǒng)或命令行中變量 MICRO_SERVER_ADDRESS 的地址

對應(yīng)更新一下 Makefile

run:
    docker run -p 50051:50051 
     -e MICRO_SERVER_ADDRESS=:50051 
     -e MICRO_REGISTRY=mdns 
     consignment-service

-e 選項(xiàng)用于設(shè)置鏡像中的環(huán)境變量,其中 MICRO_REGISTRY=mdns 會使 go-micro 在本地使用 mdns 多播作為服務(wù)發(fā)現(xiàn)的中間層。在生產(chǎn)環(huán)境一般會使用 Consul 或 Etcd 代替 mdns 做服務(wù)發(fā)現(xiàn),在本地開發(fā)先一切從簡。

現(xiàn)在執(zhí)行 make build && make run,你的 consignment-service 就有服務(wù)發(fā)現(xiàn)的功能了。

客戶端使用 go-micro

我們需要更新一下客戶端的代碼,使用 go-micro 來調(diào)用微服務(wù):

func main() {
    cmd.Init()
    // 創(chuàng)建微服務(wù)的客戶端,簡化了手動 Dial 連接服務(wù)端的步驟
    client := pb.NewShippingServiceClient("go.micro.srv.consignment", microclient.DefaultClient)
    ...
}

現(xiàn)在運(yùn)行 go run cli.go 會報(bào)錯(cuò):

因?yàn)榉?wù)端運(yùn)行在 Docker 中,而 Docker 有自己獨(dú)立的 mdns,與宿主主機(jī) Mac 的 mdns 不一致。把客戶端也 Docker 化,這樣服務(wù)端與客戶端就在同一個(gè)網(wǎng)絡(luò)層下,順利使用 mdns 做服務(wù)發(fā)現(xiàn)。

Docker 化客戶端

創(chuàng)建客戶端的 Dokerfile

FROM alpine:latest
RUN mkdir -p /app
WORKDIR /app

# 將當(dāng)前目錄下的貨物信息文件 consignment.json 拷貝到 /app 目錄下
ADD consignment.json /app/consignment.json
ADD consignment-cli /app/consignment-cli

CMD ["./consignment-cli"]

創(chuàng)建文件 consignment-cli/Makefile

build:
    GOOS=linux GOARCH=amd64 go build
    docker build -t consignment-cli .
run:
    docker run -e MICRO_REGISTRY=mdns consignment-cli
調(diào)用微服務(wù)

執(zhí)行 make build && make run,即可看到客戶端成功調(diào)用 RPC:

注明:譯者的代碼暫時(shí)未把 Golang 集成到 Dockerfile 中,讀者有興趣可參考原文。

VesselService

上邊的 consignment-service 負(fù)責(zé)記錄貨物的托運(yùn)信息,現(xiàn)在創(chuàng)建第二個(gè)微服務(wù) vessel-service 來選擇合適的貨輪來運(yùn)送貨物,關(guān)系如下:

consignment.json 文件中的三個(gè)集裝箱組成的貨物,目前可以通過 consignment-service 管理貨物的信息,現(xiàn)在用 vessel-service 去檢查貨輪是否能裝得下這批貨物。

創(chuàng)建 protobuf 文件
// vessel-service/proto/vessel/vessel.proto
syntax = "proto3";

package go.micro.srv.vessel;

service VesselService {
    // 檢查是否有能運(yùn)送貨物的輪船
    rpc FindAvailable (Specification) returns (Response) {
    }
}

// 每條貨輪的熟悉
message Vessel {
    string id = 1;          // 編號
    int32 capacity = 2;     // 最大容量(船體容量即是集裝箱的個(gè)數(shù))
    int32 max_weight = 3;   // 最大載重
    string name = 4;        // 名字
    bool available = 5;     // 是否可用
    string ower_id = 6;     // 歸屬
}

// 等待運(yùn)送的貨物
message Specification {
    int32 capacity = 1;     // 容量(內(nèi)部集裝箱的個(gè)數(shù))
    int32 max_weight = 2;   // 重量
}

// 貨輪裝得下的話
// 返回的多條貨輪信息
message Response {
    Vessel vessel = 1;
    repeated Vessel vessels = 2;
}
創(chuàng)建 Makefile 與 Dockerfile

現(xiàn)在創(chuàng)建 vessel-service/Makefile 來編譯項(xiàng)目:

build:
    protoc -I. --go_out=plugins=micro:$(GOPATH)/src/shippy/vessel-service proto/vessel/vessel.proto
    # dep 工具暫不可用,直接手動編譯
    GOOS=linux GOARCH=amd64 go build
    docker build -t vessel-service .

run:
    docker run -p 50052:50051 -e MICRO_SERVER_ADDRESS=:50051 -e MICRO_REGISTRY=mdns vessel-service

注意第二個(gè)微服務(wù)運(yùn)行在宿主主機(jī)(macOS)的 50052 端口,50051 已被第一個(gè)占用。

現(xiàn)在創(chuàng)建 Dockerfile 來容器化 vessel-service:

# 暫未將 Golang 集成到 docker 中
FROM alpine:latest
RUN mkdir /app
WORKDIR /app
ADD vessel-service /app/vessel-service
CMD ["./vessel-service"]
實(shí)現(xiàn)貨船微服務(wù)的邏輯
package main

import (
    pb "shippy/vessel-service/proto/vessel"
    "github.com/pkg/errors"
    "context"
    "github.com/micro/go-micro"
    "log"
)

type Repository interface {
    FindAvailable(*pb.Specification) (*pb.Vessel, error)
}

type VesselRepository struct {
    vessels []*pb.Vessel
}

// 接口實(shí)現(xiàn)
func (repo *VesselRepository) FindAvailable(spec *pb.Specification) (*pb.Vessel, error) {
    // 選擇最近一條容量、載重都符合的貨輪
    for _, v := range repo.vessels {
        if v.Capacity >= spec.Capacity && v.MaxWeight >= spec.MaxWeight {
            return v, nil
        }
    }
    return nil, errors.New("No vessel can"t be use")
}

// 定義貨船服務(wù)
type service struct {
    repo Repository
}

// 實(shí)現(xiàn)服務(wù)端
func (s *service) FindAvailable(ctx context.Context, spec *pb.Specification, resp *pb.Response) error {
    // 調(diào)用內(nèi)部方法查找
    v, err := s.repo.FindAvailable(spec)
    if err != nil {
        return err
    }
    resp.Vessel = v
    return nil
}

func main() {
    // 停留在港口的貨船,先寫死
    vessels := []*pb.Vessel{
        {Id: "vessel001", Name: "Boaty McBoatface", MaxWeight: 200000, Capacity: 500},
    }
    repo := &VesselRepository{vessels}
    server := micro.NewService(
        micro.Name("go.micro.srv.vessel"),
        micro.Version("latest"),
    )
    server.Init()

    // 將實(shí)現(xiàn)服務(wù)端的 API 注冊到服務(wù)端
    pb.RegisterVesselServiceHandler(server.Server(), &service{repo})

    if err := server.Run(); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
貨運(yùn)服務(wù)與貨船服務(wù)交互

現(xiàn)在需要修改 consignent-service/main.go,使其作為客戶端去調(diào)用 vessel-service,查看有沒有合適的輪船來運(yùn)輸這批貨物。

// consignent-service/main.go
package main

import (...)


// 定義微服務(wù)
type service struct {
    repo Repository
    // consignment-service 作為客戶端調(diào)用 vessel-service 的函數(shù)
    vesselClient vesselPb.VesselServiceClient
}

// 實(shí)現(xiàn) consignment.pb.go 中的 ShippingServiceHandler 接口,使 service 作為 gRPC 的服務(wù)端
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, resp *pb.Response) error {

    // 檢查是否有適合的貨輪
    vReq := &vesselPb.Specification{
        Capacity:  int32(len(req.Containers)),
        MaxWeight: req.Weight,
    }
    vResp, err := s.vesselClient.FindAvailable(context.Background(), vReq)
    if err != nil {
        return err
    }

    // 貨物被承運(yùn)
    log.Printf("found vessel: %s
", vResp.Vessel.Name)
    req.VesselId = vResp.Vessel.Id
    consignment, err := s.repo.Create(req)
    if err != nil {
        return err
    }
    resp.Created = true
    resp.Consignment = consignment
    return nil
}

// ...

func main() {
    // ...

    // 解析命令行參數(shù)
    server.Init()
    repo := Repository{}
    // 作為 vessel-service 的客戶端
    vClient := vesselPb.NewVesselServiceClient("go.micro.srv.vessel", server.Client())
    pb.RegisterShippingServiceHandler(server.Server(), &service{repo, vClient})

    if err := server.Run(); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
增加貨物并運(yùn)行

更新 consignment-cli/consignment.json 中的貨物,塞入三個(gè)集裝箱,重量和容量都變大。

{
  "description": "This is a test consignment",
  "weight": 55000,
  "containers": [
    {
      "customer_id": "cust001",
      "user_id": "user001",
      "origin": "Manchester, United Kingdom"
    },
    {
      "customer_id": "cust002",
      "user_id": "user001",
      "origin": "Derby, United Kingdom"
    },
    {
      "customer_id": "cust005",
      "user_id": "user001",
      "origin": "Sheffield, United Kingdom"
    }
  ]
}

至此,我們完整的將 consignment-cli,consignment-service,vessel-service 三者流程跑通了。

客戶端用戶請求托運(yùn)貨物,貨運(yùn)服務(wù)向貨船服務(wù)檢查容量、重量是否超標(biāo),再運(yùn)送:

總結(jié)

本節(jié)中將更為易用的 go-micro 替代了 gRPC 同時(shí)進(jìn)行了微服務(wù)的 Docker 化。最后創(chuàng)建了 vessel-service 貨輪微服務(wù)來運(yùn)送貨物,并成功與貨輪微服務(wù)進(jìn)行了通信。

不過貨物數(shù)據(jù)都是存放在文件 consignment.json 中的,第三節(jié)我們將這些數(shù)據(jù)存儲到 MongoDB 數(shù)據(jù)庫中,并在代碼中使用 ORM 對數(shù)據(jù)進(jìn)行操作,同時(shí)使用 docker-compose 來統(tǒng)一 Docker 化后的兩個(gè)微服務(wù)。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/27342.html

相關(guān)文章

  • Golang 服務(wù)教程(三)

    摘要:本節(jié)將學(xué)習(xí)來統(tǒng)一管理和部署微服務(wù),引入第三個(gè)微服務(wù)并進(jìn)行存儲數(shù)據(jù)。到目前為止,要想啟動微服務(wù)的容器,均在其中的同時(shí)設(shè)置其環(huán)境變量,服務(wù)多了以后管理起來十分麻煩。 譯文鏈接:wuYin/blog原文鏈接:ewanvalentine.io,翻譯已獲作者 Ewan Valentine 授權(quán)。 本文完整代碼:GitHub 在上節(jié)中,我們使用 go-micro 重新實(shí)現(xiàn)了微服務(wù)并進(jìn)行了 Doc...

    Drummor 評論0 收藏0
  • 2017年1月前端月報(bào)

    摘要:平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。年以前看這個(gè)網(wǎng)址概況在線地址前端開發(fā)群月報(bào)提交原則技術(shù)文章新的為主。 平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 概況 在線地址:http://www.kancloud.cn/jsfront/month/82796 JS前端開發(fā)群月報(bào) 提交原則: 技...

    FuisonDesign 評論0 收藏0
  • 2017年1月前端月報(bào)

    摘要:平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。年以前看這個(gè)網(wǎng)址概況在線地址前端開發(fā)群月報(bào)提交原則技術(shù)文章新的為主。 平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 概況 在線地址:http://www.kancloud.cn/jsfront/month/82796 JS前端開發(fā)群月報(bào) 提交原則: 技...

    ivyzhang 評論0 收藏0
  • 2017年1月前端月報(bào)

    摘要:平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。年以前看這個(gè)網(wǎng)址概況在線地址前端開發(fā)群月報(bào)提交原則技術(shù)文章新的為主。 平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個(gè)網(wǎng)址:http://www.kancloud.cn/jsfron... 概況 在線地址:http://www.kancloud.cn/jsfront/month/82796 JS前端開發(fā)群月報(bào) 提交原則: 技...

    CloudwiseAPM 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<