文章目录
- 1. 创建用户配置权限
- 2.创建存储空间
- 3. 后端项目整合
- 3.1 ruoyi-common/pom.xml添加依赖
- 3.2 application.yml添加配置
- 3.3 com.ruoyi.common.utils.oss添加工具类
- 3.4 com.ruoyi.aliyun.oss.controller添加OssController
- 3.5 callback接口配置权限
- 4. 前端项目整合
- 4.1 安装依赖
- 4.2 vuex添加用户id
- 4.3 添加src\api\oss\oss.js
- 4.4 全局挂载oss.js
- *注意事项
1. 创建用户配置权限
参考:什么是STS 阿里云创建用户及授权参考:使用STS临时访问凭证访问OSS
创建完成后我们会获得:
- 用户AccessKey ID
- 用户AccessKey Secret
- 角色ARN
2.创建存储空间
参考:创建存储空间 创建完成后我们会获得:
- BucketName
- Region
- Endpoint
注意:
4. 预览时如果想在浏览器中预览,在Bucket中->传输管理->域名管理中绑定域名即可
5. Bucket读写权限设置为私有即可
6. 跨域设置
2.1. 来源:*
2.2. 允许Methods全勾选
2.3. 允许Headers:*
2.4. 暴露Headers可以不写
*参考
3. 后端项目整合
3.1 ruoyi-common/pom.xml添加依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
3.2 application.yml添加配置
oss:
sts:
accesskey:
id: 第一步用户AccessKey ID
secret: 第一步用户AccessKey Secret
bucket: 第二步BucketName
region: 第二步Region
endpoint: 第二步Endpoint
role:
arn: 第一步角色ARN
- 之前的文章整合cas时有拆分了开发、测试、生产库的配置文件,那么在其中添加,如果没有,那么直接添加进application.yml中
oss:
sts:
callbackUrl: 该应用对外的域名[端口],如:https://dky12.xxx.com
3.3 com.ruoyi.common.utils.oss添加工具类
package com.ruoyi.common.utils.oss;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* ali oss临时授权访问
*/
public class AliyunStsOssUtil {
public static Logger logger = LoggerFactory.getLogger(AliyunStsOssUtil.class);
public static final String ACCESS_KEY_ID = "ACCESS_KEY_ID";
public static final String ACCESS_KEY_SECRET = "ACCESS_KEY_SECRET";
public static final String SECURITY_TOKEN = "SECURITY_TOKEN";
public static final String EXPIRATION = "EXPIRATION";
//这里使用cn-hangzhou区域,具体根据实际情况而定
private static final String REGION = "cn-beijing";
private static final String STS_API_VERSION = "2015-04-01";
// 自有域名 通过这个域名在浏览器中可以直接预览而不是下载
private static final String OWN_DOMAIN_NAME = "bucket中绑定的域名";
public static JSONObject getCredit(String userName, String roleArn, String accessKeyId, String accessKeySecret, String bucketName) throws ClientException {
// 用于阿里云后台审计使用的临时名称,可根据实际业务传输,具体内容不影响服务使用
String roleSessionName = userName;
//执行角色授权
IClientProfile profile = DefaultProfile.getProfile(REGION, accessKeyId, accessKeySecret);
DefaultAcsClient client = new DefaultAcsClient(profile);
final AssumeRoleRequest request = new AssumeRoleRequest();
request.setVersion(STS_API_VERSION);
request.setRoleArn(roleArn);
request.setRoleSessionName(roleSessionName);
//request.setPolicy(policyObject.toJSONString());
//临时授权有效时间,从900到3600
request.setDurationSeconds(900L);
final AssumeRoleResponse response = client.getAcsResponse(request);
JSONObject jsonObject = new JSONObject();
jsonObject.put(ACCESS_KEY_ID, response.getCredentials().getAccessKeyId());
jsonObject.put(ACCESS_KEY_SECRET, response.getCredentials().getAccessKeySecret());
//jsonObject.put(SECURITY_TOKEN, response.getCredentials().getBizSecurityToken());
jsonObject.put(SECURITY_TOKEN, response.getCredentials().getSecurityToken());
jsonObject.put(EXPIRATION, response.getCredentials().getExpiration());
return jsonObject;
}
/**
* STS方式获取图片地址
* @param userName
* @param roleArn
* @param accessKeyId
* @param accessKeySecret
* @param bucketName
* @param endpoint
* @return
* @throws ClientException
*/
public static String getStsUrl(String userName, String roleArn, String accessKeyId, String accessKeySecret, String bucketName, String endpoint, String objectName)throws ClientException {
OSS ossClient = getSTSOssClient(userName, roleArn, accessKeyId, accessKeySecret, bucketName, endpoint);
// 填写签名URL的过期时间。
Date expiration = null;
expiration = getExpiration(expiration);
// 生成签名URL。
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET);
// 设置过期时间。
request.setExpiration(expiration);
// 通过HTTP GET请求生成签名URL。
URL signedUrl = ossClient.generatePresignedUrl(request);
// 关闭OSSClient。
ossClient.shutdown();
// **如果没有绑定域名那么replaceFirst方法删除即可
return signedUrl.toString().replaceFirst( bucketName+ "." +endpoint,OWN_DOMAIN_NAME);
}
/**
* STS方式获取多张图片地址
* @param userName
* @param roleArn
* @param accessKeyId
* @param accessKeySecret
* @param bucketName
* @param endpoint
* @return
* @throws ClientException
*/
public static List<String> getStsUrl(String userName, String roleArn, String accessKeyId, String accessKeySecret, String bucketName, String endpoint, List<String> objectNames)throws ClientException {
List<String> urls = new ArrayList<>();
if(!CollectionUtils.isEmpty(objectNames)){
OSS ossClient = getSTSOssClient(userName, roleArn, accessKeyId, accessKeySecret, bucketName, endpoint);
objectNames.forEach(objectName -> {
// 填写签名URL的过期时间。
Date expiration = null;
expiration = getExpiration(expiration);
// 生成签名URL。
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET);
// 设置过期时间。
request.setExpiration(expiration);
// 通过HTTP GET请求生成签名URL。
URL signedUrl = ossClient.generatePresignedUrl(request);
urls.add(signedUrl.toString().replaceFirst( bucketName+ "." +endpoint,OWN_DOMAIN_NAME));
});
ossClient.shutdown();
}
return urls;
}
/**
* 获取签名过期时间
* @param expiration
* @return
*/
private static Date getExpiration(Date expiration) {
try {
long nowTimeMillis = System.currentTimeMillis();
nowTimeMillis = nowTimeMillis + ( 600 * 1000); // 当前时间600秒后
expiration = new Date(nowTimeMillis);
//expiration = DateUtil.parseRfc822Date("Wed, 18 Mar 2022 14:20:00 GMT");
} catch (Exception e) {
e.printStackTrace();
}
return expiration;
}
/**
* STS方式获取客户端
* @param userName
* @param roleArn
* @param accessKeyId
* @param accessKeySecret
* @param bucketName
* @param endpoint
* @return
* @throws ClientException
*/
private static OSS getSTSOssClient(String userName, String roleArn, String accessKeyId, String accessKeySecret, String bucketName, String endpoint) throws ClientException {
JSONObject credit = getCredit(userName, roleArn, accessKeyId, accessKeySecret, bucketName);
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
String stsAccessKeyId = credit.getString(ACCESS_KEY_ID);
String stsAccessKeySecret = credit.getString(ACCESS_KEY_SECRET);
String securityToken = credit.getString(SECURITY_TOKEN);
// 创建OSSClient实例。
return new OSSClientBuilder().build(endpoint, stsAccessKeyId, stsAccessKeySecret, securityToken);
}
}
3.4 com.ruoyi.aliyun.oss.controller添加OssController
package com.ruoyi.aliyun.oss.controller;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyuncs.exceptions.ClientException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.oss.AliyunStsOssUtil;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URI;
import java.net.URLDecoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/oss")
public class OssController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${oss.sts.endpoint}")
private String endpoint;
@Value("${oss.sts.region}")
private String ossRegion;
@Value("${oss.sts.bucket}")
private String ossBucket;
@Value("${oss.sts.accesskey.id}")
private String ossAccessKeyId;
@Value("${oss.sts.accesskey.secret}")
private String ossAccessKeySecret;
@Value("${oss.sts.role.arn}")
private String ossRoleArn;
@Value("${oss.sts.callbackUrl}")
private String callbackUrl;
/**
* 获取授权信息
*
* @param type 授权类型,图片类型或普通文件类型
* @return
* @throws ClientException
*/
@RequestMapping("/getCredit")
public JSONObject getCredit(String type) throws ClientException {
JSONObject jsonObject = new JSONObject();
JSONObject creditInfo;
creditInfo = AliyunStsOssUtil.getCredit("xxx", ossRoleArn, ossAccessKeyId, ossAccessKeySecret, ossBucket);
//文件存放地域
jsonObject.put("region", ossRegion);
//临时访问accessKey
jsonObject.put("AccessKeyId", creditInfo.getString(AliyunStsOssUtil.ACCESS_KEY_ID));
//临时访问accessKeySecret
jsonObject.put("AccessKeySecret", creditInfo.getString(AliyunStsOssUtil.ACCESS_KEY_SECRET));
//临时访问
jsonObject.put("SecurityToken", creditInfo.getString(AliyunStsOssUtil.SECURITY_TOKEN));
//临时访问过期时间
jsonObject.put("Expiration", creditInfo.getString(AliyunStsOssUtil.EXPIRATION));
//bucket名称
jsonObject.put("bucket", this.ossBucket);
//文件的存放基目录
//jsonObject.put("basePath", String.format("oss/%s", basePath));
jsonObject.put("StatusCode", 200);
// 回调
String callbackUrl = this.callbackUrl + "/oss/callback";
JSONObject jasonCallback = new JSONObject();
jasonCallback.put("url", callbackUrl);
jasonCallback.put("body",
"filename=${object}" +
"&size=${size}" +
"&mimeType=${mimeType}" +
"&height=${imageInfo.height}" +
"&width=${imageInfo.width}" +
"&fileOriginalName=${x:fileOriginalName}" +
"&uploadUserId=${x:uploadUserId}" +
"&funCode=${x:funCode}" +
"&dataId=${x:dataId}" +
"&newFileName=${x:newFileName}" +
"&suffixName=${x:suffixName}");
jsonObject.put("callback", jasonCallback);
return jsonObject;
}
@PostMapping("/callback")
public void callback(HttpServletRequest request, HttpServletResponse response) throws IOException {
String ossCallbackBody = GetPostBody(request.getInputStream(),
Integer.parseInt(request.getHeader("content-length")));
boolean ret = VerifyOSSCallbackRequest(request, ossCallbackBody);
logger.debug("ossCallbackBody :" + ossCallbackBody);
logger.debug("verify result : " + ret);
if (ret) {
// TODO 插入数据图片关联表
if(i == 1){
response(request, response, "{\"Status\":\"OK\"}", HttpServletResponse.SC_OK);
}else {
response(request, response, "{\"Status\":\"verdify not ok\"}", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
response(request, response, "{\"Status\":\"verdify not ok\"}", HttpServletResponse.SC_BAD_REQUEST);
}
}
/**
* 服务器响应结果
*
* @param request
* @param response
* @param results
* @param status
* @throws IOException
*/
private void response(HttpServletRequest request, HttpServletResponse response, String results, int status)
throws IOException {
String callbackFunName = request.getParameter("callback");
response.addHeader("Content-Length", String.valueOf(results.length()));
if (callbackFunName == null || callbackFunName.equalsIgnoreCase(""))
response.getWriter().println(results);
else
response.getWriter().println(callbackFunName + "( " + results + " )");
response.setStatus(status);
response.flushBuffer();
}
/**
* 获取Post消息体
*
* @param is
* @param contentLen
* @return
*/
private String GetPostBody(InputStream is, int contentLen) {
if (contentLen > 0) {
int readLen = 0;
int readLengthThisTime = 0;
byte[] message = new byte[contentLen];
try {
while (readLen != contentLen) {
readLengthThisTime = is.read(message, readLen, contentLen - readLen);
if (readLengthThisTime == -1) {// Should not happen.
break;
}
readLen += readLengthThisTime;
}
return new String(message);
} catch (IOException e) {
}
}
return "";
}
/**
* 验证上传回调的Request
*
* @param request
* @param ossCallbackBody
* @return
* @throws NumberFormatException
* @throws IOException
*/
protected boolean VerifyOSSCallbackRequest(HttpServletRequest request, String ossCallbackBody)
throws NumberFormatException, IOException {
boolean ret = false;
String autorizationInput = new String(request.getHeader("Authorization"));
String pubKeyInput = request.getHeader("x-oss-pub-key-url");
byte[] authorization = BinaryUtil.fromBase64String(autorizationInput);
byte[] pubKey = BinaryUtil.fromBase64String(pubKeyInput);
String pubKeyAddr = new String(pubKey);
if (!pubKeyAddr.startsWith("http://gosspublic.alicdn.com/")
&& !pubKeyAddr.startsWith("https://gosspublic.alicdn.com/")) {
System.out.println("pub key addr must be oss addrss");
return false;
}
String retString = executeGet(pubKeyAddr);
retString = retString.replace("-----BEGIN PUBLIC KEY-----", "");
retString = retString.replace("-----END PUBLIC KEY-----", "");
String queryString = request.getQueryString();
String uri = request.getRequestURI();
String decodeUri = java.net.URLDecoder.decode(uri, "UTF-8");
String authStr = decodeUri;
if (queryString != null && !queryString.equals("")) {
authStr += "?" + queryString;
}
authStr += "\n" + ossCallbackBody;
ret = doCheck(authStr, authorization, retString);
logger.info("------authStr:" + authStr);
logger.info("------pubKeyAddr:" + pubKeyAddr);
logger.info("------authorization:" + authorization);
logger.info("------retString:" + retString);
return ret;
}
/**
* 获取public key
*
* @param url
* @return
*/
@SuppressWarnings({"finally"})
private String executeGet(String url) {
BufferedReader in = null;
String content = null;
try {
// 定义HttpClient
CloseableHttpClient client = HttpClientBuilder.create().build();
//@SuppressWarnings("resource")
//DefaultHttpClient client = new DefaultHttpClient();
// 实例化HTTP方法
HttpGet request = new HttpGet();
request.setURI(new URI(url));
HttpResponse response = client.execute(request);
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
content = sb.toString();
} catch (Exception e) {
} finally {
if (in != null) {
try {
in.close();// 最后要关闭BufferedReader
} catch (Exception e) {
e.printStackTrace();
}
}
return content;
}
}
/**
* 验证RSA
*
* @param content
* @param sign
* @param publicKey
* @return
*/
private static boolean doCheck(String content, byte[] sign, String publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = BinaryUtil.fromBase64String(publicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance("MD5withRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes());
boolean bverify = signature.verify(sign);
return bverify;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private Map<String, String> getUrlParams(String ossCallbackBody) throws UnsupportedEncodingException {
Map<String, String> params = new HashMap<>();
String[] split = ossCallbackBody.replaceAll("\"","").split("&");
for (String s : split) {
String[] split1 = s.split("=");
if (split1.length == 2) {
params.put(split1[0], URLDecoder.decode(split1[1], "utf-8"));
}else {
params.put(split1[0],"");
}
}
return params;
}
}
3.5 callback接口配置权限
com.ruoyi.framework.config.CasSecurityConfig类中configure方法添加:
// permitAll | 用户可以任意访问
.antMatchers("/oss/callback").permitAll()
4. 前端项目整合
4.1 安装依赖
npm install ali-oss -s
4.2 vuex添加用户id
src\store\modules\user.js中state属性添加
userId: '',
mutations属性添加
SET_ID: (state, userId) => {
state.userId = userId
},
actions属性的GetInfo函数修改
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(res => {
const user = res.user
const avatar = user.avatar == "" ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_NAME', user.userName)
// 其实就是加了这一行
commit('SET_ID', user.userId)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
src\store\getters.js中getters属性添加
userId: state => state.user.userId,
4.3 添加src\api\oss\oss.js
import request from '@/utils/request'
import OSS from 'ali-oss';
import store from '@/store'
/**
* 获取ossClient
* @param {} option
* @returns
*/
const getClient = async option => {
let credit = await request({
url:'/oss/getCredit',
method: 'get'
});
console.log(option)
//从后台拿到的临时授权信息
return {
client : new OSS({
region: credit.region,
accessKeyId: credit.AccessKeyId,
accessKeySecret: credit.AccessKeySecret,
stsToken: credit.SecurityToken,
bucket: credit.bucket,
secure: true,
}),
callback : credit.callback,
};
}
/**
/ * 上传一个文件
*/
const uploadAction = async option => {
let {client, callback} = await getClient();
console.log(option)
let filePath = '/' + option.fileName;
//将多个/替换为1个/,防止oss文件路径中多个/造成空目录
filePath = filePath.replace(/[\/]+/g, '\/');
// 自定义变量传值
console.log("uploadUserId=>",store.getters.userId)
// 自定义函数,对应后端/oss/getCredit接口jasonCallback对象的body
callback.customValue = {
uploadUserId: store.getters.userId + '',
funCode: option.funCode,
dataId: option.dataId,
fileOriginalName: option.file.name,
newFileName: option.newFileName,
suffixName: option.suffixName,
};
callback.contentType = "application/json";
return client.put(
filePath,
option.file, {
callback: callback,
}
)
}
/**
* 上传多个文件
* @param {*} files 文件集合
* @param {*} fileDir oss中文件夹路径
* @param {*} funCode 自定义参数,功能编号,后端保存时用于区分业务
* @param {*} dataId 自定义参数,业务主键,后端保存时用于区分工单
* @param {*} progress
* @returns oss object路径集合
*/
const uploadFiles = async (files, fileDir, funCode, dataId, progress = console.log) => {
console.log("files=>",files)
let urls = [];
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
for (let index = 0; index < files.length; index++) {
let file = files[index];
let suffixNameIndex = file.name.lastIndexOf('.');
let suffixName = suffixNameIndex == -1 ? '' : file.name.substring(suffixNameIndex);
let newFileName = uuid().replaceAll("-","").toLocaleLowerCase() + suffixName
let res = await uploadAction({
//待上传文件信息
file: files[index],
//临时访问授权信息
// token: credit.data,
// 上传文件名称,为防止文件被覆盖(oss上传重复文件会覆盖同名文件),使用fileDir/年/月/日/uuid.文件后缀,具体可根据实际业务而定
fileName: `${fileDir}/${year}/${month}/${day}/` + newFileName,
//可选参数,图片上传过程中触发的操作
progress: progress,
funCode: funCode,
dataId: dataId + '',
newFileName,
suffixName: suffixName.length < 1 ? '': suffixName.substr(1),
});
if (res.name) {
urls.push(`${res.name}`);
}
}
//返回多个文件上传结果,为多个oss文件路径地址(不包含host路径)
return urls;
}
/**
* 下载文件
* @param {*} filePath oss object路径
* @param {*} filename 下载后的文件名
*/
const downloadFile = async (filePath, filename)=>{
let {client, callback} = await getClient();
// 配置响应头实现通过URL访问时自动下载文件,并设置下载后的文件名。
const response = {
'content-disposition': `attachment; filename=${encodeURIComponent(filename)}`
}
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
const url = client.signatureUrl(filePath, { response });
console.log(url);
return url;
}
function uuid(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [], i;
radix = radix || chars.length;
if (len) {
// Compact form
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random()*16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
export default {
upload: uploadFiles,
getClient: getClient,
download: downloadFile,
}
4.4 全局挂载oss.js
src\main.js中添加
import oss from './api/oss/oss'
// js中通过this.$oss.函数名称调用
Vue.prototype.$oss = oss;
*注意事项
- 前端上传回调签名校验失败