实现功能
继上一节,我们完成了基本的web服务。
本节我们根据语雀开放文档 www.yuque.com/yuque/devel…,
新增以下功能
- 语雀文章详情
- 语雀列表
- 语雀搜索
代码实现
本节完整代码,参考: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
}
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
}