Toc
  1. 方式一:
    1. 一. OSS
    2. 二. 需求
    3. 三. 实现
      1. 1. 输入表单
      2. 2. 前端发送图片上传的请求
      3. 3. 同时, 后端Controller接收请求
      4. 4. 前端收到结果
      5. 5. 再次发送请求
  2. 方式二:
Toc
0 results found
Goblin
图片上传策略之OSS
2017/03/31 Code 笔记 业务

方式一:

一. OSS

Oss对象存储的优势体现在:

本张图片就存放于七牛云上

二. 需求

现在, 用户可以提交意见反馈给后台, 反馈的内容同时可携带多张图片. 我们不想在数据库中直接保存图片的base64格式的字符, 因为如果图片很大, 那么数据库负担很重.

所以我们使用oss对象存储服务

三. 实现

实现的流程为

1. 输入表单

用户输入完表单, 选择好了要上传的图片, 点击 提交反馈 发送请求

2. 前端发送图片上传的请求

该请求需设置请求头Content-Type: multipart/form-data; 请求体为form-data的file类型, 同时可以选择多张要上传的图片

3. 同时, 后端Controller接收请求

package com.macro.mall.portal.controller;

import com.macro.mall.common.api.CommonResult;
import com.macro.mall.portal.domain.ImagesFile;
import com.macro.mall.portal.util.QiniuUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.UUID;

/**
* 图片上传Controller
* Created by goblin.
*/
@Controller
@Api(tags = "UploadFileController", description = "图片上传")
@RequestMapping("/upload")
public class UploadFileController {

/**
* 上传图片
* @param multipartFiles
* @return
*/
@ApiOperation("上传图片")
@RequestMapping(value = "/images", method = RequestMethod.POST)
@ResponseBody
public CommonResult upload(@RequestParam("imgFile") MultipartFile[] multipartFiles) {
ArrayList imgFile = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
ImagesFile imagesFile = new ImagesFile();
//原始文件名
String originalFileName = multipartFile.getOriginalFilename();
imagesFile.setImageName(originalFileName);
//使用UUID构造不重复的文件名
String fileName = UUID.randomUUID().toString().replace("-", "") + "." + getPicSuffix(originalFileName);
//获取输入流并上传
try (InputStream is = multipartFile.getInputStream()) {
QiniuUtils.upload2Qiniu(is, fileName);
} catch (RuntimeException| IOException e) {
CommonResult.failed();
}
//构造返回值
String pic = QiniuUtils.QINIU_IMG_URL_PRE + fileName;
imagesFile.setImageUrl(pic);
imgFile.add(imagesFile);
}
return CommonResult.success(imgFile);
}

public static String getPicSuffix(String imgPath){
if (imgPath == null || imgPath.indexOf(".") == -1){
return ""; //如果图片地址为null或者地址中没有"."就返回""
}
return imgPath.substring(imgPath.lastIndexOf(".") + 1).
trim().toLowerCase();
}
}

值得注意的是, 这里需要用注解@RequestParam(“参数名”) MultipartFile[] 来接收多张图片

multipartFile.getOriginalFilename();可以用来获取上传文件的名字以及后缀.

这里用到了七牛云上传工具类:

package com.macro.mall.portal.util;

import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* @author zhangmeng
* @description 七牛云工具类
* @date 2019/9/26
**/
@Slf4j
public class QiniuUtils {
public static final String CONFIG_FILE = "qiniu-config.properties";
public static String ACCESS_KEY;
public static String SECRET_KEY;
public static String QINIU_IMG_URL_PRE;
public static String BUCKET;

static {
Properties prop = new Properties();
// 加载配置
try(InputStream is = QiniuUtils.class.getClassLoader().getResourceAsStream("qiniu-config.properties")) {
if(null == is){
log.error("[七牛云工具类-初始化]失败,请提供配置文件:{}",CONFIG_FILE);
}else {
prop.load(is);
}
} catch (IOException e) {
// 几乎不可能事件,直接转换为运行时异常继续上抛
throw new RuntimeException(e);
}
ACCESS_KEY = prop.getProperty("access.key", "");
SECRET_KEY = prop.getProperty("secret.key", "");
QINIU_IMG_URL_PRE = prop.getProperty("img.url.prefix", "");
BUCKET = prop.getProperty("bucket", "");
log.info("[七牛云工具类-初始化]完成");
}

/**
* 上传到七牛云
*
* @param is 上传内容的输入流
* @param uploadFileName
*/
public static void upload2Qiniu(InputStream is, String uploadFileName) throws QiniuException {
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);

//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = uploadFileName;
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
String upToken = auth.uploadToken(BUCKET);
try {
Response response = uploadManager.put(is, key, upToken, null, null);
//解析上传成功的结果
log.info(response.bodyString());
// 访问路径
log.info("{}/{}", QINIU_IMG_URL_PRE, uploadFileName);
} catch (QiniuException ex) {
Response r = ex.response;
log.error(r.toString());
try {
log.error(r.bodyString());
} catch (QiniuException ex2) {
//ignore
log.error("", ex2);
}
throw ex;
}
}

public static void deleteFileFromQiniu(String fileName) throws QiniuException {
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
String key = fileName;
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
BucketManager bucketManager = new BucketManager(auth, cfg);
try {
bucketManager.delete(BUCKET, key);
} catch (QiniuException ex) {
//如果遇到异常,说明删除失败
log.error("code:{}", ex.code());
log.error(ex.response.toString());
throw ex;
}
}
}

配置文件qiniu-config.properties在resource文件夹下

img.url.prefix=专属外链
access.key=七牛云的AK
secret.key=七牛云的SK
bucket=要上传到桶的名字

4. 前端收到结果

{
"code": 200,
"message": "操作成功",
"data": [
{
"imageName": "timg (1).jpeg",
"imageUrl": "http://q850xek50.bkt.clouddn.com/6ce55d289d8f499783d86771bbf2a074.jpeg"
},
{
"imageName": "timg (2).jpeg",
"imageUrl": "http://q850xek50.bkt.clouddn.com/3deb81d57fa643c5ac9948d6ca72e59e.jpeg"
},
{
"imageName": "timg.jpeg",
"imageUrl": "http://q850xek50.bkt.clouddn.com/327fa1b7964346588bdec1b28154e619.jpeg"
}
]
}

5. 再次发送请求

此时, 请求体中带着反馈的内容和图片的url等内容, 交由后端保存到数据库中.

{
"feedback": " ",
"questionType": " ",
"images": " "
...
}

方式二:

前端直接向阿里云OSS发送上传图片请求

Controller:

package com.macro.mall.controller;


import com.macro.mall.common.api.CommonResult;
import com.macro.mall.dto.OssCallbackResult;
import com.macro.mall.dto.OssPolicyResult;
import com.macro.mall.service.impl.OssServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
* Oss相关操作接口
* Created by macro on 2018/4/26.
*/
@Controller
@Api(tags = "OssController", description = "Oss管理")
@RequestMapping("/aliyun/oss")
@CrossOrigin
public class OssController {
@Autowired
private OssServiceImpl ossService;

@ApiOperation(value = "oss上传签名生成")
@RequestMapping(value = "/policy", method = RequestMethod.GET)
@ResponseBody
public CommonResult policy() {
OssPolicyResult result = ossService.policy();
return CommonResult.success(result);
}

@ApiOperation(value = "oss上传成功回调")
@RequestMapping(value = "callback", method = RequestMethod.POST)
@ResponseBody
public CommonResult callback(HttpServletRequest request) {
OssCallbackResult ossCallbackResult = ossService.callback(request);
return CommonResult.success(ossCallbackResult);
}

}

OssServiceImpl:

  1. 前端请求后台生成 许可

    /**
    * 签名生成
    */
    @Override
    public OssPolicyResult policy() {
    OssPolicyResult result = new OssPolicyResult();
    // 存储目录
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    String dir = ALIYUN_OSS_DIR_PREFIX + sdf.format(new Date());
    // 签名有效期
    long expireEndTime = System.currentTimeMillis() + ALIYUN_OSS_EXPIRE * 1000;
    Date expiration = new Date(expireEndTime);
    // 文件大小
    long maxSize = ALIYUN_OSS_MAX_SIZE * 1024 * 1024;
    // 回调
    OssCallbackParam callback = new OssCallbackParam();
    callback.setCallbackUrl(ALIYUN_OSS_CALLBACK);
    callback.setCallbackBody("filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
    callback.setCallbackBodyType("application/x-www-form-urlencoded");
    // 提交节点
    String action = "http://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT;
    try {
    PolicyConditions policyConds = new PolicyConditions();
    policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
    policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
    String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    byte[] binaryData = postPolicy.getBytes("utf-8");
    String policy = BinaryUtil.toBase64String(binaryData);
    String signature = ossClient.calculatePostSignature(postPolicy);
    String callbackData = BinaryUtil.toBase64String(JSONUtil.parse(callback).toString().getBytes("utf-8"));
    // 返回结果
    result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
    result.setPolicy(policy);
    result.setSignature(signature);
    result.setDir(dir);
    result.setCallback(callbackData);
    result.setHost(action);
    } catch (Exception e) {
    LOGGER.error("签名生成失败", e);
    }
    return result;
    }

配置文件:

aliyun:
oss:
endpoint: # oss对外服务的访问域名
accessKeyId: # 访问身份验证中用到用户标识
accessKeySecret: # 用户用于加密签名字符串和oss用来验证签名字符串的密钥
bucketName: # oss的存储空间
policy:
expire: 300 # 签名有效期(S)
maxSize: 10 # 上传文件大小(M)
callback: http://localhost:8080/aliyun/oss/callback # 文件上传成功后的回调地址
dir:
prefix: mall/images/ # 上传文件夹路径前缀

阿里云上传成功后的回调函数

@Override
public OssCallbackResult callback(HttpServletRequest request) {
OssCallbackResult result= new OssCallbackResult();
String filename = request.getParameter("filename");
filename = "http://".concat(ALIYUN_OSS_BUCKET_NAME).concat(".").concat(ALIYUN_OSS_ENDPOINT).concat("/").concat(filename);
result.setFilename(filename);
result.setSize(request.getParameter("size"));
result.setMimeType(request.getParameter("mimeType"));
result.setWidth(request.getParameter("width"));
result.setHeight(request.getParameter("height"));
return result;
}

前端通过回调函数得到存储图片的url路径.

打赏
支付宝
微信
本文作者:Goblin
版权声明:本文首发于Goblin的博客,转载请注明出处!