實現功能
繼上一節,我們完成了基本的web服務。
本節我們根據語雀開放文檔 https://www.yuque.com/yuque/developer/api,
新增以下功能
- 語雀文章詳情
- 語雀列表
- 語雀搜索
代碼實現
本節完整代碼,參考:https://github.com/golangtips...
增加 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 }