实现功能
继上一节,我们完成了基本的web服务。
本节我们根据语雀开放文档 https://www.yuque.com/yuque/developer/api,
新增以下功能
- 语雀文章详情
- 语雀列表
- 语雀搜索
代码实现
增加 servcie 层,并创建以下文件
- service/intf/yuque.go 接口定义
- service/internal/yuque.go 具体内部实现
- service/set.go 服务集合
定义接口 service/intf/yuque.go
package intf import ( "context" "time" ) type IYuQue interface { // GetRepoDocList 获取一个仓库的文档列表 // 文档 https://www.yuque.com/yuque/developer/doc GetRepoDocList(ctx context.Context, request *GetRepoDocListRequest) (*GetRepoDocListResponse, error) // GetRepoDocDetail 获取单篇文档的详细信息 // 文档 https://www.yuque.com/yuque/developer/doc GetRepoDocDetail(ctx context.Context, request *GetRepoDocDetailRequest) (*GetRepoDocDetailResponse, error) // Search 搜索 // 文档 https://www.yuque.com/yuque/developer/high_level_api Search(ctx context.Context, request *SearchRequest) (*SearchResponse, error) } // GetRepoDocListRequest 获取一个仓库的文档列表 type GetRepoDocListRequest struct { Namespace string // Offset int // Limit int // OptionalProperties int // 获取文档浏览数 } // GetRepoDocListResponse 获取一个仓库的文档列表 type GetRepoDocListResponse struct { Data []Doc `json:"data"` } // GetRepoDocDetailRequest 获取单篇文档的详细信息 type GetRepoDocDetailRequest struct { Namespace string Slug string Raw int // raw=1 返回文档最原始的格式 } // GetRepoDocDetailResponse 获取单篇文档的详细信息 type GetRepoDocDetailResponse struct { Abilities struct { Update bool `json:"update"` Destroy bool `json:"destroy"` } `json:"abilities"` Data DocDetail `json:"data"` } // SearchRequest 搜索请求 type SearchRequest struct { Type string // 资源类型 Offset int // 分页,1、2... Scope int // 搜索路径 Related bool // 搜索与我相关的传递 true } // SearchResponse 搜索结果 type SearchResponse struct { // ... } // Doc 文档基本信息,一般用在列表场景 // https://www.yuque.com/yuque/developer/docserializer type Doc struct { CreatedAt string `json:"created_at"` ID int64 `json:"id"` Public int64 `json:"public"` Slug string `json:"slug"` Status int64 `json:"status"` Title string `json:"title"` UpdatedAt string `json:"updated_at"` } // DocDetail 文档详细信息 // https://www.yuque.com/yuque/developer/docdetailserializer type DocDetail struct { Id int `json:"id"` Slug string `json:"slug"` Title string `json:"title"` BookId int `json:"book_id"` Book struct { Id int `json:"id"` Type string `json:"type"` Slug string `json:"slug"` Name string `json:"name"` UserId int `json:"user_id"` Description string `json:"description"` CreatorId int `json:"creator_id"` Public int `json:"public"` ItemsCount int `json:"items_count"` LikesCount int `json:"likes_count"` WatchesCount int `json:"watches_count"` ContentUpdatedAt time.Time `json:"content_updated_at"` UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` Namespace string `json:"namespace"` User struct { Id int `json:"id"` Type string `json:"type"` Login string `json:"login"` Name string `json:"name"` Description interface{} `json:"description"` AvatarUrl string `json:"avatar_url"` BooksCount int `json:"books_count"` PublicBooksCount int `json:"public_books_count"` FollowersCount int `json:"followers_count"` FollowingCount int `json:"following_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Serializer string `json:"_serializer"` } `json:"user"` Serializer string `json:"_serializer"` } `json:"book"` UserId int `json:"user_id"` Creator struct { Id int `json:"id"` Type string `json:"type"` Login string `json:"login"` Name string `json:"name"` Description interface{} `json:"description"` AvatarUrl string `json:"avatar_url"` BooksCount int `json:"books_count"` PublicBooksCount int `json:"public_books_count"` FollowersCount int `json:"followers_count"` FollowingCount int `json:"following_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Serializer string `json:"_serializer"` } `json:"creator"` Format string `json:"format"` Body string `json:"body"` BodyDraft string `json:"body_draft"` BodyHtml string `json:"body_html"` BodyLake string `json:"body_lake"` BodyDraftLake string `json:"body_draft_lake"` Public int `json:"public"` Status int `json:"status"` ViewStatus int `json:"view_status"` ReadStatus int `json:"read_status"` LikesCount int `json:"likes_count"` CommentsCount int `json:"comments_count"` ContentUpdatedAt time.Time `json:"content_updated_at"` DeletedAt interface{} `json:"deleted_at"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` PublishedAt time.Time `json:"published_at"` FirstPublishedAt time.Time `json:"first_published_at"` WordCount int `json:"word_count"` Cover interface{} `json:"cover"` Description string `json:"description"` CustomDescription interface{} `json:"custom_description"` Hits int `json:"hits"` Serializer string `json:"_serializer"` }
接口实现 service/intf/yuque.go
package internal import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "strconv" "time" "github.com/golangtips/yuque/service/intf" ) var _ intf.IYuQue = (*YuQue)(nil) type YuQue struct { UserAgent string //应用名称 baseURL string token string client *http.Client } func NewYuQue(baseURL, token, userAgent string) *YuQue { client := &http.Client{ Timeout: 10 * time.Second, } return &YuQue{ UserAgent: userAgent, baseURL: baseURL, token: token, client: client, } } func (y *YuQue) GetRepoDocList(ctx context.Context, request *intf.GetRepoDocListRequest) (*intf.GetRepoDocListResponse, error) { url := fmt.Sprintf("%s/repos/%s/docs", y.baseURL, request.Namespace) req := y.buildHTTPRequest("GET", url, nil) q := req.URL.Query() if request.Offset > 0 { q.Add("offset", strconv.Itoa(request.Offset)) } if request.Limit > 0 { q.Add("limit", strconv.Itoa(request.Limit)) } req.URL.RawQuery = q.Encode() resp, err := y.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("ioutil: %w", err) } var response intf.GetRepoDocListResponse if err = json.Unmarshal(body, &response); err != nil { return nil, err } return &response, nil } func (y *YuQue) GetRepoDocDetail(_ context.Context, request *intf.GetRepoDocDetailRequest) (*intf.GetRepoDocDetailResponse, error) { url := fmt.Sprintf("%s/repos/%s/docs/%s", y.baseURL, request.Namespace, request.Slug) req := y.buildHTTPRequest("GET", url, nil) resp, err := y.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("ioutil: %w", err) } log.Println(string(body)) var detail intf.GetRepoDocDetailResponse if err = json.Unmarshal(body, &detail); err != nil { return nil, err } return &detail, nil } func (y *YuQue) Search(ctx context.Context, request *intf.SearchRequest) (*intf.SearchResponse, error) { return &intf.SearchResponse{ // }, nil } // buildHTTPRequest 辅助函数 func (y *YuQue) buildHTTPRequest(method, url string, body io.Reader) *http.Request { req, _ := http.NewRequest(method, url, body) req.Header.Add("User-Agent", y.UserAgent) req.Header.Add("X-Auth-Token", y.token) return req }
添加到服务集合 service/set.go
package service import ( "github.com/golangtips/yuque/config" "github.com/golangtips/yuque/service/internal" "github.com/golangtips/yuque/service/intf" ) type Set struct { YuQue intf.IYuQue } func NewSet(toml *config.Toml) (*Set, error) { var yueque intf.IYuQue { c := toml.YuQue yueque = internal.NewYuQue(c.BaseURL, c.Token, c.UserAgent) } return &Set{ YuQue: yueque, }, nil }