搞事情系列文章主要是为了继续延续自己的 “T” 字形战略所做,同时也代表着毕设相关内容的学习总结。本文章主要是关于使用七牛云作为图床时第一次对接时所遇到问题的总结,整体来看,七牛在作为私有图床是一个比较合适的选择。

前言

本博客最初所采用的图床就是七牛,当时因为第一次使用图床之类的服务,没有进行一个比较好的筛选,并且没有考虑过多的细节,所以直接采用了七牛。经过一段时间后,因为博客访问量上去了,超出七牛每月的免费流量额度,平均每个月花费在 10 元左右。

因为写博客是一件非常费脑费精力的事情,再纠结钱就十分的没意思了,遂开始了寻找一个免费既好用的图床。一番搜寻后,发现了这个图床,用了快一年。在这一年的时间中从未出现过任何的异常情况,而且也提供 http 服务,原本打算把毕设相关的图片资源也使用这个图床,但考虑到该图床所提供的 http 服务能了较为有限,遂放弃。

最后继续调研,看了又拍云、七牛云、阿里云 OSS 、腾讯云 OSS,最后对比完还是选择了七牛云。这里要强烈向大家图床阿里云 OSS 服务,调用方式及其繁琐!仅仅至少上传一个图片而已,所写代码之啰嗦,实在难以让我提起兴趣。更遗憾的是,所有的 SDK 均未提供 Swift 版本,这都 9102 年了!!!

准备工作

七牛云的图床免费流量额度每个月只有 10 GB,超出这个范围后就会进行计费,团队初步估计 pv/uv 再怎么次,每天至少都会稳定在百人左右,所以要做一定的防刷手段。

存储区域

这部分的内容主要针对进行上传资源时提升服务的可靠性,如果产品的上传策略是图片先过一遍自己的服务器再上传至七牛,需要根据服务器架设地点进行选择;如果产品策略是客户端上传,则考虑目标群体大部分所在区域。不过就目前情况来看,PIGPEN 因为只涉及到图片资源的上传和下载,且大部分用户在华北地区,故选择了华北地区。当然,最后的收费也会提升一些。

空间选择

七牛云提供了公开空间私有空间

  • 公开空间:可通过文件对象的 URL 直接访问;
  • 私有空间:文件对象的访问则必须获得拥有者的授权才能访问。

公开空间,符合博客、社区、论坛等属性产品的定位,但不适合做内容闭环的产品,比如微信、QQ 以及 PIGPEN 等,并不希望产品中所产生的内容被产品外的用户所知,反而私有空间是正确选择,可对需要访问的资源设置 IP 访问次数和黑白名单限制、超时限制、浏览器是否能直接访问等等防刷手段。PIGPNE 选择了私有空间。

域名管理

加速域名

七牛云的测试域名 30 天后会进行自动回收,所以各位同学确定一定要使用七牛云作为图床后,请尽快配对加速域名。该加速域名要求必须通过中国大陆 ICP 备案。推荐重新配置一个新的二级域名作为七牛云的加速域名。

需要注意的是,配置完加速域名后,七牛会让我们增加一条 CNAME 解析,当访问这个域名时,实际上转发给记录值中所填写的真实域名。配置完毕后,如下图所示:

在腾讯云中配置的七牛云加速域名.png

假设我们在七牛上配置完毕加速域名,生成的加速域名为 aha.pjhubs.com.qiniudns.com,在“主机记录”部分,需要填入的应该是我们所新增的二级域名 aha。配置妥当后,一般等待十分钟左右再去七牛控制台中查看相关信息即可。

访问控制

对于资源计费的图床,最担心的就是资源被刷了,好在七牛在四种访问控制的方法:Referer 防盗链时间戳防盗链回源鉴权IP 黑白名单

Referer 防盗链

Referer 防盗链:这里的 Referer 指的是 HTTP 头部的一个字段,也称为 HTTP 来源地址(HTTP Referer),用来表示从哪儿链接到目前的网页,采用的格式是 URL。换句话说,借着 HTTP Referer 头部网页可以检查访客从哪里而来,这也常被用来对付伪造的跨网站请求。(来源七牛官方文档)

如果我们直接通过浏览器打开一个链接,此时该请求的 Referer 字段为空,这是一个“凭空产生”的 HTTP 请求,并不是从一个地方链接过去的。

经过一番的思考,设置白名单且允许空 Referer 进行访问。原意是想着不允许空 Referer 访问的,但是在客户端上对图片资源发起的请求就是空 Referer 啊~遂放弃。

IP 黑白名单

这是解决 IP 盗刷的最终解决手段,但目前还未遇到真实盗刷场景,后续再补~

图片处理

七牛居然非常贴心的提供了一定的图片处理功能,对于一个内容产出方,最需要的就是图片资源的各种缩略图、统一水印等等功能,而且全部都是免费功能!!!

七牛的图片处理.png

实际操作

整套七牛的图片上传流程可用下图进行概括:
七牛云私有空间图片访问全流程.png

后端

PIGPEN 整体后端都是基于 python 技术栈,所以选择了七牛云的 python SDK。通过 pip3 install qiniu 后,根据已有业务编写了以下两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from qiniu import Auth

def create_upload_image_token(count, key):
"""
七牛不支持多图上传,根据官方文档描述,只能在业务层循环针对每个图生成对应 token
:param count: 需要生成的 token 个数
:param key: 文件名前缀
:return 生成的 token 列表
"""

jsons = []
while count > 0:
# 构建鉴权对象
q = Auth(settings.QINIU_ACCESS_KEY, settings.QINIU_SECRET_KEY)
# 要上传的空间
bucket_name = '你的私有空间名'
# 文件名
k = bucket_name + key + str(int(time.time())) + str(count) + '.jpeg'
token = q.upload_token(bucket_name, k)

json = {
'img_token': token,
'img_key': k
}

jsons.append(json)
count -= 1

return jsons


def create_full_image_url(keys):
"""
拼接获取完成后的图片 url
:param keys: 从客户端发送来的 keys,遍历出的每一个 key 代表一个文件名
:return image_urls: 返回拼接完成后的图片 url 数组
"""
q = Auth(settings.QINIU_ACCESS_KEY, settings.QINIU_SECRET_KEY)
bucket_name = 'pigpenimg.pjhubs.com'

image_urls = []
for index, key in enumerate(keys):
base_url = 'http://%s/%s' % (bucket_name, key)
private_url = q.private_download_url(base_url, expires=3600)

image_urls.append(private_url)

return image_urls

在构造 token 的方法 create_upload_image_token 中,一开始我并没有做文件名的指定,导致最终生成的图片资源链接并没有后缀名,这就导致了在客户端进行资源请求时,加载失败,但通过浏览器进行访问却是正常的。出现这种问题你可以选择跟我一样的方法:在该方法中指定文件名,并加上资源类型后缀

在业务层中,在接受客户端上传的 keys API 时,考虑到了多图上传的情况,在针对多 keys 情况考虑了到底是直接转为 json 请求体直接上传,还是把多个 keys 进行拼接的问题,按道理,后端接口部分应该直接解析客户端发送的 json 请求体,直接从中解析出所需要的数据,但不知道 python 具体应该怎么操作,这部分内容拖得也比较久了,就先不优化了,所以最终采用了第二种方案。

客户端

正如前文所说,原意是打算找到一家提供 Swift 版本 SDK 的服务商就直接用了,但是经过一番的调研后,发现根本没有,不管大小公司都没有提供 Swift 版本!!!真是太气人了!

最后没办法,只能选择业内老油条七牛,好在七牛提供 Objective-C 版本 SDK 调用方式足够简洁。PIGPEN 客户端整体基于 CocoaPods 进行依赖管理,只需要在依赖配置文件 podfile 中增加 use_frameworks! 即可把七牛 SDK 通过动态链接库的方式直接引入,而不用进行桥接:

1
2
3
4
5
6
7
platform :ios, '10.0'
target 'PIGPEN' do
use_frameworks!

pod 'Qiniu'
...
end

为了方便使用七牛的上传服务,抽离出了获取生成的 token 和通过 key 换取授权图片 url 的两个方法,需要注意的是七牛的 iOS SDK 并未直接提供 UIImage 的上传方法,而是通过 Data 或者 PHAsset 类型参数进行上传。

原本是想着直接把 UIImage 转为 Data 后进行上传,但不知是因为七牛 Objective-C SDK 转换成的 Swift 方法有问题还是其它的一些原因,部分参数一直未识别,最后只能更换为了使用 PHAsset 资源进行上传。调通后发现,在 PIGPEN 中实际上就是通过读取相册中的部分照片进行上传,而从相册中获取照片全都是 PHAsset 类型的数据,如果要转为 UIImage 中间还会经过一个异步操作,确实是多了一些不必要的操作。关于 iOS 中的自定义相册,如果你感兴趣可以参考这篇文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//
// PJImageUpload.swift
// PIGPEN
//
// Created by PJHubs on 2019/1/2.
// Copyright © 2019 PJHubs. All rights reserved.
//

import Foundation
import Photos
import Qiniu

class PJImageUploader {
/// 上传图片
class func upload(assets: [PHAsset],
complateHandler: @escaping (([String], [String]) -> Void),
failuredHandler: @escaping ((PJNetwork.Error) -> Void)) {
PJNetwork.shared.requstWithGet(path: URL.upload.rawValue,
parameters: ["imageCount": String(assets.count)],
complement: { (dataDict) in
if dataDict["msgCode"]?.intValue == 0 {
var dataDict = dataDict["msg"]!
let tokens = dataDict["upload_tokens"].arrayValue
// `setKey` 方法参数
var keys = ""
// complateHandler 闭包回调参数
var complateKeys = [String]()
for c_i in 0..<assets.count {
let token = tokens[c_i]["img_token"].string
let key = tokens[c_i]["img_key"].string
complateKeys.append(key!)
// 七牛上传
QNUploadManager()?.put(assets[c_i],
key: key,
token: token,
complete: { (info, key, respDict) in
guard let respDict = respDict else { return }
// key 即为文件名。拼接完成后一次性丢给 API
let key = respDict["key"]
keys += "," + String(key as! String)

if c_i == assets.count - 1 {
keys.removeFirst()
setKey(key: keys, complateHandler: { (imgUrls) in
complateHandler(imgUrls, complateKeys)
}, failuredHandler: { (error) in
falierHandler(error)
})
}
}, option: nil)
}
} else {
let error = PJNetwork.Error(errorCode: dataDict["msgCode"]?.intValue,
errorMsg: dataDict["msg"]?.string)
failuredHandler(error)
}
}) { (errorString) in
falierHandler(PJNetwork.Error(errorCode: nil, errorMsg: errorString))
}
}

/// 上传 keys 且换回图片完整 url
class func setKey(key: String,
complateHandler: @escaping ((([String]) -> Void)),
failuredHandler: @escaping ((PJNetwork.Error) -> Void)) {
PJNetwork.shared.requstWithPost(path: URL.setKey.rawValue,
parameters: ["keys": key],
complement: { (dataDict) in
if dataDict["msgCode"]?.intValue == 0 {
var dataDict = dataDict["msg"]!
let keys = dataDict["image_urls"].array
if keys != nil {
var k = [String]()
for key in keys! {
k.append(key.string!)
}
complateHandler(k)
}
}
}) { (errorString) in
let error = PJNetwork.Error(errorCode: nil, errorMsg: errorString)
failuredHandler(error)
}
}
}

// MARK: - URL
extension PJImageUploader {
enum URL: String {
case upload = "realPet/uploadToken"
case setKey = "realPet/setKeys"
}
}

总结

以上就是使用七牛云作为 PIGPEN 图床的对接历程,出现的问题主要在加速域名的配置和私有空间图片的访问上,中间也发了几次工单和七牛云工程师询问了几个问题,整体来看反应速度十分迅速,不管是工作日还是周末,工单回复最长回复耗时不到 10 分钟!

最后再吐槽一句,国内的服务商什么时候才能提供 Swift 版本的 SDK 啊!