【云岚到家】-day03-2-门户缓存实现实战

【云岚到家】-day03-2-门户缓存实现实战

  • 5 缓存实现
    • 5.2 定时任务更新缓存
      • 5.2.1 分布式调度平台
        • 5.2.1.1 jdk提供的Timer定时器
        • 5.2.1.2 使用第三方Quartz方式
        • 5.2.1.3 使用分布式调度平台XXL-JOB
      • 5.2.2 XXL-JOB
        • 5.2.2.1 介绍
        • 5.2.2.2 部署调度中心
        • 5.2.2.3 执行器
      • 5.2.2 定义缓存更新任务
        • 5.2.2.1 编写任务方法
        • 5.2.2.2 配置任务
        • 5.2.2.3 启动任务并测试
    • 5.3 首页服务列表实现
      • 5.3.1 首页服务列表实现
        • 5.3.1.1 需求分析
        • 5.3.1.2 接口定义
        • 5.3.1.3 mapper
        • 5.3.1.4 service
        • 5.3.1.5 controller
        • 5.3.1.6 测试
      • 5.3.2 首页服务列表缓存
        • 5.3.2.1 缓存方案分析
        • 5.3.2.2 查询缓存
        • 5.3.2.3 查询缓存测试
        • 5.3.2.4 定时任务更新缓存
        • 5.3.2.5 定时任务更新缓存测试
        • 5.3.2.6 禁用区域时删除缓存
  • 6 缓存-实战
    • 6.1 服务类型列表缓存
      • 6.1.1 分析设计
        • 6.1.1.1 界面原型
        • 6.1.1.2 接口定义
      • 6.1.2 服务类型列表实现
        • 6.1.2.1 mapper
        • 6.1.2.2 service
        • 6.1.2.3 controller
        • 6.1.2.4 测试
      • 6.1.3 服务类型列表缓存
        • 6.1.3.1 缓存方案分析
        • 6.1.3.2 查询缓存
        • 6.1.3.3 查询缓存测试
        • 6.1.3.4 定时任务更新缓存
        • 6.1.3.5 禁用区域时删除缓存
    • 6.2 热门服务列表
      • 6.2.1 分析设计
        • 6.2.1.1 界面原型
        • 6.2.1.2 接口设计
      • 6.2.2 热门服务列表实现
        • 6.2.2.1 mapper
        • 6.2.2.2 service
        • 6.2.2.3 controller
        • 6.2.2.4 测试
      • 6.2.3 热门服务列表缓存
        • 6.2.3.1 缓存方案分析
        • 6.2.3.2 查询缓存
        • 6.2.3.3 查询缓存测试
        • 6.2.3.4 定时任务更新缓存
        • 6.2.3.5 禁用区域时删除缓存
    • 6.3 服务详情
      • 6.3.1 分析设计
        • 6.3.1.1 界面原型
        • 6.3.1.2 接口设计
      • 6.3.2 服务详情实现
        • 6.3.2.1 mapper
        • 6.3.2.2 service
        • 6.3.2.3 controller
        • 6.3.2.4 测试
      • 6.3.3 服务详情缓存
        • 6.2.3.1 缓存方案分析
        • 6.2.3.2 缓存信息
          • 1) 缓存服务信息
          • 2) 缓存服务项信息


5 缓存实现

5.1在day03-1

5.2 定时任务更新缓存

5.2.1 分布式调度平台

5.2.1.1 jdk提供的Timer定时器

示例代码如下:

每个Timer对应一个线程,可以同时启动多个Timer定时执行多个任务。

public static void main(String[] args){  
    Timer timer = new Timer();  
    timer.schedule(new TimerTask(){
        @Override  
        public void run() {  
           //TODO:something
        }  
    }, 1000, 2000);  //1秒后开始调度,每2秒执行一次
}

Time使用简单,可以实现每隔一定的时间去执行任务,但无法实现每天凌晨去执行任务,即在某个时间点去执行任务。

5.2.1.2 使用第三方Quartz方式

Quartz 是一个功能强大的任务调度框架(项目地址:https://github.com/quartz-scheduler/quartz ),它可以满足更多更复杂的调度需求,Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。Quartz支持简单的按时间间隔调度、还支持按日历调度方式,通过设置CronTrigger表达式(包括:秒、分、时、日、月、周、年)进行任务调度。

虽然Quartz可以实现按日历调度的方式,但无法支持分布式环境下任务调度。分布式环境下通常一个服务部署多个实例即多个jvm进程,假设运营基础服务部署两个实例每个实例定时执行更新缓存的任务,两个实例就会重复执行。如下图:

在这里插入图片描述

5.2.1.3 使用分布式调度平台XXL-JOB

在分布式环境下进行任务调度需要使用分布式任务调度平台,XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

官网:https://www.xuxueli.com/xxl-job/

文档:https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B9%B3%E5%8F%B0XXL-JOB%E3%80%8B

5.2.2 XXL-JOB

5.2.2.1 介绍

XXL-JOB主要有调度中心、执行器、任务:

在这里插入图片描述

调度中心:

​ 负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码;

​ 主要职责为执行器管理、任务管理、监控运维、日志管理等

任务执行器:

​ 负责接收调度请求并执行任务逻辑;

​ 主要职责是执行任务、执行结果上报、日志服务等

使用XXL-JOB就可以解决使用多个jvm进程重复执行任务的问题,如下图:

在这里插入图片描述

XXL-JOB调度中心可以配置路由策略,比如:第一个、轮询策略、分片等,它们分别表示的意义如下:

第一个:即每次执行任务都由第一个执行器去执行。

轮询:即执行器轮番执行。

分片:每次执行任务广播给每个执行器让他们同时执行任务。

如果根据需求每次执行任务仅由一个执行器去执行任务可以设置路由策略:第一个、轮询。

如果根据需求每次执行任务由多个执行器同时执行可以设置路由策略为:分片。

5.2.2.2 部署调度中心

1.查阅xxl-job的源码

首先下载XXL-JOB

GitHub:https://github.com/xuxueli/xxl-job

码云:https://gitee.com/xuxueli0323/xxl-job

项目使用2.3.1版本: https://github.com/xuxueli/xxl-job/releases/tag/2.3.1

也可从课程资料目录获取,解压xxl-job-2.3.1.zip

使用IDEA打开解压后的目录

在这里插入图片描述

xxl-job-admin:调度中心

xxl-job-core:公共依赖

xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用)

​ :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;

​ :xxl-job-executor-sample-frameless:无框架版本;

doc :文档资料,包含数据库脚本

在下发的虚拟机的MySQL中已经创建了xxl_job_2.3.1数据库

如下图:

在这里插入图片描述

2.启动xxl-job

执行

docker start xxl-job-admin

启动xxl-job

访问:http://192.168.101.68:8088/xxl-job-admin/

账号和密码:admin/123456

在这里插入图片描述

5.2.2.3 执行器

1.添加执行器依赖

下边配置执行器,执行器负责与调度中心通信接收调度中心发起的任务调度请求,执行器负责执行微服务中定义的任务,执行器程序由xxl-job提供,在微服务中引入下边的依赖即加入了执行器的程序:

<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
</dependency>

本项目在framework中定义了jzo2o-xxl-job工程,它对执行器bean执行了定义:

在这里插入图片描述

所以在需要使用xxl-job的微服务中需要引入下边的依赖,在jzo2o-foundations服务中引入下边的依赖:

<dependency>
    <groupId>com.jzo2o</groupId>
    <artifactId>jzo2o-xxl-job</artifactId>
</dependency>

2.配置xxl-job

接下来进入nacos,配置shared-xxl-job.yaml:

在这里插入图片描述

说明:

address:调度中心的地址

appName:执行器名称,为spring.application.name表示微服务的名称(在bootstrap.yml中配置)

port:执行器端口号,通过xxl-job.port配置

在jzo2o-foundations.yaml中配置执行器的端口:

在这里插入图片描述

在jzo2o-foundations中加载shared-xxl-job.yaml:

在这里插入图片描述

3. 下边进入调度中心添加执行器

进入调度中心,进入执行器管理界面,如下图:

在这里插入图片描述

AppName:执行名称,在shared-xxl-job.yaml中指定执行器名称就是微服务的应用名。

名称:取一个中文名称。

注册方式:自动注册,只要执行器和调度中心连通执行器会自动注册到调度中心

机器地址:自动注册时不用填写。

找到应用名:jzo2o-foundations,如下图:

在这里插入图片描述

启动jzo2o-foundations,查看jzo2o-foundations的控制台:

>>>>>>>>>>> xxl-job remoting server start success, nettype = class com.xxl.job.core.server.EmbedServer, port = 11603 说明执行器启动成功。

稍等片刻进入 xxl-job调度中心,进入执行器管理界面,执行器注册成功:

在这里插入图片描述

在这里插入图片描述

点击“查看(1)”,查看执行器的地址,如下图:

在这里插入图片描述

5.2.2 定义缓存更新任务

根据本节的目标,使用xxl-job定时更新开通区域列表的缓存。

5.2.2.1 编写任务方法

定时执行任务就需要编写任务方法,此任务方法由执行器去调用。

可以参考xxl-job源码去编写任务方法,从源码目录中找到执行器示例代码:

xxl-job-2.3.1\xxl-job-executor-samples\xxl-job-executor-sample-springboot\src\main\java\com\xxl\job\executor\service\jobhandler\SampleXxlJob.java

部分示例代码如下,下边代码中demoJobHandler()就是一个任务方法,需要使用@XxlJob注解标识,所在类需要由spring去管理,所以加了@Component注解。

@Component
public class SampleXxlJob {
    private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);


    /**
     * 1、简单任务示例(Bean模式)
     */
    @XxlJob("demoJobHandler")
    public void demoJobHandler() throws Exception {
        XxlJobHelper.log("XXL-JOB, Hello World.");

        for (int i = 0; i < 5; i++) {
            XxlJobHelper.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        // default success
    }
    ....

参考上边的代码我们编写更新开通区域列表缓存的任务方法:

先删除开通区域的缓存,再查询开通区域列表进行缓存。

创建handler包,创建com.jzo2o.foundations.handler.SpringCacheSynHandler类

@Component
@Slf4j
public class SpringCacheSynHandler {

    @Resource
    private IRegionService regionService;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 已启用区域缓存更新
     * 每日凌晨1点执行
     */
    @XxlJob(value = "activeRegionCacheSync")
    public void activeRegionCacheSync() {
        log.info(">>>>>>>>开始进行缓存同步,更新已启用区域");
        //1.清理缓存
        String key = RedisConstants.CacheName.JZ_CACHE + "::ACTIVE_REGIONS";
        redisTemplate.delete(key);

        //2.刷新缓存
        regionService.queryActiveRegionListCache();
        log.info(">>>>>>>>更新已启用区域完成");
    }
}
5.2.2.2 配置任务

下边在调度中心配置任务。

进入任务管理,新增任务:

在这里插入图片描述

填写任务信息:

在这里插入图片描述
在这里插入图片描述

说明:

调度类型

在这里插入图片描述

固定速度指按固定的间隔定时调度。

Cron,通过Cron表达式实现更丰富的定时调度策略。

Cron表达式是一个字符串,通过它可以定义调度策略,格式如下:

{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}

xxl-job提供图形界面去配置:

在这里插入图片描述

一些例子如下:

0 0 0 * * ? 每天0点触发

30 10 1 * * ? 每天1点10分30秒触发

0/30 * * * * ? 每30秒触发一次

* 0/10 * * * ? 每10分钟触发一次

为了方便测试这里第5秒执行一次,设置为:0/5 * * * * ?

运行模式有BEAN和GLUE,bean模式较常用就是在项目工程中编写执行器的任务代码,GLUE是将任务代码编写在调度中心。

JobHandler即任务方法名,填写任务方法上边@XxlJob注解中的名称。

路由策略

第一个:即每次执行任务都由第一个执行器去执行。

轮询:即执行器轮番执行。

分片:每次执行任务广播给每个执行器让他们同时执行任务。

详细说明xxl-job源码中的doc目录下的文档:

在这里插入图片描述

5.2.2.3 启动任务并测试

任务配置完成,下边启动任务

在这里插入图片描述

重启foundations微服务,查看控制台

在这里插入图片描述

说明我们的xxl-job调用成功

5.3 首页服务列表实现

我们先实现从数据库查询"首页服务列表",将整体功能调试通过,再实现查询缓存。

下边我们先实现从数据库查询服务类型列表。

5.3.1 首页服务列表实现

5.3.1.1 需求分析

1.界面原型

首页服务列表在门户的中心位置,下图红框中为首页服务列表区域:

在这里插入图片描述

默认展示前两个服务分类(按后台设置的排序字段进行升序排序),每个服务分类下取前4个服务项(按后台设置的排序字段进行升序排序)如下图:

在这里插入图片描述

一级服务分类显示的内容:服务分类的图标,服务分类的名称。

服务分类下的服务项内容:服务项图标,服务项名称。

5.3.1.2 接口定义

定义首页服务列表接口,查询2个一级服务分类,每个服务分类下查询4个服务项。

接口名称:首页服务列表

接口路径:GET/foundations/customer/serve/firstPageServeList

在这里插入图片描述

在这里插入图片描述

定义controller方法:

门户信息查询类接口统一在FirstPageServeController类中定义,service统一写在HomeService下。

创建com.jzo2o.foundations.controller.consumer.FirstPageServeController类提供门户界面查询类接口。

@RestController("consumerServeController")
@RequestMapping("/customer/serve")
@Api(tags = "用户端 - 首页服务查询接口")
public class FirstPageServeController {

    @GetMapping("/firstPageServeList")
    @ApiOperation("首页服务列表")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "regionId", value = "区域id", required = true, dataTypeClass = Long.class)
    })
    public List<ServeCategoryResDTO> serveCategory(@RequestParam("regionId") Long regionId) {
        return null;
    }
}
5.3.1.3 mapper

数据来源于三张表:serve_type、serve_item、serve,区域id是非常重要的限制条件,因为要查询该区域显示在首页的服务列表。

如何查询数据?

可以先查询出服务类型,再根据服务类型id查询下边的服务项,伪代码如下:

//查询服务类型
List<ServeType> serveTypeList =...
for(ServeType serveType: serveTypeList){
      Long serveTypeId = serveType.getId();
     //再根据服务类型id查询下属的服务项信息

}

上边的代码会导致1+n次查询数据库,这种代码要避免。

我们可以一次将符合条件的数据查询出来,再通过java程序对数据进行处理,再封装为接口要求的数据格式,最后返回给前端。

接口

/**
 * 根据区域查询服务图标分类
 * @param regionId
 * @return
 */
List<ServeCategoryResDTO> findServeIconCategoryByRegionId(@Param("regionId") Long regionId);

下边编写mapper的xml映射文件

<select id="findServeIconCategoryByRegionId" resultType="com.jzo2o.foundations.model.dto.response.ServeCategoryResDTO">
    SELECT
        type.id as serve_type_id,
        type.name as serve_type_name,
        type.serve_type_icon,
        serve.city_code,
        serve.id as serve_id,
        item.id as serve_item_id,
        item.name as serve_item_name,
        item.serve_item_icon,
        item.sort_num as serve_item_sort_num
    FROM
        serve
            inner JOIN serve_item AS item ON item.id = serve.serve_item_id
            inner JOIN serve_type AS type ON type.id = item.serve_type_id
    WHERE
        serve.region_id = #{regionId}
      AND serve.sale_status = 2
    ORDER BY
        type.sort_num,
        item.sort_num
</select>

如果我们使用

<select id="findServeIconCategoryByRegionId"
        resultType="com.jzo2o.foundations.model.dto.response.ServeCategoryResDTO">

那返回的时全部的list,而不是只包含item.id as

serve_item_id,
item.name as serve_item_name,
item.serve_item_icon,
item.sort_num as serve_item_sort_num

的List,所以我们需要进行手动映射,所以结果我们需要从 resultType变为 `resultMap,并且在下面进行手动映射。

<select id="findServeIconCategoryByRegionId" resultMap="ServeCategoryMap">
<!--手动的映射-->
<resultMap id="ServeCategoryMap" type="com.jzo2o.foundations.model.dto.response.ServeCategoryResDTO">
    <!--id映射主键字段-->
    <id column="serve_type_id" property="serveTypeId"></id>
    <!--result映射普通字段-->
    <result column="serve_type_name" property="serveTypeName"></result>
    <result column="serve_type_icon" property="serveTypeIcon"></result>
    <result column="city_code" property="cityCode"></result>

    <!--column 数据库中的字段名-->
    <!--property 实体类中对应的属性 该关键字可以省略... -->
    <!--ofType 是javaType中的单个对象类型-->
    <collection property="serveResDTOList" ofType="com.jzo2o.foundations.model.dto.response.ServeSimpleResDTO">
        <id column="serve_id" property="id"></id>
        <result column="serve_item_id" property="serveItemId"></result>
        <result column="serve_item_name" property="serveItemName"></result>
        <result column="serve_item_icon" property="serveItemIcon"></result>
        <result column="serve_item_sort_num" property="serveItemSortNum"></result>
    </collection>
</resultMap>
5.3.1.4 service

创建com.jzo2o.foundations.service.HomeService

定义专门用于门户首页查询的service接口,用于实现查询缓存:

public interface HomeService {
    /**
     * 根据区域id获取服务图标信息
     *
     * @param regionId 区域id
     * @return 服务图标列表
     */
    List<ServeCategoryResDTO> queryServeIconCategoryByRegionIdCache(Long regionId);
}

实现,创建com.jzo2o.foundations.service.impl.HomeServiceImpl

@Slf4j
@Service
public class HomeServiceImpl implements HomeService {
    @Resource
    private ServeMapper serveMapper;
    @Resource
    private IRegionService regionService;
    @Override
    public List<ServeCategoryResDTO> queryServeIconCategoryByRegionIdCache(Long regionId) {
        //1.校验当前城市是否为启用状态
        Region region = regionService.getById(regionId);
        if (ObjectUtil.isEmpty(region) || ObjectUtil.equal(FoundationStatusEnum.DISABLE.getStatus(), region.getActiveStatus())) {
            return Collections.emptyList();
        }
        //2.根据城市编码查询所有的服务图标
        List<ServeCategoryResDTO> list = serveMapper.findServeIconCategoryByRegionId(regionId);
        if (ObjectUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        //3.服务类型取前两个,每个类型下服务项取前4个
        //list的截止下标
        int endIndex = list.size() >= 2 ? 2 : list.size();
        List<ServeCategoryResDTO> serveCategoryResDTOS = new ArrayList<>(list.subList(0, endIndex));
        serveCategoryResDTOS.forEach(v -> {
            List<ServeSimpleResDTO> serveResDTOList = v.getServeResDTOList();
            //serveResDTOList的截止下标
            int endIndex2 = serveResDTOList.size() >= 4 ? 4 : serveResDTOList.size();
            List<ServeSimpleResDTO> serveSimpleResDTOS = new ArrayList<>(serveResDTOList.subList(0, endIndex2));
            v.setServeResDTOList(serveSimpleResDTOS);
        });
        return serveCategoryResDTOS;
    }
}
5.3.1.5 controller

在controller中调用service查询首页服务列表。

@Validated
@RestController("consumerServeController")
@RequestMapping("/customer/serve")
@Api(tags = "用户端 - 首页服务查询接口")
public class FirstPageServeController {

    @Resource
    private HomeService homeService;

    @GetMapping("/firstPageServeList")
    @ApiOperation("首页服务列表")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "regionId", value = "区域id", required = true, dataTypeClass = Long.class)
    })
    public List<ServeCategoryResDTO> serveCategory(@RequestParam("regionId") Long regionId) {
        List<ServeCategoryResDTO> serveCategoryResDTOS = homeService.queryServeIconCategoryByRegionIdCache(regionId);
        return serveCategoryResDTOS;
    }
}
5.3.1.6 测试

重启jzo2o-foundations服务、网关服务,打开小程序。

切换到北京地区,观察小程序的访问记录

在这里插入图片描述

成功查询到

5.3.2 首页服务列表缓存

5.3.2.1 缓存方案分析

下边是门户的缓存设计:

信息内容类型缓存过期时间缓存结构缓存key缓存同步方案
开通区域列表永久缓存StringJZ_CACHE::ACTIVE_REGIONS查询缓存:查询开通区域列表进行缓存 启用区域:删除开通区域缓存 禁用区域:删除开通区域及其它信息 由定时任务每天凌晨更新缓存
首页服务列表永久缓存StringJZ_CACHE:SERVE_ICON::区域id查询缓存:初次查询进行缓存 禁用区域:删除本区域的首页服务列表缓存 由定时任务每天凌晨更新缓存
服务类型列表永久缓存StringJZ_CACHE:SERVE_TYPE::区域id查询缓存:初次查询直接缓存 禁用区域:删除本区域的服务类型列表缓存 由定时任务每天凌晨更新缓存
热门服务列表永久缓存StringJZ_CACHE:HOT_SERVE::区域id查询缓存:初次查询直接缓存 禁用区域:删除本区域的热门服务列表缓存 由定时任务每天凌晨更新缓存
服务项信息缓存1天StringJZ_CACHE:SERVE_ITEM::服务项id启动:添加缓存 禁用:删除缓存 修改: 修改缓存
服务信息缓存1天StringJZ_CACHE:SERVE_RECORD::服务id上架:添加缓存 下架:删除缓存 修改: 修改缓存

下边分析首页服务列表的缓存方案:

查询缓存:查询首页服务列表,如果缓存没有则查询数据库并缓存,如果缓存有则直接返回

注意:缓存时需要考虑缓存穿透问题。

禁用区域:删除首页服务列表缓存

定时任务:每天凌晨缓存首页服务列表。

5.3.2.2 查询缓存

下边在首页服务列表查询方法上添加Spring Cache注解实现查询缓存。

为了避免缓存穿透,如果服务列表为空则向redis缓存空值,缓存时间为30分钟;不为空则进行永久缓存。

在Cacheable注解中有两个属性可以指定条件进行缓存:

condition:指定一个 SpEL 表达式,用于决定是否要进行缓存。只有当条件表达式的结果为 true 时,方法的返回值才会被缓存。例如:

@Cacheable(value = "myCache", condition = "#id != null")

unless:与 condition 相反,只有当 SpEL 表达式的结果为 false 时,方法的返回值才会被缓存。

例如:

@Cacheable(value = "myCache", unless = "#result.length() > 100")

#result 表示方法的返回值,如果返回值结果集的长度大于100不进行缓存。

根据需求,我们需要根据方法的返回值去判断,如果结果集的长度大于0说明服务列表不空,此时缓存时间为永久缓存,否则缓存时间为30分钟。

condition不支持获取方法返回的值,不能识别#result。

我们使用unless实现。

unless 的特点是符合条件的不缓存。设置技巧:确定要缓存的条件,取反即不缓存的条件。

当方法返回的List的size为0时缓存30分钟,避免缓存穿透,设置为:#result.size() != 0

当方法返回的List的size大于0永不过期,设置为:#result.size() == 0

代码如下:

@Caching(
        cacheable = {
                //result为null时,属于缓存穿透情况,缓存时间30分钟
                @Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key = "#regionId", unless = "#result.size() != 0", cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES),
                //result不为null时,永久缓存
                @Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key = "#regionId", unless = "#result.size() == 0", cacheManager = RedisConstants.CacheManager.FOREVER)
        }
)
@Override
public List<ServeCategoryResDTO> queryServeIconCategoryByRegionIdCache(Long regionId) {
    //1.校验当前城市是否为启用状态
    Region region = regionService.getById(regionId);
    if (ObjectUtil.isEmpty(region) || ObjectUtil.equal(FoundationStatusEnum.DISABLE.getStatus(), region.getActiveStatus())) {
        return Collections.emptyList();
    }
5.3.2.3 查询缓存测试

下边进行测试:

启动:jzo2o-foundations服务、网关服务,打开小程序,等待首页服务列表正常显示,进入redis查看首页服务列表是否缓存。

预期结果:首页服务列表正常缓存。

在这里插入图片描述

5.3.2.4 定时任务更新缓存

根据缓存方案的分析,对首页服务列表进行缓存。应该拿到所有的开通城市列表然后更新一遍。

编写定时任务代码:

@Component
@Slf4j
public class SpringCacheSynHandler {

    @Resource
    private IRegionService regionService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private HomeService homeService;
    /**
     * 已启用区域缓存更新
     * 每日凌晨1点执行
     */
    @XxlJob("activeRegionCacheSync")
    public void activeRegionCacheSync() throws Exception {
        log.info(">>>>>>>>开始进行缓存同步,更新已启用区域");

        //删除缓存
        Boolean delete = redisTemplate.delete(RedisConstants.CacheName.JZ_CACHE + "::ACTIVE_REGIONS");

        //通过查询开通区域列表进行缓存
        List<RegionSimpleResDTO> regionSimpleResDTOS = regionService.queryActiveRegionListCache();

        //遍历区域对该区域下的服务类型进行缓存
        regionSimpleResDTOS.forEach(item->{
            //区域id
            Long regionId = item.getId();

            //删除该区域下的首页服务列表
            String serve_type_key = RedisConstants.CacheName.SERVE_ICON + "::" + regionId;
            redisTemplate.delete(serve_type_key);
            homeService.queryServeIconCategoryByRegionIdCache(regionId);
            //todo 删除该区域下的服务类型列表缓存
        });
    }
}
5.3.2.5 定时任务更新缓存测试

下边测试定时任务更新缓存:

先将首页服务列表的缓存手动删除。

重启foundations服务,在上边代码中打断点,保证定时任务成功执行。

预期结果:对每个运营区域的首页服务列表进行缓存。

我们开放上海和郑州

在这里插入图片描述

查看缓存

在这里插入图片描述

4 编码规范

门户信息查询类接口统一在FirstPageServeController类中定义,service统一写在HomeService下。

首先实现业务接口的功能,测试通过后再去实现缓存。

5.3.2.6 禁用区域时删除缓存

找到禁用区域代码 ,添加删除首页服务列表缓存的代码,如下:

@Override
@Caching(evict = {
        @CacheEvict(value = RedisConstants.CacheName.JZ_CACHE, key = "'ACTIVE_REGIONS'"),
        @CacheEvict(value = RedisConstants.CacheName.SERVE_ICON, key = "#id")
})
public void deactivate(Long id) {
    //区域信息
    Region region = baseMapper.selectById(id);
    //启用状态
    Integer activeStatus = region.getActiveStatus();

6 缓存-实战

6.1 服务类型列表缓存

6.1.1 分析设计

6.1.1.1 界面原型

点击首页服务列表的服务分类或直接点击“全部服务”进入全部服务界面。

在这里插入图片描述

“全部服务” 界面如下图:

在全部服务界面需要展示当前区域下的服务分类,点击服务分类查询分类下的服务。

在这里插入图片描述

服务类型列表数据包括:服务类型的名称、服务类型id。

注意:只实现上图左侧显示的服务分类列表

6.1.1.2 接口定义

服务类型列表查询接口传入参数为:区域id,输出数据为服务类型列表,列表中元素包括:服务类型的名称、服务类型id,为了可扩展,列表元素包括服务类型的图标、排序字段方便进行展示。

接口名称:服务分类列表

接口路径:GET/foundations/customer/serve/serveTypeList

在这里插入图片描述
在这里插入图片描述

6.1.2 服务类型列表实现

响应实体类

@Data
@ApiModel("服务类型列表响应值")
public class ServeTypeListDto {
    /**
     * 主键
     */
    @ApiModelProperty("主键")
    private Long serveTypeId;

    /**
     * 服务类型图片
     */
    @ApiModelProperty("服务类型图片")
    private String serveTypeImg;

    /**
     * 服务类型名称
     */
    @ApiModelProperty("服务类型名称")
    private String serveTypeName;

    /**
     * 服务类型排序字段
     */
    @ApiModelProperty("排序字段")
    private Integer serveTypeSortNum;
}
6.1.2.1 mapper

在com.jzo2o.foundations.mapper.ServeTypeMapper接口中

/**
 * 根据区域id查询服务类型
 * @param regionId
 * @return
 */
List<ServeTypeListDto> findServeTypeByRegionId(Long regionId);

xml

<select id="findServeTypeByRegionId" resultType="com.jzo2o.foundations.model.dto.response.ServeTypeListDto">
    SELECT DISTINCT
        type.id AS serve_type_id,
        type.NAME AS serve_type_name,
        type.img serve_type_img,
        type.sort_num serve_type_sort_num
    FROM
        serve
            INNER JOIN serve_item AS item ON item.id = serve.serve_item_id
            INNER JOIN serve_type AS type ON type.id = item.serve_type_id
    WHERE
        serve.region_id = #{regionId}
      AND serve.sale_status = 2
    ORDER BY
	serve_type_sort_num ASC
</select>
6.1.2.2 service

在com.jzo2o.foundations.service.HomeService接口中

List<ServeTypeListDto> queryServeTypeByRegionIdCache(Long regionId);

实现

@Override
public List<ServeTypeListDto> queryServeTypeByRegionIdCache(Long regionId) {
    //1.校验当前城市是否为启用状态
    Region region = regionService.getById(regionId);
    if (ObjectUtil.isEmpty(region) || ObjectUtil.equal(FoundationStatusEnum.DISABLE.getStatus(), region.getActiveStatus())) {
        return Collections.emptyList();
    }
    //2.根据城市编码查询所有的服务类型
    List<ServeTypeListDto> list = serveTypeMapper.findServeTypeByRegionId(regionId);
    if (ObjectUtil.isEmpty(list)) {
        return Collections.emptyList();
    }
    return list;
}
6.1.2.3 controller
@GetMapping("/serveTypeList")
@ApiOperation("服务类型列表")
@ApiImplicitParams({
        @ApiImplicitParam(name = "regionId", value = "区域id", required = true, dataTypeClass = Long.class)
})
public List<ServeTypeListDto> serveTypeList(@RequestParam("regionId") Long regionId) {
    List<ServeTypeListDto> serveTypeList = homeService.queryServeTypeByRegionIdCache(regionId);
    return serveTypeList;
}
6.1.2.4 测试

在这里插入图片描述

6.1.3 服务类型列表缓存

6.1.3.1 缓存方案分析

下边分析服务类型列表的缓存方案:

查询缓存:查询服务类型列表,如果缓存没有则查询数据库并缓存,如果缓存有则直接返回

禁用区域:删除服务类型列表缓存

定时任务:每天凌晨缓存服务类型列表。

服务类型列表缓存还需要完善的点:

定时任务更新缓存。

禁用区域时删除服务类型列表缓存。

6.1.3.2 查询缓存
@Caching(
        cacheable = {
                //result为null时,属于缓存穿透情况,缓存时间30分钟
                @Cacheable(value = RedisConstants.CacheName.SERVE_TYPE, key = "#regionId", unless = "#result.size() != 0", cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES),
                //result不为null时,永久缓存
                @Cacheable(value = RedisConstants.CacheName.SERVE_TYPE, key = "#regionId", unless = "#result.size() == 0", cacheManager = RedisConstants.CacheManager.FOREVER)
        }
)
@Override
public List<ServeTypeResDTO> queryServeTypeByRegionIdCache(Long regionId) {
    //1.校验当前城市是否为启用状态
    Region region = regionService.getById(regionId);
    if (ObjectUtil.isEmpty(region) || ObjectUtil.equal(FoundationStatusEnum.DISABLE.getStatus(), region.getActiveStatus())) {
        return Collections.emptyList();
    }
    //2.根据城市编码查询所有的服务类型
    List<ServeTypeResDTO> list = serveTypeMapper.findServeTypeByRegionId(regionId);
    if (ObjectUtil.isEmpty(list)) {
        return Collections.emptyList();
    }
    return list;
}
6.1.3.3 查询缓存测试

在这里插入图片描述

6.1.3.4 定时任务更新缓存

在com.jzo2o.foundations.handler.SpringCacheSynHandler中

@Component
@Slf4j
public class SpringCacheSynHandler {

    @Resource
    private IRegionService regionService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private HomeService homeService;
    /**
     * 已启用区域缓存更新
     * 每日凌晨1点执行
     */
    @XxlJob("activeRegionCacheSync")
    public void activeRegionCacheSync() throws Exception {
        log.info(">>>>>>>>开始进行缓存同步,更新已启用区域");

        //删除缓存
        Boolean delete = redisTemplate.delete(RedisConstants.CacheName.JZ_CACHE + "::ACTIVE_REGIONS");

        //通过查询开通区域列表进行缓存
        List<RegionSimpleResDTO> regionSimpleResDTOS = regionService.queryActiveRegionListCache();

        //遍历区域对该区域下的服务类型进行缓存
        regionSimpleResDTOS.forEach(item->{
            //区域id
            Long regionId = item.getId();

            //删除该区域下的首页服务列表
            String serve_type_key = RedisConstants.CacheName.SERVE_ICON + "::" + regionId;
            redisTemplate.delete(serve_type_key);
            homeService.queryServeIconCategoryByRegionIdCache(regionId);
            //删除该区域下的服务类型列表缓存
            String serve_icon_key = RedisConstants.CacheName.SERVE_TYPE + "::" + regionId;
            redisTemplate.delete(serve_icon_key);
            homeService.queryServeTypeByRegionIdCache(regionId);
        });
    }
}
6.1.3.5 禁用区域时删除缓存
@Override
@Transactional
@Caching(evict = {
        @CacheEvict(value = RedisConstants.CacheName.JZ_CACHE, key = "'ACTIVE_REGIONS'"),
        @CacheEvict(value = RedisConstants.CacheName.SERVE_TYPE, key = "#id")
})
public void deactivate(Long id) {
    //查询服务类型
    ServeType serveType = baseMapper.selectById(id);
    if (ObjectUtil.isNull(serveType)) {
        throw new ForbiddenOperationException("服务类型不存在");

6.2 热门服务列表

6.2.1 分析设计

6.2.1.1 界面原型

在门户有一块区域叫精选推荐,此信息为热门服务列表,如下图:

在这里插入图片描述

热门服务列表是在区域服务管理界面进行设置,如下图:

在这里插入图片描述

“设置热门”表示该服务项在本区域为精选推荐。

“取消热门”表示取消该服务项的精选推荐。

热门服务列表数据素包括:区域服务价格、价格单位、服务项id、服务项名称、服务项图标、服务详图。

信息来源于两张表:serve_item、serve

查询条件:区域id、是否热门(是)、是否上架(是)

按服务修改时间降序排序

6.2.1.2 接口设计

接口名称:首页热门服务列表

接口路径:GET/foundations/customer/serve/hotServeList

在这里插入图片描述

在这里插入图片描述

6.2.2 热门服务列表实现

6.2.2.1 mapper

在com.jzo2o.foundations.mapper.ServeMapper接口中

List<ServeAggregationSimpleResDTO> queryHotServeListByRegionId(@Param("regionId") Long regionId);

xml

<select id="queryHotServeListByRegionId"
        resultType="com.jzo2o.foundations.model.dto.response.ServeAggregationSimpleResDTO">
    SELECT
        serve.city_code,
        item.NAME serve_item_name,
        item.id serve_item_id,
        item.unit,
        item.detail_img,
        serve.price,
        item.img serve_item_img,
        serve.id id
    FROM
        serve
            INNER JOIN serve_item AS item ON item.id = serve.serve_item_id
    WHERE
        serve.region_id = 1686303222843662337
      AND serve.sale_status = 2
      AND serve.is_hot=1
    ORDER BY
        serve.update_time DESC
</select>
6.2.2.2 service

在com.jzo2o.foundations.service.HomeService接口中接口

List<ServeAggregationSimpleResDTO> queryHotServeListByRegionId(Long regionId);

实现

@Override
public List<ServeAggregationSimpleResDTO> queryHotServeListByRegionId(Long regionId) {
    //1.校验当前城市是否为启用状态
    Region region = regionService.getById(regionId);
    if (ObjectUtil.isEmpty(region) || ObjectUtil.equal(FoundationStatusEnum.DISABLE.getStatus(), region.getActiveStatus())) {
        return Collections.emptyList();
    }
    //2.根据城市编码查询所有的热门服务
    List<ServeAggregationSimpleResDTO> list = serveMapper.queryHotServeListByRegionId(regionId);
    if (ObjectUtil.isEmpty(list)) {
        return Collections.emptyList();
    }
    return list;
}
6.2.2.3 controller
@GetMapping("/hotServeList")
@ApiOperation("热门服务列表")
@ApiImplicitParams({
        @ApiImplicitParam(name = "regionId", value = "区域id", required = true, dataTypeClass = Long.class)
})
public List<ServeAggregationSimpleResDTO> hotServeList(@RequestParam("regionId") Long regionId) {
    List<ServeAggregationSimpleResDTO> serveCategoryResDTOS = homeService.queryHotServeListByRegionId(regionId);
    return serveCategoryResDTOS;
}
6.2.2.4 测试

在这里插入图片描述

6.2.3 热门服务列表缓存

6.2.3.1 缓存方案分析

查询缓存:查询热门服务列表,如果缓存没有则查询数据库并缓存,如果缓存有则直接返回

注意:缓存时需要考虑缓存穿透问题。

禁用区域:删除热门服务列表缓存

定时任务:每天凌晨缓存热门服务列表。

6.2.3.2 查询缓存
@Caching(
        cacheable = {
                //result为null时,属于缓存穿透情况,缓存时间30分钟
                @Cacheable(value = RedisConstants.CacheName.HOT_SERVE, key = "#regionId", unless = "#result.size() != 0", cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES),
                //result不为null时,永久缓存
                @Cacheable(value = RedisConstants.CacheName.HOT_SERVE, key = "#regionId", unless = "#result.size() == 0", cacheManager = RedisConstants.CacheManager.FOREVER)
        }
)
@Override
public List<ServeAggregationSimpleResDTO> queryHotServeListByRegionId(Long regionId) {
    //1.校验当前城市是否为启用状态
    Region region = regionService.getById(regionId);
    if (ObjectUtil.isEmpty(region) || ObjectUtil.equal(FoundationStatusEnum.DISABLE.getStatus(), region.getActiveStatus())) {
        return Collections.emptyList();
    }
6.2.3.3 查询缓存测试

在这里插入图片描述

6.2.3.4 定时任务更新缓存

在com.jzo2o.foundations.handler.SpringCacheSynHandler中

//删除该区域下的首页服务列表
String serve_type_key = RedisConstants.CacheName.SERVE_ICON + "::" + regionId;
redisTemplate.delete(serve_type_key);
homeService.queryServeIconCategoryByRegionIdCache(regionId);
//删除该区域下的服务类型列表缓存
String serve_icon_key = RedisConstants.CacheName.SERVE_TYPE + "::" + regionId;
redisTemplate.delete(serve_icon_key);
homeService.queryServeTypeByRegionIdCache(regionId);
//删除该区域下的热门服务列表缓存
String hot_serve_key = RedisConstants.CacheName.HOT_SERVE + "::" + regionId;
redisTemplate.delete(hot_serve_key);
homeService.queryHotServeListByRegionId(regionId);
6.2.3.5 禁用区域时删除缓存
@Override
@Caching(evict = {
        @CacheEvict(value = RedisConstants.CacheName.JZ_CACHE, key = "'ACTIVE_REGIONS'"),
        @CacheEvict(value = RedisConstants.CacheName.SERVE_ICON, key = "#id"),
        @CacheEvict(value = RedisConstants.CacheName.HOT_SERVE, key = "#id"),
})
public void deactivate(Long id) {
    //区域信息
    Region region = baseMapper.selectById(id);

6.3 服务详情

6.3.1 分析设计

6.3.1.1 界面原型

在首页服务列表区域、热门服务列表区域以及全部服务界面,点击服务项名称进入服务详情页面,如下图:

在这里插入图片描述

服务详情页面显示服务相关的信息包括:服务项的名称、服务的运营价格、服务项的图片、服务项名称、价格单位。信息来源于serve_item表和serve表。

6.3.1.2 接口设计

接口名称:根据id查询服务

接口路径:GET/foundations/customer/serve/{id}

请求参数:服务id,即serve表的主键

在这里插入图片描述

在这里插入图片描述

6.3.2 服务详情实现

6.3.2.1 mapper

在com.jzo2o.foundations.mapper.ServeMapper接口中

List<ServeAggregationSimpleResDTO> queryServeDetailById(@Param("id") Long id);

xml

<select id="queryServeDetailById"
        resultType="com.jzo2o.foundations.model.dto.response.ServeAggregationSimpleResDTO">
    SELECT
        serve.city_code,
        item.NAME serve_item_name,
        item.id serve_item_id,
        item.unit,
        item.detail_img,
        serve.price,
        item.img serve_item_img,
        serve.id id
    FROM
        serve
            INNER JOIN serve_item AS item ON item.id = serve.serve_item_id
    WHERE
        serve.id = #{id}
      AND serve.sale_status = 2
</select>
6.3.2.2 service

在com.jzo2o.foundations.service.HomeService接口中接口

List<ServeAggregationSimpleResDTO> queryServeDetail(Long id);

实现

@Override
public List<ServeAggregationSimpleResDTO> queryServeDetail(Long id) {
    return serveMapper.queryServeDetailById(id);
}
6.3.2.3 controller
@GetMapping("/{id}")
@ApiOperation("服务详情")
@ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "服务id", required = true, dataTypeClass = Long.class)
})
public List<ServeAggregationSimpleResDTO> serveDetail(@PathVariable("id") Long id) {
    List<ServeAggregationSimpleResDTO> serveCategoryResDTOS = homeService.queryServeDetail(id);
    return serveCategoryResDTOS;
}
6.3.2.4 测试

在这里插入图片描述

6.3.3 服务详情缓存

6.2.3.1 缓存方案分析

服务详情信息来源于两部分信息:服务项信息、服务信息。

分别对服务项信息、服务信息进行缓存,方案如下:

服务项信息缓存1天StringJZ_CACHE:SERVE_ITEM::服务项id启动:添加缓存 禁用:删除缓存 修改: 修改缓存
服务信息缓存1天StringJZ_CACHE:SERVE_RECORD::服务id上架:添加缓存 下架:删除缓存 修改: 修改缓存
6.2.3.2 缓存信息
1) 缓存服务信息

启动详情,com.jzo2o.foundations.service.impl.HomeServiceImpl#queryServeDetail

@Caching(
        cacheable = {
                //result为null时,属于缓存穿透情况,缓存时间30分钟
                @Cacheable(value = RedisConstants.CacheName.SERVE, key = "#id", unless = "#result.size() != 0", cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES),
                //result不为null时,永久缓存
                @Cacheable(value = RedisConstants.CacheName.SERVE, key = "#id", unless = "#result.size() == 0", cacheManager = RedisConstants.CacheManager.ONE_DAY)
        }
)
@Override
public List<ServeAggregationSimpleResDTO> queryServeDetail(Long id) {
    return serveMapper.queryServeDetailById(id);
}

上架,com.jzo2o.foundations.service.impl.ServeServiceImpl#onSale

@Override
@Transactional
@CachePut(value = RedisConstants.CacheName.SERVE, key = "#id",unless = "#result.saleStatus != 2",  cacheManager = RedisConstants.CacheManager.ONE_DAY)
public Serve onSale(Long id){
    Serve serve = baseMapper.selectById(id);
    if(ObjectUtil.isNull(serve)){
        throw new ForbiddenOperationException("区域服务不存在");

禁用,com.jzo2o.foundations.service.impl.ServeServiceImpl#offSale

@Override
@Transactional
@CacheEvict(value = RedisConstants.CacheName.SERVE, key = "#id")
public Serve offSale(Long id){
    Serve serve = baseMapper.selectById(id);
    if(ObjectUtil.isNull(serve)){
        throw new ForbiddenOperationException("区域服务不存在");

修改,com.jzo2o.foundations.service.impl.ServeServiceImpl#update

@Override
@Transactional
@CacheEvict(value = RedisConstants.CacheName.SERVE, key = "#id")
public Serve update(Long id, BigDecimal price) {
    //1.更新服务价格
    LambdaUpdateWrapper<Serve> updateWrapper = Wrappers.<Serve>lambdaUpdate()
            .eq(Serve::getId, id)
            .set(Serve::getPrice, price);
2) 缓存服务项信息

启用,com.jzo2o.foundations.service.impl.ServeItemServiceImpl#activate

@Override
@Transactional
@CachePut(value = RedisConstants.CacheName.SERVE_ITEM, key = "#id", cacheManager = RedisConstants.CacheManager.ONE_DAY)
public ServeItem activate(Long id) {

    //查询服务项
    ServeItem serveItem = baseMapper.selectById(id);
    if (ObjectUtil.isNull(serveItem)) {
        throw new ForbiddenOperationException("服务项不存在");

禁用,com.jzo2o.foundations.service.impl.ServeItemServiceImpl#deactivate

@Override
@Transactional
@CacheEvict(value = RedisConstants.CacheName.SERVE_ITEM, key = "#id", beforeInvocation = true)
public void deactivate(Long id) {
    //查询服务项
    ServeItem serveItem = baseMapper.selectById(id);
    if (ObjectUtil.isNull(serveItem)) {
        throw new ForbiddenOperationException("服务项不存在");

修改,com.jzo2o.foundations.service.impl.ServeItemServiceImpl#update

@Override
@Transactional
@CachePut(value = RedisConstants.CacheName.SERVE_ITEM, key = "#id", unless = "#result.saleStatus != 2", cacheManager = RedisConstants.CacheManager.ONE_DAY)
public ServeItem update(Long id, ServeItemUpsertReqDTO serveItemUpsertReqDTO) {
    //1.更新服务项
    ServeItem serveItem = BeanUtil.toBean(serveItemUpsertReqDTO, ServeItem.class);
    serveItem.setId(id);
    baseMapper.updateById(serveItem);

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

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

相关文章

laravel版本≥ 8.1

laravel10 php ≥ 8.1 且 ≤ 8.3&#xff1f; 8.1 < php < 8.3PHP版本要求在 8.1 到 8.3 之间&#xff0c;包括这两个版本。具体来说&#xff1a;"≥ 8.1" 表示 PHP 的版本至少是 8.1&#xff0c;也就是说 8.1 及以上的版本都可以。 "≤ 8.3" 表示 P…

2024年【广东省安全员A证第四批(主要负责人)】找解析及广东省安全员A证第四批(主要负责人)模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员A证第四批&#xff08;主要负责人&#xff09;找解析根据新广东省安全员A证第四批&#xff08;主要负责人&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将广东省安全员A证第四批&#xff…

光储充一体化,开启绿色出行新篇章

一、追光逐梦&#xff0c;绿色能源点亮未来 在蔚蓝的天空下&#xff0c;光伏发电板如同一片片金色的叶子&#xff0c;静静地捕捉着太阳的光芒。它们不仅为大地带来光明&#xff0c;更是绿色出行的强大后盾。光储充一体化充电站&#xff0c;以光伏为源&#xff0c;储能为桥&…

CV预测:快速使用LeNet-5卷积神经网络

AI预测相关目录 AI预测流程&#xff0c;包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

Vue 3深度探索:自定义渲染器与服务端渲染

title: Vue 3深度探索&#xff1a;自定义渲染器与服务端渲染 date: 2024/6/14 updated: 2024/6/14 author: cmdragon excerpt: 这篇文章介绍了如何在Vue框架中实现自定义渲染器以增强组件功能&#xff0c;探讨了虚拟DOM的工作原理&#xff0c;以及如何通过SSR和服务端预取数…

【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析

文章目录 并发简史&#x1f5a5;️初期探索&#xff08;20世纪50-60年代&#xff09;并发理论基础&#xff08;1965年以后&#xff09;并行计算的兴起&#xff08;1970年代至1980年代&#xff09;现代并发技术&#xff08;1990年代至今&#xff09; 线程的优势&#x1f60d;发挥…

体验亚马逊AIGC——Amazon Bedrock

前言 随着人工智能技术的不断发展&#xff0c;我们已经进入了一个全新的时代&#xff0c;即AI驱动的时代。在这个时代&#xff0c;人工智能已经逐渐成为我们生活中不可或缺的一部分&#xff0c;它可以帮助我们更好地处理各种复杂的问题&#xff0c;提高我们的工作效率&#xff…

单调队列——Acwing.154滑动窗口

单调队列 定义 单调队列是一个限制只能队尾插入&#xff0c;但是可以两端删除的双端队列。单调队列存储的元素值&#xff0c;是从队首到队尾单调递增或单调递减的。 运用情况 滑动窗口最大值&#xff1a;给定一个整数数组和一个窗口大小&#xff0c;计算窗口内的最大值。任…

vscode 连接 GitHub

文章目录 连接 GitHub一、通过 SSH 连接 github二、通过 HTTPS 连接 github 连接 GitHub 在 vscode 中首次使用 git push 命令时会要求输入 github 账户的 username 和 password&#xff0c;这种基本身份验证在 2021.8.13 以前还是可以的&#xff0c;之后的话&#xff0c;就会…

逆向分析-Ollydbg动态跟踪Ransomware.exe恶意锁机程序

1.认识Ollydbg Ollydbg是一个新的动态追踪工具&#xff0c;将IDA与SoftICE结合起来的思想&#xff0c;Ring 3级调试器&#xff0c;非常容易上手&#xff0c;己代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能&#xff0c;是目前最强大的调试工具。 Oll…

Python开源项目周排行 2024年第9周

#2024年第9周2024年6月3日1buku强大的浏览器书签管理工具。这是一款开源的书签命令行管理工具&#xff0c;它轻量、隐私安全且易于使用&#xff0c;支持从主流浏览器导入书签、自动获取书签信息、跨平台同步和强大的搜索功能。2flagsmith轻松管理功能开关和配置的平台。这是一个…

MJ绘画设计基础——如何玩转midjourney?

抽卡的时候经常有一个问题&#xff0c;就是整张图都还不错&#xff0c;但是某些地方有些小问题&#xff0c;比如说手很奇怪&#xff0c;比如下面这个图&#xff0c;哪都挺好看&#xff0c;就是左手有点问题。 这时候就可以局部重绘来拯救一下 第一次生成的图 点击图片下方的V…

DFS序 欧拉序

【算法分析】 ● DFS 序DFS 序表示从根结点开始对树进行 DFS 所得的结点遍历顺序。 易得上图的 DFS 序为&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&#xff0c;8&#xff0c;9。可见&#xff0c;通过 DFS 序&#xff0c;可…

Nginx+Tomcat负载均衡、动静分离群集方案

一、Tomcat简介 在现代 Web 服务架构中&#xff0c;Tomcat 和 Nginx 是两个至关重要的组件&#xff0c;负责处理用户请求并实现高性能的服务。本篇博客将深入探讨这些技术的原理和部署配置方法。 最初是由Sun的软件构架师詹姆斯邓肯戴维森开发。安装Tomcat后&#xff0c;安装…

最新区块链论文速读--CCF A会议 ICSE 2024 共13篇 附pdf下载 (2/2)

Conference&#xff1a;International Conference on Software Engineering (ICSE) CCF level&#xff1a;CCF A Categories&#xff1a;Software Engineering/System Software/Programming Languages Year&#xff1a;2024 Num&#xff1a;13 第1~7篇区块链文章请点击此处…

后端返回前端时间格式化

时间格式化的方法总共包含以下 5 种。 1.前端时间格式化 JS 版时间格式化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function dateFormat(fmt, date) { let ret; const opt { "Y": date.getFullYear().toString(), // 年 …

[Shell编程学习路线]——探讨Shell中变量的作用范围(export)

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f6e0;️Shell编程专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月14日10点14分 &#x1f004;️文章质量&#xff1a;95分 文章目录 ————前言———— 定义变量&#xff1a; 输出变…

[C][数据结构][排序][下][快速排序][归并排序]详细讲解

文章目录 1.快速排序1.基本思想2.hoare版本3.挖坑法4.前后指针版本5.非递归版本改写 2.归并排序 1.快速排序 1.基本思想 任取待排序元素序列的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右…

3389端口修改工具,3389端口修改工具的操作步骤

3389端口修改器&#xff1a; 这是一个专门用于修改3389端口的工具&#xff0c;可以方便地修改Windows远程桌面服务的端口号 使用注册表编辑器手动修改&#xff1a; 虽然这不是一个专门的工具&#xff0c;但Windows的注册表编辑器也可以用来修改3389端口。用户需要定位到特定的注…