自定义注解实现Redis分布式锁、手动控制事务和根据异常名字或内容限流的三合一的功能

自定义注解实现Redis分布式锁、手动控制事务和根据异常名字或内容限流的三合一的功能

文章目录

    • @[toc]
  • 1.依赖
  • 2.Redisson配置
    • 2.1单机模式配置
    • 2.2主从模式
    • 2.3集群模式
    • 2.4哨兵模式
  • 3.实现
    • 3.1 RedisConfig
    • 3.2 自定义注解IdempotentManualCtrlTransLimiterAnno
    • 3.3自定义切面IdempotentManualCtrlTransAspect
  • 4.测试验证
  • 5.总结

1.依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.3.9.RELEASE</version>
</dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.13.4</version>
</dependency>

2.Redisson配置

2.1单机模式配置

spring:redis:host: localhostport: 6379password: null
redisson:codec: org.redisson.codec.JsonJacksonCodecthreads: 4netty:threads: 4single-server-config:address: "redis://localhost:6379"password: null

2.2主从模式

spring:redis:sentinel:master: my-masternodes: localhost:26379,localhost:26389password: your_password
redisson:master-slave-config:master-address: "redis://localhost:6379"slave-addresses: "redis://localhost:6380,redis://localhost:6381"password: ${spring.redis.password}

2.3集群模式

spring:redis:cluster:nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384password: your_password
redisson:cluster-config:node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"password: ${spring.redis.password}

2.4哨兵模式

spring:redis:sentinel:master: my-masternodes: localhost:26379,localhost:26389password: your_password
redisson:sentinel-config:master-name: my-mastersentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"password: ${spring.redis.password}

Redission的集成还有很多种方式的,所以不局限于这种方式,条条大路通罗马,一万个读者就有一万个哈姆雷特。

3.实现

3.1 RedisConfig

package xxxxx.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Bean@SuppressWarnings("all")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {// 定义泛型为 <String, Object> 的 RedisTemplateRedisTemplate<String, Object> template = new RedisTemplate<String, Object>();// 设置连接工厂template.setConnectionFactory(factory);// 定义 Json 序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);// Json 转换工具ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//方法二:解决jackson2无法反序列化LocalDateTime的问题om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);om.registerModule(new JavaTimeModule());jackson2JsonRedisSerializer.setObjectMapper(om);// 定义 String 序列化StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key采用String的序列化方式template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}@BeanRedisTemplate<String, Long> redisTemplateLimit(RedisConnectionFactory factory) {final RedisTemplate<String, Long> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericToStringSerializer<>(Long.class));template.setValueSerializer(new GenericToStringSerializer<>(Long.class));return template;}}

3.2 自定义注解IdempotentManualCtrlTransLimiterAnno

package xxxxx.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;/*** @author zlf*/
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IdempotentManualCtrlTransLimiterAnno {/*** 是否开启RedissonLock* 1:开启* 0:不开启** @return*/boolean isOpenRedissonLock() default false;/*** 是否开启手动控制事务提交* 1:开启* 0:不开启** @return*/boolean isOpenManualCtrlTrans() default false;/*** 分布式锁key格式:* keyFormat a:b:%s** @return*/String keyFormat() default "";/*** 锁定时间* 默认 3s** @return*/long lockTime() default 3l;/*** 锁定时间单位* TimeUnit.MILLISECONDS 毫秒* TimeUnit.SECONDS 秒* TimeUnit.MINUTES 分* TimeUnit.HOURS 小时* TimeUnit.DAYS 天** @return*/TimeUnit lockTimeUnit() default TimeUnit.SECONDS;/*** 是否开启限流** @return*/boolean isOpenLimit() default false;/*** 限流redis失败次数统计key* public方法第一个string参数就是%s** @return*/String limitRedisKeyPrefix() default "limit:redis:%s";/*** 限流redisKey统计的key的过期时间* 默认10分钟后过期** @return*/long limitRedisKeyExpireTime() default 10l;/*** 锁过期单位* TimeUnit.MILLISECONDS 毫秒* TimeUnit.SECONDS 秒* TimeUnit.MINUTES 分* TimeUnit.HOURS 小时* TimeUnit.DAYS 天** @return*/TimeUnit limitRedisKeyTimeUnit() default TimeUnit.MINUTES;/*** 默认限流策略:* 异常计数器限流:可以根据异常名称和异常内容来计数限制* 根据异常次数,当异常次数达到多少次后,限制访问(异常类型为RuntimeException类型) (实现)* RedisTemplate的配置文件中需要有这个类型的bean** @return* @Bean RedisTemplate<String, Long> redisTemplateLimit(RedisConnectionFactory factory) {* final RedisTemplate<String, Long> template = new RedisTemplate<>();* template.setConnectionFactory(factory);* template.setKeySerializer(new StringRedisSerializer());* template.setHashValueSerializer(new GenericToStringSerializer<>(Long.class));* template.setValueSerializer(new GenericToStringSerializer<>(Long.class));* return template;* }* 滑动窗口限流 (未实现)* 令牌桶 (未实现)* ip限流 (未实现)* Redisson方式限流 (未实现)* <p>* limitTye() 异常计数器限流 可以写Exception的子类* 这个和下面的expContent()互斥,二选一配置即可*/String limitTye() default "";/*** 异常信息内容匹配统计** @return*/String expContent() default "";/*** 异常统计次数上线默认为10次** @return*/int limitMaxErrorCount() default 10;}

3.3自定义切面IdempotentManualCtrlTransAspect

package xxxxxx.annotation;import cn.hutool.core.lang.Tuple;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;@Slf4j
@Aspect
@Component
public class IdempotentManualCtrlTransAspect {@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate DataSourceTransactionManager transactionManager;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate RedisTemplate<String, Long> redisTemplateLimit;private static final List<String> KEY_FORMAT_MATCHS = new ArrayList<>();static {KEY_FORMAT_MATCHS.add("%s");}@Pointcut("@annotation(com.dy.member.annotation.IdempotentManualCtrlTransLimiterAnno)")public void idempotentManualCtrlTransPoint() {}@Around("idempotentManualCtrlTransPoint()")public Object deal(ProceedingJoinPoint pjp) throws Throwable {//当前线程名String threadName = Thread.currentThread().getName();log.info("-------------IdempotentManualCtrlTransLimiterAnno开始执行-----线程{}-----------", threadName);//获取参数列表Object[] objs = pjp.getArgs();String key = null;String redisLimitKey = null;String message = "";IdempotentManualCtrlTransLimiterAnno annotation = null;try {//注解加上的public方法的第一个参数就是key,只支持改参数为String类型key = (String) objs[0];if (Objects.isNull(key)) {return pjp.proceed();}//获取该注解的实例对象annotation = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(IdempotentManualCtrlTransLimiterAnno.class);//是否开启RedissonLockboolean openRedissonLock = annotation.isOpenRedissonLock();boolean openManualCtrlTrans = annotation.isOpenManualCtrlTrans();boolean openLimit = annotation.isOpenLimit();boolean bothFlag = openRedissonLock && openManualCtrlTrans;if (openLimit) {int errorCount = annotation.limitMaxErrorCount();String limitRedisKey = annotation.limitRedisKeyPrefix();redisLimitKey = String.format(limitRedisKey, key);TimeUnit timeUnit = annotation.limitRedisKeyTimeUnit();this.checkFailCount(redisLimitKey, errorCount, timeUnit);if (!openRedissonLock && !openManualCtrlTrans) {return pjp.proceed();}}if (!bothFlag) {if (openRedissonLock) {key = checkKeyFormatMatch(annotation, key);RLock lock = redissonClient.getLock(key);try {Tuple lockAnnoParamsTuple = this.getLockAnnoParams(annotation);long t = lockAnnoParamsTuple.get(0);TimeUnit uint = lockAnnoParamsTuple.get(1);if (lock.tryLock(t, uint)) {return pjp.proceed();}} catch (Exception e) {log.error("-------------IdempotentManualCtrlTransLimiterAnno锁异常ex:{}-----线程{}-----------", ExceptionUtils.getMessage(e), threadName);throw new RuntimeException(ExceptionUtils.getMessage(e));} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();log.info("-------------IdempotentManualCtrlTransLimiterAnno释放锁成功-----线程{}-----------", threadName);}}}if (openManualCtrlTrans) {TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);try {Object proceed = pjp.proceed();transactionManager.commit(transactionStatus);return proceed;} catch (Exception e) {transactionManager.rollback(transactionStatus);log.info("-------------IdempotentManualCtrlTransLimiterAnno执行异常事务回滚1-----线程{}-----------", threadName);throw new RuntimeException(ExceptionUtils.getMessage(e));}}}if (bothFlag) {key = checkKeyFormatMatch(annotation, key);RLock lock = redissonClient.getLock(key);TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);try {Tuple lockAnnoParamsTuple = this.getLockAnnoParams(annotation);long t = lockAnnoParamsTuple.get(0);TimeUnit uint = lockAnnoParamsTuple.get(1);if (lock.tryLock(t, uint)) {Object proceed = pjp.proceed();transactionManager.commit(transactionStatus);return proceed;}} catch (Exception e) {log.error("-------------IdempotentManualCtrlTransLimiterAnno处理异常ex:{}-----线程{}-----------", ExceptionUtils.getMessage(e), threadName);transactionManager.rollback(transactionStatus);log.info("-------------IdempotentManualCtrlTransLimiterAnno执行异常事务回滚2-----线程{}-----------", threadName);throw new RuntimeException(ExceptionUtils.getMessage(e));} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();log.info("-------------IdempotentManualCtrlTransLimiterAnno释放锁成功2-----线程{}-----------", threadName);}}}} catch (Exception e) {message = ExceptionUtils.getMessage(e);String stackTrace = ExceptionUtils.getStackTrace(e);String limitExType = annotation.limitTye();log.error("------------IdempotentManualCtrlTransLimiterAnno-------msg:{},stackTrace:{},limitExType:{}-------", message, stackTrace, limitExType);boolean openLimit = annotation.isOpenLimit();TimeUnit timeUnit = annotation.limitRedisKeyTimeUnit();long limitRedisKeyExpireTime = annotation.limitRedisKeyExpireTime();String expContent = annotation.expContent();if (openLimit) {if (StringUtils.isNotBlank(message) && StringUtils.isNotBlank(expContent) && message.indexOf(expContent) != -1) {log.error("------------IdempotentManualCtrlTransLimiterAnno-------openLimit:{},message:{},expContent:{}-------", openLimit, message, expContent);if (!redisTemplateLimit.hasKey(redisLimitKey)) {redisTemplateLimit.opsForValue().set(redisLimitKey, 1L, limitRedisKeyExpireTime, timeUnit);} else {redisTemplateLimit.opsForValue().increment(redisLimitKey);}} else if (stackTrace.indexOf(limitExType) != -1 && StringUtils.isBlank(expContent)) {log.error("------------IdempotentManualCtrlTransLimiterAnno-------openLimit:{},stackTrace:{},expContent:{}-------", openLimit, stackTrace, limitExType);if (!redisTemplateLimit.hasKey(redisLimitKey)) {redisTemplateLimit.opsForValue().set(redisLimitKey, 1L, limitRedisKeyExpireTime, timeUnit);} else {redisTemplateLimit.opsForValue().increment(redisLimitKey);}} else {if (!redisTemplateLimit.hasKey(redisLimitKey)) {redisTemplateLimit.opsForValue().set(redisLimitKey, 1L, limitRedisKeyExpireTime, timeUnit);} else {redisTemplateLimit.opsForValue().increment(redisLimitKey);}}}log.error("-------------IdempotentManualCtrlTransLimiterAnno开始异常ex:{}-----线程{}-----------", ExceptionUtils.getMessage(e), threadName);}throw new RuntimeException(message.replaceAll("RuntimeException", "").replaceAll("Exception", "").replaceAll(":", "").replaceAll(" ", ""));}private void checkFailCount(String key, long errorCount, TimeUnit timeUnit) {boolean isExistKey = redisTemplateLimit.hasKey(key);if (isExistKey) {Long count = (Long) redisTemplateLimit.opsForValue().get(key);log.info("=========IdempotentManualCtrlTransLimiterAnno=====key:{}=======failCount:{}=========", key, count);if (Objects.nonNull(count) && count > errorCount) {Long expire = redisTemplateLimit.getExpire(key, timeUnit);String unitStr = "";if (timeUnit.equals(TimeUnit.DAYS)) {unitStr = "天";} else if (timeUnit.equals(TimeUnit.HOURS)) {unitStr = "小时";} else if (timeUnit.equals(TimeUnit.MINUTES)) {unitStr = "分钟";} else if (timeUnit.equals(TimeUnit.SECONDS)) {unitStr = "秒钟";} else if (timeUnit.equals(TimeUnit.MILLISECONDS)) {unitStr = "毫秒";}log.error("IdempotentManualCtrlTransLimiterAnno异常次数限制,错误次数:{}", errorCount);throw new RuntimeException("请求异常,请" + expire + unitStr + "后重试!");}}}private Tuple getLockAnnoParams(IdempotentManualCtrlTransLimiterAnno annotation) {long t = annotation.lockTime();TimeUnit unit = annotation.lockTimeUnit();return new Tuple(t, unit);}private String checkKeyFormatMatch(IdempotentManualCtrlTransLimiterAnno annotation, String key) {String keyFormat = annotation.keyFormat();if (StringUtils.isNotBlank(keyFormat)) {if (!KEY_FORMAT_MATCHS.contains(keyFormat)) {throw new RuntimeException("注解key格式匹配有误!");}key = String.format(keyFormat, key);}return key;}}

4.测试验证

在controller层新建TestController1

package com.xxxx.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@Slf4j
@RequestMapping("/test")
public class TestController1 {@Autowiredprivate xxxxRecordService xxxRecordService;@PostMapping("/hellWorld")@IdempotentManualCtrlTransLimiterAnno(isOpenRedissonLock = true, isOpenManualCtrlTrans = true, isOpenLimit = true, limitRedisKeyExpireTime = 10, expContent = "我是异常")public RestResponse hellWorld(@RequestParam String key) {xxxRecord pm = new xxxRecord();pm.setOrderNo(key);xxxRecordService.save(pm);if (key.equals("2")) {//int i = 10;//i = i / 0;throw new RuntimeException("我是异常");}return RestResponse.success(key);}
}

使用postMan请求接口,在自定定义的aspect的类中的deal方法内打上断点就可以调试了:

请添加图片描述

使用这个方法才可以调试,如果使用的是springBoot的单元测试,不会进断点,这个也是奇怪的很,切面表达式可以切一个注解,也可以切指定包下的某些方法,比如可以切所有controller下的xx类的xx方法的xxx参数,这个可以参考网上的教程实现,也可以把SPEL表达式的解析实现进去,key的解析通过SPEL表达式解析配偶到第一个参数中对应的String参数或者是Json参数匹配的字段上,这种灵活性就好一点了,本文的这个约定的也是好使的,一点也不影响,复杂度没有那么高,本文约定的是公共方法的第一个String的参数为key,可以读上面的代码实现,都有注释说明的。

5.总结

在日常开发当中,进场会遇到这三个场景,所以需要写一些重复性的代码,用一次写一次,CV哥就是这样养成的,所以经过我的思考,突发了这个灵感,搞个注解全部搞定,这个多方便,要那个功能开哪个功能,而且还可以组合使用,优雅永不过时,本次分享就到这里,希望我的分享对你有所帮助,请一键三连,么么么哒!

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

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

相关文章

问题记录 springboot 事务方法中使用this调用其它方法

原因: 因为代理对象中调用了原始对象的toString()方法,所以两个不同的对象打印出的引用是相同的

快速将iPhone大量照片快速传输到电脑的办法!

很多使用iPhone 的朋友要将照片传到电脑时&#xff0c;第一时间都只想到用iTunes 或iCloud&#xff0c;但这2个工具真的都非常难用&#xff0c;今天小编分享牛学长苹果数据管理工具的照片传输功能&#xff0c;他可以快速的将iPhone照片传输到电脑上&#xff0c;并且支持最新的i…

OpenCV实现模板匹配和霍夫线检测,霍夫圆检测

一&#xff0c;模板匹配 1.1代码实现 import cv2 as cv import numpy as np import matplotlib.pyplot as plt from pylab import mplmpl.rcParams[font.sans-serif] [SimHei]#图像和模板的读取 img cv.imread("cat.png") template cv.imread(r"E:\All_in\o…

配置OSPF路由

OSPF路由 1.OSPF路由 1.1 OSPF简介 OSPF(Open Shortest Path First&#xff0c;开放式最短路径优先&#xff09;路由协议是另一个比较常用的路由协议之一&#xff0c;它通过路由器之间通告网络接口的状态&#xff0c;使用最短路径算法建立路由表。在生成路由表时&#xff0c;…

亚马逊无线鼠标FCC认证办理 FCC ID

无线鼠标是指无线缆直接连接到主机的鼠标&#xff0c;采用无线技术与计算机通信&#xff0c;从而省却电线的束缚。通常采用无线通信方式&#xff0c;包括蓝牙、Wi-Fi (IEEE 802.11)、Infrared (IrDA)、ZigBee (IEEE 802.15.4)等多个无线技术标准。随着人们对办公环境和操作便捷…

Vue中动态树形菜单,以及

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专栏…

Leetcode205. 同构字符串

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0…

CTP:关于cc和bindgen库及rust工程组织

有三个工程目录&#xff0c;cpt-api, ctp-sdk,ctp-strategy 1、ctp-sdk&#xff1a; 主要的目的是基于bindgen库生成与cpp的.h文件相对应一个binding.rs文件&#xff0c;后面供策略使用。 在这个目录下&#xff0c;建一个build.rs,用bindgen库生成cpp.h的头文件相应的rust绑定…

蓝桥杯每日一题2023.9.29

蓝桥杯大赛历届真题 - C&C 大学 B 组 - 蓝桥云课 (lanqiao.cn) 题目描述1 题目分析 看见有32位&#xff0c;我们以此为入手点&#xff0c; B代表字节1B 8b b代表位&#xff0c;32位即4个字节 (B) 1KB 1024B 1MB 1024KB (256 * 1024 * 1024) / 4 67108864 故答案…

redis主从从,redis-7.0.13

redis主从从&#xff0c;redis-7.0.13 下载redis安装redis安装redis-7.0.13过程报错1、没有gcc&#xff0c;报错2、没有python3&#xff0c;报错3、[adlist.o] 错误 127 解决安装报错安装完成 部署redis 主从从结构redis主服务器配置redis启动redis登录redisredis默认是主 redi…

Flutter笔记:用于ORM的Floor框架简记

Flutter笔记 用于ORM的Floor框架简记 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/133377191 floor 模块地址&#xff1a;https://pub.dev/packages/floor 【介绍】&#xff1a;最近想找用于Dart和Flutter的ORM框架&#xff0c;偶然间发现了Floor…

Oracle的递归公共表表达式

查询节点id为2的所有子节点的数据&#xff0c;包括向下级联 WITH T1 (id, parent_id, data) AS (SELECT id, parent_id, dataFROM nodesWHERE id 2UNION ALLSELECT t.id, t.parent_id, t.dataFROM nodes tJOIN T1 n ON t.parent_id n.id ) SELECT * FROM T1; --建表语句 C…

什么是Times New Roman 字体

如何评价 Times New Roman 字体&#xff1f;&#xff1a;https://www.zhihu.com/question/24614549?sortcreated 新罗马字体是Times New Roman字体&#xff0c;是Office Word默认自带的英文字体之一。 中英文字体 写作中&#xff0c;英文和数字的标准字体为 Times New Roma…

华为云云耀云服务器L实例评测 | 实例使用教学之软件安装:华为云云耀云服务器环境下安装 Docker

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之软件安装&#xff1a;华为云云耀云服务器环境下安装 Docker 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云…

十一,从摄像机打印HDR环境贴图

越来越接近真相了。我们很自然地想到&#xff0c;如果把漫游器放在中心打印&#xff0c;是不是就可以打印整个等距柱状投影图了呢&#xff1f;是的&#xff0c;但是&#xff0c;只是要注意的是&#xff0c;立方体贴图的内部和外部尽管一样&#xff0c;但是还是稍微有点模糊&…

spring6-IOC容器

IOC容器 1、IoC容器1.1、控制反转&#xff08;IoC&#xff09;1.2、依赖注入1.3、IoC容器在Spring的实现 2、基于XML管理Bean2.1、搭建子模块spring6-ioc-xml2.2、实验一&#xff1a;获取bean①方式一&#xff1a;根据id获取②方式二&#xff1a;根据类型获取③方式三&#xff…

安卓机型不需要解锁bl 不需要root 即可安装模块 框架 VirtualXposed使用步骤分析

​​​​​​安卓玩机教程---全机型安卓4----安卓12 框架xp edx lsp安装方法【一】 安卓系列机型 框架LSP 安装步骤 支持多机型 LSP框架通用安装步骤 通过以上两个博文基本可以了解手机正常安装框架的步骤。但很多机型局限于不能解锁bl和root&#xff0c;那么这些机型能不能使…

vue前端项目中添加独立的静态资源

如果想要在vue项目中放一些独立的静态资源&#xff0c;比如html文件或者用于下载的业务模板或其他文件等&#xff0c;需要在vue打包的时候指定一下静态资源的位置和打包后的目标位置。 使用的是 copy-webpack-plugin 插件&#xff0c;如果没有安装则需要先安装一下&#xff0c;…

大数据Flink(九十):Lookup Join(维表 Join)

文章目录 Lookup Join(维表 Join) Lookup Join(维表 Join) Lookup Join 定义(支持 Batch\Streaming):Lookup Join 其实就是维表 Join,比如拿离线数仓来说,常常会有用户画像,设备画像等数据,而对应到实时数仓场景中,这种实时获取外部缓存的 Join 就叫做维表 Join。…

python抓取网页视频

1. 喜马拉雅音频 1-1 喜马拉雅 import requests import json import time import random import hashliburl https://www.ximalaya.com/revision/play/v1/audio?id46103875&ptype1headers { user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.3…