mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-11 08:52:27 +08:00
refactor: eiblog
This commit is contained in:
296
pkg/internal/disqus.go
Normal file
296
pkg/internal/disqus.go
Normal file
@@ -0,0 +1,296 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/eiblog/eiblog/v2/pkg/config"
|
||||
"github.com/eiblog/eiblog/v2/pkg/model"
|
||||
)
|
||||
|
||||
// disqus api
|
||||
const (
|
||||
apiPostsCount = "https://disqus.com/api/3.0/threads/set.json"
|
||||
apiPostsList = "https://disqus.com/api/3.0/threads/listPosts.json"
|
||||
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"
|
||||
)
|
||||
|
||||
func checkDisqusConfig() error {
|
||||
if config.Conf.BlogApp.Disqus.ShortName != "" &&
|
||||
config.Conf.BlogApp.Disqus.PublicKey != "" &&
|
||||
config.Conf.BlogApp.Disqus.AccessToken != "" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("disqus: config incompleted")
|
||||
}
|
||||
|
||||
// postsCountResp 评论数量响应
|
||||
type postsCountResp struct {
|
||||
Code int
|
||||
Response []struct {
|
||||
ID string
|
||||
Posts int
|
||||
Identifiers []string
|
||||
}
|
||||
}
|
||||
|
||||
// PostsCount 获取文章评论数量
|
||||
func PostsCount(articles map[string]*model.Article) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", config.Conf.BlogApp.Disqus.PublicKey)
|
||||
vals.Set("forum", config.Conf.BlogApp.Disqus.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 := httpGet(apiPostsCount + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.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
|
||||
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 PostsList(slug, cursor string) (*postsListResp, error) {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", config.Conf.BlogApp.Disqus.PublicKey)
|
||||
vals.Set("forum", config.Conf.BlogApp.Disqus.ShortName)
|
||||
vals.Set("thread:ident", "post-"+slug)
|
||||
vals.Set("cursor", cursor)
|
||||
vals.Set("limit", "50")
|
||||
|
||||
resp, err := httpGet(apiPostsList + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.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
|
||||
}
|
||||
|
||||
type postCreateResp struct {
|
||||
Code int
|
||||
Response postDetail
|
||||
}
|
||||
|
||||
// PostCreate 评论文章
|
||||
func PostCreate(pc *PostComment) (*postCreateResp, error) {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", "E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F")
|
||||
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")
|
||||
|
||||
header := http.Header{"Referer": {"https://disqus.com"}}
|
||||
resp, err := httpPostHeader(apiPostCreate, vals, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.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 PostApprove(post string) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", config.Conf.BlogApp.Disqus.PublicKey)
|
||||
vals.Set("access_token", config.Conf.BlogApp.Disqus.AccessToken)
|
||||
vals.Set("post", post)
|
||||
|
||||
header := http.Header{"Referer": {"https://disqus.com"}}
|
||||
resp, err := httpPostHeader(apiPostApprove, vals, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.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 ThreadCreate(article *model.Article, btitle string) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", config.Conf.BlogApp.Disqus.PublicKey)
|
||||
vals.Set("access_token", config.Conf.BlogApp.Disqus.AccessToken)
|
||||
vals.Set("forum", config.Conf.BlogApp.Disqus.ShortName)
|
||||
vals.Set("title", article.Title+" | "+btitle)
|
||||
vals.Set("identifier", "post-"+article.Slug)
|
||||
|
||||
urlPath := fmt.Sprintf("https://%s/post/%s.html", config.Conf.BlogApp.Host, article.Slug)
|
||||
vals.Set("url", urlPath)
|
||||
|
||||
resp, err := httpPost(apiThreadCreate, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.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
|
||||
}
|
||||
248
pkg/internal/es.go
Normal file
248
pkg/internal/es.go
Normal file
@@ -0,0 +1,248 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/v2/pkg/config"
|
||||
|
||||
"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
|
||||
)
|
||||
|
||||
func init() {
|
||||
if checkESConfig() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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"}}}}}`, "article")
|
||||
err := createIndexAndMappings("article", "eiblog", []byte(mappings))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkESConfig() error {
|
||||
if config.Conf.ESHost == "" {
|
||||
return errors.New("es: elasticsearch not config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ElasticSearch 搜索文章
|
||||
func ElasticSearch(query string, size, from int) (*searchIndexResult, error) {
|
||||
if err := checkESConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 分析查询
|
||||
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.Replace(strings.Replace(`{"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, -1), "$2", fmt.Sprintf(SearchFilter, strings.Join(filter, ",")), -1)
|
||||
}
|
||||
return indexQueryDSL("article", "eiblog", size, from, []byte(dsl))
|
||||
}
|
||||
|
||||
// indicesCreateResult 索引创建结果
|
||||
type indicesCreateResult struct {
|
||||
Acknowledged bool `json:"acknowledged"`
|
||||
}
|
||||
|
||||
// createIndexAndMappings 创建索引和映射关系
|
||||
func createIndexAndMappings(index, typ string, mappings []byte) error {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s", config.Conf.ESHost, index, typ)
|
||||
resp, err := httpHead(rawurl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawurl = fmt.Sprintf("%s/%s", config.Conf.ESHost, index)
|
||||
resp, err = httpPut(rawurl, mappings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := indicesCreateResult{}
|
||||
err = json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !result.Acknowledged {
|
||||
return errors.New(string(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// indexOrUpdateDocument 创建或更新索引
|
||||
func indexOrUpdateDocument(index, typ string, id int32, doc []byte) (err error) {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s/%d", config.Conf.ESHost, index, typ, id)
|
||||
resp, err := httpPut(rawurl, doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.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 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", config.Conf.ESHost)
|
||||
resp, err := httpPost(rawurl, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.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 indexQueryDSL(index, typ string, size, from int, dsl []byte) (*searchIndexResult, error) {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s/_search?size=%d&from=%d", config.Conf.ESHost,
|
||||
index, typ, size, from)
|
||||
resp, err := httpPost(rawurl, dsl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.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
|
||||
}
|
||||
123
pkg/internal/http.go
Normal file
123
pkg/internal/http.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func newRequest(method, rawurl string, data interface{}) (*http.Request, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host := u.Host
|
||||
// 获取主机IP
|
||||
ips, err := net.LookupHost(u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("http: not found ip(%s)", u.Host)
|
||||
}
|
||||
u.Host = ips[0]
|
||||
// 创建HTTP Request
|
||||
var req *http.Request
|
||||
switch raw := data.(type) {
|
||||
case url.Values:
|
||||
req, err = http.NewRequest(method, u.String(),
|
||||
strings.NewReader(raw.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
case []byte:
|
||||
req, err = http.NewRequest(method, u.String(),
|
||||
bytes.NewReader(raw))
|
||||
case nil:
|
||||
req, err = http.NewRequest(method, u.String(), nil)
|
||||
default:
|
||||
return nil, fmt.Errorf("http: unsupported data type: %T", data)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 设置Host
|
||||
req.Host = host
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// httpHead HTTP HEAD请求
|
||||
func httpHead(rawurl string) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodHead, rawurl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpGet HTTP GET请求
|
||||
func httpGet(rawurl string) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodGet, rawurl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpPost HTTP POST请求, 自动识别是否是form
|
||||
func httpPost(rawurl string, data interface{}) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodPost, rawurl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpPostHeader HTTP POST请求,自定义Header
|
||||
func httpPostHeader(rawurl string, data interface{},
|
||||
header http.Header) (*http.Response, error) {
|
||||
|
||||
req, err := newRequest(http.MethodPost, rawurl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set header
|
||||
req.Header = header
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpPut HTTP PUT请求
|
||||
func httpPut(rawurl string, data interface{}) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodPut, rawurl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
111
pkg/internal/pinger.go
Normal file
111
pkg/internal/pinger.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/eiblog/eiblog/v2/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// feedrPingFunc http://<your-hub-name>.superfeedr.com/
|
||||
var feedrPingFunc = func(slug string) error {
|
||||
feedrHost := config.Conf.BlogApp.FeedRPC.FeedrURL
|
||||
if feedrHost == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("hub.mode", "publish")
|
||||
vals.Add("hub.url", fmt.Sprintf("https://%s/post/%s.html",
|
||||
config.Conf.BackupApp.Host, slug))
|
||||
resp, err := httpPost(feedrHost, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.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
|
||||
var rpcPingFunc = func(slug string) error {
|
||||
if len(config.Conf.BlogApp.FeedRPC.PingRPC) == 0 {
|
||||
return nil
|
||||
}
|
||||
param := rpcPingParam{MethodName: "weblogUpdates.extendedPing"}
|
||||
param.Params.Param = [4]rpcValue{
|
||||
0: rpcValue{Value: config.Conf.BlogApp.Blogger.BTitle},
|
||||
1: rpcValue{Value: "https://" + config.Conf.BlogApp.Host},
|
||||
2: rpcValue{Value: fmt.Sprintf("https://%s/post/%s.html", config.Conf.BlogApp.Host, slug)},
|
||||
3: rpcValue{Value: "https://" + config.Conf.BlogApp.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 config.Conf.BlogApp.FeedRPC.PingRPC {
|
||||
resp, err := httpPostHeader(addr, data, header)
|
||||
if err != nil {
|
||||
logrus.Error("rpcPingFunc.httpPostHeader: ", err)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logrus.Error("rpcPingFunc.ReadAll: ", err)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
logrus.Error("rpcPingFunc.failed: ", string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PingFunc ping blog article to SE
|
||||
func PingFunc(slug string) {
|
||||
err := feedrPingFunc(slug)
|
||||
if err != nil {
|
||||
logrus.Error("pinger: PingFunc feedr: ", err)
|
||||
}
|
||||
err = rpcPingFunc(slug)
|
||||
if err != nil {
|
||||
logrus.Error("pinger: PingFunc: rpc: ", err)
|
||||
}
|
||||
}
|
||||
101
pkg/internal/qiniu.go
Normal file
101
pkg/internal/qiniu.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/eiblog/eiblog/v2/pkg/config"
|
||||
|
||||
"github.com/qiniu/api.v7/v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/v7/storage"
|
||||
)
|
||||
|
||||
// QiniuUpload 上传文件
|
||||
func QiniuUpload(name string, size int64, data io.Reader) (string, error) {
|
||||
if config.Conf.BlogApp.Qiniu.AccessKey == "" ||
|
||||
config.Conf.BlogApp.Qiniu.SecretKey == "" {
|
||||
return "", errors.New("qiniu config error")
|
||||
}
|
||||
key := completeQiniuKey(name)
|
||||
|
||||
mac := qbox.NewMac(config.Conf.BlogApp.Qiniu.AccessKey,
|
||||
config.Conf.BlogApp.Qiniu.SecretKey)
|
||||
// 设置上传策略
|
||||
putPolicy := &storage.PutPolicy{
|
||||
Scope: config.Conf.BlogApp.Qiniu.Bucket,
|
||||
Expires: 3600,
|
||||
InsertOnly: 1,
|
||||
}
|
||||
// 上传token
|
||||
uploadToken := putPolicy.UploadToken(mac)
|
||||
// 上传配置
|
||||
cfg := &storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// uploader
|
||||
uploader := storage.NewFormUploader(cfg)
|
||||
ret := new(storage.PutRet)
|
||||
putExtra := &storage.PutExtra{}
|
||||
|
||||
err := uploader.Put(context.Background(), ret, uploadToken,
|
||||
key, data, size, putExtra)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
url := "https://" + config.Conf.BlogApp.Qiniu.Domain + "/" + key
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// QiniuDelete 删除文件
|
||||
func QiniuDelete(name string) error {
|
||||
key := completeQiniuKey(name)
|
||||
|
||||
mac := qbox.NewMac(config.Conf.BlogApp.Qiniu.AccessKey,
|
||||
config.Conf.BlogApp.Qiniu.SecretKey)
|
||||
// 上传配置
|
||||
cfg := &storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// manager
|
||||
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||
// Delete
|
||||
return bucketManager.Delete(config.Conf.BlogApp.Qiniu.Bucket, key)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user