SpringBoot 自定义注解实现Redis缓存功能

news2025/1/11 22:47:13

背景

最近小A的公司要做一个大屏可视化平台,主要是给领导看的,领导说这个项目要给领导演示,效果好不好直接关系到能不能拿下这个项目,领导还补了一句“这项目至少是百万级的,大伙要全力以赴”,早上小A还想着“最近这么闲,难道还能接着摸鱼?”,想什么怕是来什么,要摸代码了。

小A打起精神,认真研究了功能,几乎是统计、数据展示、地图的一些功能。

下面是小A前后优化的代码


@Service
public class TestServiceImpl implements ITestService {
	
	@Autowired
	private TestMapper testMapper;
	
	@Override
	public List<AbcVo> sczy(String type) {
		Map<String, AbcVo> map = Stream.of("统计A", "统计B", "统计C", "统计D")
				.map(str->new AbcVo(str, "0", "天"))
				.collect(Collectors.toMap(AbcVo::getType, AbcVo -> AbcVo));
		
		List<AbcVo> list = testMapper.sczy(type);
		
		for(int i=0; i<list.size(); i++) {
			AbcVo obj = map.get(list.get(i).getType());
			if(obj != null) {
				obj.setValue(new BigDecimal(list.get(i).getValue()).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
			}
		}
		
		List<AbcVo> resList = new ArrayList<AbcVo>(map.values());
		return resList;
	}
}

由于大屏可视化平台每个页面有十几个统计模块,差不多十几个接口,考虑到给领导们展示的时候页面丝滑不卡顿,小A对代码做了优化,加入了缓存,如下:

@Service
public class TestServiceImpl implements ITestService {
	
	@Autowired
	private TestMapper testMapper;
	
	@Autowired
	private RedisCache redisCache;
	
	
	@Override
	public List<AbcVo> sczy(String qydm) {
		
		List<AbcVo> resList = null;
		//先从缓存取
		Object cacheObj = redisCache.getCacheObject(CacheConstants.YPTTJ_KEY, qydm);
		if (StringUtils.isNotNull(cacheObj)) {
			resList = StringUtils.cast(cacheObj);
			return resList;
		}
		//缓存没有执行下面逻辑,从数据库取值统计
		if(CollectionUtils.isEmpty(resList)) {
			Map<String, AbcVo> map = Stream.of("统计A", "统计B", "统计C", "统计D")
					.map(str->new AbcVo(str, "0", "天"))
					.collect(Collectors.toMap(AbcVo::getType, AbcVo -> AbcVo));
			
			List<AbcVo> list = testMapper.sczy(qydm);
			
			for(int i=0; i<list.size(); i++) {
				AbcVo obj = map.get(list.get(i).getType());
				if(obj != null) {
					obj.setValue(list.get(i).getValue());
				}
			}
			
			resList = new ArrayList<AbcVo>(map.values());
			//将结果保存到缓存
			redisCache.setCacheObject(CacheConstants.YPTTJ_KEY, qydm, resList);
		}
		
		return resList;
	}

可是一个页面十几个接口,N个页面那不是N倍十几个接口,每个接口都这样加缓存,是不是改动量太大了,而且很多代码都是重复的。于是小A灵机一动,能不能写个注解,在方法上加个缓存注解,代码逻辑不变,就像这样:

@Cache(key = CacheConstants.YPTTJ_KEY)


@Service
public class TestServiceImpl implements ITestService {
	
	@Autowired
	private TestMapper testMapper;
	
	@Override
    @Cache(key = CacheConstants.YPTTJ_KEY)
	public List<AbcVo> sczy(String type) {
		Map<String, AbcVo> map = Stream.of("统计A", "统计B", "统计C", "统计D")
				.map(str->new AbcVo(str, "0", "天"))
				.collect(Collectors.toMap(AbcVo::getType, AbcVo -> AbcVo));
		
		List<AbcVo> list = testMapper.sczy(type);
		
		for(int i=0; i<list.size(); i++) {
			AbcVo obj = map.get(list.get(i).getType());
			if(obj != null) {
				obj.setValue(new BigDecimal(list.get(i).getValue()).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
			}
		}
		
		List<AbcVo> resList = new ArrayList<AbcVo>(map.values());
		return resList;
	}
}

使用自定义缓存的注解时,可以根据缓存key判断缓存中是否存在要的数据,有就直接返回,不再执行方法体中代码;如果缓存中没有,则执行方法体代码,并将方法的返回值缓存到redis中。

小A信心满满,觉得这样写代码才能匹配自己做事的态度。

实现

POM依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
</dependency>

项目中Redis配置

# redis 配置
redis:
  # 地址
  host: localhost
  # 端口,默认为6379
  port: 6379
  # 数据库索引
  database: 0
  # 密码
  password:
  # 连接超时时间
  timeout: 10s
  lettuce:
    pool:
      # 连接池中的最小空闲连接
      min-idle: 0
      # 连接池中的最大空闲连接
      max-idle: 8
      # 连接池的最大数据库连接数
      max-active: 8
      # #连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1ms

Redis配置

Redis序列化器

/**
 * Redis使用FastJson序列化
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
	
	@SuppressWarnings("unused")
	private ObjectMapper objectMapper = new ObjectMapper();

	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

	private Class<T> clazz;

	static {
		ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
	}

	public FastJson2JsonRedisSerializer(Class<T> clazz) {
		super();
		this.clazz = clazz;
	}

	@Override
	public byte[] serialize(T t) throws SerializationException {
		if (t == null) {
			return new byte[0];
		}
		return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
	}

	@Override
	public T deserialize(byte[] bytes) throws SerializationException {
		if (bytes == null || bytes.length <= 0) {
			return null;
		}
		String str = new String(bytes, DEFAULT_CHARSET);

		return JSON.parseObject(str, clazz);
	}

	public void setObjectMapper(ObjectMapper objectMapper) {
		Assert.notNull(objectMapper, "'objectMapper' must not be null");
		this.objectMapper = objectMapper;
	}

	protected JavaType getJavaType(Class<?> clazz) {
		return TypeFactory.defaultInstance().constructType(clazz);
	}
}

redis配置

/**
 * redis配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
	
	@Bean
	@SuppressWarnings(value = { "unchecked", "rawtypes" })
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(connectionFactory);

		FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

		ObjectMapper mapper = new ObjectMapper();
		mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
				JsonTypeInfo.As.PROPERTY);
		serializer.setObjectMapper(mapper);

		// 使用StringRedisSerializer来序列化和反序列化redis的key值
		template.setKeySerializer(new StringRedisSerializer());
		template.setValueSerializer(serializer);

		// Hash的key也采用StringRedisSerializer的序列化方式
		template.setHashKeySerializer(new StringRedisSerializer());
		template.setHashValueSerializer(serializer);

		template.afterPropertiesSet();
		return template;
	}

	@Bean
	public DefaultRedisScript<Long> limitScript() {
		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
		redisScript.setScriptText(limitScriptText());
		redisScript.setResultType(Long.class);
		return redisScript;
	}

	/**
	 * 限流脚本
	 */
	private String limitScriptText() {
		return "local key = KEYS[1]\n" + "local count = tonumber(ARGV[1])\n" + "local time = tonumber(ARGV[2])\n"
				+ "local current = redis.call('get', key);\n" + "if current and tonumber(current) > count then\n"
				+ "    return tonumber(current);\n" + "end\n" + "current = redis.call('incr', key)\n"
				+ "if tonumber(current) == 1 then\n" + "    redis.call('expire', key, time)\n" + "end\n"
				+ "return tonumber(current);";
	}
}

redis操作工具类:将redis的相关操作封装成类,以组件的形式注入到spring容器中。

 

创建自定义注解

/**
 * redis 缓存注解
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {

    /**缓存的key**/
    String key() default "";

    /**缓存时长,默认-1表示永久有效**/
    int time() default -1;

    /**缓存时长单位,默认单位秒**/
    TimeUnit type() default TimeUnit.SECONDS;

}

自定义缓存注解的逻辑实现

/**
 * 自定义缓存处理
 */
@Aspect
@Slf4j
@Component
public class RedisCacheAspect {
	
	@Pointcut("@annotation(com.tencet.common.annotation.Cache)")
    public void annotationPoint() throws Throwable {
		
    }
	
    @Autowired
    public RedisCache redisService;

    @SneakyThrows
    @Around(value = "annotationPoint() && @annotation(redisCache)",argNames = "proceedingJoinPoint,redisCache")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, Cache redisCache){
    	
    	//判断是否开启缓存
    	String hckg = redisService.getCacheObject(CacheConstants.YPT_HCKG); //缓存开关

    	if(StringUtils.isNotEmpty(hckg) && "1".equals(hckg)) { //开启
    		String redisKey = createKeyName(proceedingJoinPoint, redisCache);
            if(StringUtils.isNotEmpty(redisKey)) { //缓存key不为空
            	System.out.println("key=>"+redisKey);
            	//根据缓存key从缓存中取数据
            	Object cacheObject = redisService.getCacheObject(redisKey);
                if (Objects.isNull(cacheObject)){ //不存在缓存中
                	//继续执行方法内的代码逻辑, 得到方法的返回值
                    Object methodResult = proceedingJoinPoint.proceed();
                    //方法的返回值存入缓存中
                    redisService.setCacheObject(redisKey, methodResult, redisCache.time(), redisCache.type());
                    log.info("不存在缓存中,存入缓存:"+methodResult.toString());
                    return methodResult; 
                }else{ //存在缓存中
                    log.info("存在缓存中,缓存取出:"+cacheObject.toString());
                    // 缓存命中后,直接返回缓存中的数据。并不执行方法内的逻辑。
                    return cacheObject;
                }
            }
    	}
    	
    	//缓存未开启或缓存key为空
    	//忽略缓存,继续执行方法内的代码逻辑, 得到方法的返回值
    	Object methodResult = proceedingJoinPoint.proceed();
    	return methodResult;
    }

    /***
     * 
     * @MethodName: createKeyName 
     * @Description: 生成缓存的 key规则
     * @Date 2023年5月5日
     * @param proceedingJoinPoint
     * @param redisCache
     * @return
     */
    private String createKeyName(ProceedingJoinPoint proceedingJoinPoint, Cache redisCache){
        StringBuffer resultKey = new StringBuffer();
    	String key = redisCache.key();
        if (StringUtils.isNotEmpty(key)){
            //前缀
        	resultKey.append(key);

            //后缀
        	String keySuffix = getKeySuffix(proceedingJoinPoint);
            if (StringUtils.isNotEmpty(keySuffix)){
            	resultKey.append(keySuffix);
            }
        }else {
        	return null;
        }
        return resultKey.toString();
    }
    
    
    /**
     * 
     * @MethodName: getKeySuffix 
     * @Description: 获取方法参数生成缓存key的后缀
     * @Date 2023年5月5日
     * @param proceedingJoinPoint
     * @return
     */
    public String getKeySuffix(ProceedingJoinPoint proceedingJoinPoint) {
    	StringBuffer keySuffix = new StringBuffer();
    	Object[] values = proceedingJoinPoint.getArgs();
    	for(Object o : values) {
    		if(o == null) {
    			keySuffix.append("all");
    		}else {
    			keySuffix.append(o.toString());
    		}
    	}
    	return keySuffix.toString();
    }
    
    
    /**
     * 
     * @MethodName: getParam 
     * @Description: 获取参数名和参数值
     * @Date 2023年5月5日
     * @param proceedingJoinPoint
     * @return
     */
    public String getParam(ProceedingJoinPoint proceedingJoinPoint) {
        Map<String, Object> map = new HashMap<String, Object>();
        Object[] values = proceedingJoinPoint.getArgs();
        CodeSignature cs = (CodeSignature) proceedingJoinPoint.getSignature();
        String[] names = cs.getParameterNames();
        for (int i = 0; i < names.length; i++) {
            map.put(names[i], values[i]);
        }
        return JSONObject.toJSONString(map);
    }
}

在方法上使用自定义注解

/**
 * spring redis 工具类
 */
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache {
	
	@Autowired
	public RedisTemplate redisTemplate;
	
	/**
	 * 查看key是否存在
     */
    public boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

	/**
	 * 缓存基本的对象,Integer、String、实体类等
	 *
	 * @param key   缓存的键值
	 * @param value 缓存的值
	 */
	public <T> void setCacheObject(final String key, final T value) {
		redisTemplate.opsForValue().set(key, value);
	}
	
	/**
	 * 缓存基本的对象,Integer、String、实体类等
	 *
	 * @param key   缓存的键值
	 * @param value 缓存的值
	 */
	public <T> void setCacheObject(final String keyt, String keyw, final T value) {
		if(StringUtils.isEmpty(keyw)) {
			keyw = "all";
		}
		String key = keyt + keyw;
		redisTemplate.opsForValue().set(key, value);
	}

	/**
	 * 缓存基本的对象,Integer、String、实体类等
	 *
	 * @param key      缓存的键值
	 * @param value    缓存的值
	 * @param timeout  时间
	 * @param timeUnit 时间颗粒度
	 */
	public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
		if(timeout == -1){
            //永久有效
            redisTemplate.opsForValue().set(key, value);
        }else{
            redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
        }
	}

	/**
	 * 设置有效时间
	 *
	 * @param key     Redis键
	 * @param timeout 超时时间
	 * @return true=设置成功;false=设置失败
	 */
	public boolean expire(final String key, final long timeout) {
		return expire(key, timeout, TimeUnit.SECONDS);
	}

	/**
	 * 设置有效时间
	 *
	 * @param key     Redis键
	 * @param timeout 超时时间
	 * @param unit    时间单位
	 * @return true=设置成功;false=设置失败
	 */
	public boolean expire(final String key, final long timeout, final TimeUnit unit) {
		return redisTemplate.expire(key, timeout, unit);
	}

	/**
	 * 获得缓存的基本对象。
	 *
	 * @param key 缓存键值
	 * @return 缓存键值对应的数据
	 */
	public <T> T getCacheObject(final String key) {
		ValueOperations<String, T> operation = redisTemplate.opsForValue();
		return operation.get(key);
	}
	
	/**
	 * 获得缓存的基本对象。
	 *
	 * @param key 缓存键值
	 * @return 缓存键值对应的数据
	 */
	public <T> T getCacheObject(final String keyt, String keyw) {
		ValueOperations<String, T> operation = redisTemplate.opsForValue();
		if(StringUtils.isEmpty(keyw)) {
			keyw = "all";
		}
		String key = keyt + keyw;
		return operation.get(key);
	}
	

	/**
	 * 删除单个对象
	 *
	 * @param key
	 */
	public boolean deleteObject(final String key) {
		return redisTemplate.delete(key);
	}

	/**
	 * 删除集合对象
	 *
	 * @param collection 多个对象
	 * @return
	 */
	public long deleteObject(final Collection collection) {
		return redisTemplate.delete(collection);
	}
	
	/**
	 * 
	 * @MethodName: deleteKey 
	 * @Description: 删除多个缓存key 
	 * @Date 2023年5月3日
	 * @param arrs
	 * @return
	 */
	public long deleteKey(String[] arrs) {
		Set<String> keys = new HashSet<String>();
		for(String key : arrs) {
			//模糊匹配所有以keyword:开头的所有key值
			keys.addAll(redisTemplate.keys(key+"*"));
		}
		return redisTemplate.delete(keys);
	}
	

	/**
	 * 缓存List数据
	 *
	 * @param key      缓存的键值
	 * @param dataList 待缓存的List数据
	 * @return 缓存的对象
	 */
	public <T> long setCacheList(final String key, final List<T> dataList) {
		Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
		return count == null ? 0 : count;
	}

	/**
	 * 获得缓存的list对象
	 *
	 * @param key 缓存的键值
	 * @return 缓存键值对应的数据
	 */
	public <T> List<T> getCacheList(final String key) {
		return redisTemplate.opsForList().range(key, 0, -1);
	}

	/**
	 * 缓存Set
	 *
	 * @param key     缓存键值
	 * @param dataSet 缓存的数据
	 * @return 缓存数据的对象
	 */
	public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
		BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
		Iterator<T> it = dataSet.iterator();
		while (it.hasNext()) {
			setOperation.add(it.next());
		}
		return setOperation;
	}

	/**
	 * 获得缓存的set
	 *
	 * @param key
	 * @return
	 */
	public <T> Set<T> getCacheSet(final String key) {
		return redisTemplate.opsForSet().members(key);
	}

	/**
	 * 缓存Map
	 *
	 * @param key
	 * @param dataMap
	 */
	public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
		if (dataMap != null) {
			redisTemplate.opsForHash().putAll(key, dataMap);
		}
	}

	/**
	 * 获得缓存的Map
	 *
	 * @param key
	 * @return
	 */
	public <T> Map<String, T> getCacheMap(final String key) {
		return redisTemplate.opsForHash().entries(key);
	}

	/**
	 * 往Hash中存入数据
	 *
	 * @param key   Redis键
	 * @param hKey  Hash键
	 * @param value 值
	 */
	public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
		redisTemplate.opsForHash().put(key, hKey, value);
	}

	/**
	 * 获取Hash中的数据
	 *
	 * @param key  Redis键
	 * @param hKey Hash键
	 * @return Hash中的对象
	 */
	public <T> T getCacheMapValue(final String key, final String hKey) {
		HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
		return opsForHash.get(key, hKey);
	}

	/**
	 * 删除Hash中的数据
	 * 
	 * @param key
	 * @param hKey
	 */
	public void delCacheMapValue(final String key, final String hKey) {
		HashOperations hashOperations = redisTemplate.opsForHash();
		hashOperations.delete(key, hKey);
	}

	/**
	 * 获取多个Hash中的数据
	 *
	 * @param key   Redis键
	 * @param hKeys Hash键集合
	 * @return Hash对象集合
	 */
	public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
		return redisTemplate.opsForHash().multiGet(key, hKeys);
	}

	/**
	 * 获得缓存的基本对象列表
	 *
	 * @param pattern 字符串前缀
	 * @return 对象列表
	 */
	public Collection<String> keys(final String pattern) {
		return redisTemplate.keys(pattern);
	}
}

在方法上使用自定义注解

@Cache(key = CacheConstants.YPTTJ_KEY)
public Object testMethod(String param) {
	...
    ...
    ...
}

//添加过期时间的缓存
@Cache(key = CacheConstants.YPTTJ_KEY, time = 1, type = TimeUnit.HOURS)
public testMethod(String param) {
	...
    ...
    ...
}

运行结果

缓存中无数据,执行方法体代码,并将结果保存到缓存,控制台输出如下:

缓存中有数据,不执行方法体代码,将缓存中数据返回,控制台输出如下:

 缓存如下 

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

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

相关文章

走近大数据——什么是大数据、计算架构的发展

文章目录 一、什么是大数据二、大数据计算架构的发展1.RDBMS阶段2.Hadoop Map-Reduce阶段3.Spark阶段4.Flink阶段 参考 一、什么是大数据 大数据是指无法在有限时间内用常规软件工具对其进行获取、存储、管理和处理的数据集合。 大数据的特点&#xff1a; 海量化&#xff1a;数…

少年不懂孔乙己,读懂已是书中人

文章目录 前言梗从何来互联网文学背后的焦虑给学弟学妹的建议 前言 《孔乙己》是近代文学巨匠鲁迅所著的短篇小说。 大概故事讲的是孔乙己是站着喝酒而穿长衫的&#xff08;那时候穿长衫的人代表着有知识&#xff09;唯一人&#xff0c;穿的虽然是长衫&#xff0c;可是又脏又破…

SpringMVC概述

SpringMVC概述 1. SpringMVC概述1.1 SpringMVC概述 2. 入门案例【重点】2.1 实现步骤2.2 代码实现【第一步】创建web工程&#xff08;Maven结构&#xff09;【第二步】设置tomcat服务器&#xff0c;加载web工程【第三步】导入坐标&#xff08;SpringMVCServlet&#xff09;【第…

璞华助力“数字人社”,为成都市人社数字化建设提供多方位的产品与技术支持!

新的时期&#xff0c;人力资源和社会保障事业进入新一轮的制度创新和加快发展阶段。把对各项人力资源和社会保障业务的支持和服务纳入信息化建设&#xff0c;通过 “数字人社”信息化建设项目&#xff0c;是充分利用新一代信息技术&#xff0c;有效整合各类信息资源&#xff0c…

ChatGPT背后的打工人:你不干,有的是AI干

AI“出圈” 如今&#xff0c;数字技术发展速度惊人&#xff0c;AI提高了社会生产效率&#xff0c;更真切地冲击到原有的生产秩序。 年初AI技术的爆发&#xff0c;让国内看到了进一步降本增效的希望。 国内多家互联网企业相继推出类ChatGPT产品&#xff0c;复旦大学邱锡鹏教授…

清洁赛道新势力,米博凭“减法”突围?

在五四青年节这个特殊的日子&#xff0c;方太旗下的高端智能清洁品牌“米博”发布了新一代无滚布洗地机7系列。 5月4日晚&#xff0c;米博以“减法生活&#xff0c;净请7代”为主题&#xff0c;举办了新品发布会。在发布会上&#xff0c;从小红书翻红的董洁作为方太集团米博产…

持之以恒奖牌来啦,带你提前看~

加油&#xff0c;让我们继续持之以恒吧&#xff01;

Flutter 中使用 dart:html 的条件导入

Flutter 中使用 dart:html 的条件导入 Flutter 是一个跨平台的 UI 框架&#xff0c;可以让你用一套代码开发 Android、iOS、Web 和桌面应用。但是&#xff0c;不同的平台有不同的特性和限制&#xff0c;所以有时候你可能需要根据平台来导入不同的库或代码。这时候&#xff0c;…

社交“搭子”火了!小红书数据分析,品牌正用“陪伴”种草?

找搭子&#xff0c;年轻人在搞一种很新的社交 朋友&#xff0c;你找搭子了吗&#xff1f;近期&#xff0c;“搭子”这种新型社交关系走红&#xff0c;饭搭子、奶茶搭子、厕所搭子、旅游搭子……遍布于各式各样的场景中&#xff0c;主打的就是一个垂直细分领域的精准陪伴。“搭子…

数字化转型:制造业企业,如何创新技术并借力发展?

数字中国峰会刚刚拉开帷幕&#xff0c;紧跟一波潮流。 在这个数字技术全面升级的关口&#xff0c;企业如何进更时代步伐&#xff0c;更好完成数字化转型和升级&#xff1f; 到底什么是数字化转型&#xff1f; 我们当下所看到的很多对“数字化”的理解&#xff0c;依然是“信…

【c++修行之路】模板

模板 一般我们在实现一个函数的时候&#xff0c;都会使用模板&#xff0c;因为如果将类型写死&#xff0c;下次再使用的时候就要新写一个函数&#xff0c;尽管重载可以让名字方便&#xff0c;但每重载都要自己去写一个函数&#xff0c;这样非常麻烦&#xff0c;所以模板就是让…

025 - C++ 接口(纯虚函数)

上一期我们学习了虚函数&#xff0c;本期我们学习一种特殊的虚函数&#xff0c;纯虚函数。 C 纯虚函数本质上与其他语言中的抽象方法或接口相同&#xff0c;基本上&#xff0c;纯虚函数允许我们在基类中定义一个没有实现的函数&#xff0c;然后强制子类去实现该函数。 我们可…

uni-app获取手机号

登录微信公众平台拿到自己的AppID(小程序ID)和AppSecret(小程序密钥) 微信公众平台 1.获取手机号首先要先登录拿到code&#xff0c;用code去获取session_key 2.获取 code需要知道自己的AppID(小程序ID)和AppSecret(小程序密钥) 3.解密 uni.login({success: (loginRes) > {…

提取Windows中系统自带的图标资源

写应用程序&#xff0c;如果想使用Windows下的图标&#xff0c;可以使用Visual Studio中的图标&#xff0c;比如VS2008的ImageLibrary&#xff08;笔者已经打包上传到CSDN&#xff09;&#xff0c;也可以使用Windows系统自带的图标。 Windows系统自带了不少高质量的图标资源&a…

2019临沂中考数学解析

一、选择 考点&#xff1a; 绝对值&#xff1a;数轴上某个数与原点的距离叫做这个数的绝对值。 其中距离一定是非负的&#xff0c;即大于等于0 考点&#xff1a; 两直线平行&#xff0c;同位角相等邻补角&#xff1a; 指两条直线相交后所得的有一个公共顶点且有一条公共边的两…

一篇SEO指南:新手如何从零开始优化自己的网站

在如今的数字时代&#xff0c;拥有一个优化良好的网站对于任何企业或个人来说都是至关重要的。但是&#xff0c;对于SEO新手来说&#xff0c;如何从零开始优化自己的网站可能是一项看似艰巨的任务。在本文中&#xff0c;我们将为您提供一份SEO指南&#xff0c;帮助您了解从零开…

C++之STL顺序容器

目录 一、STL容器简介 二、顺序容器 一、STL容器简介 STL容器是一个通用的数据结构&#xff0c;可以处理不同数据类型&#xff0c;包含基本的数据结构如链表、堆栈、队列等。可以分为顺序容器、关联容器、 容器适配器、特殊容器。本篇博客将简要介绍一下STL容器中的顺序容器…

Mysql 学习(八)单表查询方法 一

单表访问方法 前面几节我们了解 innodb 的底层数据结构的设计&#xff0c;究其本源我们其实是为了更好的理解如何查询&#xff0c;并且如何使得查询语句更加快速的问题&#xff0c;这节我们就来好好讲一讲首先我们先来创建一个表 CREATE TABLE index_value_table (id INT NOT…

Redis --- 哨兵、分片集群

一、Redis哨兵 Redis提供了哨兵&#xff08;Sentinel&#xff09;机制来实现主从集群的自动故障恢复。 1.1、哨兵原理 集群结构和作用 哨兵的结构如图&#xff1a; 哨兵的作用如下&#xff1a; 监控&#xff1a;Sentinel 会不断检查您的master和slave是否按预期工作 自动故障…

mysql 排名与排序

MySQL 8.0 版本新增 两个排名函数 分别是 ROW_NUMBER() 与 RANK() 用法 SELECT ROW_NUMBER() OVER(ORDER BY id) AS rowNo, name FROM users; SELECT RANK() OVER(ORDER BY score DESC) AS rankNo, name, score FROM students;后面跟 OVER&#xff08;&#xff09;括号里…