前端直传阿里云OSS

news2025/7/9 9:30:38

阿里云对象存储服务(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)
            })
        }
    });
   
},

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/406963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web渗透之文件上传漏洞

目录一、原理二、利用方式三、文件上传的风险处四、文件上传漏洞的危害五、文件上传漏洞常见的绕过方式1、前端绕过检测2、mime类型检测绕过3、黑名单绕过&#xff08;1&#xff09;、相似扩展名&#xff08;2&#xff09;、apache配置文件&#xff08;3&#xff09;、大小写绕…

如何将html模板资源转为vuecli项目

1.目标 将找到的html素材模板&#xff0c;转换为vue的文件。 这个网上照了一圈&#xff0c;没找到合适的方案&#xff0c;就自己尝试弄了一下 目标效果 2.具体步骤 1. 通过vueclie 创建项目 vue create 项目名称然后把默认的样式删除了 2. 将静态资源放到vue项目的 publi…

【python】自动填写问卷星问卷及提交

前言&#xff1a;问卷是很好的网络调查方式之一&#xff0c;近年来&#xff0c;问卷星被广泛应用于各方面的调查。本文介绍了利用python代码自动填写问卷星基本题目&#xff0c;拥有自动填写、解决智能验证、批量提交问卷等功能。 目录 1.下载浏览器驱动 2. selenium基本配置…

【Java 基础篇】Java 初识、编译运行机制及开发环境搭建

一、Java 语言初识二、Java 编译运行机制2.1 JVM、JRE、JDK 三大概念介绍2.2 Java 是如何运行的三、Java 开发环境搭建(文末附下载地址)四、第一个 Java 项目4.1 创建项目和 .Java 源文件4.2 输出 hello world一、Java 语言初识 Java 是由 Sun Microsystems 公司于 1995 年 5 …

十大经典排序算法(下)

&#x1f353;个人主页&#xff1a;bit.. &#x1f352;系列专栏&#xff1a;Linux(Ubuntu)入门必看 C语言刷题 数据结构与算法 HTML和CSS3 目录 1.6 快速排序 1. 算法步骤 2. 动图演示 3.代码实现 1.7 堆排序 1. 算法步骤 2. 动图演示 3. 代码实现 1.8 计数排…

JavaScript注册监听事件与清除监听事件

JavaScript中有两种方法可以注册监听事件&#xff0c;一种是传统的注册方式&#xff0c;on事件名称的注册方法&#xff0c;如btn.onclick,还有方法监听事件注册方式&#xff0c; addEventListener()和ie8以下的attachEvent()。 注册监听事件 传统方式 语法格式&#xff1a;元…

ES6笔记————let,箭头函数,剩余参数

目录 一.let,var,const区别 let const 区别 二&#xff0c;解构 1 数组解构 2对象解构 三&#xff0c;箭头函数 1 基础语法 2 省略写法 3 对象方法里的this 4 apply/call调用时的this 5 箭头函数中this 8 箭头函数应用 四&#xff0c;剩余函数 一.let,var,con…

【bug】Failed at the node-sass@4.14.1 postinstall script(终于圆满解决)

花了两个小时才解决 首先是从git上克隆项目到本地&#xff0c;然后安装nodele_modules&#xff0c;输入npm i安装 安装到一半报错 npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! node-sass4.14.1 postinstall: node scripts/build.js npm ERR! Exit status 1 npm ERR! …

关于 Vue “__ob__:Observer“ 属性的解决方案

问题描述 我们操作 Vue 数据的时候&#xff0c;经常会看到这个属性: __ob__:Observer在我们操作这个数据的时候&#xff0c;如果想要单独拿这个数据里面的值&#xff0c;就会返回 undefined经过我在网上查找相关资料&#xff0c;发现 __ob__:Observer 是 Vue 中一个非常重要的…

【vue】diff 算法详解

一、diff算法是什么 diff算法是一种通过同层的树节点进行比较的高效算法 diff算法的目的就是找出新旧不同虚拟DOM之间的差异,使最小化的更新视图&#xff0c;所以 diff 算法本质上就是比较两个js对象的差异 特点 1. 比较只会在同层级进行&#xff0c;不会跨层级比较 2. 在diff…

原生JS实现飞机大战游戏 超详细解析 快来做一个自己玩吧

目录 1. 案例分析&#x1f4a8; 2. 适配设备 &#x1f4a8; 3. 背景滚动&#x1f4a8; 4. hero操作&#x1f4a8; 5. 敌机的创建与运动&#x1f4a8; 6. 子弹的创建与运动&#x1f4a8; 7. 碰撞检测&#x1f4a8; 8. 统计得分&#x1f4a8; 9. 设置开始与结束界面&…

疫情散去想看电影,使用css3动画实现一个阿凡达2完美开场

在历经了艰苦卓绝的3年抗疫后&#xff0c;疫情终于还是来了&#xff0c;很多小伙伴变成了小洋人酸奶&#xff0c;我相信过不了多少天&#xff0c;疫情终将散去&#xff0c;那个时候就可以和家人走进电影院啦。 今天用css布局一个阿凡达2的影院场景&#xff0c;提前过一过瘾。 目…

《uni-app》表单组件-form表单

本文分享的Form组件为uni-app的内置组件Form&#xff0c;非扩展组件&#xff0c;两者在用法上其实大同小异&#xff0c;只是扩展组件的属性以及事件更多…没有本质上的区别&#xff5e; 《uni-app》表单组件-form表单一. 简介二. 基础用法三. submit事件四. reset事件五. repor…

Tomcat安装配置及IDEA配置方法【亲测有效】

Tomcat安装配置及IDEA配置1.下载Tomcat2.配置Tomcat环境变量3.安装Tomcat4.启动Tomcat5.测试Tomcat6.IDEA配置Tomcat1.下载Tomcat Tomcat9官网下载地址 选择自己需要的版本&#xff0c;一般选择Windows 64位压缩包版本&#xff1a; 下载完后安装解压即可&#xff0c;解压后的…

如何使用nvm切换node版本

我比较懒惰,如非必要,不喜欢npm版本切换来切换去,感觉浪费我编程的时间.后来发现,现在偷的懒都是为将来的忙碌埋下的祸根. 言归正传,本文主要是讲解一下,如何使用nvm进行npm版本的切换. 工欲善其事必先利其器,我们先下载nvm;直接上下载链接(针对windows哈,毕竟我没mac本); 下载…

Code For Better 谷歌开发者之声——初识Web与谷歌,拉起兴趣之心。

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;程序是我的生命,但我相信爱她甚过爱我的生命。 目录一、了解谷歌浏览器1. 简介2.优点二、认识Web1. 简介2. 特点2. 网…

【云原生 | 21】Docker运行Web服务实战之Apache

作者简介&#xff1a;&#x1f3c5;云计算领域优质创作者&#x1f3c5;新星计划第三季python赛道第一名&#x1f3c5; 阿里云ACE认证高级工程师&#x1f3c5; ✒️个人主页&#xff1a;小鹏linux &#x1f48a;个人社区&#xff1a;小鹏linux&#xff08;个人社区&#xff09;欢…

【微信小程序】如何获得自己当前的定位呢?本文利用逆地址解析、uni-app带你实现

目录 前言 效果展示 一、在腾讯定位服务配置微信小程序JavaScript SDK 二、使用uni-app获取定位的经纬度 三、 逆地址解析&#xff0c;获取精确定位 四、小提示 前言 效果展示 一、在腾讯定位服务配置微信小程序JavaScript SDK 在浏览器搜索腾讯定位服务&#xff0c;找…

React(四) ——hooks的使用

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳React Hooks&#x1f4b8;useState(保存组件状态)&#x1f948;useEffect(处理副作用)&#x1f50b;useCallback&#xff08;记忆函数&#…

Sass 和 SCSS

▣Sass (Syntactically Awesome StyleSheets)&#xff0c;是由buby语言编写的一款css预处理语言&#xff0c;和html一样有严格的缩进风格&#xff0c;和css编写规范有着很大的出入&#xff0c;是不使用花括号和分号的&#xff0c;所以不被广为接受。 Sass 是一款强化 CSS 的辅助…