开始使用阿里云OSS
https://www.alibabacloud.com/help/zh/doc-detail/31883.htm
一.开发一个APP上传服务,需要有存储的支持,那么我们的解决方案将以下几种:
1. 1 直接将图片保存到服务的硬盘
- 1. 优点:开发便捷,成本低
- 2. 缺点:扩容困难
1.2 使用分布式文件系统进行存储
- 1. 优点:容易实现扩容
- 2. 缺点:开发复杂度稍大(尤其是开发复杂的功能)
1.3 使用nfs做存储
- 1. 优点:开发较为便捷
- 2. 缺点:需要有一定的运维知识进行部署和维护
1.4 使用第三方的存储服务
- 1. 优点:开发简单,拥有强大功能,免维护
- 2. 缺点:付费
在这我们采用第一、四解决方案,第三方服务选用阿里云的OSS服务。
二.配置
2.1 导入依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.8.3</version>
</dependency>
2.2 编写aliyun.properties配置文件
aliyun.endpoint= aliyun.accessKeyId= aliyun.accessKeySecret= aliyun.bucketName= aliyun.urlPrefix=
2.3 编写AliyunConfig
@Configuration
@PropertySource(value = {"classpath:aliyun.properties"})
@ConfigurationProperties(prefix = "aliyun")
@Data
public class AliyunConfig {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private String urlPrefix;
@Bean
public OSS oSSClient() {
return new OSSClient(endpoint, accessKeyId, accessKeySecret);
}
}
三.文件上传(图片,APP)
3.1 图片
// 允许上传的格式
private static final String[] IMAGE_TYPE = new String[]{".bmp", ".jpg",
".jpeg", ".gif", ".png"};
3.2 APP-OSS
/**
* APK上传到阿里云oss
*
* @param request
* @return
*/
// @RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> upload(HttpServletRequest request) {
try {
MultipartHttpServletRequest fileRequest = (MultipartHttpServletRequest) request;
MultipartFile file = fileRequest.getFile("file");
InputStream inputStream = file.getInputStream();
String name = file.getOriginalFilename();
systemOperLogWrite.systemLogWrite("上传文件", SystemOperLog.LOG_TYPE_INSERT, systemOperLogWrite.objectToString(name));
//本地判断是否存在
String localMd5 = AliyunOSSClientUtil.getLocalMd5(file);
AppVersionVo vo = new AppVersionVo();
vo.setMd5(localMd5);
List<AppVersionEntity> appVersionEntities = appVersionDubboService.findByVo(vo);
if (CollectionUtils.isNotEmpty(appVersionEntities)) {
return ResultUtil.getFailResJson("该版本已存在", AcsConstent.BACK_FAILED_CODE);
}
String keyNeme = "";
if (StringUtils.isBlank(name)) {
return ResultUtil.getFailResJson("文件名不能为空");
}
if (name.endsWith(AcsConstent.APP.endWithName)) {
keyNeme = AcsConstent.APP.startFileName + StringUtils.getUUID() + AcsConstent.APP.endWithName;
} else {
return ResultUtil.getFailResJson(AcsConstent.APP.uploaFileNameError, AcsConstent.BACK_FAILED_CODE);
}
long size = 1;
if (file.getSize() > AcsConstent.INT_NUM_KB) {
size = file.getSize() / AcsConstent.INT_NUM_KB / AcsConstent.INT_NUM_KB;
}
// String bucketName = "aiot-face-image";
SystemConfigEntity systemConfigField = systemConfigDubboService.getSystemConfigField(Constants.Aliyun_BucketName);
String bucketName = systemConfigField.getValue();
// Endpoint以杭州为例,其它Region请按实际情况填写
// String endpoint = "http://oss-cn-shenzhen.aliyuncs.com";
SystemConfigEntity systemConfigField1 = systemConfigDubboService.getSystemConfigField(Constants.Aliyun_Endpoint);
String endpoint = systemConfigField1.getValue();
SystemConfigEntity accessKey = systemConfigDubboService.getSystemConfigField(Constants.Aliyun_AccessKeyId);
String accessKeyId = accessKey.getValue();
SystemConfigEntity accessSecret = systemConfigDubboService.getSystemConfigField(Constants.Aliyun_AccessKeySecret);
String accessKeySecret = AESUtil.aesDecrypt(accessSecret.getValue(), AcsConstent.APP.AccessKeySecret_DecryptKey);
// 创建OSSClient实例
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 文件大小
ObjectMetadata metadata = new ObjectMetadata();
// 上传的文件的长度
metadata.setContentLength(inputStream.available());
// 指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
// 指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
// 指定该Object被下载时的内容编码格式
metadata.setContentEncoding("utf-8");
// 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成
// 如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(AliyunOSSClientUtil.getContentType(name));
// 指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
PutObjectResult putResult = ossClient.putObject(bucketName, keyNeme, inputStream, metadata);
String eTag = putResult.getETag();
// 关闭OSSClient
ossClient.shutdown();
Date expiration = new Date(System.currentTimeMillis() + AcsConstent.APP.EXPIRE_DAY);
String url = ossClient.generatePresignedUrl(bucketName, keyNeme, expiration).toString();
JSONObject jsonObject = new JSONObject();
jsonObject.put("url", url);
jsonObject.put("MD5", eTag);
jsonObject.put("appSize", size);
return StringUtils.isNotBlank(url) ? ResultUtil.getSuccessResJson("data", jsonObject.toString()) : ResultUtil.getFailResJson("上传失败", "1");
} catch (Exception e) {
logger.info("上传文件失败###################{}", e);
}
return null;
}
3.2 APP-服务器
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> uploadApk(HttpServletRequest request) {
FileOutputStream fileOutputStream = null;
InputStream inputStream = null;
try {
MultipartHttpServletRequest fileRequest = (MultipartHttpServletRequest) request;
MultipartFile file = fileRequest.getFile("file");
inputStream = file.getInputStream();
String fileName = file.getOriginalFilename();
//本地判断是否存在
String localMd5 = AliyunOSSClientUtil.fileToMd5(file);
JSONObject checkJson = checkPamams(localMd5, fileName);
if (checkJson != null) {
return checkJson;
}
long size = 1;
if (file.getSize() > AcsConstent.INT_NUM_KB) {
size = file.getSize() / AcsConstent.INT_NUM_KB / AcsConstent.INT_NUM_KB;
}
String downloadFileUrl = redisOperatorManager.getValue("upload.file.url", CommonConstants.SYSTEM_CONFIG_REDIS_INDEX, "http://%s:%s/acs-admin/system/fileManager/downloadFileByKey?key=%s");
downloadFileUrl = String.format(downloadFileUrl, host, port, localMd5);
String fileUploadPath = fileUploadBasePath + File.separator + "app";
File fileUploadPathFile = new File(fileUploadPath);
if (!fileUploadPathFile.exists()) {
fileUploadPathFile.mkdirs();
}
//新文件名为md5值
String keyName = localMd5 + AcsConstent.APP.endWithName;
String fileUploadFileName = fileUploadPath + File.separator + keyName;
File fileUploadFile = new File(fileUploadFileName);
if (!fileUploadFile.exists()) {
fileUploadFile.createNewFile();
}
fileOutputStream = new FileOutputStream(fileUploadFile);
IOUtils.copy(inputStream, fileOutputStream);
JSONObject jsonObject = new JSONObject();
jsonObject.put("url", downloadFileUrl);
jsonObject.put("MD5", localMd5);
jsonObject.put("appSize", size);
jsonObject.put("originalFilename", fileName);
FileUploadVo fileUploadVo = new FileUploadVo();
fileUploadVo.setFilePath(fileUploadPath);
fileUploadVo.setFileName(keyName);
fileUploadVo.setMd5(localMd5);
//通知
notifyFileUploadZk(fileUploadVo);
systemOperLogWrite.systemLogWrite("上传文件", SystemOperLog.LOG_TYPE_INSERT, jsonObject.toJSONString());
return ResultUtil.getSuccessResJson("data", jsonObject.toString());
} catch (RuntimeException e){
logger.info("运行时抛出异常###################{}", e);
} catch (Exception e) {
logger.info("上传文件失败###################{}", e);
} finally {
IOUtils.closeQuietly(fileOutputStream);
IOUtils.closeQuietly(inputStream);
}
return ResultUtil.getFailResJson("上传失败", "1");
}
OSS的简单使用
OSS简介
Object Storage Service,简称 OSS,是阿里云提供的海量、安全、低成本、高可靠的云存储服务。
它具有与平台无关的RESTful API接口,能够提供99.999999999%的服务持久性。
使用场景:
- 图片分享
- 热点视频
优势:
- 成本低(40G才6元,比ECS便宜太多)
- 不会影响ECS带宽
- 和服务器解耦
下面介绍一些基本功能:
- 初始化
- 创建存储空间
- 上传文件
- 跨域访问设置
- 设置读写权限
OSS使用(NET SDK使用)
1.初始化
创建一个OssClient,就可以很方便的调用OSS的方法。
const string accessKeyId = "xxxxxxxxx";
const string accessKeySecret = "xxxxxxxxxx";
const string endpoint = "oss-cn-beijing.aliyuncs.com"; //OSS对应的区域地址
private static OssClient ossClient = new OssClient(endpoint, accessKeyId, accessKeySecret);
2.创建存储空间
很简单,只需要调用OssClient.CreateBucket
ossClient.CreateBucket("myBucket"); //新建一个Bucket
3.设置读写权限
调用OssClient.SetBucketAcl
ossClient.SetBucketAcl("myBucket", CannedAccessControlList.PublicRead); //设置为公共读
CannedAccessControlList有三个属性:Private(私有),PublicRead(公共读),PublicReadWrite(公共读写)
4.跨域访问设置
调用OssClient.SetBucketCors
var req = new SetBucketCorsRequest("myBucket");
var rule = new CORSRule();
//指定允许跨域请求的来源
rule.AddAllowedOrigin("*");
//指定允许的跨域请求方法(GET/PUT/DELETE/POST/HEAD)
rule.AddAllowedMethod("POST");
//控制在OPTIONS预取指令中Access-Control-Request-Headers头中指定的header是否允许。
rule.AddAllowedHeader("*");
req.AddCORSRule(rule);
ossClient.SetBucketCors(req);
5.上传文件
调用OssClient.PutObject
var result = ossClient.PutObject("myBucket", "111.mp4", @"d:\237badef-0f6d-4a8e-a634-a44c9704b6e6.mp4");
Console.WriteLine(result.ETag);
6.列出存储空间中的所有文件
调用ossClient.ListObjects
var listObjectsRequest = new ListObjectsRequest("myBucket");
var result = ossClient.ListObjects(listObjectsRequest);
Console.WriteLine("List objects succeeded");
foreach (var summary in result.ObjectSummaries)
{
Console.WriteLine("File name:{0}", summary.Key);
}
以上步骤1到4,可以在OSS管理后端完成
WEB端直传
刚开始使用OSS的时候,是采用前端将文件流上传到Web服务器,然后通过Web服务器再上传到OSS
这种做法有三个缺点:
- 第一:上传慢。先上传到应用服务器,再上传到OSS,网络传送多了一倍。如果数据直传到OSS,不走应用服务器,速度将大大提升,而且OSS是采用BGP带宽,能保证各地各运营商的速度。
- 第二:扩展性不好。如果后续用户多了,应用服务器会成为瓶颈。
- 第三:费用高。由于OSS上传流量是免费的。如果数据直传到OSS,不走应用服务器,那么将能省下几台应用服务器
如何操作?
1.通过JS控件直传
- 采用plupload 直接提交表单数据(即PostObject)到OSS;
- 在JS端,输入OSS的认证信息(不安全)
var policyText = {
"expiration": "2020-01-01T12:00:00.000Z", //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
"conditions": [
["content-length-range", 0, 1048576000] // 设置上传文件的大小限制
]
};
accessid= '6MKOqxGiGU4AUk44';
accesskey= 'ufu7nS8kS59awNihtjSonMETLI0KLy';
host = 'http://post-test.oss-cn-hangzhou.aliyuncs.com';
var policyBase64 = Base64.encode(JSON.stringify(policyText))
message = policyBase64
var bytes = Crypto.HMAC(Crypto.SHA1, message, accesskey, { asBytes: true }) ;
var signature = Crypto.util.bytesToBase64(bytes);
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,html4',
browse_button : 'selectfiles',
container: document.getElementById('container'),
flash_swf_url : 'lib/plupload-2.1.2/js/Moxie.swf',
silverlight_xap_url : 'lib/plupload-2.1.2/js/Moxie.xap',
url : host,
multipart_params: {
'Filename': '${filename}',
'key' : '${filename}',
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
},
init: {
PostInit: function() {
document.getElementById('ossfile').innerHTML = '';
document.getElementById('postfiles').onclick = function() {
uploader.start();
return false;
};
},
FilesAdded: function(up, files) {
plupload.each(files, function(file) {
document.getElementById('ossfile').innerHTML += '<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ')<b></b>'
+'<div class="progress"><div class="progress-bar" style="width: 0%"></div></div>'
+'</div>';
});
},
UploadProgress: function(up, file) {
var d = document.getElementById(file.id);
d.getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
var prog = d.getElementsByTagName('div')[0];
var progBar = prog.getElementsByTagName('div')[0]
progBar.style.width= 2*file.percent+'px';
progBar.setAttribute('aria-valuenow', file.percent);
},
FileUploaded: function(up, file, info) {
//alert(info.status)
if (info.status >= 200 || info.status < 200)
{
document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'success';
}
else
{
document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
}
},
Error: function(up, err) {
document.getElementById('console').appendChild(document.createTextNode("\nError xml:" + err.response));
}
}
});
uploader.init();
具体可以下载OSS官网的 [例子]:https://help.aliyun.com/document_detail/31925.html?spm=a2c4g.11186623.6.629.AdgPho
2.通过服务端构建认证信息(安全,参看下一小节)
服务端构建认证信息
上一小节中,通过JS来上传文件,虽然可以上传,但是AppSecret会泄露,不安全。所以需要在服务端将认证信息构建出来,再给到前端
public virtual ActionResult GetPostPolicy()
{
string host = "http://" + bucket + "."+ endpoint;
//第一步,构造policy
//var dir = "zhangsan/";//设置用户上传指定的前缀,必须以斜线结尾
var expiration = DateTime.Now.AddMinutes(100);
var policyConds = new PolicyConditions();
//policyConds.AddConditionItem(MatchMode.StartWith, PolicyConditions.CondKey, dir);//上传目录
policyConds.AddConditionItem(PolicyConditions.CondContentLengthRange, 1, 1048576000);//允许上传的文件大小限制
var postPolicy = ossClient.GeneratePostPolicy(expiration, policyConds);//给policyConds添加过期时间并json序列化(格式iso8601:"yyyy-MM-dd'T'HH:mm:ss.fff'Z'")
//第二步 将policy 的json字符串进行base64编码
var base64Policy = Convert.ToBase64String(Encoding.UTF8.GetBytes(postPolicy));
//第三步,生成签名
var signature = ComputeSignature(accessKeySecret, base64Policy);//生成签名
//以下返回给前端
TimeSpan ts = expiration - new DateTime(1970, 1, 1, 0, 0, 0, 0);
var expire = Convert.ToInt64(ts.TotalSeconds);
Dictionary<string, object> response = new Dictionary<string, object>();
response["accessid"] = accessKeyId;
response["host"] = host;
response["policy"] = base64Policy;
response["signature"] = signature;
response["expire"] = expire;
return ResponseSuccess(response); //返回json,可以自己修改(该方法是自己封装的)
}
private static string ComputeSignature(string key, string data)
{
using (var algorithm = KeyedHashAlgorithm.Create("HmacSHA1".ToUpperInvariant()))
{
algorithm.Key = Encoding.UTF8.GetBytes(key.ToCharArray());
return Convert.ToBase64String(
algorithm.ComputeHash(Encoding.UTF8.GetBytes(data.ToCharArray())));
}
}
再结合刚才的前端,修改如下
<script type="text/javascript">
var accessid= '';
var host = 'http://muBucket.oss-cn-beijing.aliyuncs.com';
var policyBase64 = '';
var signature = '';
var uploadFileName=''; //文件名称
//获取随机字符串
function random_string(len) {
len = len || 32;
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
var maxPos = chars.length;
var pwd = '';
for (i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
//获取文件后缀名
function get_suffix(filename) {
var pos = filename.lastIndexOf('.')
suffix = ''
if (pos != -1) {
suffix = filename.substring(pos)
}
return suffix;
}
//设置plupload属性
function set_upload_param(up, filename, ret)
{
var suffix='';
if (filename != '') {
suffix = get_suffix(filename)
}else{
return;
}
uploadFileName=random_string(20)+suffix;
new_multipart_params = {
'key' : uploadFileName,
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
};
up.setOption({
'url': host,
'multipart_params': new_multipart_params
});
up.start();
}
//初始化plupload控件
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,html4',
browse_button : 'selectfiles',
container: document.getElementById('container'),
flash_swf_url : 'lib/plupload-2.1.2/js/Moxie.swf',
silverlight_xap_url : 'lib/plupload-2.1.2/js/Moxie.xap',
multi_selection:false,
filters: {
mime_types : [
{ title : "Video files", extensions : "mp4,rmvb" }
],
max_file_size : '600000kb', //最大只能上传600M的文件
prevent_duplicates : true //不允许选取重复文件
},
url : host,
init: {
PostInit: function() {
document.getElementById('ossfile').innerHTML = '';
$.ajax({
type:'GET',
url:'GetPostPolicyURL', //获取认证信息的URL
success:function(res){
if(res.rspcode==="0000"){
accessid = res.accessid;
host = res.host;
policyBase64 =res.policy;
signature =res.signature;
}else{
/* 弹出框提示错误 */
toastr.error(res.rspmsg);
}
}
})
},
FilesAdded: function(up, files) {
plupload.each(files, function(file) {
set_upload_param(up, file.name, true);
document.getElementById('ossfile').innerHTML += '<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ')<b></b>'
+'<div class="progress"><div class="progress-bar" style="width: 0%"></div></div>'
+'</div>';
});
},
BeforeUpload: function(up, file) {
},
UploadProgress: function(up, file) {
var d = document.getElementById(file.id);
d.getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
var prog = d.getElementsByTagName('div')[0];
var progBar = prog.getElementsByTagName('div')[0]
progBar.style.width= 2*file.percent+'px';
progBar.setAttribute('aria-valuenow', file.percent);
},
FileUploaded: function(up, file, info) {
if (info.status >= 200 || info.status < 200)
{
vm.formModel.VideoPath=uploadFileName; //将文件名记录下来,用于保存
document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'success';
}
else
{
document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
}
},
Error: function(up, err) {
document.getElementById('console').appendChild(document.createTextNode("\nError xml:" + err.response));
}
}
});
uploader.init();
</script>
参考文章:
[OSS官网文档]:https://help.aliyun.com/document_detail/31817.html?spm=a2c4g.11186623.6.539.PK7qiJ
