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

news2024/11/24 20:15:06

自定义注解实现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: localhost
    port: 6379
    password: null
redisson:
  codec: org.redisson.codec.JsonJacksonCodec
  threads: 4
  netty:
    threads: 4
  single-server-config:
    address: "redis://localhost:6379"
    password: null

2.2主从模式

spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: 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:6384
    password: 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-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  sentinel-config:
    master-name: my-master
    sentinel-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> 的 RedisTemplate
        RedisTemplate<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序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @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;
    }

}

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 {

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private DataSourceTransactionManager transactionManager;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private 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);
            //是否开启RedissonLock
            boolean 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 {

    @Autowired
    private 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.coloradmin.cn/o/1044068.html

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

相关文章

「Go框架」gin框架是如何处理panic的?

本文我们介绍下recover在gin框架中的应用。 首先&#xff0c;在golang中&#xff0c;如果在子协程中遇到了panic&#xff0c;那么主协程也会被终止。如下&#xff1a; package mainimport ("github.com/gin-gonic/gin" )func main() {r : gin.Default()// 在子协程中…

Apache DolphinScheduler 在奇富科技的首个调度异地部署实践

奇富科技&#xff08;原360数科&#xff09;是人工智能驱动的信贷科技服务平台&#xff0c;致力于凭借智能服务、AI研究及应用、安全科技&#xff0c;赋能金融机构提质增效&#xff0c;助推普惠金融高质量发展&#xff0c;让更多人享受到安全便捷的金融科技服务。作为国内领先的…

【RocketMQ】sendDefaultImpl call timeout 问题及其解决办法

问题描述&#xff1a; org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call timeout解决&#xff1a; 需要开放10911、10909这两个端口 需修改broker.conf&#xff0c;设置公网IP 启动broker时&#xff0c;需用-c conf/broker.…

通俗易懂-OpenCV角点检测算法(Harris、Shi-Tomas算法实现)

目录 1 图像的特征 2&#xff0c;Harris角点检测 2.1 代码实现 2.2结果展示 3&#xff0c;Shi-Tomasi角点检测算法 3.1 &#xff0c; 代码实现 3.2结果展示 1 图像的特征 2&#xff0c;Harris角点检测 、 2.1 代码实现 import cv2 as cv import matplotlib.pyplot as …

RocketMQ —消费者负载均衡

消费者从 Apache RocketMQ 获取消息消费时&#xff0c;通过消费者负载均衡策略&#xff0c;可将主题内的消息分配给指定消费者分组中的多个消费者共同分担&#xff0c;提高消费并发能力和消费者的水平扩展能力。本文介绍 Apache RocketMQ 消费者的负载均衡策略。 背景信息​ …

Magic Battery for Mac:让你的设备电量管理变得轻松简单

Mac电脑用户们&#xff0c;你们是否曾经为了给设备充电而感到烦恼&#xff1f;是否希望能够方便地查看连接设备的电量情况&#xff1f;现在&#xff0c;有了Magic Battery for macOS&#xff0c;这些问题都将成为过去&#xff01; Magic Battery是一个实用的应用程序&#xff…

Spring Boot事件机制浅析

1、概述 在设计模式中&#xff0c;观察者模式是一个比较常用的设计模式。维基百科解释如下&#xff1a; 观察者模式是软件设计模式的一种。在此种模式中&#xff0c;一个目标对象管理所有相依于它的观察者对象&#xff0c;并且在它本身的状态改变时主动发出通知。这通常透过呼…

曲线救国-通过Magisk安装burp证书到系统根目录

0x01前言 需要对某APP做渗透测试&#xff0c;但该APP做了限制&#xff1a;不信任用户证书。因此需要将burp证书导入到存放系统证书目录下。虽然手机装了Magic&#xff0c;但似乎root有点问题。其挂载有问题&#xff0c;导致无法将 最初尝试&#xff1a;mount -o rw,remount -t…

成都优优聚能带给你什么?

美团代运营是美团针对商家提供的一项全方位的代理运营服务&#xff0c;通过专业团队的协助和优质服务&#xff0c;帮助商家提高品牌知名度、在线销售额、客户粘性等多重指标。下面将详细介绍美团代运营的优势。 1. 强大的平台资源&#xff1a; 作为中国最大的外卖平台之一&…

深度学习-学习率调度,正则化,dropout

正如前面我所说的&#xff0c;各种优化函数也依赖于学习率&#xff0c;保持学习率恒定总是有所限制&#xff0c;在执行梯度下降过程中&#xff0c;我们可以使用各种方法来调节训练过程的学习率&#xff0c;这里只是稍微介绍一下&#xff0c;不会写代码实现的。同时&#xff0c;…

python基于轻量级卷积神经网络模型GhostNet开发构建养殖场景下生猪行为识别系统

养殖业的数字化和智能化是一个综合应用了互联网、物联网、人工智能、大数据、云计算、区块链等数字技术的过程&#xff0c;旨在提高养殖效率、提升产品质量以及促进产业升级。在这个过程中&#xff0c;养殖生猪的数字化智能化可以识别并管理猪的行为。通过数字化智能化系统&…

分布式微服务架构中的关键技术解析

分布式微服务架构是构建现代应用的理想选择&#xff0c;它将复杂系统拆分成小而自治的服务&#xff0c;每个服务都能独立开发、测试和部署。在实际的开发过程中&#xff0c;如何实现高效的分布式微服务架构呢&#xff1f;下面笔者根据自己多年的实战经验&#xff0c;浅谈实战过…

Linux shell编程学习笔记3:查询系统中已安装可以使用的shell

〇、更新记录 20230926 编写 一、前言 目前可以在Linux系统上运行的shell有许多种&#xff1a;sh、bash、cshell、tcsh、zsh……但是对一台具体的系统来说&#xff0c;未必包括上面列的所有这些shell&#xff0c;很可能包括其中两三个。 那么我们如何查询系统中已经安装有哪…

阿里巴巴Java开发编程规约(整理详细版)

目录 前言 1.编程规约 1.1 命名风格 1.2 常量定义 1.3 代码格式 1.4 OOP 规约 1.5 日期时间 1.6 集合处理 1.7 并发处理 1.8 控制语句 1.9 注释规约 1.10 前后端规约 1.11 其他 前言 规约依次分为【重要】、【建议】、【参考】,整理开发规范的目的在于写出更加…

Linux内核学习笔记

这个跟考试一毛钱关系没有 纯个人爱好 考试党划走 Linux 8086映像 3.1Intel 8086寄存器 INTEL处理器通常有十六个寄存器 他们之间可以相互做运算 3.2 8086的内存访问 内存的数据交换 内存和寄存器通过16根地址线建立数据的交换&#xff0c;数据线的宽度和寄存器的宽度相等 注…

最新ChatGPT网站系统源码+支持GPT4.0+支持AI绘画Midjourney绘画+支持国内全AI模型

一、SparkAI创作系统 SparkAi系统是基于很火的GPT提问进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT系统&#xff1f;小编这里写一个详细图文教程吧&a…

博客无限滚动加载(html、css、js)实现

介绍 这是一个简单实现了类似博客瀑布流加载功能的页面&#xff0c;使用html、css、js实现。简单易懂&#xff0c;值得学习借鉴。&#x1f44d; 演示地址&#xff1a;https://i_dog.gitee.io/easy-web-projects/infinite_scroll_blog/index.html 代码 index.html <!DOCT…

Visual Code 开发web 的hello world

我以前做过&#xff0c;后来忘了怎么做了&#xff0c;所以还是要做个记录。 本文介绍visual code 开发web 的hello world 参考&#xff1a; Exercise - Set up the structure of your web app - Training | Microsoft Learn 打开Visual Code &#xff0c; 打开目录Open fol…

skywalking源码本地编译运行经验总结

前言 最近工作原因在弄skywalking&#xff0c;为了进一步熟悉拉了代码下来准备debug&#xff0c;但是编译启动项目我就费了老大劲了&#xff0c;所以准备写这篇&#xff0c;帮兄弟们少踩点坑。 正确步骤 既然是用开源的东西&#xff0c;那么最好就是按照人家的方式使用&…

算法-位运算-数字范围按位与

算法-位运算-数字范围按位与 1 题目概述 1.1 题目出处 https://leetcode.cn/problems/bitwise-and-of-numbers-range/description/?envTypestudy-plan-v2&envIdtop-interview-150 1.2 题目描述 2 逐个按位与运算 2.1 思路 最简单的就是直接挨个做与运算&#xff0c;…