day4--上传图片、视频

1. 分布式文件系统

1.1 什么是分布式文件系统

        文件系统是负责管理和存储文件的系统软件,操作系统通过文件系统提供的接口去存取文件,用户通过操作系统访问磁盘上的文件。

        下图指示了文件系统所处的位置:

常见的文件系统:FAT16/FAT32、NTFS、HFS、UFS、APFS、XFS、Ext4等 。

        现在有个问题,一此短视频平台拥有大量的视频、图片,这些视频文件、图片文件该如何存储呢?如何存储可以满足互联网上海量用户的浏览。

        通过分布式文件系统实现海量用户查阅海量文件

好处

1、一台计算机的文件系统处理能力扩充到多台计算机同时处理。

2、一台计算机挂了还有另外副本计算机提供数据。

3、每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度。

1.2 MinIo

1.2.1 介绍

        本项目采用MinIO构建分布式文件系统,MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用,它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。

        它一大特点就是轻量,使用简单,功能强大,支持各种平台,单个文件最大5TB,兼容 Amazon S3接口,提供了 Java、Python、GO等多版本SDK支持。

官网:https://min.io

中文:https://www.minio.org.cn/,http://docs.minio.org.cn/docs/

        在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。

        它将分布在不同服务器上的多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。如下图:

1.2.2 测试Docker环境

开发阶段和生产阶段统一使用Docker下的MINIO。

        虚拟机中已安装了MinIO的镜像和容器,执行sh /data/soft /restart.sh启动Docker下的MinIO

启动完成登录MinIO查看是否正常。访问 http://192.168.101.65:9000,账号和密码为:minioadmin / minioadmin

本项目创建两个buckets:

mediafiles: 普通文件

video:视频文件

1.2.4 SDK

1)上传文件

MinIO提供多个语言版本SDK的支持,下边找到java版本的文档:

地址:https://docs.min.io/docs/java-client-quickstart-guide.html

  • media-service工程中导入依赖

<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.4.3</version>
</dependency>
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.8.1</version>
</dependency>

参数说明:

需要三个参数才能连接到minio服务。

参数说明
Endpoint对象存储服务的URL
Access KeyAccess key就像用户ID,可以唯一标识你的账户。
Secret KeySecret key是你账户的密码。
  • 创建一个桶进行测试

  • 在xuecheng-plus-media-service工程 的test下编写测试代码如下

package com.xuecheng.media;import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.errors.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;/*** 测试 minio*/
public class MinioTest {MinioClient minioClient =MinioClient.builder().endpoint("http://192.168.101.65:9000").credentials("minioadmin", "minioadmin").build();@Testpublic void test_upload() throws Exception {//通过扩展名得到媒体资源类型 mimeType//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".jpg");String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}//上传文件的参数信息UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder().bucket("testbucket")//桶.filename("D:\\学习\\images\\3.jpg") //指定本地文件路径
//                .object("3.jpg")//对象名 在桶下存储该文件.object("test/01/3.jpg")//对象名 放在子目录下.contentType(mimeType)//设置媒体文件类型.build();//上传文件minioClient.uploadObject(uploadObjectArgs);}//删除文件@Testpublic void test_delete() throws Exception {//RemoveObjectArgsRemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("testbucket").object("3.jpg").build();//删除文件minioClient.removeObject(removeObjectArgs);}//查询文件 从minio中下载@Testpublic void test_getFile() throws Exception {GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("test/01/1.mp4").build();//查询远程服务获取到一个流对象FilterInputStream inputStream = minioClient.getObject(getObjectArgs);//指定输出流FileOutputStream outputStream = new FileOutputStream(new File("D:\\develop\\upload\\1a.mp4"));IOUtils.copy(inputStream,outputStream);//校验文件的完整性对文件的内容进行md5FileInputStream fileInputStream1 = new FileInputStream(new File("D:\\develop\\upload\\1.mp4"));String source_md5 = DigestUtils.md5Hex(fileInputStream1);FileInputStream fileInputStream = new FileInputStream(new File("D:\\develop\\upload\\1a.mp4"));String local_md5 = DigestUtils.md5Hex(fileInputStream);if(source_md5.equals(local_md5)){System.out.println("下载成功");}}}

2. 上传图片

2.1 需求分析

2.1.1 业务流程

        课程图片是宣传课程非常重要的信息,在新增课程界面上传课程图片,也可以修改课程图片。

上传课程图片总体上包括两部分:

  • 上传课程图片前端请求媒资管理服务将文件上传至分布式文件系统,并且在媒资管理数据库保存文件信息。
  • 上传图片成功保存图片地址到课程基本信息表中。

详细流程如下:

  • 前端进入上传图片界面
  • 上传图片,请求媒资管理服务。
  • 媒资管理服务将图片文件存储在MinIO。
  • 媒资管理记录文件信息到数据库。
  • 前端请求内容管理服务保存课程信息,在内容管理数据库保存图片地址。

2.1.2 数据模型

        涉及到的数据表有:课程信息表中的图片字段、媒资数据库的文件表,下边主要看媒资数据库的文件表。

2.2 准备

        首先在minio配置bucket,bucket名称为:mediafiles,并设置bucket的权限为公开

        在nacos配置中minio的相关信息,进入media-service-dev.yaml:

minio:endpoint: http://192.168.101.65:9000accessKey: minioadminsecretKey: minioadminbucket:files: mediafilesvideofiles: video

        在media-service工程编写minio的配置类

package com.xuecheng.media.config;import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** minio配置类*/
@Configuration
public class MinioConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinioClient minioClient() {// 创建Minio客户端实例,使用endpoint、accessKey和secretKey进行配置MinioClient minioClient =MinioClient.builder().endpoint(endpoint) // 设置Minio服务地址.credentials(accessKey, secretKey) // 设置访问凭证.build(); // 构建客户端实例return minioClient; // 返回构建好的Minio客户端}}

2.3 接口定义

        根据需求分析,下边进行接口定义,此接口定义为一个通用的上传文件接口,可以上传图片或其它文件。

请求地址:/media/upload/coursefile

请求内容Content-Type: multipart/form-data;

form-data; name="filedata"; filename="具体的文件名称"

响应参数:文件信息,如下

{"id": "a16da7a132559daf9e1193166b3e7f52","companyId": 1232141425,"companyName": null,"filename": "1.jpg","fileType": "001001","tags": "","bucket": "/testbucket/2022/09/12/a16da7a132559daf9e1193166b3e7f52.jpg","fileId": "a16da7a132559daf9e1193166b3e7f52","url": "/testbucket/2022/09/12/a16da7a132559daf9e1193166b3e7f52.jpg","timelength": null,"username": null,"createDate": "2022-09-12T21:57:18","changeDate": null,"status": "1","remark": "","auditStatus": null,"auditMind": null,"fileSize": 248329
}
  • 定义上传响应模型类

package com.xuecheng.media.model.dto;import com.xuecheng.media.model.po.MediaFiles;
import lombok.Data;
import lombok.ToString;@Data
@ToString
public class UploadFileResultDto extends MediaFiles {}
  • controller层接口

        前端传过来的文件名称是filedata

package com.xuecheng.media.api;import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.service.MediaFileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;/*** 媒资文件管理接口*/@Api(value = "媒资文件管理接口",tags = "媒资文件管理接口")@RestController
public class MediaFilesController {@AutowiredMediaFileService mediaFileService;@ApiOperation("媒资列表查询接口")@PostMapping("/files")public PageResult<MediaFiles> list(PageParams pageParams, @RequestBody QueryMediaParamsDto queryMediaParamsDto){Long companyId = 1232141425L;return mediaFileService.queryMediaFiels(companyId,pageParams,queryMediaParamsDto);}@ApiOperation("上传图片")@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata")MultipartFile filedata) throws IOException {//准备上传文件的信息UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();//原始文件名称uploadFileParamsDto.setFilename(filedata.getOriginalFilename());//文件大小uploadFileParamsDto.setFileSize(filedata.getSize());//文件类型uploadFileParamsDto.setFileType("001001");//创建一个临时文件File tempFile = File.createTempFile("minio", ".temp");filedata.transferTo(tempFile);Long companyId = 1232141425L;//文件路径String localFilePath = tempFile.getAbsolutePath();//调用service上传图片UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, localFilePath);return uploadFileResultDto;}}

2.4 接口开发

2.4.1 Dao层

  • mapper层
@Mapper
public interface MediaFilesMapper extends BaseMapper<MediaFiles> {}
  • sql映射
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuecheng.media.mapper.MediaFilesMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.xuecheng.media.model.po.MediaFiles"><id column="id" property="id" /><result column="company_id" property="companyId" /><result column="company_name" property="companyName" /><result column="filename" property="filename" /><result column="file_type" property="fileType" /><result column="tags" property="tags" /><result column="bucket" property="bucket" /><result column="file_id" property="fileId" /><result column="url" property="url" /><result column="timelength" property="timelength" /><result column="username" property="username" /><result column="create_date" property="createDate" /><result column="change_date" property="changeDate" /><result column="status" property="status" /><result column="remark" property="remark" /><result column="audit_status" property="auditStatus" /><result column="audit_mind" property="auditMind" /></resultMap><!-- 通用查询结果列 --><sql id="Base_Column_List">id, company_id, company_name, filename, file_type, tags, bucket, file_id, url, timelength, username, create_date, change_date, status, remark, audit_status, audit_mind</sql></mapper>

2.4.2 Service层

1)定义请求参数类

@Data
@ToString
public class UploadFileParamsDto {/*** 文件名称*/private String filename;/*** 文件类型(文档,音频,视频)*/private String fileType;/*** 文件大小*/private Long fileSize;/*** 标签*/private String tags;/*** 上传人*/private String username;/*** 备注*/private String remark;}
2)定义service方法
 /*** 上传文件* @param companyId 机构id* @param uploadFileParamsDto 文件信息* @param localFilePath 文件本地路径* @return UploadFileResultDto*/public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath);
3)impl实现类实现service接口,重写方法

 


@Autowired
MinioClient minioClient;@Autowired
MediaFilesMapper mediaFilesMapper;//普通文件桶
@Value("${minio.bucket.files}")
private String bucket_Files;//获取文件默认存储目录路径 年/月/日
private String getDefaultFolderPath() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");String folder = sdf.format(new Date()).replace("-", "/")+"/";return folder;
}//获取文件的md5
private String getFileMd5(File file) {try (FileInputStream fileInputStream = new FileInputStream(file)) {String fileMd5 = DigestUtils.md5Hex(fileInputStream);return fileMd5;} catch (Exception e) {e.printStackTrace();return null;}
}private String getMimeType(String extension){if(extension==null)extension = "";//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);//通用mimeType,字节流String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}return mimeType;
}
/*** @description 将文件写入minIO* @param localFilePath  文件地址* @param bucket  桶* @param objectName 对象名称*/
public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName) {try {UploadObjectArgs testbucket = UploadObjectArgs.builder().bucket(bucket).object(objectName).filename(localFilePath).contentType(mimeType).build();minioClient.uploadObject(testbucket);log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucket,objectName);System.out.println("上传成功");return true;} catch (Exception e) {e.printStackTrace();log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}",bucket,objectName,e.getMessage(),e);XueChengPlusException.cast("上传文件到文件系统失败");}return false;
}/*** @description 将文件信息添加到文件表* @param companyId  机构id* @param fileMd5  文件md5值* @param uploadFileParamsDto  上传文件的信息* @param bucket  桶* @param objectName 对象名称* @return com.xuecheng.media.model.po.MediaFiles*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){//从数据库查询文件MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);if (mediaFiles == null) {mediaFiles = new MediaFiles();//拷贝基本信息BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);mediaFiles.setId(fileMd5);mediaFiles.setFileId(fileMd5);mediaFiles.setCompanyId(companyId);mediaFiles.setUrl("/" + bucket + "/" + objectName);mediaFiles.setBucket(bucket);mediaFiles.setFilePath(objectName);mediaFiles.setCreateDate(LocalDateTime.now());mediaFiles.setAuditStatus("002003");mediaFiles.setStatus("1");//保存文件信息到文件表int insert = mediaFilesMapper.insert(mediaFiles);if (insert < 0) {log.error("保存文件信息到数据库失败,{}",mediaFiles.toString());XueChengPlusException.cast("保存文件信息失败");}log.debug("保存文件信息到数据库成功,{}",mediaFiles.toString());}return mediaFiles;}
@Transactional
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {File file = new File(localFilePath);if (!file.exists()) {XueChengPlusException.cast("文件不存在");}//文件名称String filename = uploadFileParamsDto.getFilename();//文件扩展名String extension = filename.substring(filename.lastIndexOf("."));//文件mimeTypeString mimeType = getMimeType(extension);//文件的md5值String fileMd5 = getFileMd5(file);//文件的默认目录String defaultFolderPath = getDefaultFolderPath();//存储到minio中的对象名(带目录)String  objectName = defaultFolderPath + fileMd5 + exension;//将文件上传到minioboolean b = addMediaFilesToMinIO(localFilePath, mimeType, bucket_files, objectName);//文件大小uploadFileParamsDto.setFileSize(file.length());//将文件信息存储到数据库MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);//准备返回数据UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);return uploadFileResultDto;}

2.5 前后端联调测试

 

2.6 代码优化

        目前是在uploadFile方法上添加@Transactional,当调用uploadFile方法前会开启数据库事务,如果上传文件过程时间较长那么数据库的事务持续时间就会变长,这样数据库链接释放就慢,最终导致数据库链接不够用。

        我们只将addMediaFilesToDb方法添加事务控制即可,uploadFile方法上的@Transactional注解去掉。

方法上已经添加了@Transactional注解为什么该方法不能被事务控制呢?

如果是在uploadFile方法上添加@Transactional注解就可以控制事务,去掉则不行。

        现在的问题其实是一个非事务方法调同类一个事务方法,事务无法控制,这是为什么?

原因分析如下:

        如果在uploadFile方法上添加@Transactional注解,代理对象执行此方法前会开启事务,如下图

         如果在uploadFile方法上没有@Transactional注解,代理对象执行此方法前不进行事务控制,如下图:

        所以判断该方法是否可以事务控制必须保证是通过代理对象调用此方法,且此方法上添加了@Transactional注解 。现在在addMediaFilesToDb方法上添加@Transactional注解,也不会进行事务控制是因为并不是通过代理对象执行的addMediaFilesToDb方法。为了判断在uploadFile方法中去调用addMediaFilesToDb方法是否是通过代理对象去调用,我们可以打断点跟踪

        我们发现在uploadFile方法中去调用addMediaFilesToDb方法不是通过代理对象去调用 

解决方法是在MediaFileService的实现类中注入MediaFileService的代理对象,将addMediaFilesToDb方法提成接口

完整代码如下:

@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {@Autowiredprivate MediaFilesMapper mediaFilesMapper;@AutowiredMinioClient minioClient;@AutowiredMediaFileService currentProxy;//存储普通文件@Value("${minio.bucket.files}")private String bucket_mediafiles;//存储视频@Value("${minio.bucket.videofiles}")private String bucket_video;@Overridepublic PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {//构建查询条件对象LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();//分页对象Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());// 查询数据内容获得结果Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);// 获取数据列表List<MediaFiles> list = pageResult.getRecords();// 获取数据总数long total = pageResult.getTotal();// 构建结果集PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());return mediaListResult;}//根据扩展名获取mimeTypeprivate String getMimeType(String extension){if(extension == null){extension = "";}//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}return mimeType;}/*** 将文件上传到minio* @param localFilePath 文件本地路径* @param mimeType 媒体类型* @param bucket 桶* @param objectName 对象名* @return*/public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName){try {UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder().bucket(bucket)//桶.filename(localFilePath) //指定本地文件路径.object(objectName)//对象名 放在子目录下.contentType(mimeType)//设置媒体文件类型.build();//上传文件minioClient.uploadObject(uploadObjectArgs);log.debug("上传文件到minio成功,bucket:{},objectName:{},错误信息:{}",bucket,objectName);return true;} catch (Exception e) {e.printStackTrace();log.error("上传文件出错,bucket:{},objectName:{},错误信息:{}",bucket,objectName,e.getMessage());}return false;}//获取文件默认存储目录路径 年/月/日private String getDefaultFolderPath() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");String folder = sdf.format(new Date()).replace("-", "/")+"/";return folder;}//获取文件的md5private String getFileMd5(File file) {try (FileInputStream fileInputStream = new FileInputStream(file)) {String fileMd5 = DigestUtils.md5Hex(fileInputStream);return fileMd5;} catch (Exception e) {e.printStackTrace();return null;}}@Overridepublic UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {//文件名String filename = uploadFileParamsDto.getFilename();//先得到扩展名String extension = filename.substring(filename.lastIndexOf("."));//得到mimeTypeString mimeType = getMimeType(extension);//子目录String defaultFolderPath = getDefaultFolderPath();//文件的md5值String fileMd5 = getFileMd5(new File(localFilePath));String objectName = defaultFolderPath+fileMd5+extension;//上传文件到minioboolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);if(!result){XueChengPlusException.cast("上传文件失败");}//入库文件信息MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);if(mediaFiles==null){XueChengPlusException.cast("文件上传后保存信息失败");}//准备返回的对象UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);return uploadFileResultDto;}/*** @description 将文件信息添加到文件表* @param companyId  机构id* @param fileMd5  文件md5值* @param uploadFileParamsDto  上传文件的信息* @param bucket  桶* @param objectName 对象名称*/@Transactionalpublic MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){//将文件信息保存到数据库MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);if(mediaFiles == null){mediaFiles = new MediaFiles();BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);//文件idmediaFiles.setId(fileMd5);//机构idmediaFiles.setCompanyId(companyId);//桶mediaFiles.setBucket(bucket);//file_pathmediaFiles.setFilePath(objectName);//file_idmediaFiles.setFileId(fileMd5);//urlmediaFiles.setUrl("/"+bucket+"/"+objectName);//上传时间mediaFiles.setCreateDate(LocalDateTime.now());//状态mediaFiles.setStatus("1");//审核状态mediaFiles.setAuditStatus("002003");//插入数据库int insert = mediaFilesMapper.insert(mediaFiles);if(insert<=0){log.debug("向数据库保存文件失败,bucket:{},objectName:{}",bucket,objectName);return null;}return mediaFiles;}return mediaFiles;}
}

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

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

相关文章

极矢量与轴矢量

物理量分为标量和矢量&#xff0c;矢量又分为极矢量和轴矢量。 矢量是既有大小又有方向并按平行四边形法则相加的量。矢量有极矢量和轴矢量两种&#xff0c;其间的区别是在镜像反射变换下遵循不同的变换规律,许多物理量都是矢量,同样,其中也有极矢量和轴矢量的区分,在力学中,例…

文章发布易优CMS(Eyoucms)网站技巧

为了更快的上手数据采集及发布到易优CMS(eyoucms)网站&#xff0c;特地总结了些新手常常会遇到的操作问题与技巧&#xff0c;如下&#xff1a; 免费易优CMS采集发布插件下载&#xff0c;兼容火车头、八爪鱼、简数采集等 目录 1. 发布到易优CMS指定栏目 2. 发布文章到易优CM…

INA226 数据手册解读

INA226是一款数字电流检测放大器&#xff0c;配备I2C和SMBus兼容接口。该器件可提供数字电流、电压以及功率读数&#xff0c;可灵活配置测量分辨率&#xff0c;并具备连续运行与触发操作模式。该芯片通常由一个单独的电源供电&#xff0c;电压范围为 2.7V 至 5.5V引脚说明​​引…

Linux 中替换sed

以下是关于 sed&#xff08;Stream Editor&#xff09;的深度详解和日常高频使用场景&#xff0c;结合实用示例说明&#xff1a;一、sed 核心概念 流式编辑器&#xff1a;逐行处理文本&#xff0c;不直接修改源文件&#xff08;除非使用 -i 选项&#xff09;正则支持&#xff1…

ADB 调试日志全攻略:如何开启与关闭 `ADB_TRACE` 日志

ADB 调试日志全攻略&#xff1a;如何开启与关闭 ADB_TRACE 日志 ADB&#xff08;Android Debug Bridge&#xff09;是 Android 开发的核心工具&#xff0c;但在排查问题时&#xff0c;默认日志可能不够详细。通过设置环境变量 ADB_TRACE&#xff0c;可以开启 全量调试日志&…

实现druid数据源密码加密

生成加密密码集成了druid链接池的&#xff0c;可以实现数据源密码加密。加密方式如下构建单元测试&#xff0c;并输入密码即可生成加密密码以及加密公钥Test public void testPwd() throws Exception {String password "123456";String[] arr com.alibaba.druid.fi…

【TCP/IP】20. 因特网安全

20. 因特网安全20. 因特网安全20.1 安全威胁20.2 安全服务20.3 基本安全技术20.3.1 密码技术20.3.2 报文鉴别技术20.3.3 身份认证技术20.3.4 数字签名技术20.3.5 虚拟专用网&#xff08;VPN&#xff09;技术20.3.6 防火墙技术20.3.7 防病毒技术20.4 IP 层安全20.5 传输层安全20…

数据结构之位图和布隆过滤器

系列文章目录 数据结构之ArrayList_arraylist o(1) o(n)-CSDN博客 数据结构之LinkedList-CSDN博客 数据结构之栈_栈有什么方法-CSDN博客 数据结构之队列-CSDN博客 数据结构之二叉树-CSDN博客 数据结构之优先级队列-CSDN博客 常见的排序方法-CSDN博客 数据结构之Map和Se…

Web攻防-PHP反序列化魔术方法触发条件POP链构造变量属性修改黑白盒角度

知识点&#xff1a; 1.WEB攻防-PHP反序列化-序列化和反序列化 2.WEB攻防-PHP反序列化-常见魔术方法触发规则 3.WEB攻防-PHP反序列化-反序列化漏洞产生原因 4.WEB攻防-PHP反序列化-黑白盒&POP链构造 一、演示案例-WEB攻防-PHP反序列化-序列化和反序列化 什么是反序列化操作…

C# VB.NET多进程-管道通信,命名管道(Named Pipes)

要向已运行的进程发送特定命令&#xff08;如/exit&#xff09;&#xff0c;而不是启动新进程&#xff0c;需要使用进程间通信&#xff08;IPC&#xff09;机制。以下是几种常见的实现方法&#xff1a;一、使用命名管道&#xff08;Named Pipes&#xff09;如果ABC.EXE支持通过…

C++ 右值引用 (Rvalue References)

右值引用是C11引入的革命性特性&#xff0c;它彻底改变了C中资源管理和参数传递的方式。下面我将从多个维度深入讲解右值引用。一、核心概念1. 值类别(Value Categories)lvalue (左值): 有标识符、可取地址的表达式int x 10; // x是左值 int* p &x; // 可以取地址rvalue…

反激变换器设计全流程(一)——电路拓扑及工作流程

一、电路拓扑原理 拓扑结构概述 开关反激电源采用反激式拓扑结构&#xff0c;主要由开关管&#xff08;通常为 MOSFET&#xff09;、变压器、输出整流二极管、输出滤波电容以及控制电路等组成。其基本工作原理是通过开关管的周期性开关动作&#xff0c;将输入直流电压转换为高…

uniapp语音播报天气预报微信小程序

1.产品展示2.页面功能(1)点击上方按钮实现语音播报4天天气情况。3.uniapp代码<template><view class"container"><view class"header"><text class"place">地址:{{city}}</text><text class"time"&g…

Pycharm 报错 Environment location directory is not empty 如何解决

好长时间不看不写代码了&#xff0c;人也跟着犯糊涂。今天在Pycharm 导入虚拟环境时&#xff0c;一直报错&#xff1a;“Environment location directory is not empty”&#xff0c;在网上百度很多很多方法都无法解决&#xff0c;直到我翻出我之前自己写的导入虚拟环境的详细过…

React强大且灵活hooks库——ahooks入门实践之场景类(scene)hook详解

什么是 ahooks&#xff1f; ahooks 是一个 React Hooks 库&#xff0c;提供了大量实用的自定义 hooks&#xff0c;帮助开发者更高效地构建 React 应用。其中场景类 hooks 是 ahooks 的一个重要分类&#xff0c;专门针对特定业务场景提供解决方案。 安装 ahooks npm install …

大模型之Langchain篇(二)——RAG

写在前面 跟着楼兰老师学习【LangChain教程】2025吃透LangChain框架快速上手与深度实战&#xff0c;全程干货无废话&#xff0c;三天学完&#xff0c;让你少走百分之99弯路&#xff01;_哔哩哔哩_bilibili 计算相似度 一般用的余弦相似度&#xff0c;这里只是演示计算。 fr…

深入理解图像二值化:从静态图像到视频流实时处理

一、引言&#xff1a;图像分析&#xff0c;从“黑与白”开始在计算机视觉任务中&#xff0c;**图像二值化&#xff08;Image Binarization&#xff09;**是最基础也是最关键的图像预处理技术之一。它通过将灰度图像中每个像素转换为两个离散值&#xff08;通常是0和255&#xf…

云蝠智能 VoiceAgent重构企业呼入场景服务范式

在数字化转型浪潮中&#xff0c;企业呼入场景面临客户服务需求激增与人力成本攀升的双重挑战。传统呼叫中心日均处理仅 300-500 通电话&#xff0c;人力成本占比超 60%&#xff0c;且服务质量受情绪波动影响显著。云蝠智能推出的 VoiceAgent 语音智能体&#xff0c;通过全栈自研…

java进阶(一)+学习笔记

1.JAVA设计模式1.1 什么是设计模式设计模式是软件开发过程中前辈们在长期实践中针对重复出现的问题总结出来的最佳解决方案。这些模式不是具体的代码实现&#xff0c;而是经过验证的、可重用的设计思想&#xff0c;能够帮助开发者更高效地解决特定类型的问题。设计模式的重要性…

Pandas-数据清洗与处理

Pandas-数据清洗与处理一、数据清洗的核心目标二、缺失值处理1. 缺失值检测2. 缺失值处理策略&#xff08;1&#xff09;删除法&#xff08;2&#xff09;填充法三、异常值识别与处理1. 异常值检测方法&#xff08;1&#xff09;统计法&#xff08;2&#xff09;业务规则法2. 异…