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 = "文件上传";

    @Resource
    private 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) {
        //您的AccessKeyId
        String accessKeyId = "xxxxxxxxxxxxx";
        //您的AccessKeySecret
        String 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和UploadAuth
            String 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); // 7d6b8c07ab48456e932187080f42e88f

            GetMezzanineInfoResponse 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

测试

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VMware安装RHEL9.0版本Linux系统

最近在学习Linux&#xff0c;安装了Red Hat Enterprise Linux 的 9.0版本&#xff0c;简称RHEL9.0。RHEL9.0是Red Hat公司发布的面向企业用户的Linux操作系统的最新版本。我把它安装在虚拟机VMware里来减少电脑性能占用&#xff0c;也防止系统炸搞得我后面要重装。安装RHEL9.0还…

【Unity入门】MenuItem 和 ContextMenu 的使用方法

目录 一、ContextMenu描述使用示例ContextMenuItem使用示例 二、MenuItem描述使用示例 三、MenuItem 和 ContextMenu 的区别 一、ContextMenu 描述 ContextMenu 属性用于向上下文菜单添加命令。 在该附加脚本的 Inspector 中&#xff0c;当用户选择该上下文菜单时&#xff0c…

FA组件详解

1、了解FA核心组件以及功能 &#xff08;1&#xff09;TC&#xff08;Thin Client&#xff1a;瘦终端&#xff09;&#xff1a;就是类似于机顶盒的一个小盒子&#xff0c;里面有CPU、内存、USB、MIC、HDMI等接口&#xff0c;可以理解为小型电脑&#xff0c;但是它里面是没有操作…

Unity 新版 Meta XR SDK 无法导入解决方法

文章目录 &#x1f4d5;教程说明&#x1f4d5;新版 SDK 说明&#x1f4d5;从 Meta 官网导入开发包⭐依赖包⭐如何导入⭐导入后包存放在哪里了&#xff1f;⭐场景样例文件去哪了&#xff1f; 此教程相关的详细教案&#xff0c;文档&#xff0c;思维导图和工程文件会放入 Spatia…

Django 学习教程-介绍与安装

系列 Django 学习教程-第一个 Django 应用-CSDN博客 介绍 Django 是一个高级 Python Web 框架&#xff0c;它鼓励快速开发和干净、实用的设计。 它由经验丰富的开发人员构建&#xff0c;解决了 Web 开发的大部分麻烦&#xff0c;因此您可以专注于在编写应用程序时无需重新发…

C# vs报错 id为XX的进程当前未运行

报错原因&#xff1a;虚拟目录端口被占用 解决方法&#xff1a;重新配置新的目录端口就行 1、选择项目属性 2、更改端口号&#xff0c;点击创建虚拟目录 3、重新生成项目

使用python快速开发与PDF文档对话的Gemini聊天机器人

检索增强生成(Retrieval-augmented generation&#xff0c;RAG)使得我们可以让大型语言模型(LLMs)访问外部知识库数据(如pdf,word、text等)&#xff0c;从而让人们可以更加方便的通过LLM来学习外部数据的知识。今天我们将利用之前学习到的RAG方法&#xff0c;谷歌Gemini模型和l…

「微服务」Saga 模式 如何使用微服务实现业务事务-第二部分

在上一篇文章中&#xff0c;我们看到了实现分布式事务的一些挑战&#xff0c;以及如何使用Event / Choreography方法实现Saga的模式。在本文中&#xff0c;我们将讨论如何通过使用另一种类型的Saga实现&#xff08;称为Command或Orchestration&#xff09;来解决一些问题&#…

Ps:三角形工具

三角形工具 Triangle Tool可以绘制三角形形状&#xff08;矢量形状&#xff0c;或者是基于像素的形状&#xff09;和路径&#xff08;形状轮廓&#xff09;。 快捷键&#xff1a;U ◆ ◆ ◆ 常用操作方法与技巧 1、一般使用拖拽的方式绘制三角形。也可直接在画布上点击&#…

【2023】hadoop基础介绍

&#x1f4bb;目录 Hadoop组成HDFSHDFS操作HDFS分布式文件存储NameNode元数据数据读写流程 YARN和MapReduceMapReduce&#xff1a;分布式计算YARN&#xff1a;资源管控调度YARN架构提交任务到**YARN中运行** Hadoop组成 hadoop安装教程可以看我这篇文章> &#x1f345;hado…

数据结构—树的应用

文章目录 11.树的应用(1).Huffman树#1.加权外部路径长度#2.Huffman算法#3.Huffman编码 (2).二叉搜索树#1.基本定义#2.查找#3.插入结点#4.构建树#5.查找最小值和最大值#6.删除结点#7.一个问题 (3).平衡搜索树#1.满二叉树、完全二叉树和丰满二叉树#2.平衡因子和平衡树#3.左旋与右…

深入解析泛型

一、泛型的诞生 在C#1 中我们还没有泛型的时候我们收集数据通常需要使用到数组&#xff0c;或者使用封装好的数组集合Hashtable ArrayList。 举个例子&#xff1a; 我们在读取文件的时候就会需要一个数组来储存读取的数据的内容 但我们并不知数据的具体长度也就无法在声明的…

2021-05-08 51单片机74HC164、74LS164、74HCT164、74HC154、74HCT154应用三极管控制继电器

74HC164、74HCT164是8位边沿触发式移位寄存器&#xff0c;串行输入数据&#xff0c;然后并行输出。数据通过两个输入端&#xff08;DSA或DSB&#xff09;之一串行输入&#xff1b;任一输入端可以用作高电平使能端&#xff0c;控制另一输入端的数据输入。两个输入端或者连接在一…

【低代码平台】10个开源免费Airtable 的替代方案

Airtable是一个易于使用的简单低代码平台&#xff0c;有助于团队协作管理复杂的数据表&#xff0c;并创建定制的工作流程。把它想象成一个类固醇上的云电子表格。 Airtable还简化了数据输入过程&#xff0c;连接和集成第三方服务和应用程序&#xff0c;并提供了许多数据导入/导…

web综合大实验!!!

目录 一、要求 二、操作步骤 第一步&#xff1a;关闭防火墙&#xff0b;SeLinux 第二步&#xff1a;挂载 第三步&#xff1a;编辑配置文件 第四步&#xff1a;安装软件包 1、安装httpd 2、安装mod_ssl模块 第五步&#xff1a;定义主配置文件 1、创建首页文件 2、重启…

7.java——异常

异常——error&#xff08;资源耗尽&#xff0c;JVM内部系统错误&#xff0c;代码一般处理不了&#xff09;和excption&#xff08;数组越界&#xff0c;空指针访问&#xff0c;代码可以处理&#xff09; java.lang.Throwable;异常体系的根父类 -------java.lang.Error:错误。…

第三部分 连续型需要的积分

目录 温馨提示&#xff1a; 求积分 求分段函数在确定区间的定积分 方法&#xff1a; 例1 例2 例3 例4 例5 例6 例7 求分段函数在到未知数的定积分 方法&#xff1a; 例8 求简单的二重积分 方法&#xff1a; 例9 例10 例11 求f(x,y)的二重积分 方法&#xff1a; 例12 例13 …

软件工程总复习笔记

软件工程课程复习提纲 文章目录 软件工程课程复习提纲一、基本知识点1. 软件工程的概念及目标2. 软件危机的概念及典型表现3. 瀑布模型的概念及特点4. 快速原型模型的特点5. 螺旋模型的基本思想6. 软件生命周期的概念及划分为哪几个阶段7. 软件需求的定义8. 常见的软件需求获取…

Go 泛型之明确使用时机与泛型实现原理

Go 泛型之明确使用时机与泛型实现原理 文章目录 Go 泛型之明确使用时机与泛型实现原理一、引入二、何时适合使用泛型&#xff1f;场景一&#xff1a;编写通用数据结构时场景二&#xff1a;函数操作的是 Go 原生的容器类型时场景三&#xff1a;不同类型实现一些方法的逻辑相同时…

FL Studio 21.2.2官方中文版重磅发布

纯正简体中文支持&#xff0c;更快捷的音频剪辑及素材管理器&#xff0c;多样主题随心换&#xff01; Mac版新增对苹果M2/1家族芯片原生支持。 丰富的主题换肤 现在大家可以通过控制色调、饱和度、亮度、文本、仪表和步进序列器的颜色来改变你的DAW外观&#xff0c; DAW“情绪…