Golang 语雀内容系统(2) 增加语雀SDK

256 阅读3分钟

实现功能

继上一节,我们完成了基本的web服务。

本节我们根据语雀开放文档 www.yuque.com/yuque/devel…

新增以下功能

  • 语雀文章详情
  • 语雀列表
  • 语雀搜索

代码实现

本节完整代码,参考:github.com/golangtips/…

增加 servcie 层,并创建以下文件

  • service/intf/yuque.go 接口定义
  • service/internal/yuque.go 具体内部实现
  • service/set.go 服务集合
  1. 定义接口 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"`
}
  1. 接口实现 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
}


3. 添加到服务集合 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
}