大型网站架构--资源存储方案【阿里OSS存储服务】介绍及实现

大型网站架构中,资源存储是十分重要的,网站肯定会涉及到文件上传、图片上传等资源存储操作,而在网站用户量不断增长的同时,网站的资源存储也会越来越大的耗费服务器硬盘,一般架构会单独设立静态资源服务器,但是如果是电商网站架构初期,T级别的服务器也是非常昂贵的,就此阿里看到了商机,把自己用于淘宝的资源存储oss方案用于商用,用户可以注册购买oss服务,然后通过调用阿里oss api将自己的静态资源存储到oss上。

阿里oss产品介绍:

阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以通过调用 API,在任何应用、任何时间、任何地点上传和下载数据,也可以通过 Web 控制台对数据进行简单的管理。OSS 适合存放任意类型的文件,适合各种网站、开发企业及开发者使用。

OSS与自建存储对比

对比项
对象存储OSS
自建服务器存储
可靠性- 服务可用性不低于99.9%。
- 规模自动扩展,不影响对外服务。
- 数据持久性不低于99.99999999%。 
- 数据自动多重冗余备份。- 受限于硬件可靠性,易出问题,一旦出现磁盘坏道,容易出现不可逆转的数据丢失。
- 人工数据恢复困难、耗时、耗力。安全- 提供企业级多层次安全防护。
- 多用户资源隔离机制,支持异地容灾机制。
- 提供多种鉴权和授权机制及白名单、防盗链、主子账号功能。- 需要另外购买清洗和黑洞设备。
- 需要单独实现安全机制。成本- 最低只需要0.14元/GB/月,每月还送免费额度。
- 多线BGP骨干网络,无带宽限制,上行流量免费。
- 无需运维人员与托管费用,0成本运维。- 存储受硬盘容量限制,需人工扩容。 
- 单线或双线接入速度慢,有带宽限制,峰值时期需人工扩容。 
- 需专人运维,成本高。数据处理能力- 提供图片处理、音视频转码、内容加速分发、鉴黄服务、归档服务等多种数据增值服务,并不断丰富中。- 需要额外采购,单独部署。

方便、快捷的使用方式

  • 提供标准的RESTful API接口、丰富的SDK包、客户端工具、控制台。您可以像使用文件一样方便地上传、下载、检索、管理用于Web网站或者移动应用的海量数据。
  • 不限文件数量和大小。您可以根据所需存储量无限扩展存储空间,解决了传统硬件存储扩容问题。
  • 支持流式写入和读出。特别适合视频等大文件的边写边读业务场景。
  • 支持数据生命周期管理。您可以自定义将到期数据批量删除或者转入到低成本的归档服务。

强大、灵活的安全机制

  • 灵活的鉴权,授权机制。提供STS和URL鉴权和授权机制,以及白名单、防盗链、主子账号功能。
  • 提供用户级别资源隔离机制和多集群同步机制(可选)。

丰富、强大的增值服务

  • 图片处理:支持jpg、png、bmp、gif、webp、tiff等多种图片格式的转换,以及缩略图、剪裁、水印、缩放等多种操作。
  • 音视频转码:提供高质量、高速并行的音视频转码能力,让您的音视频文件轻松应对各种终端设备。
  • 内容加速分发:OSS作为源站,搭配CDN进行加速分发,具有稳定、无回源带宽限制、性价比高、一键配置的特点。

下面讲一下上传调用OSS:

模拟需求:

信息维护,可以修改信息,其中包括两项图片处理:1.公众号二维码图片上传。 2.logo上传。

前台页面代码:

<%@ page contentType="text/html;charset=UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<c:set var="ctx" value="${pageContext.request.contextPath}"/>

<html>
<head>
<title>管理</title>
</head>
<body>
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>

<small>
修改
</small>
</h1>
<ol class="breadcrumb">
<li><a href="${ctx}"><i class="fa fa-dashboard"></i> 首页</a></li>
<li class="active">
信息
</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<c:if test="${message != null && message != ''}">
<div class="alert alert-info" style="margin-top:20px;">
<button type="button" class="close" data-dismiss="alert">×</button>
${message}
</div>
</c:if>
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header"></div>
<!-- form start -->
<form id="inputForm" class="form-horizontal" action="${ctx}/org/hospital/update" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="${entity.id }"/>
<input type="hidden" name="token" value="${entity.token }"/>
<input type="hidden" name="status" value="${entity.status }"/>
<input type="hidden" id="logoUrl" name="logoUrl" value="${entity.logoUrl}"/>
<input type="hidden" id="codeUrl" name="codeUrl" value="${entity.codeUrl}"/>

<div class="form-group">
<label class="col-sm-2 control-label">名称</label>
<div class="col-sm-3">
<input id="name" name="name" value="${entity.name }" type="text" class="form-control required" readonly="readonly"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">地址</label>
<div class="col-sm-3">
<input name="address" value="${entity.address }" type="text" class="form-control required" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">电话</label>
<div class="col-sm-3">
<input name="telphone" value="${entity.telphone }" type="text" class="form-control required number" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">logo图片</label>
<div class="col-sm-5">

<input type="file" name="logoFile" id="logoFile">
<a href="javascript:uploadFile();" class="btn btn-primary">上传</a>

</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">logo缩略图</label>
<div class="col-sm-3">
<c:if test="${empty entity.logoUrl}">
<img id="logo_img" alt="" src="${ctx }/static/images/no_pro.jpg" width="40px" height="40px"/>
</c:if>
<c:if test="${!empty entity.logoUrl}">
<img id="logo_img" alt="" src="${entity.logoUrl}" width="40px" height="40px"/>
</c:if>
</div> 
</div>
<div class="form-group">
<label class="col-sm-2 control-label">微信二维码</label>
<div class="col-sm-5">
<input type="file" name="codeFile" id="codeFile">
<a href="javascript:uploadCodeFile();" class="btn btn-primary">上传</a>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">二维码缩略图</label>
<div class="col-sm-3">
<c:if test="${empty entity.codeUrl}">
<img id="code_img" alt="" src="${ctx }/static/images/no_pro.jpg" width="140px" height="140px"/>
</c:if>
<c:if test="${!empty entity.codeUrl}">
<img id="code_img" alt="" src="${entity.codeUrl}" width="140px" height="140px"/>
</c:if>
</div> 
</div>

<div class="form-group">
<label class="col-sm-2 control-label">简介</label>
<div class="col-sm-3">
<textarea class="form-control" name="summary">${entity.summary }</textarea>
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<shiro:hasPermission name="hospital:update">
<button id="submit" class="btn btn-primary">提交</button>
<a href="${ctx}/" class="btn btn-default">取消</a>
</shiro:hasPermission>
</div>
</div>
</form>
<div class="box-footer clearfix"></div>
</div>
</div>
</div>
</section>
<!-- /.content -->
<script type="text/javascript">
$(document).ready(function() {
$("#inputForm").validate();

//$('#fileButton').click(function (){

//})
});
function uploadFile(){
//alert();
//判断是否有选择上传文件
var imgPath = $("#logoFile").val();
if (imgPath == "") {
layer.alert("请选择上传图片!");
return;
}
//判断上传文件的后缀名
var strExtension = imgPath.substr(imgPath.lastIndexOf('.') + 1);
if (strExtension != 'jpg' && strExtension != 'gif'
&& strExtension != 'png' && strExtension != 'bmp') {
layer.alert("请选择图片文件!");
return;
}
$("#inputForm").ajaxSubmit({
type: "POST",
url:"${ctx}/org/hospital/uploadLogo",
dataType: "text",
success: function(data){
//alert(11111);
if(data == ""){
layer.alert("上传失败,请检查网络后重试");
return;
}

$("#logoUrl").val(data);
$("#logo_img").prop("src",data);
layer.alert("上传成功");
//layer.alert(data);

}
});
}

function uploadCodeFile(){
//alert();
//判断是否有选择上传文件
var imgPath = $("#codeFile").val();
if (imgPath == "") {
layer.alert("请选择上传图片!");
return;
}
//判断上传文件的后缀名
var strExtension = imgPath.substr(imgPath.lastIndexOf('.') + 1);
if (strExtension != 'jpg' && strExtension != 'gif'
&& strExtension != 'png' && strExtension != 'bmp') {
layer.alert("请选择图片文件!");
return;
}
$("#inputForm").ajaxSubmit({
type: "POST",
url:"${ctx}/org/hospital/uploadCode",
dataType: "text",
success: function(data){
if(data == ""){
layer.alert("上传失败,请检查网络后重试");
return;
}

$("#codeUrl").val(data);
$("#code_img").prop("src",data);
layer.alert("上传成功");
//layer.alert(data);
}
});
}
</script>
</body>
</html>

springmvc 代码:

/**
* 上传logo
*/
@RequestMapping(value = "uploadLogo", method = RequestMethod.POST)
@ResponseBody
public String uploadLogo( @RequestParam(value = "logoFile", required = false) MultipartFile logoFile,
HttpServletRequest request, HttpServletResponse response) {
if (request instanceof MultipartHttpServletRequest) {
String logoUrl = null;
try {
OSSUtil ossUtil = new OSSUtil();
String name = ossUtil.uploadImg2Oss(logoFile);
logoUrl = ossUtil.getImgUrl(name);
ossUtil.destory();
return logoUrl;
} catch (Exception e) {
//e.printStackTrace();
return "";
}
}
else {
return "";
}
}

 

阿里云OSS工具类代码OSSUtil


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;

/**
* 阿里云 OSS文件类
*
* @author gshen
*/
public class OSSUtil {

Log log = LogFactory.getLog(OSSUtil.class);
// endpoint以杭州为例,其它region请按实际情况填写

// http://oss-cn-beijing.aliyuncs.com/
private String endpoint = "oss url";
// accessKey请登录https://ak-console.aliyun.com/#/查看
private String accessKeyId = "accessKeyId";
private String accessKeySecret = "accessKeySecret";
//空间
private String bucketName = "gshen_workspace";
//文件存储目录
private String filedir = "data/";

private OSSClient ossClient;

public OSSUtil() {
ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
}

/**
* 初始化
*/
public void init() {
ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
}

/**
* 销毁
*/
public void destory() {
ossClient.shutdown();
}

/**
* 上传图片
*
* @param url
*/
public void uploadImg2Oss(String url) {
File fileOnServer = new File(url);
FileInputStream fin;
try {
fin = new FileInputStream(fileOnServer);
String[] split = url.split("/");
this.uploadFile2OSS(fin, split[split.length - 1]);
} catch (FileNotFoundException e) {
throw new RuntimeException("图片上传失败");
}
}


public String uploadImg2Oss(MultipartFile file) {
if (file.getSize() > 1024 * 1024 * 5) {
throw new RuntimeException("上传图片大小不能超过5M!");
}
String originalFilename = file.getOriginalFilename();
String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
Random random = new Random();
String name = random.nextInt(10000) + System.currentTimeMillis() + substring;
try {
InputStream inputStream = file.getInputStream();
this.uploadFile2OSS(inputStream, name);
return name;
} catch (Exception e) {
throw new RuntimeException("图片上传失败");
}
}

/**
* 获得图片路径
*
* @param fileUrl
* @return
*/
public String getImgUrl(String fileUrl) {
if (!StringUtils.isEmpty(fileUrl)) {
String[] split = fileUrl.split("/");
return this.getUrl(this.filedir + split[split.length - 1]);
}
return null;
}

/**
* 上传到OSS服务器 如果同名文件会覆盖服务器上的
*
* @param instream 文件流
* @param fileName 文件名称 包括后缀名
* @return 出错返回"" ,唯一MD5数字签名
*/
public String uploadFile2OSS(InputStream instream, String fileName) {
String ret = "";
try {
//创建上传Object的Metadata 
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(instream.available());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma", "no-cache");
objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));
objectMetadata.setContentDisposition("inline;filename=" + fileName);
//上传文件
PutObjectResult putResult = ossClient.putObject(bucketName, filedir + fileName, instream, objectMetadata);
ret = putResult.getETag();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
if (instream != null) {
instream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return ret;
}

/**
* Description: 判断OSS服务文件上传时文件的contentType
*
* @param FilenameExtension 文件后缀
* @return String
*/
public static String getcontentType(String FilenameExtension) {
if (FilenameExtension.equalsIgnoreCase("bmp")) {
return "image/bmp";
}
if (FilenameExtension.equalsIgnoreCase("gif")) {
return "image/gif";
}
if (FilenameExtension.equalsIgnoreCase("jpeg") ||
FilenameExtension.equalsIgnoreCase("jpg") ||
FilenameExtension.equalsIgnoreCase("png")) {
return "image/jpeg";
}
if (FilenameExtension.equalsIgnoreCase("html")) {
return "text/html";
}
if (FilenameExtension.equalsIgnoreCase("txt")) {
return "text/plain";
}
if (FilenameExtension.equalsIgnoreCase("vsd")) {
return "application/vnd.visio";
}
if (FilenameExtension.equalsIgnoreCase("pptx") ||
FilenameExtension.equalsIgnoreCase("ppt")) {
return "application/vnd.ms-powerpoint";
}
if (FilenameExtension.equalsIgnoreCase("docx") ||
FilenameExtension.equalsIgnoreCase("doc")) {
return "application/msword";
}
if (FilenameExtension.equalsIgnoreCase("xml")) {
return "text/xml";
}
return "image/jpeg";
}

/**
* 获得url链接
*
* @param key
* @return
*/
public String getUrl(String key) {
// 设置URL过期时间为10年 3600l* 1000*24*365*10
Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 10);
// 生成URL
URL url = ossClient.generatePresignedUrl(bucketName, key, expiration);
if (url != null) {
return url.toString();
}
return null;
}
}

图片上传成功后预览:大型网站架构--资源存储方案【阿里OSS存储服务】介绍及实现