草庐IT

前端直传阿里云OSS

风中蒲公英 2023-04-07 原文

阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。

目前通过Web端直传文件(Object)到OSS,有两种方案:

一、利用OSS Browser.js SDK将文件上传到OSS。该方案通过OSS Browser.js SDK直传数据到OSS,支持断点续传,支持各种主流浏览器,可以将File对象、Blob数据以及OSS Buffer上传OSS,该方案还支持下载和删除

二、利用OSS提供的PostObject接口来实现表单上传,不支持断点续传,支持h5,小程序,支持uniapp的uni.uploadFile接口

方案一:使用阿里云SDK上传

由于前端环境不安全,为避免暴露阿里云账号访问密钥(AccessKey ID和AccessKey Secret),该方案需要搭建STS服务获取临时访问密钥(AccessKey ID和AccessKey Secret)和安全令牌(SecurityToken),需要先开通STS服务,参考官方文档

后端

后端需要导入aliyun-sdk-oss包,用于获取前端需要的key和secret

JDK版本:jdk8

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>2.0.7</version>
    <!--<version>3.15.0</version>最新-->
</dependency>

如果是java9及以上版本,则需要添加jaxb相关依赖。添加jaxb相关依赖示例代码如下:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

搭建STS服务(部分代码)

	// STS接入地址,例如sts.cn-hangzhou.aliyuncs.com。
  	@Value("${ramEndpoint}")
    private String ramEndpoint;
    // 访问密钥AccessKey ID和AccessKey Secret
    @Value("${ramAccessKeyId}")
    private String ramAccessKeyId;
    @Value("${ramAccessKeySecret}")
    private String ramAccessKeySecret;
    // 角色ARN
    @Value("${ramRoleArn}")
    // 自定义角色会话名称,用来区分不同的令牌,例如可填写为SessionTest
    private String ramRoleArn;
    @Value("${ramRoleSessionName}")
    private String ramRoleSessionName;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 通过RAM子账号获取stsToken,作为临时凭据
     */
    @RequestMapping(value = "getStsToken", method = {RequestMethod.GET, RequestMethod.POST})
    public Object getStsToken() {
        ResponseVo responseVo = new ResponseVo();
        AssumeRoleResponse response = null;
        Object redisToken = null;
        try {
            redisToken = redisTemplate.opsForValue().get("stsToken");
        } catch (Exception e) {
            logger.error(e.getMessage());
            responseVo.setError(GlobalErrorCode.SYS_RUN_ERROR.getCode());
            return responseVo;
        }
        if (redisToken != null) {
            response = JSONObject.parseObject(redisToken.toString(), AssumeRoleResponse.class);
            responseVo.setSuccess(response);
            return responseVo;
        } else {
            String policy = "{\n" +
                    "    \"Version\": \"1\", \n" +
                    "    \"Statement\": [\n" +
                    "        {\n" +
                    "            \"Action\": [\n" +
                    "                \"oss:*\"\n" +
                    "            ], \n" +
                    "            \"Resource\": [\n" +
                    "                \"acs:oss:*:*:*\" \n" +
                    "            ], \n" +
                    "            \"Effect\": \"Allow\"\n" +
                    "        }\n" +
                    "    ]\n" +
                    "}";
            try {
                DefaultProfile.addEndpoint("", "", "Sts", ramEndpoint);
                // 构造default profile(参数留空,无需添加region ID)
                IClientProfile profile = DefaultProfile.getProfile("", ramAccessKeyId, ramAccessKeySecret);
                // 用profile构造client
                DefaultAcsClient client = new DefaultAcsClient(profile);
                final AssumeRoleRequest request = new AssumeRoleRequest();
                request.setMethod(MethodType.POST);
                request.setRoleArn(ramRoleArn);
                request.setRoleSessionName(ramRoleSessionName);
                request.setPolicy(policy); // 若policy为空,则用户将获得该角色下所有权限
                request.setDurationSeconds(20 * 60L); // 设置凭证有效时间,单位秒
                //获取凭证
                response = client.getAcsResponse(request);
                /*
                 * 缓存该凭证,凭证失效后才从OSS再次获取凭证
                 * 凭证有效时间为20分钟,Redis里只缓存10分钟
                 */
                redisTemplate.opsForValue().set("stsToken", JSONObject.toJSONString(response), 10 * 60, TimeUnit.SECONDS);
                responseVo.setSuccess(response);
                return responseVo;
            } catch (ClientException e) {
                logger.error(e.getErrMsg());
                responseVo.setError(GlobalErrorCode.SYS_RUN_ERROR.getCode());
                return responseVo;
            }
        }

    }

参考阿里云文档

前端

安装

$ npm install ali-oss --save

部分代码

onLoad() {
	this.getStsToken()
},

methods

/**
* @param {String} pathAndName Object完整路径。Object完整路径中不能包含Bucket名称("exampledir/exampleobject.txt")
* @param {Object} data (file对象、Blob数据或者OSS Buffer)
*/
async putObject(pathAndName, data) {
    try {
        // 您可以通过自定义文件名(例如exampleobject.txt)或文件完整路径(例如exampledir/exampleobject.txt)的形式实现将数据上传到当前Bucket或Bucket中的指定目录。
        const result = await this.getClient().put(
            pathAndName,
            data
        );
        console.log('result:', result);
    } catch (e) {
        console.log(e);
    }
},
    getClient() {
        if (this.client) {
            return this.client
        }
        const OSS = require('ali-oss');

        const client = new OSS({
            // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
            region: 'oss-cn-qingdao',
            // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
            accessKeyId: this.stsToken.credentials.accessKeyId,
            accessKeySecret: this.stsToken.credentials.accessKeySecret,
            // 从STS服务获取的安全令牌(SecurityToken)。
            stsToken: this.stsToken.credentials.securityToken,
            refreshSTSToken: async () => {
                // 向您搭建的STS服务获取临时访问凭证。
                let info = await this.$post(GET_STS_TOKEN)
                info = info.data
                console.log('-----------refresh--token')

                return {
                    accessKeyId: info.credentials.accessKeyId,
                    accessKeySecret: info.credentials.accessKeySecret,
                    stsToken: info.credentials.securityToken
                }
            },
            // 刷新临时访问凭证的时间间隔,单位为毫秒。每隔一段时间定时器会自动掉后台接口刷新token
            refreshSTSTokenInterval: 600000,
            // 填写Bucket名称。
            bucket: 'zxxxxth-bucket'
        });

        this.client = client
        return this.client
    },
        getStsToken() {
            //从后台获取stsToken(改成自己的前端请求接口)
            this.$post(GET_STS_TOKEN).then(rsp => {
                if (rsp.success) {
                    this.stsToken = rsp.data;
                    // 初始化一下client让定时任务启动,自动刷新token(避免过期)
                    this.getClient()
                    console.log('this.stsToken:', this.stsToken)
                } else {
                    uni.showToast({
                        title: rsp.message,
                        duration: 2000
                    });
                }
            })
        },              
                

refreshSTSToken参数说明:当初始化new OSS()时,定时器会启动,当时间到了refreshSTSTokenInterval所设置的值时,并不会立即调用后台接口获取token,只有手动触发put()接口时,才会调用后台接口获取token

参考阿里云文档

开通STS服务步骤

方案二:使用PostObject接口来实现表单上传

这个方案支持小程序上传,uniapp上传。无需开通STS服务

后端

获取postObject接口需要的policy,OSSAccessKeyId,signature 参考官方文档

这里签名使用后端签名,所以不需要申请开通STS服务

简化版,无回调
/**
     * 利用OSS提供的PostObject接口,通过表单上传的方式将文件上传到OSS。
     * 该方案兼容大部分浏览器,但在网络状况不好的时候,如果单个文件上传失败,
     * 只能重试上传。上传的Object大小不能超过5 GB。
     * @return ResponseVo{success:true,message:'',data:{},code:200}
     */
    @RequestMapping(value = "getPostObjectParams", method = {RequestMethod.GET, RequestMethod.POST})
    public Object getPostObjectParams() {
        ResponseVo responseVo = new ResponseVo();
        responseVo.setSuccess(OSSServer.getPostObjectParams());
        return responseVo;
    }

OSSServer.class

	public static OSSClient getOSSClient() {
		if (null == ossClient) {
			ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
		}
		return ossClient;
	}	

		/**
	 * 获取表单上传的方式的PostObject参数
	 * @return
	 */
	public static Map<String, Object> getPostObjectParams() {
		Map<String, Object> respMap = new LinkedHashMap();
		// 限制参数的生效时间,单位为分钟,默认值为20。
		int expireTime = 20;
		// 限制上传文件的大小,单位为MB,默认值为100。
		int maxSize = 100;
		// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
		// 如果值为"test"那么前端的key参数必须以"test"开头,如test/*、test1.jpg、test/comment/11.jpg
		String dir = "";

		// 创建OSSClient实例。
		OSS ossClient = getOSSClient();
		try {
			long expireEndTime = System.currentTimeMillis() + expireTime * 1000 * 60;
			Date expiration = new Date(expireEndTime);
			PolicyConditions policyConds = new PolicyConditions();
			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize * 1024 * 1024);
			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

			String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
			byte[] binaryData = postPolicy.getBytes("utf-8");
			String encodedPolicy = BinaryUtil.toBase64String(binaryData);
			String postSignature = ossClient.calculatePostSignature(postPolicy);


			respMap.put("accessKeyId", accessKeyId);
			respMap.put("policy", encodedPolicy);
			respMap.put("signature", postSignature);

			respMap.put("expire", expireEndTime / 1000);
		} catch (Exception e) {
			log.error("getPostObjectParams", e);
		}

		return respMap;
	}

大多数情况下,用户上传文件后,应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等,为此OSS提供了上传回调方案。

流程图:

当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器时,需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。

有回调的PostObject参数
/**
	 * 获取表单上传的方式的PostObject参数【有回调】
	 * @return
	 */
	public static Map<String, Object> getPostObjectParams() {
		Map<String, Object> respMap = new LinkedHashMap();
		// 限制参数的生效时间,单位为分钟,默认值为20。
		int expireTime = 20;
		// 限制上传文件的大小,单位为MB,默认值为10。
		int maxSize = 10;
		// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
		// 如果值为"test"那么前端的key参数必须以"test"开头,如test/*、test1.jpg、test/comment/11.jpg
		// 可以让用户没有办法上传到其他的目录,从而保证了数据的安全性
		String dir = "";
		// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
		String callbackUrl = "https://jmt.xxx.cn/common/postObjectCallBack/";

		// 创建OSSClient实例。
		OSS ossClient = getOSSClient();
		try {
			long expireEndTime = System.currentTimeMillis() + expireTime * 1000 * 60;
			Date expiration = new Date(expireEndTime);
			PolicyConditions policyConds = new PolicyConditions();
			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize * 1024 * 1024);
			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

			String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
			byte[] binaryData = postPolicy.getBytes("utf-8");
			String encodedPolicy = BinaryUtil.toBase64String(binaryData);
			String postSignature = ossClient.calculatePostSignature(postPolicy);


			respMap.put("accessKeyId", accessKeyId);
			respMap.put("policy", encodedPolicy);
			respMap.put("signature", postSignature);
			respMap.put("expire", expireEndTime / 1000);
			// 配置回调地址
			JSONObject jasonCallback = new JSONObject();
			jasonCallback.put("callbackUrl", callbackUrl);
//			jasonCallback.put("callbackBody",
//					"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");

			jasonCallback.put("callbackBody", "{\"filename\":${object},\"mimeType\":${mimeType}}");

			jasonCallback.put("callbackBodyType", "application/json");//application/x-www-form-urlencoded
			String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
			respMap.put("callback", base64CallbackBody);

		} catch (Exception e) {
			log.error("getPostObjectParams", e);
		}

		return respMap;
	}
给oss回调的接口
    @RequestMapping(value = "postObjectCallBack", method = RequestMethod.POST)
    public Object postObjectCallBack(HttpServletRequest request, @RequestBody Object callbackBody) throws IOException {

        log.info("---callbackBody={}",callbackBody);

//        "{"filename":"test/comment/tt1.jpg","mimeType":"image/png"}"
         return callbackBody;

    }

前端

小程序
const host = '<host>'; //"https://examplebucket.oss-cn-hangzhou.aliyuncs.com"
const signature = '<signatureString>';
const ossAccessKeyId = '<accessKey>';
const policy = '<policyBase64Str>';
const key = '<object name>';
const securityToken = '<x-oss-security-token>'; 
const filePath = '<filePath>'; // 待上传文件的文件路径。
wx.uploadFile({
  url: host, // 这个是阿里云bucket的根地址,使用这个地址拼接路径可以访问已上传的文件。如果是自己服务器则是开发者服务器的URL。
  filePath: filePath,// 本地文件路径,小程序chooseImage方法返回的路径
  name: 'file', // 必须填file。
  formData: {
    key,
    policy,
    OSSAccessKeyId: ossAccessKeyId,
    signature,
    // 'x-oss-security-token': securityToken // 使用STS签名时必传。
  },
  success: (res) => {
    if (res.statusCode === 204) {
      console.log('上传成功');
    }
  },
  fail: err => {
    console.log(err);
  }
});
uniapp、uView的Upload组件
uni.uploadFile({
    url: 'https://res.xxx.cn', //这个是阿里云bucket的根地址,使用这个地址拼接路径可以访问已上传的文件
    filePath: url,// 本地文件路径,小程序chooseImage方法返回的路径
    name: 'file',// 必须填file
    formData: {
        key: 'test/comment/tt1.jpg',//会把tt1.jpg图片上传至bucket(上方url所指向)的test/comment目录
        policy: this.postObject.policy,
        OSSAccessKeyId: this.postObject.accessKeyId,
        signature: this.postObject.signature,
        // callback: this.postObject.callback
    },
    success: (res) => {
        console.log('uni.uploadFile success:', res)
        if(res.statusCode===204){
            // 上传成功
            console.log('-------------success------------')
        }else if(res.statusCode===403){
            // Policy expired.
            uni.showToast({
                title: '网络超时',
                duration: 2000
            });
            // 续期
            this.getPostObjectParams()
        }else{
            console.log('上传失败')
        }
        // setTimeout(() => {
        resolve(res)
        // }, 1000)
    },
    fail(err) {
        console.error('uni.uploadFile: fail', err)
    }
});


onLoad() {
	this.getPostObjectParams()
},
...........
getPostObjectParams() {
    //从后台获取stsToken
    this.$post(GET_POST_OBJECT_PARAMS).then(rsp => {
        if (rsp.success) {
            this.postObject = rsp.data;
            console.log('this.postObject:', this.postObject)
        } else {
            uni.showToast({
                title: rsp.message,
                duration: 2000,
                icon:'none'
            });
        }
    })
},

参考官方文档

举一个uniapp例子

UI库:uView

<template>
	<!-- 发表评论 -->
	<view class="create-comment">
		<view class="star comment-common">
			<view class="title">评分</view>
			<view class="control">
				<text class="name">游玩体验</text>
				<u-rate :count="5" v-model="starCount" :touchable="false" active-color="#E65526" size="24"></u-rate>
			</view>

		</view>

		<view class="content comment-common">
			<view class="title">评价内容</view>
			<textarea v-model="resourceComment.content" maxlength="200"
				placeholder="游玩的满意吗?大家都想了解这里值得去吗?有什么亮点?期待你精彩的点评!">
			</textarea>
		</view>

		<view class="picture comment-common">
			<view class="title">图片</view>
			<!-- name=1对应fileList1 -->
			<u-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1" multiple :maxCount="15"
				:maxSize="maxSize">
			</u-upload>
		</view>

		<view class="picture comment-common">
			<view class="title">视频</view>
			<!-- name=2对应fileList2 -->
			<u-upload :fileList="fileList2" @afterRead="afterRead" @delete="deletePic" name="2" multiple :maxCount="1"
				:maxSize="maxSize" accept="video" uploadIcon="movie"></u-upload>
		</view>

		<button @click="submit()" type="warn" class="submit" :loading="loading" :disabled="loading">发布</button>
	</view>
</template>

<script>
	import {
		GET_POST_OBJECT_PARAMS
	} from '../../api/api.js'

	export default {
		data() {
			return {
				starCount: 0,
				resourceComment: {
					content: ''
				},
				fileList1: [],
				fileList2: [],
				loading: false,
				postObject: {
					expire: 0
				},
				maxSize: 100 * 1024 * 1024
			}
		},
		onLoad() {
		},
		methods: {
			// -----upload start
			// 新增图片
			async afterRead(event) {
				console.log('event:', event)

				// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
				let lists = [].concat(event.file)
				// console.log('lists:', lists)
				let fileListLen = this[`fileList${event.name}`].length
				// console.log('fileListLen:', fileListLen)
				lists.map((item) => {
					this[`fileList${event.name}`].push({
						...item,
						status: 'uploading',
						message: '上传中'
					})
				})

				let time = new Date().getTime() / 1000
				// console.log('time:', time)
				if (time > this.postObject.expire) {
					// policy过期,续期
					await this.getPostObjectParams()
				}

				for (let i = 0; i < lists.length; i++) {
					const result = await this.uploadFilePromise(lists[i].url)
					let item = this[`fileList${event.name}`][fileListLen]
					this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
						status: result ? 'success' : 'failed',
						message: '',
						url: result
					}))
					fileListLen++
				}
			},
			compressJpgImage(src) {
				return new Promise((resolve, reject) => {
					// uni.compressImage({
					//   src: src,
					//   quality: 80,
					//   success: res => {
					//     console.log(res.tempFilePath)
					//   }
					// })
				})
			},
			uploadFilePromise(url) {
				return new Promise((resolve, reject) => {
					let a = uni.uploadFile({
						url: 'https://res.xxxx.cn',
						filePath: url,
						name: 'file', // 必须填file
						formData: {
							key: 'test/comment/tt3.jpg',
							policy: this.postObject.policy,
							OSSAccessKeyId: this.postObject.accessKeyId,
							signature: this.postObject.signature,
							callback: this.postObject.callback
						},
						success: (res) => {
							// 未配置回调 上传成功返回 {date:"",errMsg:"uploadFile:ok",statusCode:204},如果配置了回调data参数才会有值
							// 配置了回调 上传成功返回 {{data:{"filename":"test/comment/tt1.jpg","mimeType":"image/png"},errMsg:"uploadFile:ok",statusCode:200}
							console.log('uni.uploadFile success():', res)
							if (res.statusCode === 204 || res.statusCode === 200) {
								// 上传成功
								console.log('-------------uploaded success')
								resolve(url)
							} else {
								console.log('-------------uploaded failed')
								uni.showToast({
									title: '上传失败',
									duration: 2000,
									icon: 'error'
								});
								resolve()
							}
						},
						fail(err) {
							console.error('uni.uploadFile: fail():', err)
						}
					});
				})
			},
			// 删除图片
			deletePic(event) {
				this[`fileList${event.name}`].splice(event.index, 1)
			},
			// -----upload end

			getPostObjectParams() {
				//从后台获取postObject
				return this.$post(GET_POST_OBJECT_PARAMS,{folderType:'comment'}).then(rsp => {
					// this.postObject = rsp
					if (rsp.success) {
						this.postObject = rsp.data;
						console.log('this.postObject:', this.postObject)
					} else {
						console.error('getPostObjectParams:', rsp.message || '系统错误')
					}
				})
			}
		}
	}
</script>

<style lang="scss">
	.create-comment {
		padding: 12px;

		.comment-common {
			margin-bottom: 10px;
			padding: 15px 10px;
			background-color: white;
			border-radius: 10px;
		}

		.title {
			margin-bottom: 10px;
			font-size: 16px;
			font-weight: bold;
		}

		.star {
			.control {
				display: flex;
				align-items: center;

				.name {
					margin-right: 10px;
				}
			}
		}

		.content {
			textarea {
				font-size: 14px;
				width: 100%;
			}
		}

		.picture {}

		.submit {
			margin-top: 20px;
			width: 80%;
			font-size: 15px;
			color: white;
			background-color: #e65526;
		}
	}
</style>


附:根据blob链接获取blob对象

			/**
			 * 根据blob链接获取blob对象
			 * @param {Object} url "blob:http://localhost:8085/d688ce4f-0f5d-418c-85ad-62bcb3f38dee"
			 * @returns Blob(31846) {size: 31846, type: "image/jpeg"}
			 */
			getBlobByUrl(url) {
				return uni.request({
					url: url,
					// 合法值:text、arraybuffer
					responseType: 'arraybuffer'
				}).then(data=>{
					const [error, rsp]  = data;
					if(error){
						console.error(`post-error:${error}, url:${url}`)
						return {message: error.errMsg}
					}else{
						let buffer = rsp.data
						// ArrayBuffer(185) {}
						console.log('buffer:', buffer)
						return new Blob([buffer])
					}
				})
				
				// return new Promise((resolve, reject) => {
					
				// 	let xhr = new XMLHttpRequest()
				// 	xhr.open('GET', url, true)
				// 	xhr.responseType = 'blob'
				// 	xhr.onload = function(e) {
				// 		if (this.status == 200) {
				// 			let myBlob = this.response

				// 			// let file = new window.File(
				// 			// 	[myBlob],
				// 			// 	'myfile.jpg', {
				// 			// 		type: myBlob.type
				// 			// 	}
				// 			// )
				// 			// console.log("files:", file)
				// 			resolve(myBlob)
				// 		} else {
				// 			reject(false)
				// 		}
				// 	}
				// 	xhr.send()
				// })
			},

blobUrl、blob、base64、file相互转化:https://www.cnblogs.com/jing-zhe/p/15402775.html

uniapp选择file

<button @click="submit()" type="warn" class="submit" :loading="loading" :disabled="loading">发布</button>

submit() {
    let utils = new Utils()
    uni.chooseImage({
        count: 6, //默认9
        sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
        sourceType: ['album'], //从相册选择
        success: function(res) {
            console.log(JSON.stringify(res.tempFilePaths));
            console.log(res.tempFiles)

            utils.getFileMD5(res.tempFiles[0], function(md5) {
                console.log('md5:', md5)
            })
        }
    });
   
},

有关前端直传阿里云OSS的更多相关文章

  1. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  2. 阿里云RDS——产品系列概述 - 2

    基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

  3. ruby-on-rails - 在 Rails 应用程序的前端获取实时日志 - 2

    在Rails3.x应用程序中,我正在使用net::ssh并向远程pc运行一些命令。我想向用户的浏览器显示实时日志。比如,如果两个命令在net中运行::ssh执行即echo"Hello",echo"Bye"被传递然后"Hello"应该在执行后立即显示在浏览器中。这是代码我在ruby​​onrails应用程序中使用ssh连接和运行命令Net::SSH.start(@servers['local'],@machine_name,:password=>@machine_pwd,:timeout=>30)do|ssh|ssh.open_channeldo|channel|channel.requ

  4. ruby - 如何在转换器插件中访问页面属性(YAML 前端) - 2

    我正在为Jekyll编写一个转换器插件,需要访问一些页眉(YAML前端)属性。只有内容被传递给主要的转换器方法,似乎无法访问上下文。例子:moduleJekyllclassUpcaseConverter关于如何在转换器插件中访问页眉数据有什么想法吗? 最佳答案 基于Jekyll源代码,无法在转换器中检索YAML前端内容。根据您的情况,我看到了两种可行的解决方案。您的文件扩展名可以具有足够的描述性,以提供您本应包含在前言中的信息。看起来Converter插件的设计就是这么基本的。如果修改Jekyll是一个选项,您可以更改Convert

  5. 阿里云,华为云,腾讯云三大公有云厂商,香港地区主机测评 - 2

    三大公有云厂商,香港地区主机测评一、ping时延比对(厦门电信本地测试):Ping时延测试腾讯云阿里云华为云延迟率最低时延44ms,最高72ms,平均46ms47.242段:最低时延59ms,最高204ms,平均107ms最低时延45ms,最高93ms,平均47ms丢包率丢包率小有的ip段丢包率较大每个段都会有概率丢包阿里云:47.242段:最低时延59ms,最高204ms,平均107ms,有的ip段丢包率较大8.210段:最低时延64ms,最高232ms,平均119ms,丢包率较好腾讯云:最低时延44ms,最高72ms,平均46ms,丢包率小华为云:最低时延45ms,最高93ms,平均47m

  6. 阿里云Web应用防火墙-WAF - 2

    WAF可以对网站进行扫描,识别API漏洞。API安全如何设置API安全_Web应用防火墙-阿里云帮助中心API安全如何划分API业务用途?登录认证手机验证码认证数据保存数据查询数据导出数据分享数据更新数据删除数据增加下线注销信息发送信息认证邮件信息发送邮箱验证码认证账号密码认证账号注册API安全支持检测哪些敏感数据?敏感数据级别敏感数据类型非敏感数据(N)不涉及。特级敏感数据(L0)与一级敏感数据(L1)或二级敏感数据(L2)相同。单次响应中一级敏感数据(L1)较多时,升级为特级敏感数据(L0)。单次响应中二级敏感数据(L2)较多时,升级为一级敏感数据(L1)或特级敏感数据(L0)。一级敏感数

  7. 阿里大文娱从做大到做强 - 2

    提起阿里巴巴,大部分人想到的是他的电商业务,其实在文娱产业,阿里巴巴的布局很早,阿里大文娱是阿里双H战略的一环,从2014年开始,通过重金收购,阿里巴巴在文学音乐游戏,影院视频体育等板块开始了布局,阿里大文娱初具规模,当时阿里巴巴的高层认为,如果不做娱乐,只能是一家电子商务公司,有了娱乐和电子商务平台,相辅相成,在扩大版图的同时,同时能增强阿里巴巴的影响力。众所周知,电子商务领域,阿里巴巴在初创期也曾遭遇挫折,但整体来说发展势头很猛,始终处于领头羊的地位,阿里大文娱的发展,虽然经历了起伏和波折,发展势头一直看好。对于企业怎样发展,一直存在着两个观点,是做大面面俱到做综合业务,还是专而精呢?阿里

  8. 前端实现文件上传(点击+拖拽) - 2

    一、简介之前在Vue项目中使用过element的上传组件,实现了点击上传+拖拽上传的两种上传功能。然后我就在想是否可以通过原生的html+js来实现文件的点击上传和拖拽上传,说干就干。首先是点击获取上传文件自然没的说,只需要借助input标签即可,但原生的点击上传按钮,实在是过于简陋,所以我的想法是通过一个div,模拟成上传按钮,然后监听其点击事件,通过input.click()去模拟点击真正的上传元素。然后是拖拽获取上传文件,这个稍有难度,我的想法是通过HTML5新增的drag拖放API+dataTransfer来实现文件的拖拽获取,但是由于是html5新增的,所以可能在某些低版本IE浏览器

  9. BigData/Cloud Computing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程 - 2

    BigData/CloudComputing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程目录一、云计算网站建设:部署与发布网站建设:简单动态网站搭建云服务器管理维护云数据库管理与数据迁移云存储:对象存储管理与安全超大流量网站的负载均衡二、大数据MOOC网站日志分析搭建企业级数据分析平台基于LBS的热点店铺搜索基于机器学习PAI实现精细化营销基于机器学习的客户流失预警分析使用DataV制作实时销售数据可视化大屏使用MaxCompute进行数据质量核查使用Quick BI制作图形化报表使用时间序列分解模型预测商品销量三、云安全云平台使用安全云上服务

  10. 教你如何使用vercel服务免费部署前端项目和serverless api - 2

    一、介绍一下vercelvercel是一个站点托管平台,提供CDN加速,同类的平台有Netlify和GithubPages,相比之下,vercel国内的访问速度更快,并且提供Production环境和development环境,对于项目开发非常的有用的,并且支持持续集成,一次push或者一次PR会自动化构建发布,发布在development环境,都会生成不一样的链接可供预览。但是vercel只是针对个人用户免费,teams是收费的首先vercel零配置部署,第二访问速度比github-page好很多,并且构建很快,还是免费使用的,对于部署个人前端项目路、接口服务非常方便vercel类似于git

随机推荐