下载中心-异步下载

下载中心

文章目录

  • 下载中心
    • 一. 概要
    • 二. 实现逻辑
  • 下载中心
    • 一. 概要
    • 二. 实现逻辑
    • 三. 主要代码逻辑
      • 1.生成任务
      • 2.消费任务
      • 3.查询方法是如何存入内存中的
      • 4.DCGenerateComponent 反射调用查询数据方法
    • 总结

一. 概要

功能概览:将文件下载修改为异步下载,引入mq队列进行削峰解耦

整体步骤:

  1. 请求后端接口
  2. 将需要下载的任务以数据的方式存储在数据库中
  3. 将任务编号发往对应的mq队列中等待消费
  4. mq消费生成文件
  5. mq根据对应任务id,查询出任务具体参数
  6. 利用具体的任务参数查询出对应的分页数据
  7. 生成对应的文件,上传oss
  8. 在下载中心进行文件下载

二. 实现逻辑

下载中心

文章目录

  • 下载中心
    • 一. 概要
    • 二. 实现逻辑
  • 下载中心
    • 一. 概要
    • 二. 实现逻辑
    • 三. 主要代码逻辑
      • 1.生成任务
      • 2.消费任务
      • 3.查询方法是如何存入内存中的
      • 4.DCGenerateComponent 反射调用查询数据方法
    • 总结

一. 概要

功能概览:将文件下载修改为异步下载,引入mq队列进行削峰解耦

整体步骤:

  1. 请求后端接口
  2. 将需要下载的任务以数据的方式存储在数据库中
  3. 将任务编号发往对应的mq队列中等待消费
  4. mq消费生成文件
  5. mq根据对应任务id,查询出任务具体参数
  6. 利用具体的任务参数查询出对应的分页数据
  7. 生成对应的文件,上传oss
  8. 在下载中心进行文件下载

二. 实现逻辑

在这里插入图片描述

  • 流程图

    在这里插入图片描述

  • 泳道图

    在这里插入图片描述

三. 主要代码逻辑

1.生成任务

  • DownloadCenterComponent接口中定义了addMsg(发送消息)方法

    /**
         * 新版的下载中心发送消息的接口
         * 在消费时,通过下载类型,拿到查询数据的方法,执行并分页查询数据
         *
         * @param downloadKey   下载的key
         * @param fileNameItems 替换文件名字中占位符的参数 例子在数据库中配置了filename
         *                      filename字段并不是一个完整的文件名,需要拼接上文件的后缀名和替换占位符
         *                      例子:filename = "销售单数据_%s_%s",
         *                      那么fileNameItems就传两个字符串--->fileNameItems[2]=["20220504","BuyerName"]
         *                      fileType = "csv"
         *                      最后组成的完整的文件名字 : 销售单数据_20220504_BuyerName.csv
         *                      (PS:如果单个文件超过最大行数,那么文件名就会变成
         *                      销售单数据_20220504_BuyerName_1.csv)
         *                      销售单数据_20220504_BuyerName_2.csv)
         *                      销售单数据_20220504_BuyerName_3.csv
         *                      最后打成一个压缩包 销售单数据_20220504_BuyerName.zip)
         * @param <T>           query参数的类型
         */
    <T extends PageQuery> void addMsg(String downloadKey,
                                      T query,
                                      String... fileNameItems);
    
    <T extends PageQuery> void addMsg(DownloadCenterFileConfigEnum downloadKey,
                                      T query,
                                      String... fileNameItems);
    

    DownloadCenterComponentImpl实现 addMsg

    @Override
    @Transactional(rollbackFor = Exception.class)
    public <T extends PageQuery> void addMsg(String downloadKey, T query, String... fileNameItems) {
    	//判断下载的key是否是合法的,是否包含在我们定义的枚举中
        boolean flag = DownloadCenterFileConfigEnum.checkDownloadKey(downloadKey);
        if (!flag) {
            throw new OristandException("下载类型不存在");
        }
        Integer userId = getCurrUserId(query);
        //根据key去查找对应的配置 tb_download_file_config
        /*DownloadFileConfigEntity中包含filename(文件名称),fileType(csv还是xlsx),queueType(快慢队列,SLOW、FAST)以及行数限制等(默认单次最大行为50000,单sheet 20w行)
        */
        DownloadFileConfigEntity config = iDownloadFileConfigService.getByDownloadKey(downloadKey);
        String filename = config.getFilename();
        String fileType = config.getFileType();
        //构建任务体
        DownloadListDO downloadListDo = DownloadListDO.builder()
            .userId(userId)
            .type(-1)
            .downloadKey(downloadKey)
            .queryParam(JSON.toJSONString(query, SerializerFeature.WriteMapNullValue))
            .fileName(getWholeFileName(filename, fileType, fileNameItems))
            .progress(new BigDecimal(REQUEST_DOWNLOAD_PROGRESS))
            .downloadVersion("2.0")
            .build();
        //根据配置选择快慢队列的枚举
        DownloadCenterQueueTypeEnum queueType = DownloadCenterQueueTypeEnum.getTypeByCode(config.getQueueType());
        //根据快慢队列选择对应的routingKey和Virtual host(虚拟分组)
        String routingKey = downLoadMQConfig.getRoutingKeyByQueueType(queueType);
        String vhost = downLoadMQConfig.getVhostByQueueType(queueType);
        saveAndSendMsg(downloadListDo, routingKey, vhost);
    }
    
    //保存并发送消息到队列
    private void saveAndSendMsg(DownloadListDO downloadListDo, String routingKey, String vhost) {
        //后台文件下载列表保存对应文件数据 tb_download_list
        downloadCenterService.save(downloadListDo);
        //将文件列表主键和mq队列等信息发送到队列
        rabbitmqSendService.addMsg(
            RabbitMqConfig.ExchangeEnum.DEFAULT_DIRECT_EXCHANGE.getCode(),
            routingKey,
            vhost,
            JSON.toJSONString(downloadListDo.getId())
        );
    }
    

2.消费任务

  • DownloadCenterComponent接口中定义了downloadConsumer(发送消息)方法

        /**
         * 真正的消费接口
         *
         * @param downloadId tb_download_list表的主键
         */
        void downloadConsumer(Integer downloadId);
    

    DownloadCenterComponentImpl实现 downloadConsumer

    @Override
    public void downloadConsumer(Integer downloadId) {
        //根据id查到downloadId tb_download_list表中的数据
        DownloadListDO downloadListDO = downloadCenterService.getById(downloadId);
        String fileName = downloadListDO.getFileName();
        // 下载类型
        String downloadKey = downloadListDO.getDownloadKey();
        // 获取配置此类任务的配置
        DownloadFileConfigEntity config = iDownloadFileConfigService.getByDownloadKey(downloadKey);
        //组装文件的名称,如替换%s这种
        fileName = getWholeFileName(fileName.substring(0, fileName.lastIndexOf(StrUtil.DOT)), config.getFileType());
        //判断文件是否是xlsx或者cvs类型,不是则抛出异常   throw new OristandException("该文件格式暂不支持生成");
        checkConfig(config);
        // 获取当时任务的下载查询参数 当时存入的时候是json
        String queryParam = downloadListDO.getQueryParam();
        // 查询的方法,这个方法是缓存在内存中的,想bean一样,一直在内存中
        DownloadMethodDTO downloadMethod = downloadCenterDataSource.getDownloadMethod(downloadKey);
        // 根据方法参数的类型
        Class<?>[] queryTypes = downloadMethod.getQueryTypes();
        // 查询参数查询参数一定继承PageQuery
        PageQuery queryObj = null;
        if (queryTypes.length != 0) {
            Class<?> pageQueryClazz = null;
            for (Class<?> queryType : queryTypes) {
                //判断PageQuery是否是queryType他的一个父类或父接口,或是否是同一个类或同一个接口
                if (PageQuery.class.isAssignableFrom(queryType)) {
                    pageQueryClazz = queryType;
                }
            }
            //断言不为空
            assert pageQueryClazz != null;
            //将查询参数由json转回对象
            queryObj = (PageQuery) JSON.parseObject(queryParam, pageQueryClazz);
        }
        assert queryObj != null;
        // 这个是查询方法的返回类型,需要根据这个类型来分情况处理
        Class<?> clazz = downloadMethod.getDownloadObjClazz();
    
        String fileType = config.getFileType();
        Integer queryRow = Math.min(config.getQueryRow(), config.getSheetRow());
        Integer sheetRow = Math.max(config.getQueryRow(), config.getSheetRow());
        // 处理默认情况
        if (queryRow < 0) {
            queryRow = tempFileConfig.getBatchSize();
        }
        // 如果查询的结果是字节流,就单独处理,否则他的返回结果一定可以得到一个list
        // 判断返回值是否是字节流
        int page = 1;
        int limit = queryRow;
        //判断是否字节流
        if (clazz.isAssignableFrom(ByteArrayOutputStream.class)) {
            limit = sheetRow;
        }
        // 处理各种返回类型,目前有这几种
        // PageBean 旧的
        // LayTableData 旧的
        // List<?> 旧的
        // List<Object[]> 目前就只有这几种类型,这种情况特殊,表头是数据库里面配置的。
        List<ByteArrayOutputStream> bosList = new ArrayList<>();
        boolean isGo;
        //判断xlsx处理方式还是csv处理方式
        DCGenerateComponent generateComponent = downloadCenterGenerateFileComponent.chooseFileType(fileType);
        do {
            DownloadCenterDTO dto = new DownloadCenterDTO();
            // 设置参数
            queryObj.setPage(page);
            queryObj.setLimit(limit);
            // 设置downloadKey
            dto.setDownloadKey(downloadKey);
            // 设置分页查询参数
            dto.setQuery(queryObj);
            // 设置查询总量
            dto.setQueryTotal(sheetRow);
            dto.setSheetName(fileName.substring(0, fileName.lastIndexOf(StrUtil.DOT)));
            
            //根据不同文件类型进入一些模板基础处理,dto里面有查询的参数
            DownloadCenterPage outputStreamInfo = generateComponent.getOutputStreamInfo(dto);
            boolean end = outputStreamInfo.isEnd();
            ByteArrayOutputStream bos = outputStreamInfo.getBos();
            if (bos != null && bos.toByteArray().length != 0) {
                // 满足两个条件,bos不等于null,字节数不等于0
                bosList.add(bos);
            }
            isGo = !end;
            page += (sheetRow / queryRow);
            // 设置序号
            dto.setSerialNumber((page - 1) * limit + 1);
        } while (isGo);
    	//保证事务生效
        DownloadCenterComponent downloadCenterComponent = (DownloadCenterComponent) AopContext.currentProxy();
        try {
            //将流文件上传到oss中,并修改了任务状态
            downloadCenterComponent.uploadFileToOss(bosList, downloadListDO);
        } catch (IOException e) {
            log.error("下载中心上传oss失败", e);
            throw new OristandException("下载中心上传oss失败", e);
        }
    }
    
  • 上传oss,并修改状态 downloadCenterComponent.uploadFileToOss(bosList, downloadListDO);

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void uploadFileToOss(List<ByteArrayOutputStream> osList, DownloadListDO downloadListDO) throws IOException {
        String fileName = downloadListDO.getFileName();
        String downloadKey = downloadListDO.getDownloadKey();
        // 获取配置
        DownloadFileConfigEntity config = iDownloadFileConfigService.getByDownloadKey(downloadKey);
        fileName = getWholeFileName(fileName.substring(0, fileName.lastIndexOf(StrUtil.DOT)), config.getFileType());
        OssFileModel model;
        BigDecimal fileSize = BigDecimal.ZERO;
        if (osList.size() == 1) {
            ByteArrayOutputStream bos = osList.get(0);
            InputStream is = new ByteArrayInputStream(bos.toByteArray());
            fileSize = new BigDecimal(is.available()).divide(new BigDecimal(ONE_ZERO_TWO_FOUR), 2, RoundingMode.HALF_UP);
            model = uploadFileToOss(is, fileName);
        } else {
            String zipPath = goZip(osList, fileName);
            Path path = Paths.get(zipPath);
            try (InputStream is = Files.newInputStream(path)) {
                fileSize = new BigDecimal(is.available()).divide(new BigDecimal(ONE_ZERO_TWO_FOUR), 2, RoundingMode.HALF_UP);
                model = uploadFileToOss(is, changeFileNameToZip(fileName));
            } catch (IOException e) {
                log.error("下载中心读取临时压缩文件失败", e);
                throw new OristandException(e);
            } finally {
                Files.deleteIfExists(path);
            }
        }
        // 文件下载完,如果状态是已取消,将文件状态改为取消,进度条回退到50%
        Integer downloadStatus = DownloadStatusEnum.GENERATED.getCode();
        BigDecimal progress = new BigDecimal(COMPLETE_DOWNLOAD_PROGRESS);
        DownloadListDO temp = downloadCenterService.getById(downloadListDO.getId());
        if (temp != null && DownloadStatusEnum.CANCELED.getCode().equals(temp.getStatus())) {
            downloadStatus = DownloadStatusEnum.CANCELED.getCode();
            progress = new BigDecimal(REQUEST_DOWNLOAD_PROGRESS);
        }
        // 得到OssModel 更新数据
        Integer id = downloadListDO.getId();
        downloadCenterService.update(
            Wrappers.lambdaUpdate(DownloadListDO.class)
            .eq(DownloadListDO::getId, id)
            .set(DownloadListDO::getStatus, downloadStatus)
            .set(DownloadListDO::getFileName, model.getFileName())
            .set(DownloadListDO::getProgress, progress)
            .set(DownloadListDO::getFileSavePath, model.getOssKey())
            .set(DownloadListDO::getFileSize, fileSize)
        );
    }
    

3.查询方法是如何存入内存中的

DownLoadCenterAnno注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DownLoadCenterAnno {

    /**
     * 下载类型的枚举
     */
    DownloadCenterFileConfigEnum downloadKey();

    /**
     * 你输出的对象叫是什么类型的
     */
    Class<?> downloadObj() default Object.class;

    /**
     * 兼容一些序号要写到文件里面的,就是上面类里面的字段
     */
    String serialNumberField() default StrUtil.EMPTY;
}

//使用方式 downloadObj 会在bean加载后将这个对象放到内存中记录
@DownLoadCenterAnno(
    downloadKey = DownloadCenterFileConfigEnum.LOGISTICS_CLAIMS_TOTAL_PAGES,
    downloadObj = ClaimsApplyVO.class)
public PageBean<ClaimsApplyVO> listClaimsApplyList(ClaimsApplyQuery query) {
    ....
}


枚举 DownloadCenterFileConfigEnum

/**
 * 数据库中所有的download_key都配在这边,避免直接写魔法值
 * 规范:下划线分隔,包装key唯一
 *
 * @author super syj
 */
@AllArgsConstructor
@Getter
public enum DownloadCenterFileConfigEnum {

    LOGISTICS_CLAIMS_TOTAL_PAGES("logistics_claims_total_pages"),
    ;

    private final String downloadKey;

    /**
     * 校验下载的key是否合法
     *
     * @param downloadKey downloadKey
     * @return 返回true,true合法,false不合法
     */
    public static boolean checkDownloadKey(String downloadKey) {
        return Arrays.stream(values())
                .map(DownloadCenterFileConfigEnum::getDownloadKey)
                .collect(Collectors.toList())
                .contains(downloadKey);
    }
}

DownloadCenterDataSourceImpl实现了BeanPostProcessor接口的postProcessAfterInitialization方法,这个方法会在每个bean对象的初始化方法调用之后被回调。

@Slf4j
@Component
public class DownloadCenterDataSourceImpl implements DownloadCenterDataSource, BeanPostProcessor {

  /**
   * 线程安全,保证key唯一
   */
  private final static Map<String, DownloadMethodDTO> METHOD_MAP = new ConcurrentHashMap<>();
  @Override
  public Object postProcessAfterInitialization(Object bean, @NotNull String beanName) throws BeansException {
      //获取所有非继承方法
      Method[] declaredMethods = bean.getClass().getDeclaredMethods();
      List<Method> methods = Arrays.stream(declaredMethods).filter(method -> {
          DownLoadCenterAnno anno = AnnotationUtils.findAnnotation(method, DownLoadCenterAnno.class);
          return Objects.nonNull(anno);
      }).collect(Collectors.toList());
      for (Method method : methods) {
          //查找对应注解中的信息
          DownLoadCenterAnno anno = AnnotationUtils.findAnnotation(method, DownLoadCenterAnno.class);
          assert anno != null;
          DownloadCenterFileConfigEnum configEnum = anno.downloadKey();
          if (configEnum == null) {
              continue;
          }
          String key = configEnum.getDownloadKey();
          Class<?>[] queryTypes = method.getParameterTypes();
          //封装DownloadMethodDTO 对象DTO存放在map中,key为枚举的DownloadKey
          DownloadMethodDTO methodDTO = DownloadMethodDTO.builder()
              .beanClazz(bean.getClass())
              .beanName(beanName)
              .method(method)
              .downloadObjClazz(anno.downloadObj())
              .queryTypes(queryTypes)
              .serialNumberField(anno.serialNumberField())
              .build();
          METHOD_MAP.putIfAbsent(key, methodDTO);
      }
      return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
  }

  @Override
  public DownloadMethodDTO getDownloadMethod(String downloadKey) {
      return METHOD_MAP.get(downloadKey);
  }
}
  • 初始化后Map内数据,根据downloadKey取出对应的方法

    在这里插入图片描述

4.DCGenerateComponent 反射调用查询数据方法

  • ExcelDCGenerateComponentImpl 实现 getOutputStreamInfo

    @Override
    public DownloadCenterPage getOutputStreamInfo(DownloadCenterDTO dto) {
        String downloadKey = dto.getDownloadKey();
        //获取查询方法
        DownloadMethodDTO downloadMethod = downloadCenterDataSource.getDownloadMethod(downloadKey);
        //查询任务配置
        DownloadFileConfigEntity config = iDownloadFileConfigService.getByDownloadKey(downloadKey);
        String fileHeaders = config.getFileHeaders();
        Method method = downloadMethod.getMethod();
        Class<?> head = downloadMethod.getDownloadObjClazz();
        // 下载方法所在的bean
        Object bean = SpringContextHolder.getBean(downloadMethod.getBeanClazz());
    
        PageQuery query = dto.getQuery();
        String lan = query.getLan();
        boolean isGo = true;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ExcelWriter writer = EasyExcel.write(bos)
            // 默认样式
            .registerWriteHandler(ExcelUtil.createDefaultHeadStyle())
            // 中英文转换
            .registerWriteHandler(new HeadZhEnAdaptiveHandler(lan))
            // 列宽自动
            .registerWriteHandler(new CustomCellWriteWeightConfig())
            // 时间转换
            .registerConverter(new LocalDateConverter())
            .registerConverter(new LocalDateTimeConverter())
            .build();
        ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet().sheetNo(0).sheetName(dto.getSheetName());
        if (head.isAssignableFrom(Object[].class)) {
            sheetBuilder.head(changeFileHeaders(fileHeaders));
        } else {
            sheetBuilder.head(head);
        }
        WriteSheet writeSheet = sheetBuilder.build();
        // 已经查询的数量
        long alreadyQueriedNum = 0;
        // 需要查询的总量,如果已经查询的数量,大于需要查询的总量,就跳出循环
        long queryTotal = dto.getQueryTotal();
        // 标记变量,是否全部结束
        boolean isEnd = false;
        Integer page = query.getPage();
        Integer limit = query.getLimit();
        do {
            Object[] funObj = new Object[]{query};
            Object result;
            List<?> data = null;
            try {
                result = method.invoke(bean, funObj);
            } catch (IllegalAccessException | InvocationTargetException e) {
                log.error("excel,下载中心执行查询方法失败,可能是查询方法本身报错,invoke方法出现异常时,不会把反射方法的异常抛出来", e);
                throw new OristandException(e);
            }
            if (result instanceof LayTableData<?>) {
                data = layTableDataFun.apply((LayTableData<?>) result);
            }
            if (result instanceof PageBean<?>) {
                data = pageBeanFun.apply((PageBean<?>) result);
            }
            if (result instanceof List<?>) {
                data = listFun.apply((List<?>) result);
            }
            //设置序号
            DownloadCenterUtil.dealWithListData(data, head, dto.getSerialNumber(), downloadMethod.getSerialNumberField());
            data = CollUtil.isEmpty(data) ? Collections.emptyList() : data;
            int queryNum = data.size();
            dto.setSerialNumber(dto.getSerialNumber() + queryNum);
            if (queryNum != 0 || page == 1) {
                if (head.isAssignableFrom(Object[].class)) {
                    List<List<Object>> finalData = data.stream()
                        .filter(Objects::nonNull)
                        .map((Function<Object, List<Object>>) o -> Arrays.asList((Object[]) o))
                        .collect(Collectors.toList());
                    writer.write(finalData, writeSheet);
                } else {
                    writer.write(data, writeSheet);
                }
            }
            query.setPage(++page);
            query.setLimit(limit);
            alreadyQueriedNum += queryNum;
            if (alreadyQueriedNum >= queryTotal) {
                isGo = false;
            }
            if (CollUtil.isEmpty(data) || !Objects.equals(query.getLimit(), data.size())) {
                isEnd = true;
                isGo = false;
            }
        } while (isGo);
        if (alreadyQueriedNum != 0 || (page - 1) == 1) {
            writer.finish();
        }
        return DownloadCenterPage.builder()
            .bos(bos)
            .isEnd(isEnd)
            .build();
    }
    

总结

核心模块在【manage处理下载】这一部分:

  1. 根据 downloadId 获取下载的所有信息。

  2. 根据downloadKey从数据源池子【Map】中获取【DownLoadCenterFunc】查询数据的方法

  3. 获取参数,设置分页,把参数填入 goQuery 方法,获取PageBean

  4. 把数据写入文件

  5. 判断文件中已经写入的行数,如大于200000,重新创建一个excel

  6. 判断数据是否全部查询完毕,全部查询完毕顺序执行 ,没有查完接着循环查询数据,回到 3

  7. 获得所有创建的excel或者csv集合

  8. 打包成压缩包

  9. 上传oss

  10. 关闭流

  11. 把oss返回的文件路径存表,并修改状态为已完成

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

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

相关文章

Ubuntu18.04安装RTX2060显卡驱动+CUDA+cuDNN

Ubuntu18.04安装RTX2060显卡驱动CUDAcuDNN 1 安装RTX2060显卡驱动1.1 查看当前显卡是否被识别1.2 安装驱动依赖1.3 安装桌面显示管理器1.4 下载显卡驱动1.5 禁用nouveau1.6 安装驱动1.7 查看驱动安装情况 2 安装CUDA2.1 查看当前显卡支持的CUDA版本2.2 下载CUDA Toolkit2.3 安装…

1.4 Word2Vec是如何工作的? Word2Vec与LDA 的区别和联系?

1.4 Word2Vec&#xff1a;词嵌入模型之一 场景描述 谷歌2013年提出的Word2Vec是目前最常用的词嵌入模型之一。 Word2Vec实际是一种浅层的神经网络模型,它有两种网络结构&#xff0c;分别是CBOW(Continues Bag of Words)和Skip-gram。 知识点 Word2Vec,隐狄利克雷模型(LDA),…

软件测试之接口测试

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. 什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组…

通信网优岗位真实面经分享!

春招来临&#xff0c;不少网优人已经踏上了面试的征程。网优面试具体涉及哪些环节&#xff1f;主要问题有哪些&#xff1f; 本文收集并整理已经获得高薪offer的优橙学员的相关简历&#xff0c;为正在投递网优岗位的你提供经验&#xff0c;也希望网优人能早日找到满意工作。 通信…

uniapp 滑动页面至某个元素或顶部

直接上代码&#xff1a; uni.pageScrollTo({selector: #top, // 需要返回顶部的元素id或class名称duration: 300 // 过渡时间&#xff08;单位为ms&#xff09; }); 官方文档&#xff1a;

计及电池储能寿命损耗的微电网经济调度(matlab代码)

目录 1 主要内容 储能寿命模型 负荷需求响应 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序参考文献《考虑寿命损耗的微网电池储能容量优化配置》模型&#xff0c;以购售电成本、燃料成本和储能寿命损耗成本三者之和为目标函数&#xff0c;创新考虑储能寿命损耗约…

【C++进阶】用哈希表封装unordered_set和unordered_map

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

算法:滑动窗口

文章目录 例题1&#xff1a;长度最小的子数组例题2&#xff1a;无重复字符的最长子串例题3&#xff1a;最大连续1的个数 III例题4&#xff1a;将 x 减到 0 的最小操作数例题5&#xff1a;水果成篮例题6&#xff1a;找到字符串中所有字母异位词例题7&#xff1a;串联所有单词的子…

网络工程技术-学习内容(非技术文)

公共基础双纹线的制作 认识网络环境 (1)ipv4 ipv4地址的构成&#xff0c;分类&#xff0c;子网刻分&#xff0c;超丽素合“ 交换机的基本配置telnet&#xff0c;ssh&#xff0c; web方式三种配置 van. sto.协议 VLAN 端口聚合 三层交换“ 路由器的基本配置《(端口 IP 地址配)《…

msvcp120.dll丢失的解决方法,教你快速解决msvcp120.dll问题

msvcp120.dll是一个在Windows操作系统中至关重要的系统文件&#xff0c;它属于Microsoft Visual C Redistributable Package的一部分。这个动态链接库文件&#xff08;DLL&#xff09;包含了运行某些应用程序所必需的C运行时库函数。当某个程序在运行过程中需要调用这些预先编译…

requests做接口测试

Requests 是用Python语言编写&#xff0c;基于 urllib&#xff0c;采用 Apache2 Licensed 开源协议的 HTTP 库。它比 urllib 更加方便&#xff0c;可以节约我们大量的工作&#xff0c;完全满足 HTTP 测试需求。Requests 的哲学是以 PEP 20 的习语为中心开发的&#xff0c;所以它…

rabbitmq基础(1)

1、背景 能实现消息队列的框架软件有很多&#xff0c;kafka、rabbitmq、RocketMq、activeMq、Redis&#xff08;非专业&#xff09;&#xff0c;各有各的特点和优缺点。但是之前的公司数据需求规模并非很大&#xff0c;所以采用rabbitmq作为消息队列。 2、rabbitMq的基础架构…

工业网关、物联网网关与PLC网关是什么?

网关是什么&#xff1f; 网关是一种用于连接不同网络的网络设备&#xff0c;其作用是实现网络之间的通信和数据交换。它负责将一个网络的数据转发到另一个网络&#xff0c;并且可以进行路由、转换和过滤等处理。通常用于连接局域网和广域网之间&#xff0c;可以是硬件设备或者软…

基于javaweb实现的学生选课系统

一、系统架构 前端&#xff1a;jsp | bootstrap | jquery | css 后端&#xff1a;spring | sprinmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | tomcat 二、代码及数据库 三、功能介绍 01. 登录页 02. 管理员-课程管理 03. 管理员-学生管理 04. 管…

网络编程:select、poll

.1、select完成TCP并发服务器 程序代码&#xff1a; #include <myhead.h> #define SER_IP "192.168.125.234" //服务端IP #define SER_PORT 8888 //服务端端口号int main(int argc, const char *argv[]) {//1.创建用于连接的套接字int sfds…

C#,电话数字键盘问题(Mobile Numeric Keypad problem)的算法与源代码

1 电话数字键盘问题 提供移动数字键盘。您只能按向上、向左、向右或向下至当前按钮的按钮。不允许您按最下面一行的角点按钮&#xff08;即.*和#&#xff09;。 移动键盘 给定一个数N&#xff0c;找出给定长度的可能数。 示例&#xff1a; 对于N1&#xff0c;可能的数字数为…

免费SSL证书有效期

免费SSL证书有效期现状 目前市场上主流的免费SSL证书提供商大多遵循行业规范&#xff0c;将免费证书的有效期设为3个月。这意味着每隔三个月&#xff0c;网站管理员必须重新申请、验证并安装新的SSL证书&#xff0c;以维持网站的HTTPS安全连接状态。这种做法已成为行业的常态&…

GEE入门篇|图像分类(一):非监督分类

在非监督分类中&#xff0c;我们有与监督分类相反的过程。 首先对光谱类进行分组&#xff0c;然后将其分类为簇。因此&#xff0c;在 Earth Engine 中&#xff0c;这些分类器是 ee.Clusterer 对象。 它们是“自学”算法&#xff0c;不使用一组标记的训练数据&#xff08;即它们…

C++复习笔记——泛型编程模板

01 模板 模板就是建立通用的模具&#xff0c;大大提高复用性&#xff1b; 02 函数模板 C另一种编程思想称为 泛型编程 &#xff0c;主要利用的技术就是模板 C 提供两种模板机制:函数模板和类模板 函数模板语法 函数模板作用&#xff1a; 建立一个通用函数&#xff0c;其函…

centos上部署k8s

环境准备 四台Linux服务器 主机名 IP 角色 k8s-master-94 192.168.0.94 master k8s-node1-95 192.168.0.95 node1 k8s-node2-96 192.168.0.96 node2 habor 192.168.0.77 镜像仓库 三台机器均执行以下命令&#xff1a; 查看centos版本 [rootlocalhost Work]# cat /…