refactor: refactor eiblog

This commit is contained in:
henry.chen
2025-07-16 19:45:50 +08:00
parent 0a410f09f3
commit 8fcabd5e15
67 changed files with 1282 additions and 1330 deletions

345
pkg/third/disqus/disqus.go Normal file
View File

@@ -0,0 +1,345 @@
package disqus
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/eiblog/eiblog/pkg/config"
"github.com/eiblog/eiblog/pkg/model"
)
// disqus api
const (
apiPostsCount = "https://disqus.com/api/3.0/threads/set.json"
apiPostsList = "https://disqus.com/api/3.0/threads/listPostsThreaded"
apiPostCreate = "https://disqus.com/api/3.0/posts/create.json"
apiPostApprove = "https://disqus.com/api/3.0/posts/approve.json"
apiThreadCreate = "https://disqus.com/api/3.0/threads/create.json"
apiThreadDetails = "https://disqus.com/api/3.0/threads/details.json"
disqusAPIKey = "E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F"
)
// DisqusClient disqus client
type DisqusClient struct {
Host string
Conf config.Disqus
}
// NewDisqusClient new disqus client
func NewDisqusClient(host string, conf config.Disqus) (*DisqusClient, error) {
if conf.ShortName == "" || conf.PublicKey == "" || conf.AccessToken == "" {
return nil, errors.New("disqus: config incompleted")
}
return &DisqusClient{Host: host, Conf: conf}, nil
}
// postsCountResp 评论数量响应
type postsCountResp struct {
Code int
Response []struct {
ID string
Posts int
Identifiers []string
}
}
// PostsCount 获取文章评论数量
func (cli *DisqusClient) PostsCount(articles map[string]*model.Article) error {
vals := url.Values{}
vals.Set("api_key", cli.Conf.PublicKey)
vals.Set("forum", cli.Conf.ShortName)
// batch get
var count, index int
for _, article := range articles {
if index < len(articles) && count < 50 {
count++
index++
vals.Add("thread:ident", "post-"+article.Slug)
continue
}
count = 0
resp, err := http.DefaultClient.Get(apiPostsCount + "?" + vals.Encode())
if err != nil {
return err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
// check http status code
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &postsCountResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
for _, v := range result.Response {
i := strings.Index(v.Identifiers[0], "-")
slug := v.Identifiers[0][i+1:]
if article := articles[slug]; article != nil {
article.Count = v.Posts
if article.Thread == "" && v.ID != "" {
article.Thread = v.ID
}
}
}
}
return nil
}
// PostsListResp 获取评论列表
type PostsListResp struct {
Cursor struct {
HasNext bool
Next string
}
Code int
Response []postDetail
}
type postDetail struct {
Parent int
ID string
CreatedAt string
Message string
IsDeleted bool
Author struct {
Name string
ProfileURL string
Avatar struct {
Cache string
}
}
Thread string
}
// PostsList 评论列表
func (cli *DisqusClient) PostsList(article *model.Article, cursor string) (*PostsListResp, error) {
vals := url.Values{}
vals.Set("api_key", disqusAPIKey)
vals.Set("forum", cli.Conf.ShortName)
vals.Set("thread:ident", "post-"+article.Slug)
vals.Set("cursor", cursor)
vals.Set("order", "popular")
vals.Set("limit", "50")
resp, err := http.DefaultClient.Get(apiPostsList + "?" + vals.Encode())
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(b))
}
result := &PostsListResp{}
err = json.Unmarshal(b, result)
if err != nil {
return nil, err
}
return result, nil
}
// PostComment 评论
type PostComment struct {
Message string
Parent string
Thread string
AuthorEmail string
AuthorName string
IPAddress string
Identifier string
UserAgent string
}
// PostCreateResp create comments resp
type PostCreateResp struct {
Code int
Response postDetail
}
// PostCreate 评论文章
func (cli *DisqusClient) PostCreate(pc *PostComment) (*PostCreateResp, error) {
vals := url.Values{}
vals.Set("api_key", disqusAPIKey)
vals.Set("message", pc.Message)
vals.Set("parent", pc.Parent)
vals.Set("thread", pc.Thread)
vals.Set("author_email", pc.AuthorEmail)
vals.Set("author_name", pc.AuthorName)
// vals.Set("state", "approved")
req, err := http.NewRequest(http.MethodPost, apiPostCreate, strings.NewReader(vals.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(b))
}
result := &PostCreateResp{}
err = json.Unmarshal(b, result)
if err != nil {
return nil, err
}
return result, nil
}
// approvedResp 批准评论通过
type approvedResp struct {
Code int
Response []struct {
ID string
}
}
// PostApprove 批准评论
func (cli *DisqusClient) PostApprove(post string) error {
vals := url.Values{}
vals.Set("api_key", disqusAPIKey)
vals.Set("access_token", cli.Conf.AccessToken)
vals.Set("post", post)
req, err := http.NewRequest(http.MethodPost, apiPostApprove, strings.NewReader(vals.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", "https://disqus.com")
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &approvedResp{}
return json.Unmarshal(b, result)
}
// threadCreateResp 创建thread
type threadCreateResp struct {
Code int
Response struct {
ID string
}
}
// ThreadCreate 创建thread
func (cli *DisqusClient) ThreadCreate(article *model.Article, btitle string) error {
vals := url.Values{}
vals.Set("api_key", disqusAPIKey)
vals.Set("access_token", cli.Conf.AccessToken)
vals.Set("forum", cli.Conf.ShortName)
vals.Set("title", article.Title+" | "+btitle)
vals.Set("identifier", "post-"+article.Slug)
urlPath := fmt.Sprintf("https://%s/post/%s.html", cli.Host, article.Slug)
vals.Set("url", urlPath)
req, err := http.NewRequest(http.MethodPost, apiThreadCreate, strings.NewReader(vals.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &threadCreateResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
article.Thread = result.Response.ID
return nil
}
// threadDetailsResp thread info
type threadDetailsResp struct {
Code int
Response struct {
ID string
}
}
// ThreadDetails thread详细
func (cli *DisqusClient) ThreadDetails(article *model.Article) error {
vals := url.Values{}
vals.Set("api_key", disqusAPIKey)
vals.Set("access_token", cli.Conf.AccessToken)
vals.Set("forum", cli.Conf.ShortName)
vals.Set("thread:ident", "post-"+article.Slug)
resp, err := http.DefaultClient.Get(apiThreadDetails + "?" + vals.Encode())
if err != nil {
return err
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &threadDetailsResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
article.Thread = result.Response.ID
return nil
}

289
pkg/third/es/es.go Normal file
View File

@@ -0,0 +1,289 @@
package es
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
"github.com/eiblog/eiblog/pkg/model"
"github.com/eiblog/eiblog/tools"
"github.com/sirupsen/logrus"
)
// search mode
const (
SearchFilter = `"filter":{"bool":{"must":[%s]}}`
SearchTerm = `{"term":{"%s":"%s"}}`
SearchDate = `{"range":{"date":{"gte":"%s","lte": "%s","format": "yyyy-MM-dd||yyyy-MM||yyyy"}}}` // 2016-10||/M
ElasticIndex = "eiblog"
ElasticType = "article"
)
// ESClient es client
type ESClient struct {
Host string
}
// NewESClient new es client
func NewESClient(host string) (*ESClient, error) {
if host == "" {
return nil, errors.New("es: elasticsearch host is empty")
}
es := &ESClient{Host: host}
err := es.createIndexAndMappings(ElasticIndex, ElasticType)
if err != nil {
return nil, err
}
return es, nil
}
// ElasticSearch 搜索文章
func (cli *ESClient) ElasticSearch(query string, size, from int) (*SearchIndexResult, error) {
// 分析查询
var (
regTerm = regexp.MustCompile(`(tag|slug|date):`)
idxs = regTerm.FindAllStringIndex(query, -1)
length = len(idxs)
str, kw string
filter []string
)
if length == 0 { // 全文搜索
kw = query
}
// 字段搜索,检出,全文搜索
for i, idx := range idxs {
if i == length-1 {
str = query[idx[0]:]
if space := strings.Index(str, " "); space != -1 && space < len(str)-1 {
kw = str[space+1:]
str = str[:space]
}
} else {
str = strings.TrimSpace(query[idx[0]:idxs[i+1][0]])
}
kv := strings.Split(str, ":")
switch kv[0] {
case "slug":
filter = append(filter, fmt.Sprintf(SearchTerm, kv[0], kv[1]))
case "tag":
filter = append(filter, fmt.Sprintf(SearchTerm, kv[0], kv[1]))
case "date":
var date string
switch len(kv[1]) {
case 4:
date = fmt.Sprintf(SearchDate, kv[1], kv[1]+"||/y")
case 7:
date = fmt.Sprintf(SearchDate, kv[1], kv[1]+"||/M")
case 10:
date = fmt.Sprintf(SearchDate, kv[1], kv[1]+"||/d")
default:
break
}
filter = append(filter, date)
}
}
// 判断是否为空,判断搜索方式
dsl := fmt.Sprintf("{"+SearchFilter+"}", strings.Join(filter, ","))
if kw != "" {
dsl = strings.ReplaceAll(strings.ReplaceAll(`{"highlight":{"fields":{"content":{},"title":{}},"post_tags":["\u003c/b\u003e"],"pre_tags":["\u003cb\u003e"]},"query":{"dis_max":{"queries":[{"match":{"title":{"boost":4,"minimum_should_match":"50%","query":"$1"}}},{"match":{"content":{"boost":4,"minimum_should_match":"75%","query":"$1"}}},{"match":{"tag":{"boost":2,"minimum_should_match":"100%","query":"$1"}}},{"match":{"slug":{"boost":1,"minimum_should_match":"100%","query":"$1"}}}],"tie_breaker":0.3}},$2}`, "$1", kw), "$2", fmt.Sprintf(SearchFilter, strings.Join(filter, ",")))
}
return cli.indexQueryDSL(ElasticIndex, ElasticType, size, from, []byte(dsl))
}
// ElasticAddIndex 添加或更新索引
func (cli *ESClient) ElasticAddIndex(article *model.Article) error {
img := tools.PickFirstImage(article.Content)
mapping := map[string]interface{}{
"title": article.Title,
"content": tools.IgnoreHTMLTag(article.Content),
"slug": article.Slug,
"tag": article.Tags,
"img": img,
"date": article.CreatedAt,
}
data, _ := json.Marshal(mapping)
return cli.indexOrUpdateDocument(ElasticIndex, ElasticType, article.ID, data)
}
// ElasticDelIndex 删除索引
func (cli *ESClient) ElasticDelIndex(ids []int) error {
var target []string
for _, id := range ids {
target = append(target, fmt.Sprint(id))
}
return cli.deleteIndexDocument(ElasticIndex, ElasticType, target)
}
// indicesCreateResult 索引创建结果
type indicesCreateResult struct {
Acknowledged bool `json:"acknowledged"`
}
// createIndexAndMappings 创建索引和映射关系
func (cli *ESClient) createIndexAndMappings(index, typ string) error {
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, typ)
rawurl := fmt.Sprintf("%s/%s/%s", cli.Host, index, typ)
resp, err := http.DefaultClient.Head(rawurl)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
rawurl = fmt.Sprintf("%s/%s", cli.Host, index)
req, err := http.NewRequest(http.MethodPut, rawurl, bytes.NewReader([]byte(mappings)))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
result := indicesCreateResult{}
err = json.Unmarshal(data, &result)
if err != nil {
return errors.New(string(data))
}
if !result.Acknowledged {
return errors.New(string(data))
}
return nil
}
// indexOrUpdateDocument 创建或更新索引
func (cli *ESClient) indexOrUpdateDocument(index, typ string, id int, doc []byte) (err error) {
rawurl := fmt.Sprintf("%s/%s/%s/%d", cli.Host, index, typ, id)
req, err := http.NewRequest(http.MethodPut, rawurl, bytes.NewReader(doc))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
logrus.Debug(string(data))
return nil
}
type deleteIndexReq struct {
Index string `json:"_index"`
Type string `json:"_type"`
ID string `json:"_id"`
}
type deleteIndexResult struct {
Errors bool `json:"errors"`
Iterms []map[string]struct {
Error string `json:"error"`
} `json:"iterms"`
}
// deleteIndexDocument 删除文档
func (cli *ESClient) deleteIndexDocument(index, typ string, ids []string) error {
buf := bytes.Buffer{}
for _, id := range ids {
dd := deleteIndexReq{Index: index, Type: typ, ID: id}
m := map[string]deleteIndexReq{"delete": dd}
b, _ := json.Marshal(m)
buf.Write(b)
buf.WriteByte('\n')
}
rawurl := fmt.Sprintf("%s/_bulk", cli.Host)
req, err := http.NewRequest(http.MethodPost, rawurl, bytes.NewReader(buf.Bytes()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
result := deleteIndexResult{}
err = json.Unmarshal(data, &result)
if err != nil {
return err
}
if result.Errors {
for _, iterm := range result.Iterms {
for _, s := range iterm {
if s.Error != "" {
return errors.New(s.Error)
}
}
}
}
return nil
}
// SearchIndexResult 查询结果
type SearchIndexResult struct {
Took float32 `json:"took"`
Hits struct {
Total int `json:"total"`
Hits []struct {
ID string `json:"_id"`
Source struct {
Slug string `json:"slug"`
Content string `json:"content"`
Date time.Time `json:"date"`
Title string `json:"title"`
Img string `json:"img"`
} `json:"_source"`
Highlight struct {
Title []string `json:"title"`
Content []string `json:"content"`
} `json:"highlight"`
} `json:"hits"`
} `json:"hits"`
}
// indexQueryDSL 语句查询文档
func (c *ESClient) indexQueryDSL(index, typ string, size, from int, dsl []byte) (*SearchIndexResult, error) {
rawurl := fmt.Sprintf("%s/%s/%s/_search?size=%d&from=%d", c.Host,
index, typ, size, from)
resp, err := http.Post(rawurl, "application/json", bytes.NewReader(dsl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
result := &SearchIndexResult{}
err = json.Unmarshal(data, result)
if err != nil {
return nil, err
}
return result, nil
}

120
pkg/third/pinger/pinger.go Normal file
View File

@@ -0,0 +1,120 @@
package pinger
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"github.com/eiblog/eiblog/pkg/config"
"github.com/sirupsen/logrus"
)
// Pinger pinger
type Pinger struct {
Host string
Conf config.FeedRPC
}
// NewPinger new pinger
func NewPinger(host string, conf config.FeedRPC) (*Pinger, error) {
if conf.FeedrURL == "" {
return nil, fmt.Errorf("feedr url is empty")
}
return &Pinger{Host: host, Conf: conf}, nil
}
// PingFunc ping blog article to SE
func (p *Pinger) PingFunc(btitle, slug string) {
err := p.feedrPingFunc(btitle, slug)
if err != nil {
logrus.Error("pinger: PingFunc feedr: ", err)
}
err = p.rpcPingFunc(btitle, slug)
if err != nil {
logrus.Error("pinger: PingFunc: rpc: ", err)
}
}
// feedrPingFunc http://<your-hub-name>.superfeedr.com/
func (p *Pinger) feedrPingFunc(btitle, slug string) error {
vals := url.Values{}
vals.Set("hub.mode", "publish")
vals.Add("hub.url", fmt.Sprintf("https://%s/post/%s.html", p.Host, slug))
resp, err := http.DefaultClient.PostForm(p.Conf.FeedrURL, vals)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 204 {
return fmt.Errorf("pinger: status code: %d, %s", resp.StatusCode, string(data))
}
return nil
}
// rpcPingParam ping to rpc, eg. google baidu
// params:
//
// BlogName string `xml:"param>value>string"`
// HomePage string `xml:"param>value>string"`
// Article string `xml:"param>value>string"`
// RSS_URL string `xml:"param>value>string"`
type rpcPingParam struct {
XMLName xml.Name `xml:"methodCall"`
MethodName string `xml:"methodName"`
Params struct {
Param [4]rpcValue `xml:"param"`
} `xml:"params"`
}
type rpcValue struct {
Value string `xml:"value>string"`
}
// rpcPingFunc ping rpc
func (p *Pinger) rpcPingFunc(btitle, slug string) error {
param := rpcPingParam{MethodName: "weblogUpdates.extendedPing"}
param.Params.Param = [4]rpcValue{
0: {Value: btitle},
1: {Value: "https://" + p.Host},
2: {Value: fmt.Sprintf("https://%s/post/%s.html", p.Host, slug)},
3: {Value: "https://" + p.Host + "/rss.html"},
}
buf := bytes.Buffer{}
buf.WriteString(xml.Header)
enc := xml.NewEncoder(&buf)
if err := enc.Encode(param); err != nil {
return err
}
data := buf.Bytes()
header := http.Header{}
header.Set("Content-Type", "text/xml")
for _, addr := range p.Conf.PingRPC {
resp, err := http.DefaultClient.Post(addr, "text/xml", bytes.NewReader(data))
if err != nil {
logrus.Error("rpcPingFunc.httpPostHeader: ", err)
continue
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
logrus.Error("rpcPingFunc.ReadAll: ", err)
continue
}
if resp.StatusCode != 200 {
logrus.Error("rpcPingFunc.failed: ", string(data), resp.StatusCode)
}
}
return nil
}

174
pkg/third/qiniu/qiniu.go Normal file
View File

@@ -0,0 +1,174 @@
package qiniu
import (
"context"
"errors"
"io"
"net/http"
"path/filepath"
"time"
"github.com/eiblog/eiblog/pkg/config"
"github.com/qiniu/go-sdk/v7/auth/qbox"
"github.com/qiniu/go-sdk/v7/storage"
)
// QiniuClient qiniu client
type QiniuClient struct {
Conf config.Qiniu
}
// NewQiniuClient new qiniu client
func NewQiniuClient(conf config.Qiniu) (*QiniuClient, error) {
if conf.AccessKey == "" ||
conf.SecretKey == "" ||
conf.Bucket == "" ||
conf.Domain == "" {
return nil, errors.New("qiniu config error")
}
return &QiniuClient{Conf: conf}, nil
}
// UploadParams upload params
type UploadParams struct {
Name string
Size int64
Data io.Reader
NoCompletePath bool
}
// Upload 上传文件
func (cli *QiniuClient) Upload(params UploadParams) (string, error) {
key := params.Name
if !params.NoCompletePath {
key = filepath.Base(params.Name)
}
mac := qbox.NewMac(cli.Conf.AccessKey,
cli.Conf.SecretKey)
// 设置上传策略
putPolicy := &storage.PutPolicy{
Scope: cli.Conf.Bucket,
Expires: 3600,
InsertOnly: 1,
}
// 上传token
uploadToken := putPolicy.UploadToken(mac)
// 上传配置
region, err := storage.GetRegion(cli.Conf.AccessKey, cli.Conf.Bucket)
if err != nil {
return "", err
}
cfg := &storage.Config{
UseHTTPS: true,
Region: region,
}
// uploader
uploader := storage.NewFormUploader(cfg)
ret := new(storage.PutRet)
putExtra := &storage.PutExtra{}
err = uploader.Put(context.Background(), ret, uploadToken,
key, params.Data, params.Size, putExtra)
if err != nil {
return "", err
}
url := "https://" + cli.Conf.Domain + "/" + key
return url, nil
}
// DeleteParams delete params
type DeleteParams struct {
Name string
Days int
NoCompletePath bool
}
// QiniuDelete 删除文件
func (cli *QiniuClient) Delete(params DeleteParams) error {
key := params.Name
if !params.NoCompletePath {
key = completeQiniuKey(params.Name)
}
mac := qbox.NewMac(cli.Conf.AccessKey,
cli.Conf.SecretKey)
// 上传配置
region, err := storage.GetRegion(cli.Conf.AccessKey, cli.Conf.Bucket)
if err != nil {
return err
}
cfg := &storage.Config{
UseHTTPS: true,
Region: region,
}
// manager
bucketManager := storage.NewBucketManager(mac, cfg)
// Delete
if params.Days > 0 {
return bucketManager.DeleteAfterDays(cli.Conf.Bucket, key, params.Days)
}
return bucketManager.Delete(cli.Conf.Bucket, key)
}
// ContentParams list params
type ContentParams struct {
Prefix string
}
// Content 获取文件内容
func (cli *QiniuClient) Content(params ContentParams) ([]byte, error) {
mac := qbox.NewMac(cli.Conf.AccessKey,
cli.Conf.SecretKey)
// region
region, err := storage.GetRegion(cli.Conf.AccessKey, cli.Conf.Bucket)
if err != nil {
return nil, err
}
cfg := &storage.Config{
UseHTTPS: true,
Region: region,
}
// manager
bucketManager := storage.NewBucketManager(mac, cfg)
// list file
files, _, _, _, err := bucketManager.ListFiles(cli.Conf.Bucket, params.Prefix, "", "", 1)
if err != nil {
return nil, err
}
if len(files) == 0 {
return nil, errors.New("no file")
}
deadline := time.Now().Add(time.Second * 60).Unix()
url := storage.MakePrivateURLv2(mac, "https://"+cli.Conf.Domain, files[0].Key, deadline)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// completeQiniuKey 修复路径
func completeQiniuKey(name string) string {
ext := filepath.Ext(name)
switch ext {
case ".bmp", ".png", ".jpg", ".gif", ".ico", ".jpeg":
name = "blog/img/" + name
case ".mov", ".mp4":
name = "blog/video/" + name
case ".go", ".js", ".css", ".cpp", ".php", ".rb", ".java",
".py", ".sql", ".lua", ".html", ".sh", ".xml", ".cs":
name = "blog/code/" + name
case ".txt", ".md", ".ini", ".yaml", ".yml", ".doc", ".ppt", ".pdf":
name = "blog/document/" + name
case ".zip", ".rar", ".tar", ".gz":
name = "blog/archive/" + name
default:
name = "blog/other/" + name
}
return name
}

View File

@@ -0,0 +1,72 @@
package qiniu
import (
"os"
"testing"
"time"
"github.com/eiblog/eiblog/pkg/config"
)
func TestQiniuUpload(t *testing.T) {
cli, err := NewQiniuClient(config.Qiniu{
AccessKey: os.Getenv("QINIU_ACCESSKEY"),
SecretKey: os.Getenv("QINIU_SECRETKEY"),
Bucket: os.Getenv("QINIU_BUCKET"),
Domain: os.Getenv("QINIU_DOMAIN"),
})
if err != nil {
t.Errorf("NewQiniuClient error = %v", err)
return
}
f, _ := os.Open("qiniu_test.go")
fi, _ := f.Stat()
type args struct {
params UploadParams
}
tests := []struct {
name string
args args
wantErr bool
}{
// TODO: Add test cases.
{"1", args{params: UploadParams{
Name: "test-" + time.Now().Format("200601021504059999") + ".go",
Size: fi.Size(),
Data: f,
}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := cli.Upload(tt.args.params)
if (err != nil) != tt.wantErr {
t.Errorf("QiniuUpload() error = %v, wantErr %v", err, tt.wantErr)
return
}
t.Logf("QiniuUpload() = %v", got)
})
}
}
func TestQiniuContent(t *testing.T) {
cli, err := NewQiniuClient(config.Qiniu{
AccessKey: os.Getenv("QINIU_ACCESSKEY"),
SecretKey: os.Getenv("QINIU_SECRETKEY"),
Bucket: os.Getenv("QINIU_BUCKET"),
Domain: os.Getenv("QINIU_DOMAIN"),
})
if err != nil {
t.Errorf("NewQiniuClient error = %v", err)
return
}
params := ContentParams{
Prefix: "blog/",
}
_, err = cli.Content(params)
if err != nil {
t.Errorf("QiniuList error = %v", err)
}
}