当前位置: 首页 > news >正文

Spring Cloud + Vue前后端分离-第10章 基于阿里云OSS的文件上传

 源代码在GitHub - 629y/course: Spring Cloud + Vue前后端分离-在线课程

Spring Cloud + Vue前后端分离-第10章 基于阿里云OSS的文件上传

前面介绍的文件上传是基于本地文件服务器的文件上传,但是自己搭文件服务器会有很多运维的问题,比如磁盘满了要扩容,高峰期要增加带宽,低谷期要减少带宽,为了安全,我们还要对文件做备份等等。

所以一般会选择云平台来存储文件,云平台有很多,比如阿里云、腾讯云、百度云等,云平台做的最好的是亚马逊,我们这里选择国内最大的阿里云。

10-1 阿里云OSS简介

面向海量数据规模的分布式存储服务

从浏览器通过ECS( 服务器)访问OSS,算内网访问,不算流量费,但是要考虑ECS的带宽问题。从浏览器直接访问OSS资源,算外网访问OSS,需付流量费,但是可以不用考虑带宽。

10-2 基于OSS接口的文件上传

阿里云OSS控台的使用

bucket:存储空间,名称必须是全阿里云唯一

这个文件地址可以直接打开观看

基于OSS接口实现文件上传

1.大文件断点续传与极速秒传:基于OSS接口实现文件上传,增加文件追加上传oss-append 和简单上传oss-simple

pom.xml

OssController.java

package com.course.file.controller.admin;import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.AppendObjectRequest;
import com.aliyun.oss.model.AppendObjectResult;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.course.server.dto.FileDto;
import com.course.server.dto.ResponseDto;
import com.course.server.enums.FileUseEnum;
import com.course.server.service.FileService;
import com.course.server.util.Base64ToMultipartFile;
import com.course.server.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.ByteArrayInputStream;@RestController
@RequestMapping("/admin")
public class OssController {private static final Logger LOG = LoggerFactory.getLogger(OssController.class);@Value("${oss.accessKeyId}")private String accessKeyId;@Value("${oss.accessKeySecret}")private String accessKeySecret;@Value("${oss.endpoint}")private String endpoint;@Value("${oss.bucket}")private String bucket;@Value("${oss.domain}")private String ossDomain;public static final String BUSINESS_NAME = "文件上传";@Resourceprivate FileService fileService;@PostMapping("/oss-append")public ResponseDto fileUpload(@RequestBody FileDto fileDto) throws Exception {LOG.info("上传文件开始");String use = fileDto.getUse();String key = fileDto.getKey();String suffix = fileDto.getSuffix();Integer shardIndex = fileDto.getShardIndex();Integer shardSize = fileDto.getShardSize();String shardBase64 = fileDto.getShard();MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(shardBase64);// 保存文件到本地FileUseEnum useEnum = FileUseEnum.getByCode(use);//        //如果文件夹不存在则创建String dir = useEnum.name().toLowerCase();
//        File fullDir = new File(FILE_PATH + dir);
//        if (!fullDir.exists()) {
//            fullDir.mkdir();
//        }//        String path = dir + File.separator + key + "." + suffix + "." + fileDto.getShardIndex();String path = new StringBuffer(dir).append("/").append(key).append(".").append(suffix).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4
//        String localPath = new StringBuffer(path)
//                .append(".")
//                .append(fileDto.getShardIndex())
//                .toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1
//        String fullPath = FILE_PATH + localPath;
//        File dest = new File(fullPath);
//        shard.transferTo(dest);
//        LOG.info(dest.getAbsolutePath());// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);ObjectMetadata meta = new ObjectMetadata();
// 指定上传的内容类型。meta.setContentType("text/plain");// 通过AppendObjectRequest设置多个参数。AppendObjectRequest appendObjectRequest = new AppendObjectRequest(bucket, path, new ByteArrayInputStream(shard.getBytes()), meta);// 通过AppendObjectRequest设置单个参数。// 设置存储空间名称。//appendObjectRequest.setBucketName("<yourBucketName>");// 设置文件名称。//appendObjectRequest.setKey("<yourObjectName>");// 设置待追加的内容。有两种可选类型:InputStream类型和File类型。这里为InputStream类型。//appendObjectRequest.setInputStream(new ByteArrayInputStream(content1.getBytes()));// 设置待追加的内容。有两种可选类型:InputStream类型和File类型。这里为File类型。//appendObjectRequest.setFile(new File("<yourLocalFile>"));// 指定文件的元信息,第一次追加时有效。//appendObjectRequest.setMetadata(meta);// 第一次追加。// 设置文件的追加位置。
//        appendObjectRequest.setPosition(0L);appendObjectRequest.setPosition((long) ((shardIndex - 1) * shardSize));AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);// 文件的64位CRC值。此值根据ECMA-182标准计算得出。System.out.println(appendObjectResult.getObjectCRC());System.out.println(JSONObject.toJSONString(appendObjectResult));//        // 第二次追加。
//        // nextPosition指明下一次请求中应当提供的Position,即文件当前的长度。
//        appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
//        appendObjectRequest.setInputStream(new ByteArrayInputStream(content2.getBytes()));
//        appendObjectResult = ossClient.appendObject(appendObjectRequest);
//
//        // 第三次追加。
//        appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
//        appendObjectRequest.setInputStream(new ByteArrayInputStream(content3.getBytes()));
//        appendObjectResult = ossClient.appendObject(appendObjectRequest);// 关闭OSSClient。ossClient.shutdown();LOG.info("保存文件记录开始");fileDto.setPath(path);fileService.save(fileDto);ResponseDto responseDto = new ResponseDto();fileDto.setPath(ossDomain + path);responseDto.setContent(fileDto);//        if (fileDto.getShardIndex().equals(fileDto.getShardTotal())) {
//            this.merge(fileDto);
//        }return responseDto;}@PostMapping("/oss-simple")public ResponseDto fileUpload(@RequestParam MultipartFile file, String use) throws Exception {LOG.info("上传文件开始");FileUseEnum useEnum = FileUseEnum.getByCode(use);String key = UuidUtil.getShortUuid();String fileName = file.getOriginalFilename();String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();String dir = useEnum.name().toLowerCase();String path = dir + "/" + key + "." + suffix;// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);// 创建PutObjectRequest对象。
//        String content = "Hello OSS";// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, path, new ByteArrayInputStream(file.getBytes()));// 如果需要上传时设置存储类型与访问权限,请参考以下示例代码。// ObjectMetadata metadata = new ObjectMetadata();// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());// metadata.setObjectAcl(CannedAccessControlList.Private);// putObjectRequest.setMetadata(metadata);// 上传字符串。ossClient.putObject(putObjectRequest);//        LOG.info("保存文件记录开始");
//        fileDto.setPath(path);
//        fileService.save(fileDto);ResponseDto responseDto = new ResponseDto();FileDto fileDto = new FileDto();fileDto.setPath(ossDomain + path);responseDto.setContent(fileDto);return responseDto;}
}

application.properties

big-file.vue

file.vue

演示:头像上传是用的单文件传输到OSS;视频上传是用的分片传输到OSS

teacher.vue

测试

上传视频和头像

10-3{white}阿里云视频点播服务介绍{white}{white}

阿里云视频点播服务介绍

视频点播控台的使用

视频点播帮助文档概览

10-4 基于OSS原生SDK上传视频到点播服务

VOD上传并加密转码

VOD上传并加密转码

1.视频加密与授权播放:基于 OSS原生SDK上传视频到点播服务,官方示例代码

pom.xml(course)

pom.xml(server)

VodUtil.java

package com.course.server.util;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSSClient;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.vod.model.v20170321.*;
import org.apache.commons.codec.binary.Base64;import java.io.File;
import java.io.InputStream;public class VodUtil {/*** 使用AK初始化VOD客户端* @param accessKeyId* @param accessKeySecret* @return* @throws ClientException*/public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {// 点播服务接入区域,国内请填cn-shanghai,其他区域请参考文档[点播中心](~~98194~~)String regionId = "cn-shanghai";DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);DefaultAcsClient client = new DefaultAcsClient(profile);return client;}/*** 获取视频上传地址和凭证* @param vodClient* @return* @throws ClientException*/public static CreateUploadVideoResponse createUploadVideo(DefaultAcsClient vodClient, String fileName) throws ClientException {CreateUploadVideoRequest request = new CreateUploadVideoRequest();request.setFileName(fileName);request.setTitle(fileName);//request.setDescription("this is desc");//request.setTags("tag1,tag2");
//        request.setCoverURL("http://vod.aliyun.com/test_cover_url.jpg");
//        request.setCateId(1000115308L);
//        request.setTemplateGroupId("78fffb8c0c2426efd5baaaafed76fe36");//request.setWorkflowId("");//request.setStorageLocation("");//request.setAppId("app-1000000");//设置请求超时时间request.setSysReadTimeout(1000);request.setSysConnectTimeout(1000);return vodClient.getAcsResponse(request);}/*** 使用上传凭证和地址初始化OSS客户端(注意需要先Base64解码并Json Decode再传入)* @param uploadAuth* @param uploadAddress* @return*/public static OSSClient initOssClient(JSONObject uploadAuth, JSONObject uploadAddress) {String endpoint = uploadAddress.getString("Endpoint");String accessKeyId = uploadAuth.getString("AccessKeyId");String accessKeySecret = uploadAuth.getString("AccessKeySecret");String securityToken = uploadAuth.getString("SecurityToken");return new OSSClient(endpoint, accessKeyId, accessKeySecret, securityToken);}/*** 简单上传* @param ossClient* @param uploadAddress* @param inputStream*/public static void uploadLocalFile(OSSClient ossClient, JSONObject uploadAddress, InputStream inputStream){String bucketName = uploadAddress.getString("Bucket");String objectName = uploadAddress.getString("FileName");// 单文件上传ossClient.putObject(bucketName, objectName, inputStream);/* 视频点播不支持追加上传// 追加上传ObjectMetadata meta = new ObjectMetadata();meta.setContentType("text/plain");AppendObjectRequest request = new AppendObjectRequest(bucketName, objectName, file, meta);request.setPosition(0L);ossClient.appendObject(request);*/}/*** 上传本地文件* @param ossClient* @param uploadAddress* @param localFile*/public static void uploadLocalFile(OSSClient ossClient, JSONObject uploadAddress, String localFile){String bucketName = uploadAddress.getString("Bucket");String objectName = uploadAddress.getString("FileName");File file = new File(localFile);// 单文件上传ossClient.putObject(bucketName, objectName, file);/* 视频点播不支持追加上传// 追加上传ObjectMetadata meta = new ObjectMetadata();meta.setContentType("text/plain");AppendObjectRequest request = new AppendObjectRequest(bucketName, objectName, file, meta);request.setPosition(0L);ossClient.appendObject(request);*/}/*** 刷新上传凭证* @param vodClient* @return* @throws ClientException*/public static RefreshUploadVideoResponse refreshUploadVideo(DefaultAcsClient vodClient) throws ClientException {RefreshUploadVideoRequest request = new RefreshUploadVideoRequest();request.setAcceptFormat(FormatType.JSON);request.setVideoId("VideoId");//设置请求超时时间request.setSysReadTimeout(1000);request.setSysConnectTimeout(1000);return vodClient.getAcsResponse(request);}/*** 获取源文件信息* @param client 发送请求客户端* @return GetMezzanineInfoResponse 获取源文件信息响应数据* @throws Exception*/public static GetMezzanineInfoResponse getMezzanineInfo(DefaultAcsClient client, String videoId) throws Exception {GetMezzanineInfoRequest request = new GetMezzanineInfoRequest();request.setVideoId(videoId);//源片下载地址过期时间request.setAuthTimeout(3600L);return client.getAcsResponse(request);}/*** 获取播放凭证函数* @param client* @return* @throws Exception*/public static GetVideoPlayAuthResponse getVideoPlayAuth(DefaultAcsClient client, String videoId) throws Exception {GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();request.setVideoId(videoId);return client.getAcsResponse(request);}public static void main(String[] argv) {//您的AccessKeyIdString accessKeyId = "xxxxxxxxxxxxx";//您的AccessKeySecretString accessKeySecret = "xxxxxxxxxxxxx";//需要上传到VOD的本地视频文件的完整路径,需要包含文件扩展名String localFile = "/Users/zhaoxiaoyun/Downloads/fourcats.mp4";try {// 初始化VOD客户端并获取上传地址和凭证DefaultAcsClient vodClient = initVodClient(accessKeyId, accessKeySecret);String fileName = "test.mp4";CreateUploadVideoResponse createUploadVideoResponse = createUploadVideo(vodClient, fileName);// 执行成功会返回VideoId、UploadAddress和UploadAuthString videoId = createUploadVideoResponse.getVideoId();JSONObject uploadAuth = JSONObject.parseObject(Base64.decodeBase64(createUploadVideoResponse.getUploadAuth()), JSONObject.class);JSONObject uploadAddress = JSONObject.parseObject(Base64.decodeBase64(createUploadVideoResponse.getUploadAddress()), JSONObject.class);// 使用UploadAuth和UploadAddress初始化OSS客户端OSSClient ossClient = initOssClient(uploadAuth, uploadAddress);// 上传文件,注意是同步上传会阻塞等待,耗时与文件大小和网络上行带宽有关uploadLocalFile(ossClient, uploadAddress, localFile);System.out.println("上传视频成功, VideoId : " + videoId); // 7d6b8c07ab48456e932187080f42e88fGetMezzanineInfoResponse response = new GetMezzanineInfoResponse();response = getMezzanineInfo(vodClient, videoId);System.out.println("获取视频信息, response : " + JSON.toJSONString(response));} catch (Exception e) {System.out.println("上传视频失败, ErrorMessage : " + e.getLocalizedMessage());}}
}

 记得修改自己的

VOD上传到指定分类,并自动转码

1.视频加密与授权播放:基于 OSS原生SDK上传视频到点播服务,上传到指定分类,并自动转码

VodUtil.java

视频点播不支持追加上传

1.视频加密与授权播放:基于 OSS原生SDK上传视频到点播服务,视频点播不支持追加上传

官方示例中,用的是简单上传putObject ,我们要改成我们需要的追加上传

但是有严格封装编码要求的文件类型,都是不支持追加方式上传的;比如音视频、图片等,上传到视频点播不支持追加上传

VodUtil.java

效果就不演示了,感兴趣的自己尝试一下,把这段代码打开,把上面的单文件上传注释,然后看效果,是一直在上传,但是无法上传成功!

项目集成VOD上传

小节增加vod字段

1.视频加密与授权播放:小节增加vod字段

由于视频点播不支持追加上传,我们只能改成单文件传

all.sql

generatorConfig.xml

SectionDto.java

section.vue

简单快速的方案:在阿里云控台做文件上传和转码,然后将vod复制到我们自己的控台

增加vod组件,用于上传视频到视频点播服务

1.视频加密与授权播放:增加vod组件,用于上传视频到视频点播服务

big-file.vue

vod组件就是对big-file组件做了一层封装。程序员天然喜欢对代码做封装,要注意度,封装得越多,灵活度就越低

vod.vue

<template><big-file v-bind:input-id="inputId"v-bind:text="text"v-bind:suffixs="suffixs"v-bind:use="use"v-bind:after-upload="afterUpload"v-bind:shard-size="shardSize"v-bind:url="'oss-append'"></big-file>
</template><script>
import BigFile from "./big-file";
export default {name: "vod",components: {BigFile},props: {text: {default: "上传VOD"},inputId: {default: "vod-upload"},suffixs: {default: []},use: {default: ""},shardSize: {default: 50 * 1024},afterUpload: {type: Function,default: null},},
}
</script>

目前为止,vod和big-file功能一样,需要先测试下vod组件是否可用

section.vue

 我们在js里对video赋值,vue会监听到值的变化,并渲染视频控件。如果还没渲染完,我们就去获取时长,这时就会得到NaN,所以需要加延时获取

测试,记得先清空数据库和oss文件

file表增加vod字段

1.视频加密与授权播放:file表增加vod字段

all.sql

generatorConfig.xml

FileDto.java

增加视频点播文件上传功能

1.视频加密与授权播放:增加视频点播文件上传功能

application.properties

vod.vue

由于视频点播不支持追加上传,所以使用vod组件进行上传的,只能有一个分片

section.vue

获取到的可播放地址是有时效的,所以就算保存到数据库也会过期,没用。以后会根据vod来播放。

文件检查时,根据是否是视频点播文件来获取视频信息

1.视频加密与授权播放:文件检查时,根据是否是视频点播文件来获取视频信息

UploadController.java

 测试 

10-6 视频授权播放功能开发

阿里云播放器的基本使用

1.视频加密与授权播放:集成阿里云播放器,制作player播放器组件

player.vue

<template><div v-bind:id="playerId">
<!--要做成动态的,所以不在这个地方写了-->
<!--    <div class="prism-player" id="J_prismPlayer"></div>--></div>
</template>
<script>
export default {name: "player",props: {playerId: {default: "player-div"},},data: function () {return {aliPlayer: {},//播放器实例}},methods: {playUrl(url) {let _this = this;console.log("开始播放:", url);//如果已经有播放器了,则将播放器div删除if (_this.aliPlayer) {_this.aliPlayer = null;$("#J_prismPlayer").remove();}// 初始化播放器$("#" + _this.playerId).append("<div class=\"prism-player\" id=\"J_prismPlayer\"></div>");_this.aliPlayer = new Aliplayer({id: "J_prismPlayer",width: '100%',autoplay: false,source: url,cover: 'http://imooc-coursemac.oss-cn-shanghai.aliyuncs.com/头像1.jpg',}, function (player) {console.log('播放器创建好了。')});},}
}
</script>

index.html

section.vue

 video控件隐藏掉,不要删除,要通过它获取时长。也可以通过视频点播的API去获取视频时长。

测试

功能更强,可以倍速等等

获取vod授权码并授权播放

1.视频加密与授权播放:获取vod授权码并授权播放,需要在存储管理中把权限设置成公共读

默认是私有的,更改为公共读

VodUtil.java

VodController.java

player.vue

一个页面可能会放多个player组件,所以需要把id做成动态变化的,一个页面的元素,id值要是唯一的。

modal-player.vue

带有模态框的播放器组件modal-player,里面包含了player组件

<template><div id="player-modal" class="modal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-body"><player v-bind:player-id="'modal-player-div'"ref="player"></player></div></div></div></div>
</template>
<script>
import Player from "./player";
export default {name: 'modal-player',components: {Player},data: function () {return {aliPlayer: {}, // 播放器实例}},methods: {playUrl(url) {let _this = this;_this.$refs.player.playUrl(url);},playVod(vod) {let _this = this;_this.$refs.player.playVod(vod);$("#player-modal").modal("show");}}
}
</script><style scoped>
#player-modal .modal-body {padding: 0;
}
</style>

section.vue

测试

相关文章:

Spring Cloud + Vue前后端分离-第10章 基于阿里云OSS的文件上传

源代码在GitHub - 629y/course: Spring Cloud Vue前后端分离-在线课程 Spring Cloud Vue前后端分离-第10章 基于阿里云OSS的文件上传 前面介绍的文件上传是基于本地文件服务器的文件上传&#xff0c;但是自己搭文件服务器会有很多运维的问题&#xff0c;比如磁盘满了要扩容…...

C++ 中的耗时计算函数

#include <time.h>int clock_gettime (clockid_t clock_id, struct timespec *tp) 获取当前 clock_id 的时钟值并存储在 tp 中。 其中 tp 是一个 timespec 结构体&#xff0c;在 time.h 头文件中定义&#xff1a; #include <time.h>:struct timespec {time_t t…...

【Element】el-form和el-table嵌套实现表格编辑并提交表单校验

一、背景 页面需要用到表格采集用户数据&#xff0c;提交时进行表单校验&#xff1b;即表格中嵌套着表单&#xff0c;保存时进行表单校验 二、功能实现 2.1、el-form和el-table嵌套说明 ① :model"formData" 给表单绑定数据&#xff0c;formData是表单的数据对象 …...

初识Winform

什么是winform&#xff1f; WinForms&#xff08;Windows Forms&#xff09;是Microsoft .NET框架中的一个用户界面&#xff08;UI&#xff09;技术&#xff0c;用于创建Windows应用程序。它提供了一组用于构建图形用户界面的类和控件&#xff0c;以及与用户交互的事件模型。 …...

Redis:原理速成+项目实战——Redis实战5(互斥锁、逻辑过期解决缓存击穿问题)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;Redis&#xff1a;原理速成项目实战——Redis实战4&#xff08;解决Redis缓存穿透、雪崩、击穿&#xff09; &#x1f4da;订阅专…...

前端优化之一:dns预获取 dns-prefetch 提升页面载入速度

问题&#xff1a;怎么做到dns域解析&#xff1f; 用于优化网站页面的图片 问题&#xff1a;怎么提升网站性能&#xff1f; dns域解析&#xff0c;是提升网站的一个办法。 DNS Prefetch&#xff0c;即DNS预获取&#xff0c;是前端优化的一部分。 一般来说&#xff0c;在前端…...

C语言中一些基本数据类型的典型大小

char&#xff1a;通常是1字节。表示一个字符。int&#xff1a;通常在现代系统中是4字节&#xff08;但这取决于编译器和架构&#xff0c;有时可能是2字节&#xff09;。float&#xff1a;通常是4字节。double&#xff1a;通常是8字节。short 和 short int&#xff1a;通常是2字…...

[C/C++]排序算法 快速排序 (递归与非递归)

目录 &#x1f6a9;概念: &#x1f6a9;实现: ⚡1.hoare ⚡2.挖坑法 ⚡3.双指针法 &#x1f6a9;快速排序递归实现 &#x1f6a9;快速排序非递归实现 &#x1f6a9;概念: 通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据比另一部分的所有…...

『年度总结』逐梦编程之始:我的2023学习回顾与展望

目录 前言 我与Python 我与C语言 第一篇正式博客&#xff1a; 第二篇正式博客&#xff08;扫雷&#xff09;&#xff1a; 指针学习笔记: C语言学习笔记&#xff1a; 我与数据结构&#xff1a; yuan 这篇博客&#xff0c;我将回顾2023年编程之旅的起点&#xff0c;同时展…...

MyBatis学习二:Mapper代理开发、配置文件完成增删改查、注解开发

前言 公司要求没办法&#xff0c;前端也要了解一下后端知识&#xff0c;这里记录一下自己的学习 学习教程&#xff1a;黑马mybatis教程全套视频教程&#xff0c;2天Mybatis框架从入门到精通 文档&#xff1a; https://mybatis.net.cn/index.html Mapper代理开发 目的 解决…...

【React系列】受控非受控组件

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. refs 的使用 在React的开发模式中&#xff0c;通常情况下不需要、也不建议直接操作DOM原生&#xff0c;但是某些…...

OpenCV-Python(22):2D直方图

目标 了解图像的2D直方图绘制2D直方图 介绍 在前面的部分我们介绍了如何绘制一维直方图&#xff0c;之所以称为一维&#xff0c;是因为我们只考虑了图像的一个特征&#xff1a;灰度值。但是在2D 直方图中我们就需要考虑两个图像特征。对于彩色图像的直方图通常情况下我们需要…...

Kubernetes 100个常用命令

本文简单总结关于使用 Kubectl 进行 Kubernetes 诊断的指南。列出了 100 个 Kubectl 命令&#xff0c;这些命令对于诊断 Kubernetes 集群中的问题非常有用。这些问题包括但不限于&#xff1a; 集群信息 Pod 诊断 服务诊断 部署诊断 网络诊断 持久卷和持久卷声明诊断 资源…...

labuladong日常刷题-差分数组 | LeetCode 1109航班预定统计 | 花式遍历 151反转字符串里的单词

差分数组–前缀和数组的升级 LeetCode 1109 航班预定统计 2024.1.1 题目链接labuladong讲解[链接] class Solution { public:vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {//构建航班人数数组&#xff0c;数组大小为n,初…...

HbuilderX中的git的使用

原文链接https://blog.csdn.net/Aom_yt/article/details/119924356...

LeetCode每日一题 | 1944. 队列中可以看到的人数

文章目录 队列中可以看到的人数题目描述问题分析程序代码&#xff08;Golang 版本&#xff09; 队列中可以看到的人数 题目描述 原题链接 有 n 个人排成一个队列&#xff0c;从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights &#xff0c;每个整数 互不相同&#xff…...

React16源码: JSX2JS及React.createElement源码实现

JSX 到 Javascript 的转换 React中的 JSX 类似于 Vue中的template模板文件&#xff0c;Vue是基于编译时将template模板转换成render函数在React中&#xff0c;JSX是类似于html和javascript混编的语法&#xff0c;而javascript是真的javascript, html并非真的html它的可阅读性可…...

整理composer安装版本的python脚本

整理composer安装版本的python脚本 脚本实现的功能是去除composer安装命令后的版本号 def remove_version_numbers(commands):"""Remove version numbers from composer require commands.Args:commands (list of str): List of composer require commands.Retu…...

十、基本对话框大集合(Qt5 GUI系列)

目录 一、设计需求 二、实现代码 三、代码解析 四、总结 一、设计需求 Qt提供了很多标准的对话框。例如标准文件对话框(QFileDialog)、标准颜色对话框(QColorDialog)、标准字体对话框 (QFontDialog)、标准输入对话框 (QInputDialog) 及消息对话框 (QMessageBox)。本文展示各…...

大A又跌了

才开盘几天&#xff0c;又开始下跌了。生活更加苦难。期待高深算法。...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

云安全与网络安全:核心区别与协同作用解析

在数字化转型的浪潮中&#xff0c;云安全与网络安全作为信息安全的两大支柱&#xff0c;常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异&#xff0c;并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全&#xff1a;聚焦于保…...

OCR MLLM Evaluation

为什么需要评测体系&#xff1f;——背景与矛盾 ​​ 能干的事&#xff1a;​​ 看清楚发票、身份证上的字&#xff08;准确率>90%&#xff09;&#xff0c;速度飞快&#xff08;眨眼间完成&#xff09;。​​干不了的事&#xff1a;​​ 碰到复杂表格&#xff08;合并单元…...

【51单片机】4. 模块化编程与LCD1602Debug

1. 什么是模块化编程 传统编程会将所有函数放在main.c中&#xff0c;如果使用的模块多&#xff0c;一个文件内会有很多代码&#xff0c;不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里&#xff0c;在.h文件里提供外部可调用函数声明&#xff0c;其他.c文…...

云原生安全实战:API网关Envoy的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口&#xff0c;负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...