Compare commits

...

33 Commits

Author SHA1 Message Date
henry.chen
f20c4a6063 fix docker image: exec user process caused "no such file or directory" 2017-11-27 18:17:41 +08:00
henry.chen
c24e6bf7bd update .travis.yml 2017-11-27 16:43:30 +08:00
henry.chen
ade94168d3 update .travis.yml 2017-11-27 16:32:39 +08:00
henry.chen
552d010650 fix background turn page 2017-11-27 15:21:28 +08:00
deepzz0
1c3106cbb0 update vendor 2017-11-24 22:58:59 +08:00
henry.chen
168937f1b2 fix gopkg.in/mgo import conflict 2017-11-23 13:57:20 +08:00
henry.chen
730cffcb5b 修复文章自动保存bug+发布文章不成功bug 2017-11-17 13:30:47 +08:00
deepzz0
8c3f1c2aba update travis.yml 2017-11-05 13:03:52 +08:00
deepzz0
ea375ea76c update travis.yml 2017-11-05 12:56:46 +08:00
deepzz0
275a6c0c31 update travis.yml 2017-11-05 12:46:01 +08:00
deepzz0
360204995d 使用github的七牛SDK,配置名称Kodo->Qiniu 2017-11-05 12:27:22 +08:00
deepzz0
c9fc0cc75a Merge branch 'master' of github.com:eiblog/eiblog 2017-10-19 20:23:45 +08:00
deepzz0
41daaa322e fix mod date panic 2017-10-19 20:23:36 +08:00
Deepzz
894535fbe5 Update README.md 2017-10-10 20:16:01 -05:00
Deepzz
6fc5af1b0f Update eiblog.conf 2017-09-26 22:42:49 -05:00
henry.chen
5ce806a7d7 挑战 acme.sh 文件验证路径 2017-08-25 18:01:37 +08:00
Deepzz
25cb23fdb3 Update README.md 2017-08-20 17:48:44 +08:00
deepzz0
a89a1a2bc9 update 2017-08-19 14:26:19 +08:00
deepzz0
93e170f9ac fix es/config/scripts 2017-08-19 14:25:28 +08:00
Deepzz
59d9a616aa Update README.md 2017-08-17 17:00:52 +08:00
Deepzz
2ff0934206 Update install.md 2017-08-15 21:31:34 +08:00
Deepzz
cde7cba2f0 Update README.md 2017-08-15 21:23:12 +08:00
Deepzz
2be7501afe Update README.md 2017-08-15 21:12:03 +08:00
deepzz0
487d35dae2 add comments 2017-08-08 20:59:45 +08:00
henry.chen
19af9376cb add comments 2017-08-08 12:45:58 +08:00
deepzz0
3ddd2a0b33 fix disqus 基础评论bug 2017-08-08 01:03:10 +08:00
Deepzz
ee7523b124 Update helper.go 2017-08-07 18:07:58 +08:00
Deepzz
cc1dbac1f0 clean eiblog.conf 2017-07-27 22:03:43 +08:00
deepzz0
04532ba8a6 fix conflict 2017-07-26 22:48:16 +08:00
deepzz0
0a2a132b11 rm some cod in domain.cnf 2017-07-26 22:45:54 +08:00
Deepzz
3ff712d407 Update eiblog.conf 2017-07-25 09:22:08 +08:00
deepzz0
27162d2205 fix unuse tag <!--more-->
intercept errors
2017-07-15 13:46:29 +08:00
deepzz0
f150974566 rm .travis.yml about glide 2017-07-13 21:29:23 +08:00
464 changed files with 28387 additions and 14560 deletions

View File

@@ -1,37 +1,26 @@
sudo: required # 超级权限
dist: trusty # 在ubuntu:trusty
language: go # 声明构建语言环境
go: # 只构建最新版本
- 1.8
services: # docker环境
- tip
services: # docker环境
- docker
branches: # 限定项目分支
only:
- /^v[0-9](\.[0-9]){2}(-rc[1-9])?$/
install:
- curl https://glide.sh/get | sh # 安装glide包管理
script:
- glide up
- GOOS=linux GOARCH=amd64 go build # 编译版本
- docker build -t registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog . # 构建镜像
after_success:
# - if [ "$TRAVIS_BRANCH" =~ ^v[0-9](\.[0-9])+.*$ ]; then
# docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com;
# docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog;
# fi
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
- docker tag registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
before_deploy:
- ./dist.sh
deploy:
provider: releases
api_key:

View File

@@ -7,4 +7,4 @@ ADD static/tzdata/Shanghai /etc/localtime
COPY . /eiblog
EXPOSE 9000
WORKDIR /eiblog
CMD ["./eiblog"]
CMD ["sh","-c","eiblog"]

View File

@@ -15,7 +15,7 @@ test:
build:
@echo "go build..."
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
docker build -t $(docker_registry)/deepzz/eiblog:latest .
docker build -t $(docker_registry)/deepzz/eiblog:latest .
deploy:build
@docker push $(docker_registry)/deepzz/eiblog:latest
@@ -24,47 +24,37 @@ dist:
@./dist.sh
gencert:makedir
@echo $(Ali_Key) $(Ali_Secret)
@if [ ! -n "$(sans)" ]; then \
printf "Need one argument [sans=params]\n"; \
printf "example: sans=\"-d domain -d domain\"\n"; \
exit 1; \
fi; \
printf "Need one argument [sans=params]\n"; \
printf "example: sans=\"-d domain -d domain\"\n"; \
exit 1; \
fi; \
if [ ! -n "$(cn)" ]; then \
printf "Need one argument [cn=params]\n"; \
printf "example: cn=domain\n"; \
exit 1; \
fi
printf "Need one argument [cn=params]\n"; \
printf "example: cn=domain\n"; \
exit 1; \
fi
@if [ ! -f $(acme.sh) ]; then \
curl https://get.acme.sh | sh; \
curl https://get.acme.sh | sh; \
fi
@echo "generate rsa cert..."
@$(acme.sh) --force --issue --dns dns_ali \
$(sans) --log --renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/venafi.sct && \
ct-submit ctlog.wosign.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/wosign.sct"
@$(acme.sh) --force --issue --dns dns_ali $(sans) --log \
--renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/venafi.sct \
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/wosign.sct"
@$(acme.sh) --install-cert -d $(cn) \
--key-file $(config)/ssl/domain.rsa.key \
--fullchain-file $(config)/ssl/domain.rsa.pem \
--reloadcmd "service nginx force-reload"
--key-file $(config)/ssl/domain.rsa.key \
--fullchain-file $(config)/ssl/domain.rsa.pem \
--reloadcmd "service nginx force-reload"
@echo "generate ecc cert..."
@$(acme.sh) --force --issue --dns dns_ali \
$(sans) -k ec-256 --log --renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/venafi.sct && \
ct-submit ctlog.wosign.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/wosign.sct"
@$(acme.sh) --force --issue --dns dns_ali $(sans) -k ec-256 --log \
--renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/venafi.sct \
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/wosign.sct"
@$(acme.sh) --install-cert -d $(cn) --ecc \
--key-file $(config)/ssl/domain.ecc.key \
--fullchain-file $(config)/ssl/domain.ecc.pem \
--reloadcmd "service nginx force-reload"
# fullchained:
# @if [ ! -n "$(cn)" ]; then \
# printf "Use acme.sh generated certs, Need one argument [cn=params]\n"; \
# printf "example: cn=domain\n"; \
# exit 1; \
# fi
# @cp $(acme)/$(cn)/ca.cer $(config)/ssl/full_chained.pem && \
# echo $(X3) >> $(config)/ssl/full_chained.pem
--key-file $(config)/ssl/domain.ecc.key \
--fullchain-file $(config)/ssl/domain.ecc.pem \
--reloadcmd "service nginx force-reload"
dhparams:
@openssl dhparam -out $(config)/ssl/dhparams.pem 2048

View File

@@ -1,4 +1,4 @@
# EiBlog [![Build Status](https://travis-ci.org/eiblog/eiblog.svg?branch=master)](https://travis-ci.org/eiblog/eiblog) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![Versuib](https://img.shields.io/github/tag/eiblog/eiblog.svg)](https://github.com/eiblog/eiblog/releases)
# EiBlog [![Build Status](https://travis-ci.org/eiblog/eiblog.svg?branch=v1.3.0)](https://travis-ci.org/eiblog/eiblog) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![Versuib](https://img.shields.io/github/tag/eiblog/eiblog.svg)](https://github.com/eiblog/eiblog/releases)
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建期间获得了QuQu的很大帮助在此表示感谢。
@@ -11,8 +11,8 @@
整个博客系统涉及到模块如下:
* 自动更新证书:
* 接入 [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),在 TLS 层开启全自动更新证书,从此证书的更新再也不用惦记了。
* 如果你采用如 Nginx 代理,你可能需要 [acme.sh](https://github.com/Neilpang/acme.sh) 实现证书的自动。博主实现 aliyun dns 的自动验证方式,详见 [Makefile/gencert](https://github.com/eiblog/eiblog/blob/master/Makefile)。
* 接入 [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),在 TLS 层开启全自动更新证书,从此证书的更新再也不用惦记了,不过 Go 的 HTTPS 兼容性不够好(不想兼容),在如部分 IE 和 UC 之类的浏览器不能访问,请悉知
* 如果你采用如 Nginx 代理,推荐使用 [acme.sh](https://github.com/Neilpang/acme.sh) 实现证书的自动部署。博主实现 aliyun dns 的自动验证方式,详见 [Makefile/gencert](https://github.com/eiblog/eiblog/blob/master/Makefile)。
* `MongoDB`,博客采用 mongodb 作为存储数据库。
* `Elasticsearch`,采用 `elasticsearch` 作为博客的站内搜索,尽管占用内存稍高。
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
@@ -65,8 +65,8 @@
* `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
* `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
* `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
* `HPKP`HTTP 公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`
* `SSL Protocols`,罗列支持的 `TLS` 协议SSLv3 被证实是不安全的。
 * `HPKP`HTTP 公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`请不要轻易尝试 Nginx 线上运行,因为该配置目前只指定了 Letsencrypt X3 和 TrustAsia G5 证书 pin-sha256。
 * `SSL Protocols`,罗列支持的 `TLS` 协议SSLv3 被证实是不安全的。
* `SSL dhparam`,迪菲赫尔曼密钥交换。
* `Cipher suite`,罗列服务器支持加密套件。
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
@@ -84,6 +84,7 @@
### 成功搭建者博客
* [https://razeencheng.com/](https://razeencheng.com/) - Razeen's Blog
* [https://razeen.me](https://razeen.me) - Razeen's Blog
* [https://mxthd.me](https://mxthd.me) - 梦醒逃荒岛
如果你的博客使用`Eiblog`搭建,你可以在 [这里](https://github.com/eiblog/eiblog/issues/1) 提交网址。

23
api.go
View File

@@ -11,16 +11,20 @@ import (
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/mgo"
"github.com/gin-gonic/gin"
"gopkg.in/mgo.v2/bson"
)
const (
// 成功
NOTICE_SUCCESS = "success"
NOTICE_NOTICE = "notice"
NOTICE_ERROR = "error"
// 注意
NOTICE_NOTICE = "notice"
// 错误
NOTICE_ERROR = "error"
)
// 全局 API
var APIs = make(map[string]func(c *gin.Context))
func init() {
@@ -61,7 +65,8 @@ func apiAccount(c *gin.Context) {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err := UpdateAccountField(bson.M{"$set": bson.M{"email": e, "phonen": pn, "address": ad}})
err := UpdateAccountField(mgo.M{"$set": mgo.M{"email": e, "phonen": pn, "address": ad}})
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
@@ -83,7 +88,8 @@ func apiBlog(c *gin.Context) {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err := UpdateAccountField(bson.M{"$set": bson.M{"blogger.blogname": bn, "blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st, "blogger.seriessay": ss, "blogger.archivessay": as}})
err := UpdateAccountField(mgo.M{"$set": mgo.M{"blogger.blogname": bn, "blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st, "blogger.seriessay": ss, "blogger.archivessay": as}})
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
@@ -117,7 +123,8 @@ func apiPassword(c *gin.Context) {
return
}
newPwd := EncryptPasswd(Ei.Username, nw)
err := UpdateAccountField(bson.M{"$set": bson.M{"password": newPwd}})
err := UpdateAccountField(mgo.M{"$set": mgo.M{"password": newPwd}})
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
@@ -136,6 +143,7 @@ func apiPostDelete(c *gin.Context) {
}
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
}()
err = c.Request.ParseForm()
if err != nil {
return
@@ -187,6 +195,7 @@ func apiPostAdd(c *gin.Context) {
c.Redirect(http.StatusFound, "/admin/manage-posts")
}
}()
do = c.PostForm("do") // auto or save or publish
slug := c.PostForm("slug")
title := c.PostForm("title")
@@ -245,7 +254,7 @@ func apiPostAdd(c *gin.Context) {
if CheckBool(update) {
artc.UpdateTime = time.Now()
}
err = UpdateArticle(bson.M{"id": artc.ID}, artc)
err = UpdateArticle(mgo.M{"id": artc.ID}, artc)
if err != nil {
logd.Error(err)
return

14
back.go
View File

@@ -11,11 +11,12 @@ import (
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/mgo"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"gopkg.in/mgo.v2/bson"
)
// 是否登录
func isLogin(c *gin.Context) bool {
session := sessions.Default(c)
v := session.Get("username")
@@ -25,6 +26,7 @@ func isLogin(c *gin.Context) bool {
return true
}
// 登陆过滤
func AuthFilter() gin.HandlerFunc {
return func(c *gin.Context) {
if !isLogin(c) {
@@ -51,6 +53,7 @@ func HandleLogin(c *gin.Context) {
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
}
// 登陆接口
func HandleLoginPost(c *gin.Context) {
user := c.PostForm("user")
pwd := c.PostForm("password")
@@ -61,7 +64,7 @@ func HandleLoginPost(c *gin.Context) {
return
}
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
logd.Printf("账号或密码错误 %s, %s", user, pwd)
logd.Printf("账号或密码错误 %s, %s\n", user, pwd)
c.Redirect(http.StatusFound, "/admin/login")
return
}
@@ -70,12 +73,12 @@ func HandleLoginPost(c *gin.Context) {
session.Save()
Ei.LoginIP = c.ClientIP()
Ei.LoginTime = time.Now()
UpdateAccountField(bson.M{"$set": bson.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
UpdateAccountField(mgo.M{"$set": mgo.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
c.Redirect(http.StatusFound, "/admin/profile")
}
func GetBack() gin.H {
return gin.H{"Author": Ei.Username, "Kodo": setting.Conf.Kodo}
return gin.H{"Author": Ei.Username, "Qiniu": setting.Conf.Qiniu}
}
// 个人配置
@@ -120,6 +123,7 @@ func HandlePost(c *gin.Context) {
RenderHTMLBack(c, "admin-post", h)
}
// 删除草稿
func HandleDraftDelete(c *gin.Context) {
id, err := strconv.Atoi(c.Query("cid"))
if err != nil || id < 1 {
@@ -184,6 +188,7 @@ func HandleSeries(c *gin.Context) {
RenderHTMLBack(c, "admin-series", h)
}
// 编辑专题
func HandleSerie(c *gin.Context) {
h := GetBack()
id, err := strconv.Atoi(c.Query("mid"))
@@ -276,6 +281,7 @@ func HandleAPI(c *gin.Context) {
api(c)
}
// 渲染 html
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
if name == "login.html" {
err := Tmpl.ExecuteTemplate(c.Writer, name, data)

View File

@@ -6,25 +6,30 @@ import (
"time"
)
// 检查 email
func CheckEmail(e string) bool {
reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
return reg.MatchString(e)
}
// 检查 domain
func CheckDomain(domain string) bool {
reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
return reg.MatchString(domain)
}
// 检查 sms
func CheckSMS(sms string) bool {
reg := regexp.MustCompile(`^\+\d+$`)
return reg.MatchString(sms)
}
// 检查 password
func CheckPwd(pwd string) bool {
return len(pwd) > 5 && len(pwd) < 19
}
// 检查日期
func CheckDate(date string) time.Time {
if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
return t
@@ -32,6 +37,7 @@ func CheckDate(date string) time.Time {
return time.Now()
}
// 检查 id
func CheckSerieID(sid string) int32 {
if id, err := strconv.Atoi(sid); err == nil {
return int32(id)
@@ -39,6 +45,7 @@ func CheckSerieID(sid string) int32 {
return 0
}
// bool
func CheckBool(str string) bool {
return str == "true" || str == "1"
}

View File

@@ -53,8 +53,8 @@ google:
v: "1"
t: pageview
# 七牛CDN
kodo:
name: eiblog
qiniu:
bucket: eiblog
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf

View File

View File

@@ -65,28 +65,6 @@ server {
expires 1d;
}
# imququ 的上传文件相关,未用到
location ^~ /static/uploads/ {
root /home/jerry/www/imququ.com/www;
add_header Access-Control-Allow-Origin *;
set $expires_time max;
valid_referers blocked none server_names *.qgy18.com *.inoreader.com feedly.com *.feedly.com www.udpwork.com theoldreader.com digg.com *.feiworks.com *.newszeit.com r.mail.qq.com yuedu.163.com *.w3ctech.com;
if ($invalid_referer) {
set $expires_time -1;
return 403;
}
expires $expires_time;
}
location ^~ /static/ {
root /data/eiblog;
add_header Access-Control-Allow-Origin *;
expires max;
}
location ^~ /admin/ {
proxy_http_version 1.1;
@@ -95,7 +73,7 @@ server {
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
# https://imququ.com/post/web-security-and-response-header.html#toc-1
add_header X-Frame-Options deny;
add_header X-Powered-By eiblog/1.2.1;
add_header X-Powered-By eiblog/1.3.0;
add_header X-Content-Type-Options nosniff;
proxy_set_header Connection "";
@@ -116,15 +94,14 @@ server {
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; media-src https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
# 中间证书证书指纹
# https://imququ.com/post/http-public-key-pinning.html
add_header Public-Key-Pins 'pin-sha256="IiSbZ4pMDEyXvtl7Lg8K3FNmJcTAhKUTrB2FQOaAO/s="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000; includeSubDomains';
add_header Public-Key-Pins 'pin-sha256="IiSbZ4pMDEyXvtl7Lg8K3FNmJcTAhKUTrB2FQOaAO/s="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000;';
add_header Cache-Control no-cache;
add_header X-Via Aliyun.QingDao;
add_header X-XSS-Protection "1; mode=block";
add_header X-Powered-By eiblog/1.3.0;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Vary;
proxy_hide_header X-Powered-By;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
@@ -147,7 +124,7 @@ server {
# letsencrypt file verify
location ^~ /.well-known/acme-challenge/ {
alias /data/letsencrypt/challenges/;
alias /data/eiblog/challenges/;
try_files $uri =404;
}

117
db.go
View File

@@ -13,9 +13,7 @@ import (
"github.com/eiblog/blackfriday"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
db "github.com/eiblog/utils/mgo"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"github.com/eiblog/utils/mgo"
)
// 数据库及表名
@@ -62,40 +60,20 @@ var (
func init() {
// 数据库加索引
ms, c := db.Connect(DB, COLLECTION_ACCOUNT)
index := mgo.Index{
Key: []string{"username"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
if err := c.EnsureIndex(index); err != nil {
err := mgo.Index(DB, COLLECTION_ACCOUNT, []string{"username"})
if err != nil {
logd.Fatal(err)
}
ms.Close()
ms, c = db.Connect(DB, COLLECTION_ARTICLE)
index = mgo.Index{
Key: []string{"id"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
if err := c.EnsureIndex(index); err != nil {
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"id"})
if err != nil {
logd.Fatal(err)
}
index = mgo.Index{
Key: []string{"slug"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
if err := c.EnsureIndex(index); err != nil {
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"slug"})
if err != nil {
logd.Fatal(err)
}
ms.Close()
// 读取帐号信息
Ei = loadAccount()
// 获取文章数据
@@ -111,7 +89,7 @@ func init() {
// 读取或初始化帐号信息
func loadAccount() (a *Account) {
a = &Account{}
err := db.FindOne(DB, COLLECTION_ACCOUNT, bson.M{"username": setting.Conf.Account.Username}, a)
err := mgo.FindOne(DB, COLLECTION_ACCOUNT, mgo.M{"username": setting.Conf.Account.Username}, a)
// 初始化用户数据
if err == mgo.ErrNotFound {
a = &Account{
@@ -127,7 +105,7 @@ func loadAccount() (a *Account) {
a.BeiAn = setting.Conf.Blogger.BeiAn
a.BTitle = setting.Conf.Blogger.BTitle
a.Copyright = setting.Conf.Blogger.Copyright
err = db.Insert(DB, COLLECTION_ACCOUNT, a)
err = mgo.Insert(DB, COLLECTION_ACCOUNT, a)
generateTopic()
} else if err != nil {
logd.Fatal(err)
@@ -139,7 +117,7 @@ func loadAccount() (a *Account) {
}
func loadArticles() (artcs SortArticles) {
err := db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": false, "deletetime": bson.M{"$eq": time.Time{}}}, &artcs)
err := mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": false, "deletetime": mgo.M{"$eq": time.Time{}}}, &artcs)
if err != nil {
logd.Fatal(err)
}
@@ -209,26 +187,26 @@ func generateMarkdown() {
// init account: generate blogroll and about page
func generateTopic() {
about := &Article{
ID: db.NextVal(DB, COUNTER_ARTICLE),
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "关于",
Slug: "about",
CreateTime: time.Now(),
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
blogroll := &Article{
ID: db.NextVal(DB, COUNTER_ARTICLE),
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "友情链接",
Slug: "blogroll",
UpdateTime: time.Now(),
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
err := db.Insert(DB, COLLECTION_ARTICLE, blogroll)
err := mgo.Insert(DB, COLLECTION_ARTICLE, blogroll)
if err != nil {
logd.Fatal(err)
}
err = db.Insert(DB, COLLECTION_ARTICLE, about)
err = mgo.Insert(DB, COLLECTION_ARTICLE, about)
if err != nil {
logd.Fatal(err)
}
@@ -273,6 +251,7 @@ func PageList(p, n int) (prev int, next int, artcs []*Article) {
return
}
// 管理 tag
func ManageTagsArticle(artc *Article, s bool, do string) {
switch do {
case ADD:
@@ -297,6 +276,7 @@ func ManageTagsArticle(artc *Article, s bool, do string) {
}
}
// 管理专题
func ManageSeriesArticle(artc *Article, s bool, do string) {
switch do {
case ADD:
@@ -305,7 +285,6 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
if s {
sort.Sort(Ei.Series[i].Articles)
Ei.CH <- SERIES_MD
return
}
}
@@ -316,7 +295,6 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
for j, v := range serie.Articles {
if v == artc {
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j], Ei.Series[i].Articles[j+1:]...)
Ei.CH <- SERIES_MD
return
}
}
@@ -325,6 +303,7 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
}
}
// 管理归档
func ManageArchivesArticle(artc *Article, s bool, do string) {
switch do {
case ADD:
@@ -337,7 +316,6 @@ func ManageArchivesArticle(artc *Article, s bool, do string) {
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
if s {
sort.Sort(Ei.Archives[i].Articles)
Ei.CH <- ARCHIVE_MD
break
}
}
@@ -352,7 +330,9 @@ func ManageArchivesArticle(artc *Article, s bool, do string) {
for j, v := range archive.Articles {
if v == artc {
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j], Ei.Archives[i].Articles[j+1:]...)
Ei.CH <- ARCHIVE_MD
if len(Ei.Archives[i].Articles) == 0 {
Ei.Archives = append(Ei.Archives[:i], Ei.Archives[i+1:]...)
}
return
}
}
@@ -374,6 +354,7 @@ func GenerateExcerptAndRender(artc *Article) {
artc.Content = artc.Content[index:]
}
// 查找目录
content := renderPage([]byte(artc.Content))
index := regH.FindIndex(content)
if index != nil {
@@ -397,14 +378,14 @@ func GenerateExcerptAndRender(artc *Article) {
// 读取草稿箱
func LoadDraft() (artcs SortArticles, err error) {
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": true}, &artcs)
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": true}, &artcs)
sort.Sort(artcs)
return
}
// 读取回收箱
func LoadTrash() (artcs SortArticles, err error) {
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$ne": time.Time{}}}, &artcs)
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$ne": time.Time{}}}, &artcs)
sort.Sort(artcs)
return
}
@@ -413,14 +394,16 @@ func LoadTrash() (artcs SortArticles, err error) {
func AddArticle(artc *Article) error {
// 分配ID, 占位至起始id
for {
if id := db.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
if id := mgo.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
continue
} else {
artc.ID = id
break
}
}
if !artc.IsDraft {
// 正式发布文章
defer GenerateExcerptAndRender(artc)
Ei.MapArticles[artc.Slug] = artc
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
@@ -434,7 +417,7 @@ func AddArticle(artc *Article) error {
Ei.CH <- SERIES_MD
}
}
return db.Insert(DB, COLLECTION_ARTICLE, artc)
return mgo.Insert(DB, COLLECTION_ARTICLE, artc)
}
// 删除文章,移入回收箱
@@ -449,7 +432,7 @@ func DelArticles(ids ...int32) error {
ManageTagsArticle(artc, false, DELETE)
ManageSeriesArticle(artc, false, DELETE)
ManageArchivesArticle(artc, false, DELETE)
err := UpdateArticle(bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Now()}})
err := UpdateArticle(mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Now()}})
if err != nil {
return err
}
@@ -460,6 +443,7 @@ func DelArticles(ids ...int32) error {
return nil
}
// 从链表里删除文章
func DelFromLinkedList(artc *Article) {
if artc.Prev == nil && artc.Next != nil {
artc.Next.Prev = nil
@@ -471,6 +455,7 @@ func DelFromLinkedList(artc *Article) {
}
}
// 将文章添加到链表
func AddToLinkedList(id int32) {
i, artc := GetArticle(id)
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
@@ -501,34 +486,34 @@ func timer() {
delT := time.NewTicker(time.Duration(setting.Conf.General.Clean) * time.Hour)
for {
<-delT.C
db.Remove(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
}
}
// 操作帐号字段
func UpdateAccountField(M bson.M) error {
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username}, M)
func UpdateAccountField(M mgo.M) error {
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username}, M)
}
// 删除草稿箱或回收箱,永久删除
func RemoveArticle(id int32) error {
return db.Remove(DB, COLLECTION_ARTICLE, bson.M{"id": id})
return mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"id": id})
}
// 恢复删除文章到草稿箱
func RecoverArticle(id int32) error {
return db.Update(DB, COLLECTION_ARTICLE, bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Time{}, "isdraft": true}})
return mgo.Update(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Time{}, "isdraft": true}})
}
// 更新文章
func UpdateArticle(query, update interface{}) error {
return db.Update(DB, COLLECTION_ARTICLE, query, update)
return mgo.Update(DB, COLLECTION_ARTICLE, query, update)
}
// 编辑文档
func QueryArticle(id int32) *Article {
artc := &Article{}
if err := db.FindOne(DB, COLLECTION_ARTICLE, bson.M{"id": id}, artc); err != nil {
if err := mgo.FindOne(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, artc); err != nil {
return nil
}
return artc
@@ -536,17 +521,17 @@ func QueryArticle(id int32) *Article {
// 添加专题
func AddSerie(name, slug, desc string) error {
serie := &Serie{db.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
serie := &Serie{mgo.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
Ei.Series = append(Ei.Series, serie)
sort.Sort(Ei.Series)
Ei.CH <- SERIES_MD
return UpdateAccountField(bson.M{"$addToSet": bson.M{"blogger.series": serie}})
return UpdateAccountField(mgo.M{"$addToSet": mgo.M{"blogger.series": serie}})
}
// 更新专题
func UpdateSerie(serie *Serie) error {
Ei.CH <- SERIES_MD
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username, "blogger.series.id": serie.ID}, bson.M{"$set": bson.M{"blogger.series.$": serie}})
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username, "blogger.series.id": serie.ID}, mgo.M{"$set": mgo.M{"blogger.series.$": serie}})
}
// 删除专题
@@ -556,7 +541,7 @@ func DelSerie(id int32) error {
if len(serie.Articles) > 0 {
return fmt.Errorf("请删除该专题下的所有文章")
}
err := UpdateAccountField(bson.M{"$pull": bson.M{"blogger.series": bson.M{"id": id}}})
err := UpdateAccountField(mgo.M{"$pull": mgo.M{"blogger.series": mgo.M{"id": id}}})
if err != nil {
return err
}
@@ -580,24 +565,24 @@ func QuerySerie(id int32) *Serie {
// 后台分页
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
M := bson.M{}
M := mgo.M{}
if draft {
M["isdraft"] = true
} else if del {
M["deletetime"] = bson.M{"$ne": time.Time{}}
M["deletetime"] = mgo.M{"$ne": time.Time{}}
} else {
M["isdraft"] = false
M["deletetime"] = bson.M{"$eq": time.Time{}}
M["deletetime"] = mgo.M{"$eq": time.Time{}}
if se > 0 {
M["serieid"] = se
}
if kw != "" {
M["title"] = bson.M{"$regex": kw, "$options": "$i"}
M["title"] = mgo.M{"$regex": kw, "$options": "$i"}
}
}
ms, c := db.Connect(DB, COLLECTION_ARTICLE)
ms, c := mgo.Connect(DB, COLLECTION_ARTICLE)
defer ms.Close()
err := c.Find(M).Select(bson.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
err := c.Find(M).Select(mgo.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
if err != nil {
logd.Error(err)
}

View File

@@ -17,7 +17,8 @@ import (
var ErrDisqusConfig = errors.New("disqus config incorrect")
type result struct {
// 定时获取所有文章评论数量
type postsCountResp struct {
Code int
Response []struct {
Id string
@@ -26,7 +27,6 @@ type result struct {
}
}
// 定时获取所有文章评论数量
func PostsCount() error {
if setting.Conf.Disqus.PostsCount == "" ||
setting.Conf.Disqus.PublicKey == "" ||
@@ -68,12 +68,12 @@ func PostsCount() error {
return errors.New(string(b))
}
rst := result{}
err = json.Unmarshal(b, &rst)
result := &postsCountResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
for _, v := range rst.Response {
for _, v := range result.Response {
i := strings.Index(v.Identifiers[0], "-")
artc := Ei.MapArticles[v.Identifiers[0][i+1:]]
if artc != nil {
@@ -86,35 +86,39 @@ func PostsCount() error {
return nil
}
type postsList struct {
// 获取文章评论列表
type postsListResp struct {
Cursor struct {
HasNext bool
Next string
}
Code int
Response []struct {
Parent int
Id string
CreatedAt string
Message string
Author struct {
Name string
ProfileUrl string
Avatar struct {
Cache string
}
}
Thread string
}
Response []postDetail
}
// 获取文章评论列表
func PostsList(slug, cursor string) (*postsList, error) {
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
}
func PostsList(slug, cursor string) (*postsListResp, error) {
if setting.Conf.Disqus.PostsList == "" ||
setting.Conf.Disqus.PublicKey == "" ||
setting.Conf.Disqus.ShortName == "" {
return nil, ErrDisqusConfig
}
url := setting.Conf.Disqus.PostsList + "?limit=50&api_key=" +
setting.Conf.Disqus.PublicKey + "&forum=" + setting.Conf.Disqus.ShortName +
"&cursor=" + cursor + "&thread:ident=post-" + slug
@@ -123,6 +127,7 @@ func PostsList(slug, cursor string) (*postsList, error) {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
@@ -130,12 +135,13 @@ func PostsList(slug, cursor string) (*postsList, error) {
if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(b))
}
pl := &postsList{}
err = json.Unmarshal(b, pl)
result := &postsListResp{}
err = json.Unmarshal(b, result)
if err != nil {
return nil, err
}
return pl, nil
return result, nil
}
type PostCreate struct {
@@ -149,19 +155,17 @@ type PostCreate struct {
UserAgent string `json:"user_agent"`
}
type PostResponse struct {
Code int `json:"code"`
Response struct {
Id string `json:"id"`
} `json:"response"`
type postCreateResp struct {
Code int
Response postDetail
}
// 评论文章
func PostComment(pc *PostCreate) (string, error) {
func PostComment(pc *PostCreate) (*postCreateResp, error) {
if setting.Conf.Disqus.PostsList == "" ||
setting.Conf.Disqus.PublicKey == "" ||
setting.Conf.Disqus.ShortName == "" {
return "", ErrDisqusConfig
return nil, ErrDisqusConfig
}
url := setting.Conf.Disqus.PostCreate +
"?api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F" +
@@ -171,37 +175,38 @@ func PostComment(pc *PostCreate) (string, error) {
request, err := http.NewRequest("POST", url, nil)
if err != nil {
return "", err
return nil, err
}
request.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(request)
if err != nil {
return "", err
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
return nil, err
}
if resp.StatusCode != http.StatusOK {
return "", errors.New(string(b))
return nil, errors.New(string(b))
}
pr := &PostResponse{}
err = json.Unmarshal(b, pr)
result := &postCreateResp{}
err = json.Unmarshal(b, result)
if err != nil {
return "", err
return nil, err
}
return pr.Response.Id, nil
return result, nil
}
type ApprovedResponse struct {
// 批准评论通过
type approvedResp struct {
Code int `json:"code"`
Response []struct {
Id string `json:"id"`
} `json:"response"`
}
// 批准评论通过
func PostApprove(post string) error {
if setting.Conf.Disqus.PostsList == "" ||
setting.Conf.Disqus.PublicKey == "" ||
@@ -223,6 +228,7 @@ func PostApprove(post string) error {
return err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
@@ -232,8 +238,8 @@ func PostApprove(post string) error {
return errors.New(string(b))
}
ar := &ApprovedResponse{}
err = json.Unmarshal(b, ar)
result := &approvedResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}

View File

@@ -6,7 +6,7 @@ $ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.
2、如果有幸你也是 `Gopher`,相信你会亲自动手,你可以通过:
``` sh
$ go get -u https://github.com/eiblog/eiblog
$ git clone https://github.com/eiblog/eiblog.git
```
进行源码编译二进制文件运行。
@@ -95,7 +95,9 @@ $ docker run -d --name eisearch \
| default_avatar.png | st.example.com/static/img/default_avatar.png | disqus 默认图片,[下载](https://st.deepzz.com/static/img/default_avatar.png) |
| disqus.js | st.example.com/static/js/disqus_xxx.js | disqus 文件,你可以通过 https://short_name.disqus.com/embed.js 下载你的专属文件,并上传到七牛。更新配置文件 app.yml。 |
> 注意:每次修改 views 内的以 `st_` 开头的文件,请将 `app.yml` 中的 staticversion 提高一个版本。 cdn 提到的文件下载,请复制链接进行下载,因为博主使用了防盗链功能
> 注意cdn 提到的文件下载,请复制链接进行下载,因为博主使用了防盗链功能,还有:
 1、每次修改 app.yml 文件(如:更换 cdn 域名或更新头像),如果你不知道是否应该提高 staticversion 一个版本,那么最好提高一个 +1。
2、每次手动修改 views 内的以 `st_` 开头的文件,请将 `app.yml` 中的 staticversion 提高一个版本。
#### 配置说明
走到这里,我相信只走到 `60%` 的路程。放弃还来得及。
@@ -141,7 +143,7 @@ $ docker run -d --name eisearch \
| ----------- | ---------------------------------------- |
| app.yml | 整个程序的配置文件,里面已经列出了所有配置项的说明,这里不再阐述。 |
| blackip.yml | 如果没有使用 `Nginx`,博客内置 `ip` 过滤系统。 |
| es | elasticsearch非常强大的分布式搜索引擎`github` 用的就是它。里面的配置基本不用修改,但 `es/analysis/synonym.txt` 是同义词,你可以照着已有的随意增加。注意,scripts 虽然是空的,但请保持存在。 |
| es | elasticsearch非常强大的分布式搜索引擎`github` 用的就是它。里面的配置基本不用修改,但 `es/analysis/synonym.txt` 是同义词你可以照着已有的随意增加。scripts 是 es 的脚本文件夹 |
| nginx | 系统采用 `nginx` 作为代理(相信博客系统也不会独占一台服务器~)。请使用 `nginx.conf` 替换原 `nginx` 的配置。博客系统的配置文件是 `domain/eiblog.conf`,或则重命名(只要是满足`*.conf`)。`eiblog.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。注意本配置需要更新 nginx 到最新版openssl 更新到1.0.2j,具体请到 Jerry Qu 的 [本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html) 查看,了解详情。 |
| scts | 存放 ct 文件。 |
| ssl | 这里存放了所有证书相关的内容。 |
@@ -186,8 +188,6 @@ $ docker run -d --name eiblog --restart=always \
首先,请将本地测试好的 `conf``docker-compose.yml` 文件夹和文件上传至服务器。`conf` 建议存储到服务器 `/data/eiblog` 下,`docker-compose.yml` 存放在你使用方便的地方。
> 注意检查 `conf/es/config/scripts` 空文件夹是否存在,不存在即创建。
``` sh
$ tree /data/eiblog -L 1

View File

@@ -25,11 +25,13 @@ const (
var es *ElasticService
// 初始化 Elasticsearch 服务器
func init() {
es = &ElasticService{url: "http://elasticsearch:9200", c: new(http.Client)}
initIndex()
}
// 创建索引
func initIndex() {
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"}}}}}`, TYPE)
err := CreateIndexAndMappings(INDEX, TYPE, []byte(mappings))
@@ -38,6 +40,7 @@ func initIndex() {
}
}
// 查询
func Elasticsearch(qStr string, size, from int) *ESSearchResult {
// 分析查询字符串
reg := regexp.MustCompile(`(tag|slug|date):`)
@@ -95,6 +98,7 @@ func Elasticsearch(qStr string, size, from int) *ESSearchResult {
return docs
}
// 添加或更新索引
func ElasticIndex(artc *Article) error {
img := PickFirstImage(artc.Content)
mapping := map[string]interface{}{
@@ -109,6 +113,7 @@ func ElasticIndex(artc *Article) error {
return IndexOrUpdateDocument(INDEX, TYPE, artc.ID, b)
}
// 删除索引
func ElasticDelIndex(ids []int32) error {
var target []string
for _, id := range ids {
@@ -127,10 +132,12 @@ type IndicesCreateResult struct {
Acknowledged bool `json:"acknowledged"`
}
// 返回 url
func (s *ElasticService) ParseURL(format string, params ...interface{}) string {
return fmt.Sprintf(s.url+format, params...)
}
// Elastic 相关操作请求
func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
resp, err := s.c.Do(req)
if err != nil {
@@ -152,11 +159,8 @@ func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
return b, nil
case "HEAD":
return resp.StatusCode, nil
default:
return nil, errors.New("unknown methods")
}
return nil, nil
return nil, errors.New("unknown methods")
}
func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
@@ -187,6 +191,7 @@ func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
return nil
}
// 创建或更新索引
func IndexOrUpdateDocument(index, typ string, id int32, doc []byte) (err error) {
req, err := http.NewRequest("PUT", es.ParseURL("/%s/%s/%d", index, typ, id), bytes.NewReader(doc))
if err != nil {
@@ -213,6 +218,7 @@ type ESDeleteResult struct {
} `json:"iterms"`
}
// 删除文档
func DeleteDocument(index, typ string, ids []string) error {
var buff bytes.Buffer
for _, id := range ids {
@@ -247,6 +253,7 @@ func DeleteDocument(index, typ string, ids []string) error {
return nil
}
// 查询结果
type ESSearchResult struct {
Took float32 `json:"took"`
Hits struct {
@@ -268,6 +275,7 @@ type ESSearchResult struct {
} `json:"hits"`
}
// DSL 语句查询文档
func IndexQueryDSL(index, typ string, size, from int, dsl []byte) (*ESSearchResult, error) {
req, err := http.NewRequest("POST", es.ParseURL("/%s/%s/_search?size=%d&from=%d", index, typ, size, from), bytes.NewReader(dsl))
if err != nil {

View File

@@ -86,11 +86,12 @@ func GetBase() gin.H {
"BTitle": Ei.BTitle,
"BeiAn": Ei.BeiAn,
"Domain": setting.Conf.Mode.Domain,
"Kodo": setting.Conf.Kodo,
"Qiniu": setting.Conf.Qiniu,
"Disqus": setting.Conf.Disqus,
}
}
// not found
func HandleNotFound(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -101,6 +102,7 @@ func HandleNotFound(c *gin.Context) {
RenderHTMLFront(c, "notfound", h)
}
// 首页
func HandleHomePage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -117,6 +119,7 @@ func HandleHomePage(c *gin.Context) {
RenderHTMLFront(c, "home", h)
}
// 专题页
func HandleSeriesPage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -129,6 +132,7 @@ func HandleSeriesPage(c *gin.Context) {
RenderHTMLFront(c, "series", h)
}
// 归档页
func HandleArchivesPage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -141,6 +145,7 @@ func HandleArchivesPage(c *gin.Context) {
RenderHTMLFront(c, "archives", h)
}
// 文章
func HandleArticlePage(c *gin.Context) {
path := c.Param("slug")
if !strings.HasSuffix(path, ".html") || Ei.MapArticles[path[:len(path)-5]] == nil {
@@ -178,6 +183,7 @@ func HandleArticlePage(c *gin.Context) {
RenderHTMLFront(c, name, h)
}
// 搜索页
func HandleSearchPage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -220,6 +226,7 @@ func HandleSearchPage(c *gin.Context) {
RenderHTMLFront(c, "search", h)
}
// 评论页
func HandleDisqusFrom(c *gin.Context) {
params := strings.Split(c.Param("slug"), "|")
if len(params) != 4 || params[1] == "" {
@@ -240,26 +247,32 @@ func HandleDisqusFrom(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
}
// feed
func HandleFeed(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/feed.xml")
}
// opensearch
func HandleOpenSearch(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
}
// robots
func HandleRobots(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/robots.txt")
}
// sitemap
func HandleSitemap(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
}
// cross domain
func HandleCrossDomain(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/crossdomain.xml")
}
// favicon
func HandleFavicon(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/favicon.ico")
}
@@ -321,9 +334,9 @@ type commentsDetail struct {
Name string `json:"name"`
Url string `json:"url"`
Avatar string `json:"avatar"`
CreatedAt string `json:"createdAt"`
CreatedAtStr string `json:"createdAtStr"`
Message string `json:"message"`
IsDeleted bool `json:"isDeleted"`
}
func HandleDisqus(c *gin.Context) {
@@ -356,27 +369,35 @@ func HandleDisqus(c *gin.Context) {
Parent: v.Parent,
Url: v.Author.ProfileUrl,
Avatar: v.Author.Avatar.Cache,
CreatedAt: v.CreatedAt,
CreatedAtStr: ConvertStr(v.CreatedAt),
Message: v.Message,
IsDeleted: v.IsDeleted,
}
}
}
c.JSON(http.StatusOK, dcs)
}
// 发表评论
// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https] next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
type DisqusCreate struct {
ErrNo int `json:"errno"`
ErrMsg string `json:"errmsg"`
Data commentsDetail `json:"data"`
}
func HandleDisqusCreate(c *gin.Context) {
rep := gin.H{"errno": SUCCESS, "errmsg": ""}
defer c.JSON(http.StatusOK, rep)
resp := &DisqusCreate{}
defer c.JSON(http.StatusOK, resp)
msg := c.PostForm("message")
email := c.PostForm("author_email")
name := c.PostForm("author_name")
thread := c.PostForm("thread")
identifier := c.PostForm("identifier")
if msg == "" || email == "" || name == "" || thread == "" || identifier == "" {
rep["errno"] = FAIL
rep["errmsg"] = "参数错误"
resp.ErrNo = FAIL
resp.ErrMsg = "参数错误"
return
}
pc := &PostCreate{
@@ -389,24 +410,34 @@ func HandleDisqusCreate(c *gin.Context) {
IpAddress: c.ClientIP(),
}
id, err := PostComment(pc)
postDetail, err := PostComment(pc)
if err != nil {
logd.Error(err)
rep["errno"] = FAIL
rep["errmsg"] = "系统错误"
resp.ErrNo = FAIL
resp.ErrMsg = "系统错误"
return
}
err = PostApprove(id)
err = PostApprove(postDetail.Response.Id)
if err != nil {
logd.Error(err)
rep["errno"] = FAIL
rep["errmsg"] = "系统错误"
resp.ErrNo = FAIL
resp.ErrMsg = "系统错误"
return
}
rep["errno"] = SUCCESS
rep["data"] = gin.H{"id": id}
resp.ErrNo = SUCCESS
resp.Data = commentsDetail{
Id: postDetail.Response.Id,
Name: name,
Parent: postDetail.Response.Parent,
Url: postDetail.Response.Author.ProfileUrl,
Avatar: postDetail.Response.Author.Avatar.Cache,
CreatedAtStr: ConvertStr(postDetail.Response.CreatedAt),
Message: postDetail.Response.Message,
IsDeleted: postDetail.Response.IsDeleted,
}
}
// 渲染页面
func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
var buf bytes.Buffer
err := Tmpl.ExecuteTemplate(&buf, name, data)

41
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: bd360fa297ed66950543990f9433cdcdf13c29dd99d9a01b49027e236b2cb9da
updated: 2017-07-13T01:29:28.226895963+08:00
hash: c733fa4abeda21b59b001578b37a168bd33038d337b61198cc5fd94be8bfdf77
updated: 2017-11-24T22:55:44.759966+08:00
imports:
- name: github.com/boj/redistore
version: 4562487a4bee9a7c272b72bfaeda4917d0a47ab9
@@ -8,21 +8,21 @@ imports:
- name: github.com/eiblog/blackfriday
version: c0ec111761ae784fe31cc076f2fa0e2d2216d623
- name: github.com/eiblog/utils
version: ddfd888542f9a093000f71c3709009c1440a0789
version: d4873fe859435121012ce87b6b8407bd09f89ce0
subpackages:
- logd
- mgo
- tmpl
- uuid
- name: github.com/garyburd/redigo
version: 9f3a0116c9f72c5a56f958206a43dc881b502c37
version: 4a7d9db4333c65288dd5fc8c8de7d1f229bb09ec
subpackages:
- internal
- redis
- name: github.com/gin-gonic/autotls
version: 9261e1c52a0eb595c531ff77c06cdfb6fdb111a4
version: 8ca25fbde72bb72a00466215b94b489c71fcb815
- name: github.com/gin-gonic/contrib
version: d4fc5a96cc0d29cb0e862bb1312dd6f4fedfcaee
version: 8f08bc9b92a9734916abda03656c5f1b99ad10be
subpackages:
- sessions
- name: github.com/gin-gonic/gin
@@ -39,32 +39,29 @@ imports:
- name: github.com/gorilla/securecookie
version: e59506cc896acb7f7bf732d4fdf5e25f7ccd8983
- name: github.com/gorilla/sessions
version: 8b6b4cd75f07f7ee036eb37b8127bd40ab1efc49
version: a3acf13e802c358d65f249324d14ed24aac11370
- name: github.com/manucorporat/sse
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
- name: github.com/mattn/go-isatty
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
version: 6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c
- name: github.com/qiniu/api.v7
version: 9c12a67868f8f94d6a75dd6bb59b095db8d40d77
version: b7c7d6a2ce0aff8e5e7d14c39c3cde867efa1123
subpackages:
- api
- auth/qbox
- conf
- kodocli
- storage
- name: github.com/qiniu/x
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
subpackages:
- bytes.v7
- bytes.v7/seekable
- ctype.v7
- log.v7
- rpc.v7
- url.v7
- xlog.v7
- name: github.com/shurcooL/sanitized_anchor_name
version: 541ff5ee47f1dddf6a5281af78307d921524bcb5
version: 86672fcb3f950f35f2e675df2240550f2a50762f
- name: golang.org/x/crypto
version: dd85ac7e6a88fc6ca420478e934de5f1a42dd3c6
version: b080dc9a8c480b08e698fb1219160d598526310f
subpackages:
- acme
- acme/autocert
@@ -73,7 +70,7 @@ imports:
subpackages:
- context
- name: golang.org/x/sys
version: abf9c25f54453410d0c6668e519582a9e1115027
version: a13efeb2fd213cf4be7227992aa54519af3b2ac0
subpackages:
- unix
- name: gopkg.in/go-playground/validator.v8
@@ -86,19 +83,13 @@ imports:
- internal/sasl
- internal/scram
- name: gopkg.in/yaml.v2
version: 1be3d31502d6eabc0dd7ce5b0daab022e14a5538
- name: qiniupkg.com/api.v7
version: 9c12a67868f8f94d6a75dd6bb59b095db8d40d77
subpackages:
- kodo
- kodocli
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
- name: qiniupkg.com/x
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
version: 946c4a16076d6d98aeb78619e2bd4012357f7228
subpackages:
- bytes.v7
- log.v7
- reqid.v7
- url.v7
testImports:
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
@@ -109,6 +100,6 @@ testImports:
subpackages:
- difflib
- name: github.com/stretchr/testify
version: f390dcf405f7b83c997eac1b06768bb9f44dec18
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert

View File

@@ -1,5 +1,6 @@
package: github.com/eiblog/eiblog
import:
- package: github.com/deepzz0/logd
- package: github.com/eiblog/blackfriday
- package: github.com/eiblog/utils
subpackages:
@@ -7,19 +8,20 @@ import:
- mgo
- tmpl
- uuid
- package: github.com/gin-gonic/autotls
- package: github.com/gin-gonic/contrib
subpackages:
- sessions
- package: github.com/gin-gonic/gin
version: ~1.1.4
- package: github.com/qiniu/api.v7
subpackages:
- auth/qbox
- storage
- package: gopkg.in/mgo.v2
subpackages:
- bson
- package: gopkg.in/yaml.v2
- package: qiniupkg.com/api.v7
testImport:
- package: github.com/stretchr/testify
subpackages:
- kodo
- kodocli
- package: qiniupkg.com/x
subpackages:
- url.v7
- assert

View File

@@ -18,6 +18,7 @@ const (
FAIL
)
// 月份转换
var monthToDays = map[time.Month]int{
time.January: 31,
time.February: 28,
@@ -43,14 +44,17 @@ func EncryptPasswd(name, pass string) string {
return fmt.Sprintf("%x", h.Sum(nil))
}
// 验证密码
func VerifyPasswd(origin, name, input string) bool {
return origin == EncryptPasswd(name, input)
}
// 随机 uuid
func RandUUIDv4() string {
return uuid.NewV4().String()
}
// 读取目录
func ReadDir(dir string, filter func(name string) bool) (files []string) {
fis, err := ioutil.ReadDir(dir)
if err != nil {
@@ -69,16 +73,18 @@ func ReadDir(dir string, filter func(name string) bool) (files []string) {
return
}
// 去掉 html tag
func IgnoreHtmlTag(src string) string {
//去除所有尖括号内的HTML代码
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
// 去除所有尖括号内的HTML代码
re, _ := regexp.Compile(`<[\S\s]+?>`)
src = re.ReplaceAllString(src, "")
//去除换行符
re, _ = regexp.Compile("\\s{2,}")
// 去除换行符
re, _ = regexp.Compile(`\s+`)
return re.ReplaceAllString(src, "")
}
// 获取第一张图片
func PickFirstImage(html string) string {
re, _ := regexp.Compile(`data-src="(.*?)"`)
sli := re.FindAllStringSubmatch(html, 1)
@@ -98,15 +104,16 @@ const (
YEARS_AGO = "%d年前"
)
// 时间转换为间隔
func ConvertStr(str string) string {
t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
if err != nil {
logd.Error(err, str)
return JUST_NOW
}
now := time.Now()
now := time.Now().UTC()
y1, m1, d1 := t.Date()
y2, m2, d2 := now.UTC().Date()
y2, m2, d2 := now.Date()
h1, mi1, s1 := t.Clock()
h2, mi2, s2 := now.Clock()
if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
@@ -123,6 +130,7 @@ func ConvertStr(str string) string {
return JUST_NOW
}
// 获取天数
func dayIn(year int, m time.Month) int {
if m == time.February && isLeap(year) {
return 29
@@ -130,6 +138,7 @@ func dayIn(year int, m time.Month) int {
return monthToDays[m]
}
// 是否是闰年
func isLeap(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}

View File

@@ -105,6 +105,7 @@ func init() {
Pings = append(Pings, pr)
}
// ping
func DoPings(slug string) {
for _, p := range Pings {
go p.PingFunc(slug)

View File

@@ -18,7 +18,7 @@ func TestPingRPC(t *testing.T) {
}
pr.Params.Param = [4]rpcValue{
rpcValue{Value: Ei.BTitle},
rpcValue{Value: "https://" + setting.Conf.Mode.Domains[0]},
rpcValue{Value: "https://" + setting.Conf.Mode.Domain},
rpcValue{Value: "https://deepzz.com/post/gdb-debug.html"},
rpcValue{Value: "https://deepzz.com/rss.html"},
}

View File

@@ -4,20 +4,14 @@ import (
"errors"
"fmt"
"io"
"net/url"
"path/filepath"
"github.com/eiblog/eiblog/setting"
"qiniupkg.com/api.v7/kodo"
"qiniupkg.com/api.v7/kodocli"
url "qiniupkg.com/x/url.v7"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var qiniu_cfg = &kodo.Config{
AccessKey: setting.Conf.Kodo.AccessKey,
SecretKey: setting.Conf.Kodo.SecretKey,
Scheme: "https",
}
type bucket struct {
name string
domain string
@@ -30,71 +24,79 @@ type PutRet struct {
Key string `json:"key"`
}
// 进度条
func onProgress(fsize, uploaded int64) {
d := int(float64(uploaded) / float64(fsize) * 100)
if fsize == uploaded {
fmt.Printf("\rUpload completed! ")
fmt.Printf("\rUpload completed! \n")
} else {
fmt.Printf("\r%02d%% uploaded ", int(d))
}
}
// 上传文件
func FileUpload(name string, size int64, data io.Reader) (string, error) {
if setting.Conf.Kodo.AccessKey == "" || setting.Conf.Kodo.SecretKey == "" {
if setting.Conf.Qiniu.AccessKey == "" || setting.Conf.Qiniu.SecretKey == "" {
return "", errors.New("qiniu config error")
}
// 创建一个client
c := kodo.New(0, qiniu_cfg)
// 设置上传的策略
policy := &kodo.PutPolicy{
Scope: setting.Conf.Kodo.Name,
Expires: 3600,
InsertOnly: 1,
}
// 生成一个上传token
token := c.MakeUptoken(policy)
// 构建一个uploader
zone := 0
uploader := kodocli.NewUploader(zone, nil)
key := getKey(name)
if key == "" {
return "", errors.New("不支持的文件类型")
}
var ret PutRet
var extra = kodocli.PutExtra{OnProgress: onProgress}
err := uploader.Put(nil, &ret, token, key, data, size, &extra)
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
// 设置上传的策略
putPolicy := &storage.PutPolicy{
Scope: setting.Conf.Qiniu.Bucket,
Expires: 3600,
InsertOnly: 1,
}
// 上传token
upToken := putPolicy.UploadToken(mac)
// 上传配置
cfg := &storage.Config{
Zone: &storage.ZoneHuadong,
UseHTTPS: true,
}
// uploader
uploader := storage.NewFormUploader(cfg)
ret := new(storage.PutRet)
putExtra := &storage.PutExtra{OnProgress: onProgress}
err := uploader.Put(nil, ret, upToken, key, data, size, putExtra)
if err != nil {
return "", err
}
url := "https://" + setting.Conf.Kodo.Domain + "/" + url.Escape(key)
url := "https://" + setting.Conf.Qiniu.Domain + "/" + url.QueryEscape(key)
return url, nil
}
// 删除文件
func FileDelete(name string) error {
// new一个Bucket管理对象
c := kodo.New(0, qiniu_cfg)
p := c.Bucket(setting.Conf.Kodo.Name)
key := getKey(name)
if key == "" {
return errors.New("不支持的文件类型")
}
// 调用Delete方法删除文件
err := p.Delete(nil, key)
// 打印返回值以及出错信息
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
// 上传配置
cfg := &storage.Config{
Zone: &storage.ZoneHuadong,
UseHTTPS: true,
}
// manager
bucketManager := storage.NewBucketManager(mac, cfg)
// Delete
err := bucketManager.Delete(setting.Conf.Qiniu.Bucket, key)
if err != nil {
return err
}
return nil
}
// 修复路径
func getKey(name string) string {
ext := filepath.Ext(name)
var key string

View File

@@ -7,11 +7,13 @@ import (
)
func TestUpload(t *testing.T) {
path := "/Users/chen/Desktop/png-MicroService-by-StuQ.png"
path := "qiniu.go"
file, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()
info, _ := file.Stat()
url, err := FileUpload(info.Name(), info.Size(), file)
if err != nil {

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"html/template"
"text/template"
"time"
"github.com/eiblog/eiblog/setting"
@@ -20,10 +20,12 @@ var (
)
func init() {
// 运行模式
if setting.Conf.RunMode == setting.PROD {
gin.SetMode(gin.ReleaseMode)
logd.SetLevel(logd.Lerror)
}
router = gin.Default()
store := sessions.NewCookieStore([]byte("eiblog321"))
store.Options(sessions.Options{
@@ -90,6 +92,7 @@ func init() {
}
}
// 开始运行
func Run() {
var (
endRunning = make(chan bool, 1)

View File

@@ -51,8 +51,8 @@ type Config struct {
V string
T string
}
Kodo struct { // 七牛CDN
Name string
Qiniu struct { // 七牛CDN
Bucket string
Domain string
AccessKey string
SecretKey string

View File

@@ -9,6 +9,7 @@ go:
- 1.6
- 1.7
- 1.8
- 1.9
- tip
script:

View File

@@ -21,6 +21,7 @@ Documentation
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples)
Installation
------------

View File

@@ -31,7 +31,6 @@ import (
// conn is the low-level implementation of Conn
type conn struct {
// Shared
mu sync.Mutex
pending int
@@ -73,10 +72,11 @@ type DialOption struct {
type dialOptions struct {
readTimeout time.Duration
writeTimeout time.Duration
dialer *net.Dialer
dial func(network, addr string) (net.Conn, error)
db int
password string
dialTLS bool
useTLS bool
skipVerify bool
tlsConfig *tls.Config
}
@@ -95,17 +95,27 @@ func DialWriteTimeout(d time.Duration) DialOption {
}}
}
// DialConnectTimeout specifies the timeout for connecting to the Redis server.
// DialConnectTimeout specifies the timeout for connecting to the Redis server when
// no DialNetDial option is specified.
func DialConnectTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
dialer := net.Dialer{Timeout: d}
do.dial = dialer.Dial
do.dialer.Timeout = d
}}
}
// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
// when no DialNetDial option is specified.
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
func DialKeepAlive(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
do.dialer.KeepAlive = d
}}
}
// DialNetDial specifies a custom dial function for creating TCP
// connections. If this option is left out, then net.Dial is
// used. DialNetDial overrides DialConnectTimeout.
// connections, otherwise a net.Dialer customized via the other options is used.
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
return DialOption{func(do *dialOptions) {
do.dial = dial
@@ -135,30 +145,43 @@ func DialTLSConfig(c *tls.Config) DialOption {
}}
}
// DialTLSSkipVerify to disable server name verification when connecting
// over TLS. Has no effect when not dialing a TLS connection.
// DialTLSSkipVerify disables server name verification when connecting over
// TLS. Has no effect when not dialing a TLS connection.
func DialTLSSkipVerify(skip bool) DialOption {
return DialOption{func(do *dialOptions) {
do.skipVerify = skip
}}
}
// DialUseTLS specifies whether TLS should be used when connecting to the
// server. This option is ignore by DialURL.
func DialUseTLS(useTLS bool) DialOption {
return DialOption{func(do *dialOptions) {
do.useTLS = useTLS
}}
}
// Dial connects to the Redis server at the given network and
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{
dial: net.Dial,
dialer: &net.Dialer{
KeepAlive: time.Minute * 5,
},
}
for _, option := range options {
option.f(&do)
}
if do.dial == nil {
do.dial = do.dialer.Dial
}
netConn, err := do.dial(network, address)
if err != nil {
return nil, err
}
if do.dialTLS {
if do.useTLS {
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
if tlsConfig.ServerName == "" {
host, _, err := net.SplitHostPort(address)
@@ -202,10 +225,6 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
return c, nil
}
func dialTLS(do *dialOptions) {
do.dialTLS = true
}
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
// DialURL connects to a Redis server at the given URL using the Redis
@@ -257,9 +276,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
}
if u.Scheme == "rediss" {
options = append([]DialOption{{dialTLS}}, options...)
}
options = append(options, DialUseTLS(u.Scheme == "rediss"))
return Dial("tcp", address, options...)
}
@@ -344,43 +361,55 @@ func (c *conn) writeFloat64(n float64) error {
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
}
func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
func (c *conn) writeCommand(cmd string, args []interface{}) error {
c.writeLen('*', 1+len(args))
err = c.writeString(cmd)
if err := c.writeString(cmd); err != nil {
return err
}
for _, arg := range args {
if err != nil {
break
}
switch arg := arg.(type) {
case string:
err = c.writeString(arg)
case []byte:
err = c.writeBytes(arg)
case int:
err = c.writeInt64(int64(arg))
case int64:
err = c.writeInt64(arg)
case float64:
err = c.writeFloat64(arg)
case bool:
if arg {
err = c.writeString("1")
} else {
err = c.writeString("0")
}
case nil:
err = c.writeString("")
case Argument:
var buf bytes.Buffer
fmt.Fprint(&buf, arg.RedisArg())
err = c.writeBytes(buf.Bytes())
default:
var buf bytes.Buffer
fmt.Fprint(&buf, arg)
err = c.writeBytes(buf.Bytes())
if err := c.writeArg(arg, true); err != nil {
return err
}
}
return err
return nil
}
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
switch arg := arg.(type) {
case string:
return c.writeString(arg)
case []byte:
return c.writeBytes(arg)
case int:
return c.writeInt64(int64(arg))
case int64:
return c.writeInt64(arg)
case float64:
return c.writeFloat64(arg)
case bool:
if arg {
return c.writeString("1")
} else {
return c.writeString("0")
}
case nil:
return c.writeString("")
case Argument:
if argumentTypeOK {
return c.writeArg(arg.RedisArg(), false)
}
// See comment in default clause below.
var buf bytes.Buffer
fmt.Fprint(&buf, arg)
return c.writeBytes(buf.Bytes())
default:
// This default clause is intended to handle builtin numeric types.
// The function should return an error for other types, but this is not
// done for compatibility with previous versions of the package.
var buf bytes.Buffer
fmt.Fprint(&buf, arg)
return c.writeBytes(buf.Bytes())
}
}
type protocolError string

View File

@@ -16,6 +16,9 @@ package redis_test
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"math"
"net"
@@ -40,9 +43,34 @@ func (*testConn) SetDeadline(t time.Time) error { return nil }
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
func dialTestConn(r io.Reader, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(net, addr string) (net.Conn, error) {
return &testConn{Reader: r, Writer: w}, nil
func dialTestConn(r string, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
return &testConn{Reader: strings.NewReader(r), Writer: w}, nil
})
}
type tlsTestConn struct {
net.Conn
done chan struct{}
}
func (c *tlsTestConn) Close() error {
c.Conn.Close()
<-c.done
return nil
}
func dialTestConnTLS(r string, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
client, server := net.Pipe()
tlsServer := tls.Server(server, &serverTLSConfig)
go io.Copy(tlsServer, strings.NewReader(r))
done := make(chan struct{})
go func() {
io.Copy(w, tlsServer)
close(done)
}()
return &tlsTestConn{Conn: client, done: done}, nil
})
}
@@ -54,6 +82,10 @@ func (t durationArg) RedisArg() interface{} {
return t.Seconds()
}
type recursiveArg int
func (v recursiveArg) RedisArg() interface{} { return v }
var writeTests = []struct {
args []interface{}
expected string
@@ -94,6 +126,10 @@ var writeTests = []struct {
[]interface{}{"SET", "key", durationArg{time.Minute}},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n",
},
{
[]interface{}{"SET", "key", recursiveArg(123)},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n",
},
{
[]interface{}{"ECHO", true, false},
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
@@ -103,7 +139,7 @@ var writeTests = []struct {
func TestWrite(t *testing.T) {
for _, tt := range writeTests {
var buf bytes.Buffer
c, _ := redis.Dial("", "", dialTestConn(nil, &buf))
c, _ := redis.Dial("", "", dialTestConn("", &buf))
err := c.Send(tt.args[0].(string), tt.args[1:]...)
if err != nil {
t.Errorf("Send(%v) returned error %v", tt.args, err)
@@ -202,7 +238,7 @@ var readTests = []struct {
func TestRead(t *testing.T) {
for _, tt := range readTests {
c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil))
c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil))
actual, err := c.Receive()
if tt.expected == errorSentinel {
if err == nil {
@@ -514,41 +550,85 @@ func TestDialURLHost(t *testing.T) {
}
}
func TestDialURLPassword(t *testing.T) {
var buf bytes.Buffer
_, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf))
if err != nil {
t.Error("dial error:", err)
var dialURLTests = []struct {
description string
url string
r string
w string
}{
{"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
{"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"},
{"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"},
{"no database", "redis://localhost/", "+OK\r\n", ""},
}
func TestDialURL(t *testing.T) {
for _, tt := range dialURLTests {
var buf bytes.Buffer
// UseTLS should be ignored in all of these tests.
_, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true))
if err != nil {
t.Errorf("%s dial error: %v", tt.description, err)
continue
}
if w := buf.String(); w != tt.w {
t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w)
}
}
expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"
}
func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {
resp, err := c.Do("PING")
if err != nil {
t.Fatal("ping error:", err)
}
// Close connection to ensure that writes to buf are complete.
c.Close()
expected := "*1\r\n$4\r\nPING\r\n"
actual := buf.String()
if actual != expected {
t.Errorf("commands = %q, want %q", actual, expected)
}
if resp != "PONG" {
t.Errorf("resp = %v, want %v", resp, "PONG")
}
}
func TestDialURLDatabase(t *testing.T) {
var buf3 bytes.Buffer
_, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3))
if err3 != nil {
t.Error("dial error:", err3)
const pingResponse = "+PONG\r\n"
func TestDialURLTLS(t *testing.T) {
var buf bytes.Buffer
c, err := redis.DialURL("rediss://example.com/",
redis.DialTLSConfig(&clientTLSConfig),
dialTestConnTLS(pingResponse, &buf))
if err != nil {
t.Fatal("dial error:", err)
}
expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"
actual3 := buf3.String()
if actual3 != expected3 {
t.Errorf("commands = %q, want %q", actual3, expected3)
checkPingPong(t, &buf, c)
}
func TestDialUseTLS(t *testing.T) {
var buf bytes.Buffer
c, err := redis.Dial("tcp", "example.com:6379",
redis.DialTLSConfig(&clientTLSConfig),
dialTestConnTLS(pingResponse, &buf),
redis.DialUseTLS(true))
if err != nil {
t.Fatal("dial error:", err)
}
// empty DB means 0
var buf0 bytes.Buffer
_, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0))
if err0 != nil {
t.Error("dial error:", err0)
}
expected0 := ""
actual0 := buf0.String()
if actual0 != expected0 {
t.Errorf("commands = %q, want %q", actual0, expected0)
checkPingPong(t, &buf, c)
}
func TestDialTLSSKipVerify(t *testing.T) {
var buf bytes.Buffer
c, err := redis.Dial("tcp", "example.com:6379",
dialTestConnTLS(pingResponse, &buf),
redis.DialTLSSkipVerify(true),
redis.DialUseTLS(true))
if err != nil {
t.Fatal("dial error:", err)
}
checkPingPong(t, &buf, c)
}
// Connect to local instance of Redis running on the default port.
@@ -680,3 +760,64 @@ func BenchmarkDoPing(b *testing.B) {
}
}
}
var clientTLSConfig, serverTLSConfig tls.Config
func init() {
// The certificate and key for testing TLS dial options was created
// using the command
//
// go run GOROOT/src/crypto/tls/generate_cert.go \
// --rsa-bits 1024 \
// --host 127.0.0.1,::1,example.com --ca \
// --start-date "Jan 1 00:00:00 1970" \
// --duration=1000000h
//
// where GOROOT is the value of GOROOT reported by go env.
localhostCert := []byte(`
-----BEGIN CERTIFICATE-----
MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+
LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+
JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+
ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9
6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt
rrKgNsltzMk=
-----END CERTIFICATE-----`)
localhostKey := []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi
bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l
SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB
AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB
Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y
HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm
KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw
KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa
m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0
pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci
Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH
diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc
Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
-----END RSA PRIVATE KEY-----`)
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
panic(fmt.Sprintf("error creating key pair: %v", err))
}
serverTLSConfig.Certificates = []tls.Certificate{cert}
certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0])
if err != nil {
panic(fmt.Sprintf("error parsing x509 certificate: %v", err))
}
clientTLSConfig.RootCAs = x509.NewCertPool()
clientTLSConfig.RootCAs.AddCert(certificate)
}

View File

@@ -38,7 +38,7 @@
//
// n, err := conn.Do("APPEND", "key", "value")
//
// The Do method converts command arguments to binary strings for transmission
// The Do method converts command arguments to bulk strings for transmission
// to the server as follows:
//
// Go Type Conversion
@@ -48,7 +48,7 @@
// float64 strconv.FormatFloat(v, 'g', -1, 64)
// bool true -> "1", false -> "0"
// nil ""
// all other types fmt.Print(v)
// all other types fmt.Fprint(w, v)
//
// Redis command reply types are represented using the following Go types:
//

View File

@@ -115,7 +115,6 @@ var (
// }
//
type Pool struct {
// Dial is an application supplied function for creating and configuring a
// connection.
//
@@ -181,6 +180,26 @@ func (p *Pool) Get() Conn {
return &pooledConnection{p: p, c: c}
}
// PoolStats contains pool statistics.
type PoolStats struct {
// ActiveCount is the number of connections in the pool. The count includes idle connections and connections in use.
ActiveCount int
// IdleCount is the number of idle connections in the pool.
IdleCount int
}
// Stats returns pool's statistics.
func (p *Pool) Stats() PoolStats {
p.mu.Lock()
stats := PoolStats{
ActiveCount: p.active,
IdleCount: p.idle.Len(),
}
p.mu.Unlock()
return stats
}
// ActiveCount returns the number of connections in the pool. The count includes idle connections and connections in use.
func (p *Pool) ActiveCount() int {
p.mu.Lock()
@@ -249,7 +268,6 @@ func (p *Pool) get() (Conn, error) {
}
for {
// Get idle connection.
for i, n := 0, p.idle.Len(); i < n; i++ {

View File

@@ -92,12 +92,15 @@ func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse in
d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
}
if active := p.ActiveCount(); active != open {
d.t.Errorf("%s: active=%d, want %d", message, active, open)
stats := p.Stats()
if stats.ActiveCount != open {
d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open)
}
if idle := p.IdleCount(); idle != open-inuse {
d.t.Errorf("%s: idle=%d, want %d", message, idle, open-inuse)
if stats.IdleCount != open-inuse {
d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse)
}
d.mu.Unlock()
}

View File

@@ -18,7 +18,6 @@ import "errors"
// Subscription represents a subscribe or unsubscribe notification.
type Subscription struct {
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
Kind string
@@ -31,7 +30,6 @@ type Subscription struct {
// Message represents a message notification.
type Message struct {
// The originating channel.
Channel string
@@ -41,7 +39,6 @@ type Message struct {
// PMessage represents a pmessage notification.
type PMessage struct {
// The matched pattern.
Pattern string
@@ -94,6 +91,9 @@ func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
}
// Ping sends a PING to the server with the specified data.
//
// The connection must be subscribed to at least one channel or pattern when
// calling this method.
func (c PubSubConn) Ping(data string) error {
c.Conn.Send("PING", data)
return c.Conn.Flush()

View File

@@ -40,18 +40,20 @@ type Conn interface {
Receive() (reply interface{}, err error)
}
// Argument is implemented by types which want to control how their value is
// interpreted when used as an argument to a redis command.
// Argument is the interface implemented by an object which wants to control how
// the object is converted to Redis bulk strings.
type Argument interface {
// RedisArg returns the interface that represents the value to be used
// in redis commands.
// RedisArg returns a value to be encoded as a bulk string per the
// conversions listed in the section 'Executing Commands'.
// Implementations should typically return a []byte or string.
RedisArg() interface{}
}
// Scanner is implemented by types which want to control how their value is
// interpreted when read from redis.
// Scanner is implemented by an object which wants to control its value is
// interpreted when read from Redis.
type Scanner interface {
// RedisScan assigns a value from a redis value.
// RedisScan assigns a value from a Redis value. The argument src is one of
// the reply types listed in the section `Executing Commands`.
//
// An error should be returned if the value cannot be stored without
// loss of information.

View File

@@ -243,34 +243,67 @@ func Values(reply interface{}, err error) ([]interface{}, error) {
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
}
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
if err != nil {
return err
}
switch reply := reply.(type) {
case []interface{}:
makeSlice(len(reply))
for i := range reply {
if reply[i] == nil {
continue
}
if err := assign(i, reply[i]); err != nil {
return err
}
}
return nil
case nil:
return ErrNil
case Error:
return reply
}
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
}
// Float64s is a helper that converts an array command reply to a []float64. If
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
// converted to 0 in the output slice. Floats64 returns an error if an array
// item is not a bulk string or nil.
func Float64s(reply interface{}, err error) ([]float64, error) {
var result []float64
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
p, ok := v.([]byte)
if !ok {
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
}
f, err := strconv.ParseFloat(string(p), 64)
result[i] = f
return err
})
return result, err
}
// Strings is a helper that converts an array command reply to a []string. If
// err is not equal to nil, then Strings returns nil, err. Nil array items are
// converted to "" in the output slice. Strings returns an error if an array
// item is not a bulk string or nil.
func Strings(reply interface{}, err error) ([]string, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []interface{}:
result := make([]string, len(reply))
for i := range reply {
if reply[i] == nil {
continue
}
p, ok := reply[i].([]byte)
if !ok {
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
}
result[i] = string(p)
var result []string
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case string:
result[i] = v
return nil
case []byte:
result[i] = string(v)
return nil
default:
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
}
return result, nil
case nil:
return nil, ErrNil
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
})
return result, err
}
// ByteSlices is a helper that converts an array command reply to a [][]byte.
@@ -278,43 +311,64 @@ func Strings(reply interface{}, err error) ([]string, error) {
// items are stay nil. ByteSlices returns an error if an array item is not a
// bulk string or nil.
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []interface{}:
result := make([][]byte, len(reply))
for i := range reply {
if reply[i] == nil {
continue
}
p, ok := reply[i].([]byte)
if !ok {
return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i])
}
result[i] = p
var result [][]byte
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
p, ok := v.([]byte)
if !ok {
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
}
return result, nil
case nil:
return nil, ErrNil
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
result[i] = p
return nil
})
return result, err
}
// Ints is a helper that converts an array command reply to a []int. If
// err is not equal to nil, then Ints returns nil, err.
// Int64s is a helper that converts an array command reply to a []int64.
// If err is not equal to nil, then Int64s returns nil, err. Nil array
// items are stay nil. Int64s returns an error if an array item is not a
// bulk string or nil.
func Int64s(reply interface{}, err error) ([]int64, error) {
var result []int64
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case int64:
result[i] = v
return nil
case []byte:
n, err := strconv.ParseInt(string(v), 10, 64)
result[i] = n
return err
default:
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
}
})
return result, err
}
// Ints is a helper that converts an array command reply to a []in.
// If err is not equal to nil, then Ints returns nil, err. Nil array
// items are stay nil. Ints returns an error if an array item is not a
// bulk string or nil.
func Ints(reply interface{}, err error) ([]int, error) {
var ints []int
values, err := Values(reply, err)
if err != nil {
return ints, err
}
if err := ScanSlice(values, &ints); err != nil {
return ints, err
}
return ints, nil
var result []int
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case int64:
n := int(v)
if int64(n) != v {
return strconv.ErrRange
}
result[i] = n
return nil
case []byte:
n, err := strconv.Atoi(string(v))
result[i] = n
return err
default:
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
}
})
return result, err
}
// StringMap is a helper that converts an array of strings (alternating key, value)

View File

@@ -37,24 +37,44 @@ var replyTests = []struct {
expected valueError
}{
{
"ints([v1, v2])",
"ints([[]byte, []byte])",
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
ve([]int{4, 5}, nil),
},
{
"ints([nt64, int64])",
ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),
ve([]int{4, 5}, nil),
},
{
"ints([[]byte, nil, []byte])",
ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)),
ve([]int{4, 0, 5}, nil),
},
{
"ints(nil)",
ve(redis.Ints(nil, nil)),
ve([]int(nil), redis.ErrNil),
},
{
"strings([v1, v2])",
"int64s([[]byte, []byte])",
ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)),
ve([]int64{4, 5}, nil),
},
{
"int64s([int64, int64])",
ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),
ve([]int64{4, 5}, nil),
},
{
"strings([[]byte, []bytev2])",
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
ve([]string{"v1", "v2"}, nil),
},
{
"strings(nil)",
ve(redis.Strings(nil, nil)),
ve([]string(nil), redis.ErrNil),
"strings([string, string])",
ve(redis.Strings([]interface{}{"v1", "v2"}, nil)),
ve([]string{"v1", "v2"}, nil),
},
{
"byteslices([v1, v2])",
@@ -62,9 +82,9 @@ var replyTests = []struct {
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
},
{
"byteslices(nil)",
ve(redis.ByteSlices(nil, nil)),
ve([][]byte(nil), redis.ErrNil),
"float64s([v1, v2])",
ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)),
ve([]float64{1.234, 5.678}, nil),
},
{
"values([v1, v2])",

View File

@@ -38,6 +38,7 @@ var (
ErrNegativeInt = errNegativeInt
serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
serverAddress = flag.String("redis-address", "127.0.0.1", "The address of the server")
serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
serverLog = ioutil.Discard
@@ -96,7 +97,8 @@ func (s *Server) watch(r io.Reader, ready chan error) {
text = scn.Text()
fmt.Fprintf(serverLog, "%s\n", text)
if !listening {
if strings.Contains(text, "The server is now ready to accept connections on port") {
if strings.Contains(text, " * Ready to accept connections") ||
strings.Contains(text, " * The server is now ready to accept connections on port") {
listening = true
ready <- nil
}
@@ -135,6 +137,7 @@ func startDefaultServer() error {
defaultServer, defaultServerErr = NewServer(
"default",
"--port", strconv.Itoa(*serverBasePort),
"--bind", *serverAddress,
"--save", "",
"--appendonly", "no")
return defaultServerErr
@@ -146,7 +149,7 @@ func DialDefaultServer() (Conn, error) {
if err := startDefaultServer(); err != nil {
return nil, err
}
c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
c, err := Dial("tcp", fmt.Sprintf("%v:%d", *serverAddress, *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
if err != nil {
return nil, err
}

View File

@@ -4,6 +4,7 @@ go:
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- master
git:

View File

@@ -1,4 +1,4 @@
// Support Let's Encrypt for a Go server application.
// Package autotls support Let's Encrypt for a Go server application.
//
// package main
//

View File

@@ -34,3 +34,4 @@ Each author is responsible of maintaining his own code, although if you submit a
+ [gin-oauth2](https://github.com/zalando/gin-oauth2) - for working with OAuth2
+ [static](https://github.com/hyperboloide/static) An alternative static assets handler for the gin framework.
+ [xss-mw](https://github.com/dvwright/xss-mw) - XssMw is a middleware designed to "auto remove XSS" from user submitted input
+ [gin-helmet](https://github.com/danielkov/gin-helmet) - Collection of simple security middleware.

View File

@@ -5,9 +5,9 @@ import (
"os"
"time"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/contrib/ginrus"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func main() {

View File

@@ -6,8 +6,8 @@ package ginrus
import (
"time"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.

View File

@@ -44,14 +44,14 @@ Let's start with an example that shows the sessions API in a nutshell:
First we initialize a session store calling `NewCookieStore()` and passing a
secret key used to authenticate the session. Inside the handler, we call
`store.Get()` to retrieve an existing session or a new one. Then we set some
session values in session.Values, which is a `map[interface{}]interface{}`.
`store.Get()` to retrieve an existing session or create a new one. Then we set
some session values in session.Values, which is a `map[interface{}]interface{}`.
And finally we call `session.Save()` to save the session in the response.
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
with
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
as or else you will leak memory! An easy way to do this is to wrap the top-level
or else you will leak memory! An easy way to do this is to wrap the top-level
mux when calling http.ListenAndServe:
```go

View File

@@ -59,6 +59,10 @@ func TestGH2MaxLength(t *testing.T) {
w := httptest.NewRecorder()
session, err := store.New(req, "my session")
if err != nil {
t.Fatal("failed to create session", err)
}
session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2))
err = session.Save(req, w)
if err == nil {

View File

@@ -2,6 +2,10 @@ language: go
go:
- tip
os:
- linux
- osx
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover

View File

@@ -1,5 +1,5 @@
// +build linux
// +build !appengine
// +build !appengine,!ppc64,!ppc64le
package isatty

View File

@@ -0,0 +1,19 @@
// +build linux
// +build ppc64 ppc64le
package isatty
import (
"unsafe"
syscall "golang.org/x/sys/unix"
)
const ioctlReadTermios = syscall.TCGETS
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}

View File

@@ -3,7 +3,7 @@
package isatty
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false

View File

@@ -6,14 +6,8 @@ go:
env:
global:
- QINIU_KODO_TEST=1
- QINIU_ACCESS_KEY="QWYn5TFQsLLU1pL5MFEmX3s5DmHdUThav9WyOWOm"
- QINIU_SECRET_KEY="Bxckh6FA-Fbs9Yt3i3cbKVK22UPBmAOHJcL95pGz"
- QINIU_TEST_BUCKET="gosdk"
- QINIU_TEST_DOMAIN="gosdk.qiniudn.com"
install:
- export QINIU_SRC=$HOME/gopath/src
- mkdir -p $QINIU_SRC/github.com/qiniu
- export TRAVIS_BUILD_DIR=$QINIU_SRC/github.com/qiniu/api.v7
- cd $TRAVIS_BUILD_DIR
- go get github.com/qiniu/x

View File

@@ -1,4 +1,21 @@
#Changelog
# Changelog
# 7.2.3 (2017-09-25)
* 增加Qiniu的鉴权方式
* 删除prefop域名检测功能
* 暴露分片上传的接口以支持复杂的自定义业务逻辑
## 7.2.2 (2017-09-19)
* 为表单上传和分片上传增加代理支持
* 优化表单上传的crc32计算方式减少内存消耗
* 增加网页图片的Base64上传方式
## 7.2.1 (2017-08-20)
* 设置FormUpload默认支持crc32校验
* ResumeUpload从API层面即支持crc32校验
## 7.2.0 (2017-07-28)
* 重构了v7 SDK的所有代码
## 7.1.0 (2016-6-22)

5
vendor/github.com/qiniu/api.v7/Makefile generated vendored Normal file
View File

@@ -0,0 +1,5 @@
test:
go test -v ./auth/...
go test -v ./conf/...
go test -v ./cdn/...
go test -v ./storage/...

View File

@@ -1,7 +1,7 @@
github.com/qiniu/api.v7 (Qiniu Go SDK v7.x)
===============
[![Build Status](https://travis-ci.org/qiniu/api.v7.svg?branch=develop)](https://travis-ci.org/qiniu/api.v7) [![GoDoc](https://godoc.org/github.com/qiniu/api.v7?status.svg)](https://godoc.org/github.com/qiniu/api.v7)
[![Build Status](https://travis-ci.org/qiniu/api.v7.svg?branch=master)](https://travis-ci.org/qiniu/api.v7) [![GoDoc](https://godoc.org/github.com/qiniu/api.v7?status.svg)](https://godoc.org/github.com/qiniu/api.v7)
[![Qiniu Logo](http://open.qiniudn.com/logo.png)](http://qiniu.com/)
@@ -10,16 +10,11 @@ github.com/qiniu/api.v7 (Qiniu Go SDK v7.x)
```
go get -u github.com/qiniu/api.v7
```
如果碰到golang.org/x/net/context 不能下载,请把 http://devtools.qiniu.com/golang.org.x.net.context.tgz 下载到代码目录下并解压到src目录或者直接下载全部 http://devtools.qiniu.com/qiniu_api_v7.tgz。
# 使用文档
# 文档
## KODO Blob Storage (七牛对象存储)
[七牛SDK文档站](https://developer.qiniu.com/kodo/sdk/1238/go) 或者 [项目WIKI](https://github.com/qiniu/api.v7/wiki)
* [github.com/qiniu/api.v7/kodo](http://godoc.org/github.com/qiniu/api.v7/kodo)
* [github.com/qiniu/api.v7/kodocli](http://godoc.org/github.com/qiniu/api.v7/kodocli)
如果您是在业务服务器(服务器端)调用七牛云存储的服务,请使用 [github.com/qiniu/api.v7/kodo](http://godoc.org/github.com/qiniu/api.v7/kodo)。
如果您是在客户端比如Android/iOS 设备、Windows/Mac/Linux 桌面环境)调用七牛云存储的服务,请使用 [github.com/qiniu/api.v7/kodocli](http://godoc.org/github.com/qiniu/api.v7/kodocli)。注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,会影响您的数据安全。
# 示例
[参考代码](https://github.com/qiniu/api.v7/tree/master/examples)

View File

@@ -1,99 +0,0 @@
package api
import (
. "context"
"sync"
"time"
"github.com/qiniu/x/rpc.v7"
)
const DefaultApiHost string = "http://uc.qbox.me"
var (
bucketMu sync.RWMutex
bucketCache = make(map[string]BucketInfo)
)
type Client struct {
*rpc.Client
host string
scheme string
}
func NewClient(host string, scheme string) *Client {
if host == "" {
host = DefaultApiHost
}
client := rpc.DefaultClient
return &Client{&client, host, scheme}
}
type BucketInfo struct {
UpHosts []string `json:"up"`
IoHost string `json:"io"`
Expire int64 `json:"expire"` // expire == 0 means no expire
}
func (p *Client) GetBucketInfo(ak, bucketName string) (ret BucketInfo, err error) {
key := ak + ":" + bucketName + ":" + p.scheme
bucketMu.RLock()
bucketInfo, ok := bucketCache[key]
bucketMu.RUnlock()
if ok && (bucketInfo.Expire == 0 || bucketInfo.Expire > time.Now().Unix()) {
ret = bucketInfo
return
}
hostInfo, err := p.bucketHosts(ak, bucketName)
if err != nil {
return
}
ret.Expire = time.Now().Unix() + hostInfo.Ttl
if p.scheme == "https" {
ret.UpHosts = hostInfo.Https["up"]
if iohosts, ok := hostInfo.Https["io"]; ok && len(iohosts) != 0 {
ret.IoHost = iohosts[0]
}
} else {
ret.UpHosts = hostInfo.Http["up"]
if iohosts, ok := hostInfo.Http["io"]; ok && len(iohosts) != 0 {
ret.IoHost = iohosts[0]
}
}
bucketMu.Lock()
bucketCache[key] = ret
bucketMu.Unlock()
return
}
type HostsInfo struct {
Ttl int64 `json:"ttl"`
Http map[string][]string `json:"http"`
Https map[string][]string `json:"https"`
}
/*
请求包:
GET /v1/query?ak=<ak>&&bucket=<bucket>
返回包:
200 OK {
"ttl": <ttl>, // 有效时间
"http": {
"up": [],
"io": [], // 当bucket为global时我们不需要iohost, io缺省
},
"https": {
"up": [],
"io": [], // 当bucket为global时我们不需要iohost, io缺省
}
}
*/
func (p *Client) bucketHosts(ak, bucket string) (info HostsInfo, err error) {
ctx := Background()
err = p.CallWithForm(ctx, &info, "GET", p.host+"/v1/query", map[string][]string{
"ak": []string{ak},
"bucket": []string{bucket},
})
return
}

2
vendor/github.com/qiniu/api.v7/auth/qbox/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// qbox 包提供了该SDK需要的相关鉴权方法
package qbox

View File

@@ -4,61 +4,45 @@ import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"net/http"
. "github.com/qiniu/api.v7/conf"
"github.com/qiniu/x/bytes.v7/seekable"
)
// ----------------------------------------------------------
// Mac 七牛AK/SK的对象AK/SK可以从 https://portal.qiniu.com/user/key 获取。
type Mac struct {
AccessKey string
SecretKey []byte
}
// NewMac 构建一个新的拥有AK/SK的对象
func NewMac(accessKey, secretKey string) (mac *Mac) {
if accessKey == "" {
accessKey, secretKey = ACCESS_KEY, SECRET_KEY
}
return &Mac{accessKey, []byte(secretKey)}
}
// Sign 对数据进行签名,一般用于私有空间下载用途
func (mac *Mac) Sign(data []byte) (token string) {
h := hmac.New(sha1.New, mac.SecretKey)
h.Write(data)
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
return mac.AccessKey + ":" + sign[:27]
return fmt.Sprintf("%s:%s", mac.AccessKey, sign)
}
// SignWithData 对数据进行签名,一般用于上传凭证的生成用途
func (mac *Mac) SignWithData(b []byte) (token string) {
blen := base64.URLEncoding.EncodedLen(len(b))
key := mac.AccessKey
nkey := len(key)
ret := make([]byte, nkey+30+blen)
base64.URLEncoding.Encode(ret[nkey+30:], b)
encodedData := base64.URLEncoding.EncodeToString(b)
h := hmac.New(sha1.New, mac.SecretKey)
h.Write(ret[nkey+30:])
h.Write([]byte(encodedData))
digest := h.Sum(nil)
copy(ret, key)
ret[nkey] = ':'
base64.URLEncoding.Encode(ret[nkey+1:], digest)
ret[nkey+29] = ':'
return string(ret)
sign := base64.URLEncoding.EncodeToString(digest)
return fmt.Sprintf("%s:%s:%s", mac.AccessKey, sign, encodedData)
}
func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err error) {
// SignRequest 对数据进行签名,一般用于管理凭证的生成
func (mac *Mac) SignRequest(req *http.Request) (token string, err error) {
h := hmac.New(sha1.New, mac.SecretKey)
u := req.URL
@@ -68,7 +52,7 @@ func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err
}
io.WriteString(h, data+"\n")
if incbody {
if incBody(req) {
s2, err2 := seekable.New(req)
if err2 != nil {
return "", err2
@@ -77,18 +61,74 @@ func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err
}
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
token = mac.AccessKey + ":" + sign
token = fmt.Sprintf("%s:%s", mac.AccessKey, sign)
return
}
func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
// SignRequestV2 对数据进行签名,一般用于高级管理凭证的生成
func (mac *Mac) SignRequestV2(req *http.Request) (token string, err error) {
h := hmac.New(sha1.New, mac.SecretKey)
u := req.URL
//write method path?query
io.WriteString(h, fmt.Sprintf("%s %s", req.Method, u.Path))
if u.RawQuery != "" {
io.WriteString(h, "?")
io.WriteString(h, u.RawQuery)
}
//write host and posrt
io.WriteString(h, "\nHost: ")
io.WriteString(h, req.Host)
if req.URL.Port() != "" {
io.WriteString(h, ":")
io.WriteString(h, req.URL.Port())
}
//write content type
contentType := req.Header.Get("Content-Type")
if contentType != "" {
io.WriteString(h, "\n")
io.WriteString(h, fmt.Sprintf("Content-Type: %s", contentType))
}
io.WriteString(h, "\n\n")
//write body
if incBodyV2(req) {
s2, err2 := seekable.New(req)
if err2 != nil {
return "", err2
}
h.Write(s2.Bytes())
}
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
token = fmt.Sprintf("%s:%s", mac.AccessKey, sign)
return
}
// 管理凭证生成时是否同时对request body进行签名
func incBody(req *http.Request) bool {
return req.Body != nil &&
req.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
}
func incBodyV2(req *http.Request) bool {
contentType := req.Header.Get("Content-Type")
return req.Body != nil && (contentType == "application/x-www-form-urlencoded" ||
contentType == "application/json")
}
// VerifyCallback 验证上传回调请求是否来自七牛
func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
auth := req.Header.Get("Authorization")
if auth == "" {
return false, nil
}
token, err := mac.SignRequest(req, true)
token, err := mac.SignRequest(req)
if err != nil {
return false, err
}
@@ -96,76 +136,17 @@ func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
return auth == "QBox "+token, nil
}
// ---------------------------------------------------------------------------------------
// Sign 一般用于下载凭证的签名
func Sign(mac *Mac, data []byte) string {
if mac == nil {
mac = NewMac(ACCESS_KEY, SECRET_KEY)
}
return mac.Sign(data)
}
// SignWithData 一般用于上传凭证的签名
func SignWithData(mac *Mac, data []byte) string {
if mac == nil {
mac = NewMac(ACCESS_KEY, SECRET_KEY)
}
return mac.SignWithData(data)
}
// ---------------------------------------------------------------------------------------
type Transport struct {
mac Mac
Transport http.RoundTripper
// VerifyCallback 验证上传回调请求是否来自七牛
func VerifyCallback(mac *Mac, req *http.Request) (bool, error) {
return mac.VerifyCallback(req)
}
func incBody(req *http.Request) bool {
if req.Body == nil {
return false
}
if ct, ok := req.Header["Content-Type"]; ok {
switch ct[0] {
case "application/x-www-form-urlencoded":
return true
}
}
return false
}
func (t *Transport) NestedObject() interface{} {
return t.Transport
}
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
token, err := t.mac.SignRequest(req, incBody(req))
if err != nil {
return
}
req.Header.Set("Authorization", "QBox "+token)
return t.Transport.RoundTrip(req)
}
func NewTransport(mac *Mac, transport http.RoundTripper) *Transport {
if mac == nil {
mac = NewMac(ACCESS_KEY, SECRET_KEY)
}
if transport == nil {
transport = http.DefaultTransport
}
t := &Transport{mac: *mac, Transport: transport}
return t
}
func NewClient(mac *Mac, transport http.RoundTripper) *http.Client {
t := NewTransport(mac, transport)
return &http.Client{Transport: t}
}
// ---------------------------------------------------------------------------------------

View File

@@ -7,10 +7,9 @@ import (
"time"
)
// CreateTimestampAntileechURL 构建带时间戳防盗链的链接
// encryptKey 七牛防盗链key
func CreateTimestampAntileechURL(urlStr string, encryptKey string, durationInSeconds int64) (antileechURL string, err error) {
// CreateTimestampAntileechURL 用来构建七牛CDN时间戳防盗链的访问链接
func CreateTimestampAntileechURL(urlStr string, encryptKey string,
durationInSeconds int64) (antileechURL string, err error) {
u, err := url.Parse(urlStr)
if err != nil {
return
@@ -27,7 +26,6 @@ func CreateTimestampAntileechURL(urlStr string, encryptKey string, durationInSec
if u.RawQuery == "" {
antileechURL = u.String() + "?" + q.Encode()
} else {
antileechURL = u.String() + "&" + q.Encode()
}

View File

@@ -6,7 +6,7 @@ import (
func TestCreateTimestampAntiLeech(t *testing.T) {
type args struct {
urlStr string
urlStr string
encryptKey string
durationInSeconds int64
}
@@ -18,20 +18,21 @@ func TestCreateTimestampAntiLeech(t *testing.T) {
{
name: "antileech_1",
args: args{
urlStr: "http://www.abc.com/abc.jpg?stat",
encryptKey: "abc",
durationInSeconds: 20,
urlStr: "http://www.example.com/testfile.jpg",
encryptKey: "abc123",
durationInSeconds: 3600,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := CreateTimestampAntileechURL(tt.args.urlStr, tt.args.encryptKey, tt.args.durationInSeconds)
targetUrl, err := CreateTimestampAntileechURL(tt.args.urlStr, tt.args.encryptKey, tt.args.durationInSeconds)
if (err != nil) != tt.wantErr {
t.Errorf("CreateTimestampAntiLeech() error = %v, wantErr %v", err, tt.wantErr)
return
}
t.Log(targetUrl)
})
}
}

View File

@@ -3,73 +3,72 @@ package cdn
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/qiniu/api.v7/auth/qbox"
. "github.com/qiniu/api.v7/conf"
)
// Fusion CDN服务域名
var (
FUSION_HOST = "http://fusion.qiniuapi.com"
FusionHost = "http://fusion.qiniuapi.com"
)
/* TrafficReqBody
// CdnManager 提供了文件和目录刷新,文件预取,获取域名带宽和流量数据,获取域名日志列表等功能
type CdnManager struct {
mac *qbox.Mac
}
批量查询带宽/流量 请求内容
// NewCdnManager 用来构建一个新的 CdnManager
func NewCdnManager(mac *qbox.Mac) *CdnManager {
return &CdnManager{mac: mac}
}
StartDate string 开始日期例如2016-07-01
EndDate string 结束日期例如2016-07-03
Granularity string 粒度取值5min hour day
Domains string 域名列表,以 ;分割
*/
type TrafficReqBody struct {
// TrafficReq 为批量查询带宽/流量的API请求内容
// StartDate 开始日期,格式例如2016-07-01
// EndDate 结束日期格式例如2016-07-03
// Granularity 取值粒度取值可选值5min/hour/day
// Domains 域名列表,彼此用 ; 连接
type TrafficReq struct {
StartDate string `json:"startDate"`
EndDate string `json:"endDate"`
Granularity string `json:"granularity"`
Domains string `json:"domains"`
}
// TrafficResp
// 带宽/流量查询响应内容
// TrafficResp 为带宽/流量查询响应内容
type TrafficResp struct {
Code int `json:"code"`
Error string `json:"error"`
Time []string `json:"time,omitempty"`
Data map[string]TrafficRespData `json:"data,omitempty"`
Code int `json:"code"`
Error string `json:"error"`
Time []string `json:"time,omitempty"`
Data map[string]TrafficData `json:"data,omitempty"`
}
// TrafficRespData
// 带宽/流量数据
type TrafficRespData struct {
// TrafficData 为带宽/流量数据
type TrafficData struct {
DomainChina []int `json:"china"`
DomainOversea []int `json:"oversea"`
}
/*
// BandWidth
获取域名访问带宽数据
http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
StartDate string 必须 开始日期例如2016-07-01
EndDate string 必须 结束日期例如2016-07-03
Granularity string 必须 粒度取值5min hour day
Domains []string 必须 域名列表
*/
func GetBandWidthData(startDate, endDate, granularity string, domainList []string) (bandwidthData TrafficResp, err error) {
// GetBandwidthData 方法用来获取域名访问带宽数据
// StartDate string 必须 开始日期例如2016-07-01
// EndDate string 必须 结束日期例如2016-07-03
// Granularity string 必须 粒度取值5min hour day
// Domains []string 必须 域名列表
func (m *CdnManager) GetBandwidthData(startDate, endDate, granularity string,
domainList []string) (bandwidthData TrafficResp, err error) {
domains := strings.Join(domainList, ";")
reqBody := TrafficReqBody{
reqBody := TrafficReq{
StartDate: startDate,
EndDate: endDate,
Granularity: granularity,
Domains: domains,
}
resData, reqErr := postRequest("v2/tune/bandwidth", reqBody)
resData, reqErr := postRequest(m.mac, "/v2/tune/bandwidth", reqBody)
if reqErr != nil {
err = reqErr
return
@@ -82,27 +81,22 @@ func GetBandWidthData(startDate, endDate, granularity string, domainList []strin
return
}
/* Flux
获取域名访问流量数据
http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
StartDate string 必须 开始日期例如2016-07-01
EndDate string 必须 结束日期例如2016-07-03
Granularity string 必须 粒度取值5min hour day
Domains []string 必须 域名列表
*/
func GetFluxData(startDate, endDate, granularity string, domainList []string) (fluxData TrafficResp, err error) {
// GetFluxData 方法用来获取域名访问流量数据
// StartDate string 必须 开始日期例如2016-07-01
// EndDate string 必须 结束日期例如2016-07-03
// Granularity string 必须 粒度取值5min hour day
// Domains []string 必须 域名列表
func (m *CdnManager) GetFluxData(startDate, endDate, granularity string,
domainList []string) (fluxData TrafficResp, err error) {
domains := strings.Join(domainList, ";")
reqBody := TrafficReqBody{
reqBody := TrafficReq{
StartDate: startDate,
EndDate: endDate,
Granularity: granularity,
Domains: domains,
}
resData, reqErr := postRequest("v2/tune/flux", reqBody)
resData, reqErr := postRequest(m.mac, "/v2/tune/flux", reqBody)
if reqErr != nil {
err = reqErr
return
@@ -117,43 +111,46 @@ func GetFluxData(startDate, endDate, granularity string, domainList []string) (f
return
}
// RefreshReq
// 缓存刷新请求内容
// RefreshReq 为缓存刷新请求内容
type RefreshReq struct {
Urls []string `json:"urls"`
Dirs []string `json:"dirs"`
}
// RefreshResp
// 缓存刷新响应内容
// RefreshResp 缓存刷新响应内容
type RefreshResp struct {
Code int `json:"code"`
Error string `json:"error"`
RequestID string `json:"requestId,omitempty"`
InvalidUrls []string `json:"invalidUrls,omitempty"`
InvalidDirs []string `json:"invalidDirs,omitempty"`
UrlQuotaDay int `json:"urlQuotaDay,omitempty"`
UrlSurplusDay int `json:"urlSurplusDay,omitempty"`
URLQuotaDay int `json:"urlQuotaDay,omitempty"`
URLSurplusDay int `json:"urlSurplusDay,omitempty"`
DirQuotaDay int `json:"dirQuotaDay,omitempty"`
DirSurplusDay int `json:"dirSurplusDay,omitempty"`
}
/* RefreshUrlsAndDirs
刷新链接列表每次最多不可以超过100条链接
http://developer.qiniu.com/article/fusion/api/refresh.html
urls 要刷新的单个url列表总数不超过100条单个url即一个具体的url例如http://bar.foo.com/index.html
dirs 要刷新的目录url列表总数不超过10条目录dir即表示一个目录级的url例如http://bar.foo.com/dir/也支持在尾部使用通配符例如http://bar.foo.com/dir/*
*/
func RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
// RefreshUrlsAndDirs 方法用来刷新文件或目录
// urls 要刷新的单个url列表单次方法调用总数不超过100条单个url即一个具体的url
// 例如http://bar.foo.com/index.html
// dirs 要刷新的目录url列表单次方法调用总数不超过10条目录dir即表示一个目录级的url
// 例如http://bar.foo.com/dir/
func (m *CdnManager) RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
if len(urls) > 100 {
err = errors.New("urls count exceeds the limit of 100")
return
}
if len(dirs) > 10 {
err = errors.New("dirs count exceeds the limit of 10")
return
}
reqBody := RefreshReq{
Urls: urls,
Dirs: dirs,
}
resData, reqErr := postRequest("v2/tune/refresh", reqBody)
resData, reqErr := postRequest(m.mac, "/v2/tune/refresh", reqBody)
if reqErr != nil {
err = reqErr
return
@@ -167,26 +164,22 @@ func RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
return
}
// RefreshUrls
// 刷新文件
func RefreshUrls(urls []string) (result RefreshResp, err error) {
return RefreshUrlsAndDirs(urls, nil)
// RefreshUrls 刷新文件
func (m *CdnManager) RefreshUrls(urls []string) (result RefreshResp, err error) {
return m.RefreshUrlsAndDirs(urls, nil)
}
// RefreshDirs
// 刷新目录
func RefreshDirs(dirs []string) (result RefreshResp, err error) {
return RefreshUrlsAndDirs(nil, dirs)
// RefreshDirs 刷新目录
func (m *CdnManager) RefreshDirs(dirs []string) (result RefreshResp, err error) {
return m.RefreshUrlsAndDirs(nil, dirs)
}
// PrefetchReq
// 文件预取请求内容
// PrefetchReq 文件预取请求内容
type PrefetchReq struct {
Urls []string `json:"urls"`
}
// PrefetchResp
// 文件预取响应内容
// PrefetchResp 文件预取响应内容
type PrefetchResp struct {
Code int `json:"code"`
Error string `json:"error"`
@@ -196,16 +189,18 @@ type PrefetchResp struct {
SurplusDay int `json:"surplusDay,omitempty"`
}
// PrefetchUrls
// 预取文件链接每次最多不可以超过100条
// http://developer.qiniu.com/article/fusion/api/prefetch.html
func PrefetchUrls(urls []string) (result PrefetchResp, err error) {
// PrefetchUrls 预取文件链接每次最多不可以超过100条
func (m *CdnManager) PrefetchUrls(urls []string) (result PrefetchResp, err error) {
if len(urls) > 100 {
err = errors.New("urls count exceeds the limit of 100")
return
}
reqBody := PrefetchReq{
Urls: urls,
}
resData, reqErr := postRequest("v2/tune/prefetch", reqBody)
resData, reqErr := postRequest(m.mac, "/v2/tune/prefetch", reqBody)
if reqErr != nil {
err = reqErr
return
@@ -220,12 +215,59 @@ func PrefetchUrls(urls []string) (result PrefetchResp, err error) {
return
}
// RequestWithBody
// 带body对api发出请求并且返回response body
func postRequest(path string, body interface{}) (resData []byte, err error) {
// ListLogRequest 日志下载请求内容
type ListLogRequest struct {
Day string `json:"day"`
Domains string `json:"domains"`
}
urlStr := fmt.Sprintf("%s/%s", FUSION_HOST, path)
// ListLogResult 日志下载相应内容
type ListLogResult struct {
Code int `json:"code"`
Error string `json:"error"`
Data map[string][]LogDomainInfo `json:"data"`
}
// LogDomainInfo 日志下载信息
type LogDomainInfo struct {
Name string `json:"name"`
Size int64 `json:"size"`
ModifiedTime int64 `json:"mtime"`
URL string `json:"url"`
}
// GetCdnLogList 获取CDN域名访问日志的下载链接
func (m *CdnManager) GetCdnLogList(day string, domains []string) (
listLogResult ListLogResult, err error) {
//new log query request
logReq := ListLogRequest{
Day: day,
Domains: strings.Join(domains, ";"),
}
resData, reqErr := postRequest(m.mac, "/v2/tune/log/list", logReq)
if reqErr != nil {
err = fmt.Errorf("get response error, %s", reqErr)
return
}
if decodeErr := json.Unmarshal(resData, &listLogResult); decodeErr != nil {
err = fmt.Errorf("get response error, %s", decodeErr)
return
}
if listLogResult.Error != "" {
err = fmt.Errorf("get log list error, %d %s", listLogResult.Code, listLogResult.Error)
return
}
return
}
// RequestWithBody 带body对api发出请求并且返回response body
func postRequest(mac *qbox.Mac, path string, body interface{}) (resData []byte,
err error) {
urlStr := fmt.Sprintf("%s%s", FusionHost, path)
reqData, _ := json.Marshal(body)
req, reqErr := http.NewRequest("POST", urlStr, bytes.NewReader(reqData))
if reqErr != nil {
@@ -233,8 +275,7 @@ func postRequest(path string, body interface{}) (resData []byte, err error) {
return
}
mac := qbox.NewMac(ACCESS_KEY, SECRET_KEY)
accessToken, signErr := mac.SignRequest(req, false)
accessToken, signErr := mac.SignRequest(req)
if signErr != nil {
err = signErr
return
@@ -243,9 +284,9 @@ func postRequest(path string, body interface{}) (resData []byte, err error) {
req.Header.Add("Authorization", "QBox "+accessToken)
req.Header.Add("Content-Type", "application/json")
resp, httpErr := http.DefaultClient.Do(req)
if httpErr != nil {
err = httpErr
resp, respErr := http.DefaultClient.Do(req)
if respErr != nil {
err = respErr
return
}
defer resp.Body.Close()

View File

@@ -1,75 +1,86 @@
package cdn
import (
"math/rand"
"os"
"reflect"
"strconv"
"testing"
"time"
"github.com/qiniu/api.v7/kodo"
"github.com/qiniu/api.v7/auth/qbox"
)
//global variables
var (
ak = os.Getenv("QINIU_ACCESS_KEY")
sk = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
testBucketName = os.Getenv("QINIU_TEST_BUCKET")
ak = os.Getenv("QINIU_ACCESS_KEY")
sk = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
testDate = time.Now().AddDate(0, 0, -3).Format("2006-01-02")
bucket = newBucket()
client *kodo.Client
testKey = "fusionTest"
testURL string
layout = "2006-01-02"
now = time.Now()
startDate = now.AddDate(0, 0, -2).Format(layout)
endDate = now.AddDate(0, 0, -1).Format(layout)
logDate = now.AddDate(0, 0, -1).Format(layout)
testUrls = []string{
"http://gosdk.qiniudn.com/qiniu1.png",
"http://gosdk.qiniudn.com/qiniu2.png",
}
testDirs = []string{
"http://gosdk.qiniudn.com/dir1/",
"http://gosdk.qiniudn.com/dir2/",
}
)
func init() {
kodo.SetMac(ak, sk)
rand.Seed(time.Now().UnixNano())
testKey += strconv.Itoa(rand.Int())
bucket.PutFile(nil, nil, testKey, "doc.go", nil)
testURL = domain + "/" + testKey
var mac *qbox.Mac
var cdnManager *CdnManager
func init() {
if ak == "" || sk == "" {
panic("please run ./test-env.sh first")
}
mac = qbox.NewMac(ak, sk)
cdnManager = NewCdnManager(mac)
}
func TestGetBandWidthData(t *testing.T) {
//TestGetBandwidthData
func TestGetBandwidthData(t *testing.T) {
type args struct {
startDate string
endDate string
granularity string
domainList []string
}
tests := []struct {
name string
args args
wantTraffic TrafficResp
wantErr bool
testCases := []struct {
name string
args args
wantCode int
}{
{
name: "BandWidthTest_1",
name: "CdnManager_TestGetBandwidthData",
args: args{
testDate,
testDate,
startDate,
endDate,
"5min",
[]string{domain},
},
wantCode: 200,
},
}
kodo.SetMac(ak, sk)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := GetBandWidthData(tt.args.startDate, tt.args.endDate, tt.args.granularity, tt.args.domainList)
if (err != nil) != tt.wantErr {
t.Errorf("GetBandWidthData() error = %v, wantErr %v", err, tt.wantErr)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ret, err := cdnManager.GetBandwidthData(tc.args.startDate, tc.args.endDate,
tc.args.granularity, tc.args.domainList)
if err != nil || ret.Code != tc.wantCode {
t.Errorf("GetBandwidth() error = %v, %v", err, ret.Error)
return
}
})
}
}
//TestGetFluxData
func TestGetFluxData(t *testing.T) {
type args struct {
startDate string
@@ -77,167 +88,160 @@ func TestGetFluxData(t *testing.T) {
granularity string
domainList []string
}
tests := []struct {
name string
args args
wantTraffic TrafficResp
wantErr bool
testCases := []struct {
name string
args args
wantCode int
}{
{
name: "BandWidthTest_1",
name: "CdnManager_TestGetFluxData",
args: args{
testDate,
testDate,
startDate,
endDate,
"5min",
[]string{domain},
},
wantCode: 200,
},
}
kodo.SetMac(ak, sk)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := GetFluxData(tt.args.startDate, tt.args.endDate, tt.args.granularity, tt.args.domainList)
if (err != nil) != tt.wantErr {
t.Errorf("GetFluxData() error = %v, wantErr %v", err, tt.wantErr)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ret, err := cdnManager.GetFluxData(tc.args.startDate, tc.args.endDate,
tc.args.granularity, tc.args.domainList)
if err != nil || ret.Code != tc.wantCode {
t.Errorf("GetFlux() error = %v, %v", err, ret.Error)
return
}
})
}
}
func TestRefreshUrlsAndDirs(t *testing.T) {
kodo.SetMac(ak, sk)
type args struct {
urls []string
dirs []string
}
tests := []struct {
name string
args args
wantResult RefreshResp
wantErr bool
}{
{
name: "refresh_test_1",
args: args{
urls: []string{testURL},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := RefreshUrlsAndDirs(tt.args.urls, tt.args.dirs)
if (err != nil) != tt.wantErr {
t.Errorf("RefreshUrlsAndDirs() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
//TestRefreshUrls
func TestRefreshUrls(t *testing.T) {
type args struct {
urls []string
}
tests := []struct {
name string
args args
wantResult RefreshResp
wantErr bool
testCases := []struct {
name string
args args
wantCode int
}{
{
name: "refresh_test_1",
name: "CdnManager_TestRefresUrls",
args: args{
urls: []string{testURL},
urls: testUrls,
},
wantErr: false,
wantCode: 200,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := RefreshUrls(tt.args.urls)
if (err != nil) != tt.wantErr {
t.Errorf("RefreshUrls() error = %v, wantErr %v", err, tt.wantErr)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ret, err := cdnManager.RefreshUrls(tc.args.urls)
if err != nil || ret.Code != tc.wantCode {
t.Errorf("RefreshUrls() error = %v, %v", err, ret.Error)
return
}
})
}
}
//TestRefreshDirs
func TestRefreshDirs(t *testing.T) {
type args struct {
dirs []string
}
tests := []struct {
name string
args args
wantResult RefreshResp
wantErr bool
}{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotResult, err := RefreshDirs(tt.args.dirs)
if (err != nil) != tt.wantErr {
t.Errorf("RefreshDirs() error = %v, wantErr %v", err, tt.wantErr)
testCases := []struct {
name string
args args
wantCode int
}{
{
name: "CdnManager_TestRefreshDirs",
args: args{
dirs: testDirs,
},
wantCode: 200,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ret, err := cdnManager.RefreshDirs(tc.args.dirs)
if err != nil || ret.Code != tc.wantCode {
if ret.Error == "refresh dir limit error" {
t.Logf("RefreshDirs() error=%v", ret.Error)
} else {
t.Errorf("RefreshDirs() error = %v, %v", err, ret.Error)
}
return
}
if !reflect.DeepEqual(gotResult, tt.wantResult) {
t.Errorf("RefreshDirs() = %v, want %v", gotResult, tt.wantResult)
}
})
}
}
//TestPrefetchUrls
func TestPrefetchUrls(t *testing.T) {
type args struct {
urls []string
}
tests := []struct {
name string
args args
wantResult PrefetchResp
wantErr bool
testCases := []struct {
name string
args args
wantCode int
}{
{
name: "refresh_test_1",
name: "CdnManager_PrefetchUrls",
args: args{
urls: []string{testURL},
urls: testUrls,
},
wantErr: false,
wantCode: 200,
},
}
kodo.SetMac(ak, sk)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := PrefetchUrls(tt.args.urls)
if (err != nil) != tt.wantErr {
t.Errorf("PrefetchUrls() error = %v, wantErr %v", err, tt.wantErr)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ret, err := cdnManager.PrefetchUrls(tc.args.urls)
if err != nil || ret.Code != tc.wantCode {
t.Errorf("PrefetchUrls() error = %v, %v", err, ret.Error)
return
}
})
}
}
func newBucket() (bucket kodo.Bucket) {
ak := os.Getenv("QINIU_ACCESS_KEY")
sk := os.Getenv("QINIU_SECRET_KEY")
if ak == "" || sk == "" {
panic("require ACCESS_KEY & SECRET_KEY")
//TestGetCdnLogList
func TestGetCdnLogList(t *testing.T) {
type args struct {
date string
domains []string
}
kodo.SetMac(ak, sk)
testBucketName = os.Getenv("QINIU_TEST_BUCKET")
domain = os.Getenv("QINIU_TEST_DOMAIN")
if testBucketName == "" || domain == "" {
panic("require test env")
testCases := []struct {
name string
args args
}{
{
name: "CdnManager_TestGetCdnLogList",
args: args{
date: logDate,
domains: []string{domain},
},
},
}
client = kodo.NewWithoutZone(nil)
return client.Bucket(testBucketName)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := cdnManager.GetCdnLogList(tc.args.date, tc.args.domains)
if err != nil {
t.Errorf("GetCdnLogList() error = %v", err)
return
}
})
}
}

View File

@@ -1,25 +1,3 @@
/*
包 github.com/qiniu/api.v7/cdn 提供了七牛CDN的API功能
首先,我们要配置下 AccessKey/SecretKey,
import "github.com/qiniu/api.v7/kodo"
kodo.SetMac("ak", "sk")
设置了AccessKey/SecretKey 就可以使用cdn的各类功能
比如我们要生成一个带时间戳防盗链的链接:
q :=url.Values{}// url.Values 请求参数
link, err := cdn.CreateTimestampAntileechURL(""http://www.qiniu.com/abc/bcc/aa-s.mp4?x=2&y=3", "encryptedkey", 20)
if err != nil {
fmt.Println(err)
}
fmt.Println(link)
又或者我们要列出CDN日志及其下载地址
resp, err := cdn.GetCdnLogList("2016-12-26", "x-mas.com")
if err != nil {
fmt.Println(err)
}
fmt.Println(resp)
*/
// cdn 包提供了 Fusion CDN的常见功能。相关功能的文档参考https://developer.qiniu.com/fusion。
// 目前提供了文件和目录刷新,文件预取,获取域名带宽和流量数据,获取域名日志列表等功能。
package cdn

View File

@@ -1,92 +0,0 @@
package cdn
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/qiniu/api.v7/auth/qbox"
)
const (
LIST_LOG_API = "http://fusion.qiniuapi.com/v2/tune/log/list"
)
// ListLogRequest 日志下载请求内容
type ListLogRequest struct {
Day string `json:"day"`
Domains string `json:"domains"`
}
// ListLogResult 日志下载相应内容
type ListLogResult struct {
Code int `json:"code"`
Error string `json:"error"`
Data map[string][]LogDomainInfo `json:"data"`
}
// LogDomainInfo 日志下载信息
type LogDomainInfo struct {
Name string `json:"name"`
Size int64 `json:"size"`
ModifiedTime int64 `json:"mtime"`
URL string `json:"url"`
}
// GetCdnLogList 获取CDN域名访问日志的下载链接
// http://developer.qiniu.com/article/fusion/api/log.html
func GetCdnLogList(date, domains string) (domainLogs []LogDomainInfo, err error) {
//new log query request
logReq := ListLogRequest{
Day: date,
Domains: domains,
}
logReqBytes, _ := json.Marshal(&logReq)
req, reqErr := http.NewRequest("POST", LIST_LOG_API, bytes.NewReader(logReqBytes))
if reqErr != nil {
err = fmt.Errorf("New request error, %s", reqErr)
return
}
mac := qbox.NewMac("", "")
token, signErr := mac.SignRequest(req, false)
if signErr != nil {
err = signErr
return
}
req.Header.Add("Authorization", "QBox "+token)
req.Header.Add("Content-Type", "application/json")
resp, respErr := http.DefaultClient.Do(req)
if respErr != nil {
err = fmt.Errorf("Get response error, %s", respErr)
return
}
defer resp.Body.Close()
listLogResult := ListLogResult{}
decoder := json.NewDecoder(resp.Body)
if decodeErr := decoder.Decode(&listLogResult); decodeErr != nil {
err = fmt.Errorf("Parse response error, %s", decodeErr)
return
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("Get log list error, %d %s", listLogResult.Code, listLogResult.Error)
return
}
domainItems := strings.Split(domains, ";")
for _, domain := range domainItems {
for _, v := range listLogResult.Data[domain] {
domainLogs = append(domainLogs, v)
}
}
return
}

View File

@@ -1,48 +0,0 @@
package cdn
import (
"fmt"
"reflect"
"testing"
"github.com/qiniu/api.v7/kodo"
)
func init() {
kodo.SetMac(ak, sk)
}
func TestGetCdnLogList(t *testing.T) {
type args struct {
date string
domains string
}
tests := []struct {
name string
args args
wantDomainLogs []LogDomainInfo
wantErr bool
}{
{
name: "getCdnLogListTest",
args: args{
date: testDate,
domains: domain,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotDomainLogs, err := GetCdnLogList(tt.args.date, tt.args.domains)
if (err != nil) != tt.wantErr {
t.Errorf("GetCdnLogList() error = %v, wantErr %v", err, tt.wantErr)
return
}
fmt.Println(domain, gotDomainLogs)
if !reflect.DeepEqual(gotDomainLogs, tt.wantDomainLogs) {
t.Errorf("GetCdnLogList() = %v, want %v", gotDomainLogs, tt.wantDomainLogs)
}
})
}
}

View File

@@ -9,19 +9,13 @@ import (
"github.com/qiniu/x/rpc.v7"
)
var version = "7.1.0"
var ACCESS_KEY string
var SECRET_KEY string
// ----------------------------------------------------------
var version = "7.2.3"
const (
ctypeAppName = ctype.ALPHA | ctype.DIGIT | ctype.UNDERLINE | ctype.SPACE_BAR | ctype.SUB | ctype.DOT
)
// userApp should be [A-Za-z0-9_\ \-\.]*
//
func SetAppName(userApp string) error {
if userApp != "" && !ctype.IsType(ctypeAppName, userApp) {
return syscall.EINVAL
@@ -34,5 +28,3 @@ func SetAppName(userApp string) error {
func init() {
SetAppName("")
}
// ----------------------------------------------------------

2
vendor/github.com/qiniu/api.v7/conf/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// conf 包提供了设置APP名称的方法。该APP名称会被放入API请求的UserAgent中方便后续查询日志分析问题。
package conf

View File

@@ -1,16 +1,13 @@
/*
包 github.com/qiniu/api.v7 是七牛 Go 语言 SDK v7.x 版本
七牛对象存储,我们取了一个好听的名字,叫 KODO Blob Storage。要使用它你主要和以下两个包打交道
包 github.com/qiniu/api.v7 是七牛 Go 语言 SDK v7.x 版本。
import "github.com/qiniu/api.v7/kodo"
import "github.com/qiniu/api.v7/kodocli"
主要提供了存储的数据上传下载管理以及CDN相关的功能。要求Go语言版本>=1.7.0。
如果您是在业务服务器(服务器端)调用七牛云存储的服务,请使用 github.com/qiniu/api.v7/kodo。
Go SDK 中主要包含几个包:
auth 包提供鉴权相关方法conf 包提供配置相关方法cdn包提供CDN相关的功能storage包提供存储相关的功能。
如果您是在客户端比如Android/iOS 设备、Windows/Mac/Linux 桌面环境)调用七牛云存储的服务,请使用 github.com/qiniu/api.v7/kodocli。
注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,
会影响您的数据安全。
*/
package api
@@ -18,6 +15,5 @@ import (
_ "github.com/qiniu/api.v7/auth/qbox"
_ "github.com/qiniu/api.v7/cdn"
_ "github.com/qiniu/api.v7/conf"
_ "github.com/qiniu/api.v7/kodo"
_ "github.com/qiniu/api.v7/kodocli"
_ "github.com/qiniu/api.v7/storage"
)

View File

@@ -0,0 +1,35 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
cfg := storage.Config{}
mac := qbox.NewMac(accessKey, secretKey)
bucketManger := storage.NewBucketManager(mac, &cfg)
siteURL := "http://devtools.qiniu.com"
// 设置镜像存储
err := bucketManger.SetImage(siteURL, bucket)
if err != nil {
fmt.Println(err)
}
// 取消设置镜像存储
err = bucketManger.UnsetImage(bucket)
if err != nil {
fmt.Println(err)
}
}

View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"time"
"github.com/qiniu/api.v7/cdn"
)
func main() {
urlStr := "http://image.example.com/qiniu_do_not_delete.gif"
cryptKey := "your crypt key"
deadline := time.Now().Add(time.Second * 3600).Unix()
accessUrl, err := cdn.CreateTimestampAntileechURL(urlStr, cryptKey, deadline)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(accessUrl)
}

View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/cdn"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cdnManager := cdn.NewCdnManager(mac)
startDate := "2017-07-20"
endDate := "2017-07-30"
g := "day"
data, err := cdnManager.GetBandwidthData(startDate, endDate, g, []string{domain})
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v\n", data)
}

View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/cdn"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cdnManager := cdn.NewCdnManager(mac)
startDate := "2017-07-20"
endDate := "2017-07-30"
g := "day"
data, err := cdnManager.GetFluxData(startDate, endDate, g, []string{domain})
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v\n", data)
}

View File

@@ -0,0 +1,37 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/cdn"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cdnManager := cdn.NewCdnManager(mac)
domains := []string{
domain,
}
day := "2017-07-30"
ret, err := cdnManager.GetCdnLogList(day, domains)
if err != nil {
fmt.Println(err)
return
}
domainLogs := ret.Data
for domain, logs := range domainLogs {
fmt.Println(domain)
for _, item := range logs {
fmt.Println(item.Name, item.URL, item.Size, item.ModifiedTime)
}
}
}

View File

@@ -0,0 +1,33 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/cdn"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cdnManager := cdn.NewCdnManager(mac)
// 预取链接单次请求链接不可以超过100个如果超过请分批发送请求
urlsToPrefetch := []string{
"http://if-pbl.qiniudn.com/qiniu.png",
"http://if-pbl.qiniudn.com/github.png",
}
ret, err := cdnManager.PrefetchUrls(urlsToPrefetch)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.Code)
fmt.Println(ret.RequestID)
}

View File

@@ -0,0 +1,48 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/cdn"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
domain = os.Getenv("QINIU_TEST_DOMAIN")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cdnManager := cdn.NewCdnManager(mac)
//刷新链接单次请求链接不可以超过100个如果超过请分批发送请求
urlsToRefresh := []string{
"http://if-pbl.qiniudn.com/qiniu.png",
"http://if-pbl.qiniudn.com/github.png",
}
ret, err := cdnManager.RefreshUrls(urlsToRefresh)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.Code)
fmt.Println(ret.RequestID)
// 刷新目录,刷新目录需要联系七牛技术支持开通权限
// 单次请求链接不可以超过10个如果超过请分批发送请求
dirsToRefresh := []string{
"http://if-pbl.qiniudn.com/images/",
"http://if-pbl.qiniudn.com/static/",
}
ret, err = cdnManager.RefreshDirs(dirsToRefresh)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.Code)
fmt.Println(ret.RequestID)
fmt.Println(ret.Error)
}

View File

@@ -0,0 +1,91 @@
package main
import (
"encoding/base64"
"fmt"
"os"
"strings"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
// 简单上传凭证
putPolicy := storage.PutPolicy{
Scope: bucket,
}
mac := qbox.NewMac(accessKey, secretKey)
upToken := putPolicy.UploadToken(mac)
fmt.Println(upToken)
// 设置上传凭证有效期
putPolicy = storage.PutPolicy{
Scope: bucket,
}
putPolicy.Expires = 7200 //示例2小时有效期
upToken = putPolicy.UploadToken(mac)
fmt.Println(upToken)
// 覆盖上传凭证
// 需要覆盖的文件名
keyToOverwrite := "qiniu.mp4"
putPolicy = storage.PutPolicy{
Scope: fmt.Sprintf("%s:%s", bucket, keyToOverwrite),
}
upToken = putPolicy.UploadToken(mac)
fmt.Println(upToken)
// 自定义上传回复凭证
putPolicy = storage.PutPolicy{
Scope: bucket,
ReturnBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
}
upToken = putPolicy.UploadToken(mac)
fmt.Println(upToken)
// 带回调业务服务器的凭证(JSON方式)
putPolicy = storage.PutPolicy{
Scope: bucket,
CallbackURL: "http://api.example.com/qiniu/upload/callback",
CallbackBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
CallbackBodyType: "application/json",
}
upToken = putPolicy.UploadToken(mac)
fmt.Println(upToken)
// 带回调业务服务器的凭证URL方式
putPolicy = storage.PutPolicy{
Scope: bucket,
CallbackURL: "http://api.example.com/qiniu/upload/callback",
CallbackBody: "key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)",
}
upToken = putPolicy.UploadToken(mac)
fmt.Println(upToken)
// 带数据处理的凭证
saveMp4Entry := base64.URLEncoding.EncodeToString([]byte(bucket + ":avthumb_test_target.mp4"))
saveJpgEntry := base64.URLEncoding.EncodeToString([]byte(bucket + ":vframe_test_target.jpg"))
//数据处理指令,支持多个指令
avthumbMp4Fop := "avthumb/mp4|saveas/" + saveMp4Entry
vframeJpgFop := "vframe/jpg/offset/1|saveas/" + saveJpgEntry
//连接多个操作指令
persistentOps := strings.Join([]string{avthumbMp4Fop, vframeJpgFop}, ";")
pipeline := "test"
putPolicy = storage.PutPolicy{
Scope: bucket,
PersistentOps: persistentOps,
PersistentPipeline: pipeline,
PersistentNotifyURL: "http://api.example.com/qiniu/pfop/notify",
}
upToken = putPolicy.UploadToken(mac)
fmt.Println(upToken)
}

View File

@@ -0,0 +1,76 @@
package main
import (
"context"
"fmt"
"net"
"os"
"net/http"
"net/url"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
localFile := "/Users/jemy/Documents/github.png"
key := "github-x.png"
putPolicy := storage.PutPolicy{
Scope: bucket + ":" + key,
}
mac := qbox.NewMac(accessKey, secretKey)
upToken := putPolicy.UploadToken(mac)
cfg := storage.Config{}
// 空间对应的机房
cfg.Zone = &storage.ZoneHuadong
// 是否使用https域名
cfg.UseHTTPS = false
// 上传是否使用CDN上传加速
cfg.UseCdnDomains = false
//设置代理
proxyURL := "http://localhost:8888"
proxyURI, _ := url.Parse(proxyURL)
//绑定网卡
nicIP := "100.100.33.138"
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{
IP: net.ParseIP(nicIP),
},
}
//构建代理client对象
client := http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURI),
Dial: dialer.Dial,
},
}
// 构建表单上传的对象
formUploader := storage.NewFormUploaderEx(&cfg, &rpc.Client{Client: &client})
ret := storage.PutRet{}
// 可选配置
putExtra := storage.PutExtra{
Params: map[string]string{
"x:name": "github logo",
},
}
//putExtra.NoCrc32Check = true
err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.Key, ret.Hash)
}

32
vendor/github.com/qiniu/api.v7/examples/prefop.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
operationManager := storage.NewOperationManager(mac, &cfg)
persistentId := "z0.597f28b445a2650c994bb208"
ret, err := operationManager.Prefop(persistentId)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.String())
}

View File

@@ -0,0 +1,127 @@
package main
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"context"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func md5Hex(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
type ProgressRecord struct {
Progresses []storage.BlkputRet `json:"progresses"`
}
func main() {
localFile := "your local file path"
key := "your file save key"
putPolicy := storage.PutPolicy{
Scope: bucket,
}
mac := qbox.NewMac(accessKey, secretKey)
upToken := putPolicy.UploadToken(mac)
cfg := storage.Config{}
// 空间对应的机房
cfg.Zone = &storage.ZoneHuadong
// 是否使用https域名
cfg.UseHTTPS = false
// 上传是否使用CDN上传加速
cfg.UseCdnDomains = false
// 必须仔细选择一个能标志上传唯一性的 recordKey 用来记录上传进度
// 我们这里采用 md5(bucket+key+local_path+local_file_last_modified)+".progress" 作为记录上传进度的文件名
fileInfo, statErr := os.Stat(localFile)
if statErr != nil {
fmt.Println(statErr)
return
}
fileSize := fileInfo.Size()
fileLmd := fileInfo.ModTime().UnixNano()
recordKey := md5Hex(fmt.Sprintf("%s:%s:%s:%s", bucket, key, localFile, fileLmd)) + ".progress"
// 指定的进度文件保存目录,实际情况下,请确保该目录存在,而且只用于记录进度文件
recordDir := "/Users/jemy/Temp/progress"
mErr := os.MkdirAll(recordDir, 0755)
if mErr != nil {
fmt.Println("mkdir for record dir error,", mErr)
return
}
recordPath := filepath.Join(recordDir, recordKey)
progressRecord := ProgressRecord{}
// 尝试从旧的进度文件中读取进度
recordFp, openErr := os.Open(recordPath)
if openErr == nil {
progressBytes, readErr := ioutil.ReadAll(recordFp)
if readErr == nil {
mErr := json.Unmarshal(progressBytes, &progressRecord)
if mErr == nil {
// 检查context 是否过期避免701错误
for _, item := range progressRecord.Progresses {
if storage.IsContextExpired(item) {
fmt.Println(item.ExpiredAt)
progressRecord.Progresses = make([]storage.BlkputRet, storage.BlockCount(fileSize))
break
}
}
}
}
recordFp.Close()
}
if len(progressRecord.Progresses) == 0 {
progressRecord.Progresses = make([]storage.BlkputRet, storage.BlockCount(fileSize))
}
resumeUploader := storage.NewResumeUploader(&cfg)
ret := storage.PutRet{}
progressLock := sync.RWMutex{}
putExtra := storage.RputExtra{
Progresses: progressRecord.Progresses,
Notify: func(blkIdx int, blkSize int, ret *storage.BlkputRet) {
progressLock.Lock()
defer progressLock.Unlock()
//将进度序列化,然后写入文件
progressRecord.Progresses[blkIdx] = *ret
progressBytes, _ := json.Marshal(progressRecord)
fmt.Println("write progress file", blkIdx, recordPath)
wErr := ioutil.WriteFile(recordPath, progressBytes, 0644)
if wErr != nil {
fmt.Println("write progress file error,", wErr)
}
},
}
err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
if err != nil {
fmt.Println(err)
return
}
//上传成功之后,一定记得删除这个进度文件
os.Remove(recordPath)
fmt.Println(ret.Key, ret.Hash)
}

View File

@@ -0,0 +1,72 @@
package main
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
localFile := "/Users/jemy/Documents/github.png"
key := "qiniu-x.png"
putPolicy := storage.PutPolicy{
Scope: bucket,
}
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{}
// 空间对应的机房
cfg.Zone = &storage.ZoneHuadong
// 是否使用https域名
cfg.UseHTTPS = false
// 上传是否使用CDN上传加速
cfg.UseCdnDomains = false
//设置代理
proxyURL := "http://localhost:8888"
proxyURI, _ := url.Parse(proxyURL)
//绑定网卡
nicIP := "100.100.33.138"
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{
IP: net.ParseIP(nicIP),
},
}
//构建代理client对象
client := http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURI),
Dial: dialer.Dial,
},
}
resumeUploader := storage.NewResumeUploaderEx(&cfg, &rpc.Client{Client: &client})
upToken := putPolicy.UploadToken(mac)
ret := storage.PutRet{}
err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, nil)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.Key, ret.Hash)
}

View File

@@ -0,0 +1,64 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
chgmKeys := map[string]string{
"github1.png": "image/x-png",
"github2.png": "image/x-png",
"github3.png": "image/x-png",
"github4.png": "image/x-png",
"github5.png": "image/x-png",
}
chgmOps := make([]string, 0, len(chgmKeys))
for key, newMime := range chgmKeys {
chgmOps = append(chgmOps, storage.URIChangeMime(bucket, key, newMime))
}
rets, err := bucketManager.Batch(chgmOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
}
}

View File

@@ -0,0 +1,67 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
//每个batch的操作数量不可以超过1000个如果总数量超过1000需要分批发送
chtypeKeys := map[string]int{
"github1.png": 1,
"github2.png": 1,
"github3.png": 1,
"github4.png": 1,
"github5.png": 1,
}
chtypeOps := make([]string, 0, len(chtypeKeys))
for key, fileType := range chtypeKeys {
chtypeOps = append(chtypeOps, storage.URIChangeType(bucket, key, fileType))
}
rets, err := bucketManager.Batch(chtypeOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
}
}

View File

@@ -0,0 +1,67 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
//每个batch的操作数量不可以超过1000个如果总数量超过1000需要分批发送
srcBucket := bucket
destBucket := bucket
force := true
copyKeys := map[string]string{
"github1.png": "github1-copy.png",
"github2.png": "github2-copy.png",
"github3.png": "github3-copy.png",
"github4.png": "github4-copy.png",
"github5.png": "github5-copy.png",
}
copyOps := make([]string, 0, len(copyKeys))
for srcKey, destKey := range copyKeys {
copyOps = append(copyOps, storage.URICopy(srcBucket, srcKey, destBucket, destKey, force))
}
rets, err := bucketManager.Batch(copyOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
fmt.Printf("%v\n", ret.Data)
}
}
}

View File

@@ -0,0 +1,63 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
//每个batch的操作数量不可以超过1000个如果总数量超过1000需要分批发送
keys := []string{
"github1.png",
"github2.png",
"github3.png",
"github4.png",
"github5.png",
}
deleteOps := make([]string, 0, len(keys))
for _, key := range keys {
deleteOps = append(deleteOps, storage.URIDelete(bucket, key))
}
rets, err := bucketManager.Batch(deleteOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
}
}
}

View File

@@ -0,0 +1,66 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
//每个batch的操作数量不可以超过1000个如果总数量超过1000需要分批发送
expireKeys := map[string]int{
"github1.png": 7,
"github2.png": 8,
"github3.png": 9,
"github4.png": 10,
"github5.png": 11,
}
expireOps := make([]string, 0, len(expireKeys))
for key, expire := range expireKeys {
expireOps = append(expireOps, storage.URIDeleteAfterDays(bucket, key, expire))
}
rets, err := bucketManager.Batch(expireOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
}
}

View File

@@ -0,0 +1,67 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
//每个batch的操作数量不可以超过1000个如果总数量超过1000需要分批发送
srcBucket := bucket
destBucket := bucket
force := true
moveKeys := map[string]string{
"github1.png": "github1-move.png",
"github2.png": "github2-move.png",
"github3.png": "github3-move.png",
"github4.png": "github4-move.png",
"github5.png": "github5-move.png",
}
moveOps := make([]string, 0, len(moveKeys))
for srcKey, destKey := range moveKeys {
moveOps = append(moveOps, storage.URIMove(srcBucket, srcKey, destBucket, destKey, force))
}
rets, err := bucketManager.Batch(moveOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
fmt.Printf("%v\n", ret.Data)
}
}
}

View File

@@ -0,0 +1,66 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
"github.com/qiniu/x/rpc.v7"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
//每个batch的操作数量不可以超过1000个如果总数量超过1000需要分批发送
keys := []string{
"github1.png",
"github2.png",
"github3.png",
"github4.png",
"github5.png",
}
statOps := make([]string, 0, len(keys))
for _, key := range keys {
statOps = append(statOps, storage.URIStat(bucket, key))
}
rets, err := bucketManager.Batch(statOps)
if err != nil {
// 遇到错误
if _, ok := err.(*rpc.ErrorInfo); ok {
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
if ret.Code != 200 {
fmt.Printf("%s\n", ret.Data.Error)
} else {
fmt.Printf("%v\n", ret.Data)
}
}
} else {
fmt.Printf("batch error, %s", err)
}
} else {
// 完全成功
for _, ret := range rets {
// 200 为成功
fmt.Printf("%d\n", ret.Code)
fmt.Printf("%v\n", ret.Data)
}
}
}

View File

@@ -0,0 +1,36 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
key := "github.png"
newMime := "image/x-png"
err := bucketManager.ChangeMime(bucket, key, newMime)
if err != nil {
fmt.Println(err)
return
}
}

View File

@@ -0,0 +1,36 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
key := "github.png"
fileType := 1 // 0 表示普通存储1表示低频存储
err := bucketManager.ChangeType(bucket, key, fileType)
if err != nil {
fmt.Println(err)
return
}
}

42
vendor/github.com/qiniu/api.v7/examples/rs_copy.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
srcBucket := "if-pbl"
srcKey := "github.png"
//目标空间可以和源空间相同,但是不能为跨机房的空间
destBucket := srcBucket
//目标文件名可以和源文件名相同,也可以不同
destKey := "github-new.png"
//如果目标文件存在是否强制覆盖如果不覆盖默认返回614 file exists
force := false
err := bucketManager.Copy(srcBucket, srcKey, destBucket, destKey, force)
if err != nil {
fmt.Println(err)
return
}
}

35
vendor/github.com/qiniu/api.v7/examples/rs_delete.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
key := "github.png"
err := bucketManager.Delete(bucket, key)
if err != nil {
fmt.Println(err)
return
}
}

View File

@@ -0,0 +1,36 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
key := "github.png"
days := 7
err := bucketManager.DeleteAfterDays(bucket, key, days)
if err != nil {
fmt.Println(err)
return
}
}

33
vendor/github.com/qiniu/api.v7/examples/rs_download.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"fmt"
"os"
"time"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
// 公开空间访问
domain := "https://image.example.com"
key := "这是一个测试文件.jpg"
publicAccessURL := storage.MakePublicURL(domain, key)
fmt.Println(publicAccessURL)
// 私有空间访问
domain = "https://image.example.com"
key = "这是一个测试文件.jpg"
deadline := time.Now().Add(time.Second * 3600).Unix() //1小时有效期
privateAccessURL := storage.MakePrivateURL(mac, domain, key, deadline)
fmt.Println(privateAccessURL)
}

44
vendor/github.com/qiniu/api.v7/examples/rs_fetch.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
resURL := "http://devtools.qiniu.com/qiniu.png"
// 指定保存的key
fetchRet, err := bucketManager.Fetch(resURL, bucket, "qiniu.png")
if err != nil {
fmt.Println("fetch error,", err)
} else {
fmt.Println(fetchRet.String())
}
// 不指定保存的key默认用文件hash作为文件名
fetchRet, err = bucketManager.FetchWithoutKey(resURL, bucket)
if err != nil {
fmt.Println("fetch error,", err)
} else {
fmt.Println(fetchRet.String())
}
}

View File

@@ -0,0 +1,51 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
limit := 1000
prefix := "qiniu"
delimiter := ""
//初始列举marker为空
marker := ""
for {
entries, _, nextMarker, hashNext, err := bucketManager.ListFiles(bucket, prefix, delimiter, marker, limit)
if err != nil {
fmt.Println("list error,", err)
break
}
//print entries
for _, entry := range entries {
fmt.Println(entry.Key)
}
if hashNext {
marker = nextMarker
} else {
//list end
break
}
}
}

42
vendor/github.com/qiniu/api.v7/examples/rs_move.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
srcBucket := bucket
srcKey := "github.png"
//目标空间可以和源空间相同,但是不能为跨机房的空间
destBucket := srcBucket
//目标文件名可以和源文件名相同,也可以不同
destKey := "github-new.png"
//如果目标文件存在是否强制覆盖如果不覆盖默认返回614 file exists
force := false
err := bucketManager.Move(srcBucket, srcKey, destBucket, destKey, force)
if err != nil {
fmt.Println(err)
return
}
}

34
vendor/github.com/qiniu/api.v7/examples/rs_prefetch.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
key := "qiniu.png"
err := bucketManager.Prefetch(bucket, key)
if err != nil {
fmt.Println("fetch error,", err)
}
}

38
vendor/github.com/qiniu/api.v7/examples/rs_stat.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"fmt"
"os"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
bucketManager := storage.NewBucketManager(mac, &cfg)
key := "qiniu.png"
fileInfo, sErr := bucketManager.Stat(bucket, key)
if sErr != nil {
fmt.Println(sErr)
return
}
fmt.Println(fileInfo.String())
//可以解析文件的PutTime
fmt.Println(storage.ParsePutTime(fileInfo.PutTime))
}

52
vendor/github.com/qiniu/api.v7/examples/video_pfop.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package main
import (
"encoding/base64"
"fmt"
"os"
"strings"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var (
accessKey = os.Getenv("QINIU_ACCESS_KEY")
secretKey = os.Getenv("QINIU_SECRET_KEY")
bucket = os.Getenv("QINIU_TEST_BUCKET")
// 数据处理的私有队列,必须指定以保障处理速度
pipeline = os.Getenv("QINIU_TEST_PIPELINE")
)
func main() {
mac := qbox.NewMac(accessKey, secretKey)
cfg := storage.Config{
UseHTTPS: true,
}
// 指定空间所在的区域,如果不指定将自动探测
// 如果没有特殊需求,默认不需要指定
//cfg.Zone=&storage.ZoneHuabei
operationManager := storage.NewOperationManager(mac, &cfg)
key := "qiniu.mp4"
saveBucket := bucket
// 处理指令集合
fopAvthumb := fmt.Sprintf("avthumb/mp4/s/480x320/vb/500k|saveas/%s",
storage.EncodedEntry(saveBucket, "pfop_test_qiniu.mp4"))
fopVframe := fmt.Sprintf("vframe/jpg/offset/10|saveas/%s",
storage.EncodedEntry(saveBucket, "pfop_test_qiniu.jpg"))
fopVsample := fmt.Sprintf("vsample/jpg/interval/20/pattern/%s",
base64.URLEncoding.EncodeToString([]byte("pfop_test_$(count).jpg")))
fopBatch := []string{fopAvthumb, fopVframe, fopVsample}
fops := strings.Join(fopBatch, ";")
// 强制重新执行数据处理任务
force := true
// 数据处理指令全部完成之后,通知该地址
notifyURL := "http://api.example.com/pfop/callback"
// 数据处理的私有队列,必须指定以保障处理速度
persistentId, err := operationManager.Pfop(bucket, key, fops, pipeline, notifyURL, force)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(persistentId)
}

View File

@@ -1,435 +0,0 @@
package kodo
import (
. "context"
"encoding/base64"
"fmt"
"io"
"net/url"
"strconv"
"github.com/qiniu/api.v7/api"
"github.com/qiniu/x/log.v7"
)
// ----------------------------------------------------------
// Batch 批量操作。
//
func (p *Client) Batch(ctx Context, ret interface{}, op []string) (err error) {
return p.CallWithForm(ctx, ret, "POST", p.RSHost+"/batch", map[string][]string{"op": op})
}
// ----------------------------------------------------------
type Bucket struct {
api.BucketInfo
Conn *Client
Name string
}
// Buckets 获取所有地区的所有空间(bucket)
//
// shared 是否获取所有授权获得空间true为包含授权空间
//
func (p *Client) Buckets(ctx Context, shared bool) (buckets []string, err error) {
if shared {
err = p.Call(ctx, &buckets, "POST", p.RSHost+"/buckets?shared=trye")
} else {
err = p.Call(ctx, &buckets, "POST", p.RSHost+"/buckets")
}
return
}
// Bucket 取七牛空间bucket的对象实例。
//
// name 是创建该七牛空间bucket时采用的名称。
//
func (p *Client) Bucket(name string) Bucket {
b, err := p.BucketWithSafe(name)
if err != nil {
log.Errorf("Bucket(%s) failed: %+v", name, err)
}
return b
}
// BucketWithSafe 确认空间存在并获取七牛空间bucket的对象实例。
func (p *Client) BucketWithSafe(name string) (Bucket, error) {
var info api.BucketInfo
if len(p.UpHosts) == 0 {
var err error
info, err = p.apiCli.GetBucketInfo(p.mac.AccessKey, name)
if err != nil {
return Bucket{}, err
}
} else {
info.IoHost = p.IoHost
info.UpHosts = p.UpHosts
}
return Bucket{info, p, name}, nil
}
// Entry 资源元信息
type Entry struct {
Hash string `json:"hash"`
Fsize int64 `json:"fsize"`
PutTime int64 `json:"putTime"`
MimeType string `json:"mimeType"`
Type int `json:"type"`
EndUser string `json:"endUser"`
}
// Stat 取文件属性。
//
// ctx 是请求的上下文。
// key 是要访问的文件的访问路径。
//
func (p Bucket) Stat(ctx Context, key string) (entry Entry, err error) {
err = p.Conn.Call(ctx, &entry, "POST", p.Conn.RSHost+URIStat(p.Name, key))
return
}
// Delete 删除一个文件。
//
// ctx 是请求的上下文。
// key 是要删除的文件的访问路径。
//
func (p Bucket) Delete(ctx Context, key string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIDelete(p.Name, key))
}
// Move 移动一个文件。
//
// ctx 是请求的上下文。
// keySrc 是要移动的文件的旧路径。
// keyDest 是要移动的文件的新路径。
//
func (p Bucket) Move(ctx Context, keySrc, keyDest string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIMove(p.Name, keySrc, p.Name, keyDest))
}
// MoveEx 跨空间bucket移动一个文件。
//
// ctx 是请求的上下文。
// keySrc 是要移动的文件的旧路径。
// bucketDest 是文件的目标空间。
// keyDest 是要移动的文件的新路径。
//
func (p Bucket) MoveEx(ctx Context, keySrc, bucketDest, keyDest string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIMove(p.Name, keySrc, bucketDest, keyDest))
}
// Copy 复制一个文件。
//
// ctx 是请求的上下文。
// keySrc 是要复制的文件的源路径。
// keyDest 是要复制的文件的目标路径。
//
func (p Bucket) Copy(ctx Context, keySrc, keyDest string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URICopy(p.Name, keySrc, p.Name, keyDest))
}
// ChangeMime 修改文件的MIME类型。
//
// ctx 是请求的上下文。
// key 是要修改的文件的访问路径。
// mime 是要设置的新MIME类型。
//
func (p Bucket) ChangeMime(ctx Context, key, mime string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIChangeMime(p.Name, key, mime))
}
// ChangeType 修改文件的存储类型。
//
// ctx 是请求的上下文。
// key 是要修改的文件的访问路径。
// fileType 是要设置的新存储类型。0 表示标准存储1 表示低频存储。
//
func (p Bucket) ChangeType(ctx Context, key string, fileType int) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIChangeType(p.Name, key, fileType))
}
// Fetch 从网上抓取一个资源并存储到七牛空间bucket中。
//
// ctx 是请求的上下文。
// key 是要存储的文件的访问路径。如果文件已经存在则覆盖。
// url 是要抓取的资源的URL。
//
func (p Bucket) Fetch(ctx Context, key string, url string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.IoHost+uriFetch(p.Name, key, url))
}
// DeleteAfterDays 更新文件生命周期
//
// ctx 是请求的上下文。
// key 是要更新的文件的访问路径。
// deleteAfterDays 设置为0表示取消 lifecycle
//
func (p Bucket) DeleteAfterDays(ctx Context, key string, days int) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIDeleteAfterDays(p.Name, key, days))
}
// Image 设置镜像源
//
// srcSiteURL 镜像源的访问域名。必须设置为形如 `http://source.com/` 或 `http://114.114.114.114/` 的字符串
// host 回源时使用的 Host 头部值
//
// 镜像源地址支持两种格式:
// 格式 1`http(s)://绑定域名/源站资源相对路径`
// 格式 2`http(s)://绑定 IP/源站资源相对路径`
//
func (p Bucket) Image(ctx Context, srcSiteURL, host string) (err error) {
return p.Conn.Call(ctx, nil, "POST", "http://pu.qbox.me:10200"+URIImage(p.Name, srcSiteURL, host))
}
// UnImage 取消镜像源
//
func (p Bucket) UnImage(ctx Context) (err error) {
return p.Conn.Call(ctx, nil, "POST", "http://pu.qbox.me:10200"+URIUnImage(p.Name))
}
// Prefetch 镜像资源更新
//
// key 被抓取资源名称
//
func (p Bucket) Prefetch(ctx Context, key string) (err error) {
return p.Conn.Call(ctx, nil, "POST", p.Conn.IoHost+URIPrefetch(p.Name, key))
}
// PfopResult pfop返回信息
type PfopResult struct {
PersistentID string `json:"persistentId,omitempty"`
}
// FopRet 持久化云处理结果
type FopRet struct {
ID string `json:"id"`
Code int `json:"code"`
Desc string `json:"desc"`
InputBucket string `json:"inputBucket,omitempty"`
InputKey string `json:"inputKey,omitempty"`
Pipeline string `json:"pipeline,omitempty"`
Reqid string `json:"reqid,omitempty"`
Items []FopResult
}
// FopResult 云处理操作列表,包含每个云处理操作的状态信息
type FopResult struct {
Cmd string `json:"cmd"`
Code int `json:"code"`
Desc string `json:"desc"`
Error string `json:"error,omitempty"`
Hash string `json:"hash,omitempty"`
Key string `json:"key,omitempty"`
Keys []string `json:"keys,omitempty"`
}
// Pfop 持久化数据处理
//
// bucket 资源空间
// key 源资源名
// fops 云处理操作列表,用`;``分隔,如:`avthumb/flv;saveas/cWJ1Y2tldDpxa2V5`是将上传的视频文件转码成flv格式后存储为 qbucket:qkey ,其中 cWJ1Y2tldDpxa2V5 是 qbucket:qkey 的URL安全的Base64编码结果。
// notifyURL 处理结果通知接收 URL七牛将会向你设置的 URL 发起 Content-Type: application/json 的 POST 请求。
// pipeline 为空则表示使用公用队列,处理速度比较慢。建议指定私有队列,转码的时候使用独立的计算资源。
// force 强制执行数据处理。当服务端发现 fops 指定的数据处理结果已经存在,那就认为已经处理成功,避免重复处理浪费资源。本字段设为 `true`,则可强制执行数据处理并覆盖原结果。
//
func (p *Client) Pfop(ctx Context, bucket, key, fops, notifyURL, pipeline string, force bool) (persistentID string, err error) {
pfopParams := map[string][]string{
"bucket": []string{bucket},
"key": []string{key},
"fops": []string{fops},
}
if notifyURL != "" {
pfopParams["notifyURL"] = []string{notifyURL}
}
if pipeline != "" {
pfopParams["pipeline"] = []string{pipeline}
}
if force {
pfopParams["force"] = []string{"1"}
}
var ret PfopResult
err = p.CallWithForm(ctx, &ret, "POST", "http://api.qiniu.com/pfop/", pfopParams)
if err != nil {
return
}
persistentID = ret.PersistentID
return
}
// Prefop 持久化处理状态查询
func (p *Client) Prefop(ctx Context, persistentID string) (ret FopRet, err error) {
err = p.Call(ctx, &ret, "GET", "http://api.qiniu.com/status/get/prefop?id="+persistentID)
return
}
// ----------------------------------------------------------
// ListItem List借口返回结果
type ListItem struct {
Key string `json:"key"`
Hash string `json:"hash"`
Fsize int64 `json:"fsize"`
PutTime int64 `json:"putTime"`
MimeType string `json:"mimeType"`
EndUser string `json:"endUser"`
}
// List 首次请求,请将 marker 设置为 ""。
// 无论 err 值如何,均应该先看 entries 是否有内容。
// 如果后续没有更多数据err 返回 EOFmarkerOut 返回 ""(但不通过该特征来判断是否结束)。
//
func (p Bucket) List(
ctx Context, prefix, delimiter, marker string, limit int) (entries []ListItem, commonPrefixes []string, markerOut string, err error) {
listUrl := p.makeListURL(prefix, delimiter, marker, limit)
var listRet struct {
Marker string `json:"marker"`
Items []ListItem `json:"items"`
Prefixes []string `json:"commonPrefixes"`
}
err = p.Conn.Call(ctx, &listRet, "POST", listUrl)
if err != nil {
return
}
if listRet.Marker == "" {
return listRet.Items, listRet.Prefixes, "", io.EOF
}
return listRet.Items, listRet.Prefixes, listRet.Marker, nil
}
func (p Bucket) makeListURL(prefix, delimiter, marker string, limit int) string {
query := make(url.Values)
query.Add("bucket", p.Name)
if prefix != "" {
query.Add("prefix", prefix)
}
if delimiter != "" {
query.Add("delimiter", delimiter)
}
if marker != "" {
query.Add("marker", marker)
}
if limit > 0 {
query.Add("limit", strconv.FormatInt(int64(limit), 10))
}
return p.Conn.RSFHost + "/list?" + query.Encode()
}
// ----------------------------------------------------------
type BatchStatItemRet struct {
Data Entry `json:"data"`
Error string `json:"error"`
Code int `json:"code"`
}
// BatchStat 批量取文件属性
func (p Bucket) BatchStat(ctx Context, keys ...string) (ret []BatchStatItemRet, err error) {
b := make([]string, len(keys))
for i, key := range keys {
b[i] = URIStat(p.Name, key)
}
err = p.Conn.Batch(ctx, &ret, b)
return
}
type BatchItemRet struct {
Error string `json:"error"`
Code int `json:"code"`
}
// BatchDelete 批量删除
func (p Bucket) BatchDelete(ctx Context, keys ...string) (ret []BatchItemRet, err error) {
b := make([]string, len(keys))
for i, key := range keys {
b[i] = URIDelete(p.Name, key)
}
err = p.Conn.Batch(ctx, &ret, b)
return
}
type KeyPair struct {
Src string
Dest string
}
// BatchMove 批量移动文件
func (p Bucket) BatchMove(ctx Context, entries ...KeyPair) (ret []BatchItemRet, err error) {
b := make([]string, len(entries))
for i, e := range entries {
b[i] = URIMove(p.Name, e.Src, p.Name, e.Dest)
}
err = p.Conn.Batch(ctx, &ret, b)
return
}
// BatchCopy 批量复制文件
func (p Bucket) BatchCopy(ctx Context, entries ...KeyPair) (ret []BatchItemRet, err error) {
b := make([]string, len(entries))
for i, e := range entries {
b[i] = URICopy(p.Name, e.Src, p.Name, e.Dest)
}
err = p.Conn.Batch(ctx, &ret, b)
return
}
// ----------------------------------------------------------
func encodeURI(uri string) string {
return base64.URLEncoding.EncodeToString([]byte(uri))
}
func uriFetch(bucket, key, url string) string {
return "/fetch/" + encodeURI(url) + "/to/" + encodeURI(bucket+":"+key)
}
func URIDelete(bucket, key string) string {
return "/delete/" + encodeURI(bucket+":"+key)
}
func URIStat(bucket, key string) string {
return "/stat/" + encodeURI(bucket+":"+key)
}
func URICopy(bucketSrc, keySrc, bucketDest, keyDest string) string {
return "/copy/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
}
func URIMove(bucketSrc, keySrc, bucketDest, keyDest string) string {
return "/move/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
}
func URIChangeMime(bucket, key, mime string) string {
return "/chgm/" + encodeURI(bucket+":"+key) + "/mime/" + encodeURI(mime)
}
func URIChangeType(bucket, key string, fileType int) string {
return "/chtype/" + encodeURI(bucket+":"+key) + "/type/" + strconv.Itoa(fileType)
}
func URIDeleteAfterDays(bucket, key string, days int) string {
return fmt.Sprintf("/deleteAfterDays/%s/%d", encodeURI(bucket+":"+key), days)
}
func URIImage(bucket, srcSiteURL, host string) string {
return fmt.Sprintf("/image/%s/from/%s/host/%s", bucket, encodeURI(srcSiteURL), encodeURI(host))
}
func URIUnImage(bucket string) string {
return fmt.Sprintf("/unimage/%s", bucket)
}
func URIPrefetch(bucket, key string) string {
return fmt.Sprintf("/prefetch/%s", encodeURI(bucket+":"+key))
}
// ----------------------------------------------------------

View File

@@ -1,167 +0,0 @@
package kodo
import (
"context"
"math/rand"
"strconv"
"testing"
"time"
)
var (
batchTestKey = "abatch"
batchTestNewKey1 = "abatch/newkey1"
batchTestNewKey2 = "abatch/newkey2"
)
func init() {
if skipTest() {
return
}
rand.Seed(time.Now().UnixNano())
batchTestKey += strconv.Itoa(rand.Int())
batchTestNewKey1 += strconv.Itoa(rand.Int())
batchTestNewKey2 += strconv.Itoa(rand.Int())
// 删除 可能存在的 key
bucket.BatchDelete(nil, batchTestKey, batchTestNewKey1, batchTestNewKey2)
}
func TestAll(t *testing.T) {
if skipTest() {
return
}
//上传一个文件用用于测试
err := upFile("bucket_test.go", batchTestKey)
if err != nil {
t.Fatal(err)
}
defer bucket.Delete(nil, batchTestKey)
testBatchStat(t)
testBatchCopy(t)
testBatchMove(t)
testBatchDelete(t)
testBatch(t)
testClient_MakeUptokenBucket(t)
testDeleteAfterDays(t)
}
func testBatchStat(t *testing.T) {
rets, err := bucket.BatchStat(nil, batchTestKey, batchTestKey, batchTestKey)
if err != nil {
t.Fatal("bucket.BatchStat failed:", err)
}
if len(rets) != 3 {
t.Fatal("BatchStat failed: len(rets) = ", 3)
}
stat, err := bucket.Stat(nil, batchTestKey)
if err != nil {
t.Fatal("bucket.Stat failed:", err)
}
if rets[0].Data != stat || rets[1].Data != stat || rets[2].Data != stat {
t.Fatal("BatchStat failed : returns err")
}
}
func testBatchMove(t *testing.T) {
stat0, err := bucket.Stat(nil, batchTestKey)
if err != nil {
t.Fatal("BathMove get stat failed:", err)
}
_, err = bucket.BatchMove(nil, KeyPair{batchTestKey, batchTestNewKey1}, KeyPair{batchTestNewKey1, batchTestNewKey2})
if err != nil {
t.Fatal("bucket.BatchMove failed:", err)
}
defer bucket.Move(nil, batchTestNewKey2, batchTestKey)
stat1, err := bucket.Stat(nil, batchTestNewKey2)
if err != nil {
t.Fatal("BathMove get stat failed:", err)
}
if stat0.Hash != stat1.Hash {
t.Fatal("BatchMove failed : Move err", stat0, stat1)
}
}
func testBatchCopy(t *testing.T) {
_, err := bucket.BatchCopy(nil, KeyPair{batchTestKey, batchTestNewKey1}, KeyPair{batchTestKey, batchTestNewKey2})
if err != nil {
t.Fatal(err)
}
defer bucket.Delete(nil, batchTestNewKey1)
defer bucket.Delete(nil, batchTestNewKey2)
stat0, _ := bucket.Stat(nil, batchTestKey)
stat1, _ := bucket.Stat(nil, batchTestNewKey1)
stat2, _ := bucket.Stat(nil, batchTestNewKey2)
if stat0.Hash != stat1.Hash || stat0.Hash != stat2.Hash {
t.Fatal("BatchCopy failed : Copy err")
}
}
func testBatchDelete(t *testing.T) {
bucket.Copy(nil, batchTestKey, batchTestNewKey1)
bucket.Copy(nil, batchTestKey, batchTestNewKey2)
_, err := bucket.BatchDelete(nil, batchTestNewKey1, batchTestNewKey2)
if err != nil {
t.Fatal(err)
}
_, err1 := bucket.Stat(nil, batchTestNewKey1)
_, err2 := bucket.Stat(nil, batchTestNewKey2)
//这里 err1 != nil否则文件没被成功删除
if err1 == nil || err2 == nil {
t.Fatal("BatchDelete failed : File do not delete")
}
}
func testBatch(t *testing.T) {
ops := []string{
URICopy(bucketName, batchTestKey, bucketName, batchTestNewKey1),
URIDelete(bucketName, batchTestKey),
URIMove(bucketName, batchTestNewKey1, bucketName, batchTestKey),
}
var rets []BatchItemRet
err := client.Batch(nil, &rets, ops)
if err != nil {
t.Fatal(err)
}
if len(rets) != 3 {
t.Fatal("len(rets) != 3")
}
}
func testDeleteAfterDays(t *testing.T) {
ctx := context.Background()
err := bucket.DeleteAfterDays(ctx, batchTestNewKey1, 5)
if err == nil {
t.Fatal("Expect an error")
}
bucket.Copy(ctx, batchTestKey, batchTestNewKey1)
err = bucket.DeleteAfterDays(ctx, batchTestNewKey1, 5)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,52 +0,0 @@
/*
包 github.com/qiniu/api.v7/kodo 提供了在您的业务服务器(服务端)调用七牛云存储服务的能力
首先,我们要配置下 AccessKey/SecretKey这可以在七牛 Portal 中查到:
kodo.SetMac("your-access-key", "your-secret-key")
然后我们创建一个 Client 对象:
zone := kodo.ZoneZ0 // 您空间(Bucket)所在的区域
c := kodo.New(zone, nil) // 用默认配置创建 Client
有了 Client你就可以操作您的空间(Bucket)了,比如我们要上传一个文件:
import "golang.org/x/net/context"
bucket := c.Bucket("your-bucket-name")
ctx := context.Background()
...
localFile := "/your/local/image/file.jpg"
err := bucket.PutFile(ctx, nil, "foo/bar.jpg", localFile, nil)
if err != nil {
... // 上传文件失败处理
return
}
// 上传文件成功
// 这时登录七牛Portal在 your-bucket-name 空间就可以看到一个 foo/bar.jpg 的文件了
当然,除了上传文件,各种空间(Bucket)相关的操作都可以有,最常见自然是增删改查了:
entry, err := bucket.Stat(ctx, "foo/bar.jpg") // 看看空间中是否存在某个文件,其属性是什么
bucket.Delete(ctx, "foo/bar.jpg") // 删除空间中的某个文件
bucket.ChangeMime(ctx, "foo/bar.jpg", "image/jpeg") // 修改某个文件的 MIME 属性
bucket.Move(ctx, "foo/bar.jpg", "new-name.jpg") // 移动文件
bucket.Copy(ctx, "foo/bar.jpg", "new-copy-file.jpg") // 复制文件
等等... 请问怎么下载文件?如果是公开文件,我们只需要:
import "net/http"
domain := "domain-of-your-bucket.com" // 您的空间绑定的域名这个可以在七牛的Portal中查到
baseUrl := kodo.MakeBaseUrl(domain, "foo/bar.jpg") // 得到下载 url
resp, err := http.Get(baseUrl)
...
但是对于私有空间,事情要复杂一些,访问上面的 baseUrl 会被拒绝。我们需要多做一步:
privateUrl := c.MakePrivateUrl(baseUrl, nil) // 用默认的下载策略去生成私有下载的 url
resp, err := http.Get(privateUrl)
...
*/
package kodo

View File

@@ -1,154 +0,0 @@
package kodo
import (
"net/http"
"github.com/qiniu/api.v7/api"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/conf"
"github.com/qiniu/x/rpc.v7"
)
// ----------------------------------------------------------
type zoneConfig struct {
IoHost string
UpHosts []string
}
const (
// ZoneZ0 华东机房
ZoneZ0 = iota
// ZoneZ1 华北机房
ZoneZ1
// ZoneZ2 华南机房
ZoneZ2
// ZoneNa0 北美机房
ZoneNa0
)
var zones = []zoneConfig{
// z0 华东机房:
{
IoHost: "http://iovip.qbox.me",
UpHosts: []string{
"http://up.qiniu.com",
"http://upload.qiniu.com",
"-H up.qiniu.com http://183.136.139.16",
},
},
// z1 华北机房:
{
IoHost: "http://iovip-z1.qbox.me",
UpHosts: []string{
"http://up-z1.qiniu.com",
"http://upload-z1.qiniu.com",
"-H up-z1.qiniu.com http://106.38.227.27",
},
},
// z2 华南机房:
{
IoHost: "http://iovip-z2.qbox.me",
UpHosts: []string{
"http://up-z2.qiniu.com",
"http://upload-z2.qiniu.com",
},
},
// na0 北美机房:
{
IoHost: "http://iovip-na0.qbox.me",
UpHosts: []string{
"http://up-na0.qiniu.com",
"http://upload-na0.qiniu.com",
},
},
}
const (
defaultRsHost = "http://rs.qbox.me"
defaultRsfHost = "http://rsf.qbox.me"
)
// ----------------------------------------------------------
type Config struct {
AccessKey string
SecretKey string
RSHost string
RSFHost string
APIHost string
Scheme string
IoHost string
UpHosts []string
Transport http.RoundTripper
}
// ----------------------------------------------------------
type Client struct {
rpc.Client
mac *qbox.Mac
Config
apiCli *api.Client
}
func New(zone int, cfg *Config) (p *Client) {
p = new(Client)
if cfg != nil {
p.Config = *cfg
}
p.mac = qbox.NewMac(p.AccessKey, p.SecretKey)
p.Client = rpc.Client{qbox.NewClient(p.mac, p.Transport)}
if p.RSHost == "" {
p.RSHost = defaultRsHost
}
if p.RSFHost == "" {
p.RSFHost = defaultRsfHost
}
if p.Scheme != "https" {
p.Scheme = "http"
}
if p.APIHost == "" {
p.APIHost = api.DefaultApiHost
}
p.apiCli = api.NewClient(p.APIHost, p.Scheme)
if zone < 0 || zone >= len(zones) {
return
}
if len(p.UpHosts) == 0 {
p.UpHosts = zones[zone].UpHosts
}
if p.IoHost == "" {
p.IoHost = zones[zone].IoHost
}
return
}
func NewWithoutZone(cfg *Config) (p *Client) {
return New(-1, cfg)
}
// ----------------------------------------------------------
// 设置全局默认的 ACCESS_KEY, SECRET_KEY 变量。
//
func SetMac(accessKey, secretKey string) {
conf.ACCESS_KEY, conf.SECRET_KEY = accessKey, secretKey
}
// ----------------------------------------------------------
// 设置使用这个SDK的应用程序名。userApp 必须满足 [A-Za-z0-9_\ \-\.]*
//
func SetAppName(userApp string) error {
return conf.SetAppName(userApp)
}
// ----------------------------------------------------------

View File

@@ -1,151 +0,0 @@
package kodo
import (
"fmt"
"math/rand"
"os"
"strconv"
"testing"
"time"
)
var (
key = "aa"
keyFetch = "afetch"
newkey1 = "bbbb"
newkey2 = "cccc"
fetchURL = "http://www-static.u.qiniucdn.com/public/v1645/img/css-sprite.png"
bucketName string
domain string
client *Client
bucket = newBucket()
QINIU_KODO_TEST string
)
func init() {
if skipTest() {
return
}
rand.Seed(time.Now().UnixNano())
key += strconv.Itoa(rand.Int())
keyFetch += strconv.Itoa(rand.Int())
newkey1 += strconv.Itoa(rand.Int())
newkey2 += strconv.Itoa(rand.Int())
bucket.BatchDelete(nil, key, keyFetch, newkey1, newkey2)
}
func newBucket() (bucket Bucket) {
QINIU_KODO_TEST = os.Getenv("QINIU_KODO_TEST")
if skipTest() {
println("[INFO] QINIU_KODO_TEST: skipping to test github.com/qiniu/api.v7")
return
}
ak := os.Getenv("QINIU_ACCESS_KEY")
sk := os.Getenv("QINIU_SECRET_KEY")
if ak == "" || sk == "" {
panic("require ACCESS_KEY & SECRET_KEY")
}
SetMac(ak, sk)
bucketName = os.Getenv("QINIU_TEST_BUCKET")
domain = os.Getenv("QINIU_TEST_DOMAIN")
if bucketName == "" || domain == "" {
panic("require test env")
}
client = NewWithoutZone(nil)
return client.Bucket(bucketName)
}
func skipTest() bool {
return QINIU_KODO_TEST == ""
}
func upFile(localFile, key string) error {
return bucket.PutFile(nil, nil, key, localFile, nil)
}
func TestFetch(t *testing.T) {
if skipTest() {
return
}
err := bucket.Fetch(nil, keyFetch, fetchURL)
if err != nil {
t.Fatal("bucket.Fetch failed:", err)
}
entry, err := bucket.Stat(nil, keyFetch)
if err != nil || entry.MimeType != "image/png" {
t.Fatal("bucket.Fetch: Stat failed -", err, "entry:", entry)
}
fmt.Println(entry)
}
func TestEntry(t *testing.T) {
if skipTest() {
return
}
//上传一个文件用用于测试
err := upFile("doc.go", key)
if err != nil {
t.Fatal(err)
}
defer bucket.Delete(nil, key)
einfo, err := bucket.Stat(nil, key)
if err != nil {
t.Fatal(err)
}
mime := "text/plain"
err = bucket.ChangeMime(nil, key, mime)
if err != nil {
t.Fatal(err)
}
einfo, err = bucket.Stat(nil, key)
if err != nil {
t.Fatal(err)
}
if einfo.MimeType != mime {
t.Fatal("mime type did not change")
}
err = bucket.Copy(nil, key, newkey1)
if err != nil {
t.Fatal(err)
}
enewinfo, err := bucket.Stat(nil, newkey1)
if err != nil {
t.Fatal(err)
}
if einfo.Hash != enewinfo.Hash {
t.Fatal("invalid entryinfo:", einfo, enewinfo)
}
err = bucket.Move(nil, newkey1, newkey2)
if err != nil {
t.Fatal(err)
}
enewinfo2, err := bucket.Stat(nil, newkey2)
if err != nil {
t.Fatal(err)
}
if enewinfo.Hash != enewinfo2.Hash {
t.Fatal("invalid entryinfo:", enewinfo, enewinfo2)
}
err = bucket.Delete(nil, newkey2)
if err != nil {
t.Fatal(err)
}
}

Some files were not shown because too many files have changed in this diff Show More