web上传超大文件(最大测试过6G)

news/2025/1/16 5:53:29/

HTML页面

这里只是显示页面大概内容,就不把CSS贴出来了

<div class="file-manager"><div class="hr-line-dashed"></div><label title="上传图片" for="inputFile" class="btn btn-primary"><input type="file" accept=".mp3,.mp4,.rar,.7z,.iso,.zip" name="file" id="inputFile" class="hide"> 选择文件</label><div><input type="text" disabled="" id="fileText"  class="form-control"></div><div>保存路径(不填为默认路径):<input type="text" name="saveFilePath" placeholder="C:/Download(注意反斜杠)" value="E:/Download" id="saveFilePath" class="form-control"></div><button onclick="upload()" class="btn btn-primary btn-block">上传文件(直接合并方式)</button><div><span>上传进度</span><small class="pull-right" id="upFilePer">未上传文件 </small></div><div class="progress progress-small"><div style="width: 0%;" class="progress-bar" id="upFilePerImg"></div></div><div class="hr-line-dashed"></div></div>
<script src="js/jquery.min.js?v=2.1.4"></script>
<!-- 用了 layer 弹出层 -->
<script src="js/plugins/layer/layer.min.js"></script>
<script src="js/request.js"></script>

效果图

效果图

JS

    $(document).ready(function () {//选择文件触发$('input[type="file"]').change(function() {var fileName = $(this).prop('files')[0].name;$("#upFilePer").text("0%");$("#upFilePerImg").attr("style", "width: 0%");$("#fileText").val(fileName);});});//上传 这里可以进行多文件上传 已经做了处理 在上方选择多个文件就可以了async function upload(){var fileList = $("#inputFile")[0].files;if(fileList && fileList.length > 0) {// 单个切片文件大小为5MBconst chunkSize = 5242880;// 需要分片上传的文件的最小值 10Mconst chunkMinSize = 10485760;let chunkFlag = false;for(let i = 0; i < fileList.length; i++) {if(fileList[i].size > chunkMinSize) {chunkFlag = true;}// 获取文件的总分片数const chunkNum = Math.ceil(fileList[i].size / chunkSize)// 取两个md5值作为整体文件的唯一标识let fileMd5 = ''if (chunkNum >= 2) {let startMd5 = await getMD5(fileList[i].slice(0, 1 * chunkSize))let endMd5 = await getMD5(fileList[i].slice((chunkNum-1) * chunkSize, chunkNum * chunkSize))fileMd5 = startMd5 + endMd5;} else {fileMd5 = await getMD5(fileList[i])}// 判断文件是否存在const res = await checkFileExist(fileMd5);const { code, data, msg } = res;if(code === 21000) {layer.msg('文件秒传成功')console.log('文件在服务器已存在,文件上传成功(大文件秒传原理就是不传)')// 跳过这个文件,不传了continue;} else {// 文件不存在,准备上传// 需要分片上传if(chunkFlag) {var loadIndex = layer.load("文件上传中。。。");let start = new Date()for(let currentChunk = 0; currentChunk < chunkNum; currentChunk++) {let formData = new FormData();formData.append("saveFilePath", $("#saveFilePath").val());// 分片上传formData.append("chunkFlag", chunkFlag);// 分片总数formData.append("chunks", chunkNum);// 当前分片数formData.append("currentChunk", currentChunk);// 分片大小formData.append("chunkSize", chunkSize);// 文件类型formData.append('type', fileList[i].type)// 文件总大小formData.append("size", fileList[i].size);// 文件名formData.append("name", fileList[i].name);// 整个文件的id值,及md5值formData.append("fileMd5", fileMd5);// 计算当前文件分片的md5值let currentChunkMd5 = await getMD5(fileList[i].slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize));formData.append("currentChunkMd5", currentChunkMd5);formData.append("file", fileList[i].slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize));//await uploadFile(formData) 因发送太快了服务器无法响应 发送完毕后再发下一个//在调试时候本机完全可以响应,但是放到服务器上后,有延时导致会失败let res = await uploadFile(formData);if(res && res.code == 20000){var cc = parseFloat(currentChunk) + 1;var cn = parseFloat(chunkNum);var num = (cc/cn) * 100;num = parseFloat(num).toFixed(2);$("#upFilePer").text(num + "%");$("#upFilePerImg").attr("style", "width: " + num + "%");}else{success = false;layer.msg(res.message);break;}}let end = new Date()layer.close(loadIndex);setTimeout(() => {layer.msg(fileList[i].name + "上传完成,耗时:" + (end - start));//重选获取所有的文件 或者刷新页面 //getFiles();},200)} else {// 直接传,不分片let formData = new FormData();formData.append("saveFilePath", $("#saveFilePath").val());// 分片上传formData.append("chunkFlag", chunkFlag);// 文件类型formData.append('type', fileList[i].type)// 文件总大小formData.append("size", fileList[i].size);// 文件名formData.append("name", fileList[i].name);// 整个文件的id值,及md5值formData.append("fileMd5", fileMd5);// 计算当前文件分片的md5值formData.append("file", fileList[i]);let res = await uploadFile(formData);if(res && res.code == 20000){$("#upFilePer").text("100%");$("#upFilePerImg").attr("style", "width: 100%");layer.msg(fileList[i].name + "上传完成!" );//重选获取所有的文件 或者刷新页面 //getFiles();}else{layer.msg(res.message);}}}}} else {console.log('请先选择文件')layer.msg('请先选择文件');}}// 计算MD5function getMD5(file, fileListID){return new Promise((resove, reject) => {// 使用sparkMD5的ArrayBuffer类,读取二进制文件const spark = new SparkMD5.ArrayBuffer()const fileReader = new FileReader()// 异步操作,读完后的结果fileReader.onload = (e) => {// 把文件开始传入sparkspark.append(e.target.result)// spark计算出MD5后的结果const _md5 = spark.end()resove(_md5)// 下面可以写一些自己需要的业务代码, 例如 fileItem.fileMD5 = _md5}// fileReader读取二进制文件fileReader.readAsArrayBuffer(file)})}//后台检查文件是否已经上传过了async function checkFileExist(fileMd5Id){return await getUrlAsync("admin/checkFileExist", {fileMd5Id: fileMd5Id}) ;}//下面两个函数 应该在抽取出来的request.js文件中,这里为了方便放到这里 
function getUrlAsync(strUrl, dataParams) {return new Promise((resove, reject) => {$.ajax({url: strUrl,data: dataParams,type: "GET",dataType: 'json',success: function (result) {if (result.code === 20000) {return resove(result);} else if (result.code === 20002) {layer.msg(result.message);setTimeout(function () {window.top.location.href = "index.html";}, 1500);} else {layer.alert(result.message);}},error: function (XMLHttpRequest, textStatus, errorThrown) {layer.msg("连接超时:" + XMLHttpRequest.status + ","+ XMLHttpRequest.readyState + "," + textStatus);},complete: function (XMLHttpRequest, textStatus) {this; // 调用本次AJAX请求时传递的options参数}});})
}function uploadFile(formdata) {return new Promise((resove, reject) => {$.ajax({url:"admin/uploadFile",type:'post',contentType:false,processData:false,data:formdata,success: function (result) {if (result.code === 20000) {resove(result);} else if (result.code === 20002) {layer.msg(result.message);setTimeout(function () {window.top.location.href = "index.html";}, 500);} else {reject(result);}},error: function (XMLHttpRequest, textStatus, errorThrown) {layer.msg("连接超时:" + XMLHttpRequest.status + ","+ XMLHttpRequest.readyState + "," + textStatus);reject();}})})
}

JAVA后台

adminCotroller


import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/admin")
public class AdminCotroller {private static final Logger logger = LoggerFactory.getLogger(AdminCotroller.class);@Autowiredprivate FileUploadService fileUploadService;@PostMapping("/uploadFile")@ResponseBodypublic R uploadFile(FileUploadDTO dto){if (ObjectUtils.isEmpty(dto)) {return R.error("参数错误,文件上传失败");}if (dto.getChunkFlag()) {return fileUploadService.chunkFileUpload(dto);} else {return fileUploadService.singleFileUpload(dto);}}}

FileUploadDTO.java

import lombok.Data;
import org.springframework.web.multipart.MultipartFile;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;/*** description:** @author ZhouYi* @version 1.0.0* @ClassName FileUploadDTO* @date 2023/10/04 10:26:37*/
@Data
public class FileUploadDTO {/*** 是否是分片上传*/@NotNull(message = "是否分片不能为空")private Boolean chunkFlag;/*** 文件*/@NotNull(message = "文件不能为空")private MultipartFile file;/*** 文件名*/@NotBlank(message = "文件名不能为空")private String name;/*** 文件总大小*/private Long size;/*** 文件md5*/@NotBlank(message = "文件md5不能为空")private String fileMd5;/*** 文件类型*/private String type;/*** 当前分片*/private Integer currentChunk;/*** 分片长度*/private Long chunkSize;/*** 总分片数量*/private Integer chunks;/*** 分片文件md5*/private String currentChunkMd5;/*** 文件保存路径*/private String saveFilePath;
}

R.java

import java.io.Serializable;/*** description:** @author ZhouYi* @version 1.0.0* @ClassName R* @date 2022/04/06 21:38:12*/
public class R implements Serializable {private static final long serialVersionUID = -4301232631736358183L;/*** 请求成功或失败*/private boolean status;private int code;private Object data;private String message;/*** 返回成功无需数据 ok** @return*/public static R success() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE, ServerConstant.MESSAGE_SUCCESS);}/*** 提交成功有提示 操作成功* @return*/public static R successMsg() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE, ServerConstant.ERROR_SAVE_SUCCESS);}/*** 返回成功 data* @param data 自定义数据* @return*/public static R success(Object data) {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE, data, ServerConstant.MESSAGE_SUCCESS);}/*** 返回成功 message* @param message 自定义数据* @return*/public static R success(String  message) {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE,message);}/*** 自定义IS_SUCCESS  错误代码 错误消息** @param code         错误代码* @param errorMessage 错误消息* @return*/public static R error(int code, String errorMessage) {return new R(ServerConstant.IS_SUCCESS, code, errorMessage);}/*** 保存失败 无需数据** @return*/public static R error() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_SAVE_FILE);}/*** 错误 IS_SUCCESS message** @param message message* @return*/public static R error(String message) {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, message);}/*** 服务器错误 请重试 IS_ERROR** @return*/public static R errorException() {return new R(ServerConstant.IS_ERROR, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_EXCEPTION);}/*** 服务器错误 错误内容自定义 IS_ERROR** @param message 自定义内容* @return R*/public static R errorException(String message) {return new R(ServerConstant.IS_ERROR, message);}/*** 数据格式错误** @return*/public static R errorDataFormat() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_DATA_FORMAT);}/*** 请求太频繁** @return*/public static R errorRequestsTooFrequently() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_REQUESTS_TOO_FREQUENTLY);}public R() {}/*** 全部** @param status* @param code* @param data* @param message*/public R(boolean status, int code, Object data, String message) {this.status = status;this.code = code;this.data = data;this.message = message;}/*** 返回  status data code** @param code status* @param message message* @param code code*/public R(boolean status, int code, String message) {this.status = status;this.code = code;this.message = message;}/*** 只需返回  status message** @param status  status* @param message message*/public R(boolean status, String message) {this.status = status;this.code = ServerConstant.MESSAGE_SUCCESS_CODE;this.message = message;}public boolean isStatus() {return status;}public void setStatus(boolean status) {this.status = status;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "R{" +"code=" + code +", data=" + data +", message='" + message + '\'' +'}';}
}

ServerConstant.java

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;public interface ServerConstant {/*** 请求成功*/public static final boolean IS_SUCCESS = true;/*** 请求失败 一般为服务器抛异常*/public static final boolean IS_ERROR = false;/*** 前台显示*/public static final Integer IS_SHOW = 1;/*** 前台不显示*/public static final Integer IS_NOT_SHOW = 0;/*** 未删除*/public static final Integer IS_NOT_DELETE = 0;/*** 删除*/public static final Integer IS_DELETE = 1;/*** 每页数量*/public static final Integer SIZE = 35;/*** 当前页*/public static final Integer CURRENT = 1;/*** 默认*/public static final long COMPARE = 10 * 60 * 60 * 60;/*** 请求成功 20000  ok*/public static final int MESSAGE_SUCCESS_CODE = 20000;public static final String MESSAGE_SUCCESS = "ok";/*** 请求失败 20001  error*/public static final int MESSAGE_ERROR_CODE = 20001;/*** 登录失效 20002  error*/public static final int MESSAGE_LOG_FILE_CODE = 20002;/*** 文件存在 21000*/public static final int FILE_EXSIT = 21000;/*** 文件正在获取hash 220000*/public static final int FILE_GET_HASH = 220000;public static final String FILE_GET_HASH_ING = "FILE_GET_HASH_ING";public static final String FILE_GET_HASH_FINISH = "FILE_GET_HASH_FINISH";/*** 20010 操作成功*/public static final String ERROR_SAVE_SUCCESS = "操作成功!";/*** 20011 操作失败*/public static final String ERROR_SAVE_FILE = "操作失败!";/*** 30001 数据格式错误*/public static final String ERROR_DATA_FORMAT = "数据格式错误!";/*** 30002 请稍后再试*/public static final String ERROR_REQUESTS_TOO_FREQUENTLY = "请稍后再试!";/*** 服务器错误 30003 请重试*/public static final String ERROR_EXCEPTION = "请重试!";public static final String COMMUNICATION_TOKEN = "communicationToken";public static final String OPEN = "open";public static final String CLOSE = "close";public static final int FILE_MD5_CHECK_EXCEPTION = 23000;public static final String MD5_CHECK_EXCEPTION = "MD5校验异常";public static final String FILE_EXSIT_MSG = "文件已存在!";/*** 上传完成*/public static final int FILE_UPLOAD_COMPLETED = 21001;/*** 上传中*/public static final int FILE_UPLOAD_UPLOADING = 21002;/*** 上传失败*/public static final int FILE_UPLOAD_FAILED = 21003;/*** 文件未查询到*/public static final int FILE_NOT_FIND = 21004;public static final String WX_ACCESS_TOKEN = "WX_ACCESS_TOKEN";/*** description 保存文件上传状态*/public static Cache<String, FILE_STATE> fileStateCahe = CacheUtil.newFIFOCache(100);}

FileUploadService.java

package com.xyhz.backup.server.service;import com.xyhz.backup.server.entity.VO.FileUploadDTO;
import com.xyhz.backup.server.entity.VO.R;/*** @ClassName FileUploadService*/
public interface FileUploadService {/*** 分片上传* @param dto*/R chunkFileUpload(FileUploadDTO dto);/*** 单文件上传* @param dto*/R singleFileUpload(FileUploadDTO dto);}

FileUploadServiceImpl.java

@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {@Autowiredprivate FileUploadUtil fileUploadUtil;@Autowiredprivate SysFileMapper sysFileMapper;@Autowiredprivate SysChunkRecordMapper sysChunkRecordMapper;@Overridepublic R chunkFileUpload(FileUploadDTO dto) {UploadResultVo vo = new UploadResultVo();// 检测文件是否存在,大文件秒传//SysFile sysFile = queryMd5(dto.getFileMd5());//if (null != sysFile) {//    vo.setFileKid(sysFile.getKid()).setUploaded(Boolean.TRUE);//    return R.success(vo);//}// 检测分片是否存在//SysChunkRecord scr = queryChunkMd5(dto.getCurrentChunkMd5());//if (scr != null) {//    vo.setFileKid(scr.getKid()).setUploaded(Boolean.TRUE);//    return R.success(vo);//}try {MultipartFile multipartFile = dto.getFile();String filePath = fileUploadUtil.getFilePath(dto.getSaveFilePath());File file = new File(filePath, dto.getName());RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");if (randomAccessFile.length() == 0L) {randomAccessFile.setLength(dto.getSize());}// 计算分片文件的位置long pos = dto.getCurrentChunk() * dto.getChunkSize();FileChannel channel = randomAccessFile.getChannel();//log.info( "pos: "+ pos + "size: " + multipartFile.getSize());MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, pos, multipartFile.getSize());map.put(multipartFile.getBytes());cleanBuffer(map);channel.close();randomAccessFile.close();// 存分片数据String chunkKid = saveSysChunkRecord(file, dto);vo.setChunkKid(chunkKid).setUploaded(Boolean.TRUE);if (dto.getCurrentChunk() == dto.getChunks() - 1) {LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysChunkRecord::getFileMd5, dto.getFileMd5());Integer integer = sysChunkRecordMapper.selectCount(wrapper);int flag = 0;while (!integer.equals(dto.getChunks()) && flag < 10) {//Thread.sleep(100);integer = sysChunkRecordMapper.selectCount(wrapper);flag++;}if(integer.equals(dto.getChunks())) {// 存文件SysFile fileInfo = buildSysFile(dto, file);int insert = sysFileMapper.insert(fileInfo);if (insert == 1) {// 清除分片数据cleanChunkData(dto.getFileMd5());}vo.setFileKid(fileInfo.getKid()).setUploaded(Boolean.TRUE);} else {// 清除分片数据cleanChunkData(dto.getFileMd5());// 文件上传失败return R.error("分片上传失败!");}}return R.success(vo);} catch (Exception e) {log.error("分片上传失败!",e);return R.error("分片上传失败!");}}@Overridepublic R singleFileUpload(FileUploadDTO dto) {try {UploadResultVo vo = new UploadResultVo();SysFile sysFile = queryMd5(dto.getFileMd5());if (null != sysFile) {vo.setFileKid(sysFile.getKid()).setUploaded(Boolean.TRUE);return R.success(vo);}// 存文件String filePath = fileUploadUtil.getFilePath(dto.getSaveFilePath());File file = new File(filePath, dto.getName());dto.getFile().transferTo(file);// 存数据库SysFile fileInfo = buildSysFile(dto, file);sysFileMapper.insert(fileInfo);vo.setFileKid(fileInfo.getKid()).setUploaded(Boolean.TRUE);return R.success(vo);} catch (IOException e) {log.error("单文件上传失败!",e);return R.error("单文件上传失败!");}}/*** 根据md5值清除分片数据*/private void cleanChunkData(String md5) {LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<SysChunkRecord>().eq(SysChunkRecord::getFileMd5, md5);sysChunkRecordMapper.delete(wrapper);}/*** 构建SysFile* @param dto* @param file* @return*/private SysFile buildSysFile(FileUploadDTO dto, File file) {return SysFile.builder().fileMd5(dto.getFileMd5()).fileName(dto.getName()).filePath(file.getAbsolutePath()).fileType(dto.getType()).fileSize(dto.getSize()).createTime(LocalDateTime.now()).extension(dto.getName().substring(dto.getName().lastIndexOf("."))).build();}
}

SysChunkRecordMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import server.entity.SysChunkRecord;public interface SysChunkRecordMapper extends BaseMapper<SysChunkRecord> {
}

SysChunkRecord.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDateTime;
@TableName(value ="sys_chunk_record")
@Data
@Accessors(chain = true)
public class SysChunkRecord implements Serializable {/*** kid*/@TableId(type = IdType.ASSIGN_UUID)private String kid;/*** 文件名称*/private String chunkFileName;/*** 文件存放路径*/private String chunkFilePath;/*** 文件md5*/private String fileMd5;/*** 文件分片md5*/private String currentChunkMd5;/*** 分片大小*/private Long chunkSize;/*** 第几块分片*/private Integer currentChunk;/*** 总分片数量*/private Integer chunks;/*** 创建时间*/private LocalDateTime createTime;@TableField(exist = false)private static final long serialVersionUID = 1L;
}

FileUploadUtil.java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;@Slf4j
@Component
public class FileUploadUtil {/*** 生成文件路径* @return*/public String getFilePath(String saveFilePath){if (StringUtils.isEmpty(saveFilePath)) {//没有上传路径就获取默认配置,这里可以直接指定一个saveFilePath = "E:/Download"//saveFilePath = serverConfig.getDefaultUploadPath();}File file = new File(saveFilePath);if (!file.exists()) {file.mkdirs();}DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");LocalDate now = LocalDate.now();String format = formatter.format(now);String datePath = saveFilePath + File.separator + format;File fullFile = new File(datePath);if (!fullFile.exists()) {fullFile.mkdirs();}return datePath;}
}

SysFile.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@TableName(value ="sys_file")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysFile implements Serializable {/*** kid*/@TableId(type = IdType.ASSIGN_UUID)private String kid;/*** 文件名称*/private String fileName;/*** 文件扩展名*/private String extension;/*** 文件大小*/private Long fileSize;/*** 文件类型*/private String fileType;/*** 文件存放路径*/private String filePath;/*** 文件md5*/private String fileMd5;/*** 备注*/private String remark;/*** 创建时间*/private LocalDateTime createTime;@TableField(exist = false)private static final long serialVersionUID = 1L;
}

Maven

	<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-core</artifactId><version>3.4.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.0.M2</version></dependency>

http://www.ppmy.cn/news/1413383.html

相关文章

第十五届蓝桥杯测试组模拟赛两期

文章目录 功能测试一期-场景法-登录功能一期-等价类-边界值-添加用户账号输入框一期-登录-缺陷报告一期- UI自动化测试一期-单元测试-路径覆盖二期-正交法-搜索条件组合二期-测试用例二期-缺陷报告二期-自动化测试二期-单元测试-基本路径覆盖 功能测试 一期-场景法-登录功能 …

【单片机家电产品学习记录--蜂鸣器】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 单片机家电产品–蜂鸣器 前言 记录学习单片机家电产品内容 已转载记录为主 一、知识点 1电子电路学习笔记&#xff08;17&#xff09;——蜂鸣器 蜂鸣器种类和原理 2疑…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.2 月末操作:GR/IR重组

2.6.2 月末操作&#xff1a;GR/IR重组 SAP在采购订单收货和发票校验时分别产生凭证&#xff0c;中间采用GR/IR过渡。GR即为收货&#xff0c;IR即为收票。月末&#xff0c;GR/IR的余额根据收货和收票的情况进行判断&#xff0c;转入“应付暂估”或“在途物资”&#xff0c;次月自…

Windows11下Docker使用记录(一)

Docker使用记录&#xff08;一&#xff09; 简单介绍Docker安装Docker 常用命令Docker 可视化Docker 使用GPU可视化rviz、gazebo 在进行ROS项目开发时&#xff0c;如果只有一台Windows电脑&#xff0c;我们可以考虑使用WSL或Docker来搭建ROS环境。在尝试了两种方式后&#xff0…

vue2与vue3相比哪个更好

vue2与vue3相比哪个更好 2是选项式api&#xff0c;3是组合式api 2的话数据如果是响应式的话需要定义在data里面 3的话因为要return出去所以需要进行定义const&#xff0c;借助ref&#xff08;&#xff09;或者reactive&#xff08;&#xff09;进行响应式数据切换 对于小型的…

【LeetCode热题100】【二叉树】二叉树的最大深度

题目链接&#xff1a;104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 最大深度等于左子树的最大深度和右子树的最大深度中的较大者加一 class Solution { public:int maxDepth(TreeNode *root) {if (!root)return 0;return max(maxDepth(root->left), max…

【python】Flask Web框架

文章目录 WSGI(Web服务器网关接口)示例Web应用程序Web框架Flask框架创建项目安装Flask创建一个基本的 Flask 应用程序调试模式路由添加变量构造URLHTTP方法静态文件模板—— Jinja2模板文件(Template File)<

appium图像识别之images-plugin插件

在进行App自动化测试的过程中&#xff0c;由于页面的复杂性&#xff0c;需要根据页面的技术实现&#xff0c;通过上下文来切换不同的定位类型&#xff0c;给定位元素的稳定性带来了不小的挑战&#xff1b;图像识别技术则不依赖于页面本身是用什么技术实现的&#xff0c;只要能识…