-
在原来的工程基础上加上redis缓存的配置。增加redis依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 增加redis配置application.yml 上增加:spring: redis: host: localhost port: 6379 配置redis继承 CachingConfigurerSupport,增加 EnableCaching 注解,需要重写 keyGenerator 方法。@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport在类里面配置 RestTemplate ,需要配置key和value的序列化类。key序列化使用StringRedisSerializer, 不配置的话key前面会出现乱码。value序列化使用 GenericJackson2JsonRedisSerializer ,保存为Json格式。该类目前反序列化还有一些bug,只能反序列化实现了Serialize的类。@Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); RedisSerializer keySerializer = new StringRedisSerializer(); // 设置key序列化类,否则key前面会多了一些乱码 template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); // FIXME 有些版本有bug,没有序列化的只能序列化,但无法反序列化 GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); // 设置内容序列化类 template.setValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; }配置 CacheManager,包括指定缓存和默认缓存的超时时间的配置。@Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); Map<String, Long> expires = new HashMap<>(); expires.put(CacheNames.CONFIG, 60L); // 设置超时 cacheManager.setExpires(expires); // 没有设置的缓存默认过期时间 cacheManager.setDefaultExpiration(60 * 60); return cacheManager; }重写 keyGenerator,可以支持使用@Cacheable不指定Key。@Override public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getSimpleName()); sb.append('.').append(method.getName()); // FIXME 参数太长的时候请指定key属性,否则key太长 for (Object obj : params) { if (obj != null) { sb.append(obj.toString()); } } return sb.toString(); } }; }使用缓存保存的时候使用 @Cacheable,清空使用 @CacheEvict ,更新的时候使用 @CachePut 。反序列化有bug,没有实现 Serializable 的只能序列化,无法反序列化。可能后续版本会解决该问题。所以把下面的查询代码修改一下,用 实现了 Serializable 的 ArrayList 包装返回。@Cacheable(CacheNames.CONFIG) @Override public Collection<Config> getAll() { System.out.println("\n----------GetAll----------\n"); return new ArrayList<>(configs.values()); } @CacheEvict(value = CacheNames.CONFIG, allEntries = true) @Override public long add(Config config) { } /** * 删除配置项 */ @CacheEvict(value = CacheNames.CONFIG, allEntries = true) @Override public boolean delete(long id) { }定时清空缓存也可以定时清空cache,使用 @EnableScheduling 和 @Scheduled 注解。@Component @EnableScheduling public class ClearCacheTask { /** * 定时清空缓存 */ @Scheduled(fixedRate = 60 * 1000L) @CacheEvict(value = { CacheNames.CONFIG }, allEntries = true) public void clearCaches() { System.out.println("\n------------ clear caches ------------\n"); } }redis 怎么样保存cache增加2条数据,一个是类型为 zset 的 缓存名~keys , 里面存放了该缓存所有的key, 一个是对应的key,值为序列化后的json。zset 是带权重的有序集合,可以使用 zrange config~keys -1 1 withscores 查看元素,新加入的都是 0.0 。使用 zcount config~keys -1 1 查看个数。可以使用 ttl 命令查看超时时间,单位为秒。安装redishttps://github.com/MicrosoftArchive/redis/releases 下载最新版本,3.2启动redis-server.exe redis.windows.conf也可以使用 Redis Client 查看。dis 比较重要命令keys * / keys cn* 查看数据type keyname 查看数据类型dbsize 查看记录数flushdb 删除【当前数据库】所有记录flushall 删除所有数据库里面的所有数据,注意不是当前数据库,而是所有数据库。expire,ttl 设置和查看超时时间select 选择0-15号数据库===========GITHUB地址===========xwjie/ElementVueSpringbootCodeTemplate
-
AsynchronousFunction.java 一个异步更新缓存的方法。 @Component public class AsychronousFunction{ private static final Logger logger = LoggerFactory.getLogger(AsychronousFunction.class); @Autowired private ApplicationContext applicationContext; private final long EXPIRE_SECONDS = 5; @Autowired private DistributedLock distributeLock; private String lockUuid = "kshdfsjdfhsfs43hhj34khj33jkj_"; /** *刷新缓存 *@param cacheKey *@Param methodInvoker *return **/ @Async public void refreshCache(String cacheKey,MethodInvoker methodInvoker){ String methodInvokeKey = cacheKey; if(methodInvoker != null){ Class<?> targetClass = methodInvoker.getTargetClass(); Object target = AopProxyUtils.getSingletonTarget(applicationContext.getBean(targetClass)); methodInvoker.setTargetObject(target); //枷锁(分布式锁) if(!distributedLock.getLock(DistributedLock.REDIS_LOCK_KEY+lockUuid,DistributedLock.REDIS_LOCK_KEY+lockUuid,DistributedLock.REDIS_LOCK_TIME) == false){ try{ methodInvoker.prepare(); Object invoke = methodInvoker.invoke(); //然后设置进缓存和重新设置过期时间 redisTemplate.opsForValue().set(cacheKey,invoke); redisTemplate.expire(cacheKey,EXPIRE_SECONDS,TimeUnit.MINUTES); } catch(InvocationTargetException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException e){ logger.error("刷新缓存失败:"+e.getMessage(),e); } finally { distributedLock.releaseLock(DistributedLock.REDIS_LOCK_KEY+lockUuid,DistributedLock.REDIS_LOCK_KEY+lockUuid); } } } } } 结语 到这里就实现了对缓存@Cacheable注解的扩展,通过注解实现redis缓存异步刷新功能,下面是github项目源码。 ———————————————— 原文链接:https://blog.csdn.net/weixin_43526498/article/details/106621180
-
一、SpringDataRedis简介 1、Redis redis是一款开源的Key-Value数据库,运行在内存中,由C语言编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 等。 2、Jedis Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等,推荐使用Jedis。 3、Spring Data Redis Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。 spring-data-redis针对jedis提供了如下功能: 连接池自动管理,提供了一个高度封装的“RedisTemplate”类 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口 ValueOperations:简单K-V操作 SetOperations:set类型数据操作 ZSetOperations:zset类型数据操作 HashOperations:针对map类型的数据操作 ListOperations:针对list类型的数据操作 提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations: BoundValueOperations BoundSetOperations BoundListOperations BoundSetOperations BoundHashOperations 将事务操作封装,有容器控制。 针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer) JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。 StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。 JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】 二、RedisTemplate中API使用 1、pom.xml依赖 <!--Redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 2、配置文件 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.jedis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.jedis.pool.max-wait=-1ms # 连接池中的最大空闲连接 spring.redis.jedis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.jedis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=5000ms 3、RedisTemplate的直接方法 首先使用@Autowired注入RedisTemplate(后面直接使用,就不特殊说明) @Autowired private RedisTemplate redisTemplate; 1、删除单个key // 删除key public void delete(String key){ redisTemplate.delete(key); } 2、删除多个key // 删除多个key public void deleteKey (String ...keys){ redisTemplate.delete(keys); } 3、指定key的失效时间 // 指定key的失效时间 public void expire(String key,long time){ redisTemplate.expire(key,time,TimeUnit.MINUTES); } 4、根据key获取过期时间 // 根据key获取过期时间 public long getExpire(String key){ Long expire = redisTemplate.getExpire(key); return expire; } 5、判断key是否存在 // 判断key是否存在 public boolean hasKey(String key){ return redisTemplate.hasKey(key); } 4、String类型相关操作 1)、添加缓存(2/3是1的递进值) //1、通过redisTemplate设置值 redisTemplate.boundValueOps("StringKey").set("StringValue"); redisTemplate.boundValueOps("StringKey").set("StringValue",1, TimeUnit.MINUTES); //2、通过BoundValueOperations设置值 BoundValueOperations stringKey = redisTemplate.boundValueOps("StringKey"); stringKey.set("StringVaule"); stringKey.set("StringValue",1, TimeUnit.MINUTES); //3、通过ValueOperations设置值 ValueOperations ops = redisTemplate.opsForValue(); ops.set("StringKey", "StringVaule"); ops.set("StringValue","StringVaule",1, TimeUnit.MINUTES); 2)、设置过期时间(单独设置) redisTemplate.boundValueOps("StringKey").expire(1,TimeUnit.MINUTES); redisTemplate.expire("StringKey",1,TimeUnit.MINUTES); 3)、获取缓存值(2/3是1的递进值) //1、通过redisTemplate设置值 String str1 = (String) redisTemplate.boundValueOps("StringKey").get(); //2、通过BoundValueOperations获取值 BoundValueOperations stringKey = redisTemplate.boundValueOps("StringKey"); String str2 = (String) stringKey.get(); //3、通过ValueOperations获取值 ValueOperations ops = redisTemplate.opsForValue(); String str3 = (String) ops.get("StringKey"); 4)、删除key Boolean result = redisTemplate.delete("StringKey"); 5)、顺序递增 redisTemplate.boundValueOps("StringKey").increment(3L); 6)、顺序递减 redisTemplate.boundValueOps("StringKey").increment(-3L); 5、Hash类型相关操作 1)、添加缓存(2/3是1的递进值) //1、通过redisTemplate设置值 redisTemplate.boundHashOps("HashKey").put("SmallKey", "HashVaue"); //2、通过BoundValueOperations设置值 BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey"); hashKey.put("SmallKey", "HashVaue"); //3、通过ValueOperations设置值 HashOperations hashOps = redisTemplate.opsForHash(); hashOps.put("HashKey", "SmallKey", "HashVaue"); 2)、设置过期时间(单独设置) redisTemplate.boundValueOps("HashKey").expire(1,TimeUnit.MINUTES); redisTemplate.expire("HashKey",1,TimeUnit.MINUTES); 3)、添加一个Map集合 HashMap<String, String> hashMap = new HashMap<>(); redisTemplate.boundHashOps("HashKey").putAll(hashMap ); 4)、设置过期时间(单独设置) redisTemplate.boundValueOps("HashKey").expire(1,TimeUnit.MINUTES); redisTemplate.expire("HashKey",1,TimeUnit.MINUTES); 5)、提取所有的小key //1、通过redisTemplate获取值 Set keys1 = redisTemplate.boundHashOps("HashKey").keys(); //2、通过BoundValueOperations获取值 BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey"); Set keys2 = hashKey.keys(); //3、通过ValueOperations获取值 HashOperations hashOps = redisTemplate.opsForHash(); Set keys3 = hashOps.keys("HashKey"); 6)、提取所有的value值 //1、通过redisTemplate获取值 List values1 = redisTemplate.boundHashOps("HashKey").values(); //2、通过BoundValueOperations获取值 BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey"); List values2 = hashKey.values(); //3、通过ValueOperations获取值 HashOperations hashOps = redisTemplate.opsForHash(); List values3 = hashOps.values("HashKey"); 7)、根据key提取value值 //1、通过redisTemplate获取 String value1 = (String) redisTemplate.boundHashOps("HashKey").get("SmallKey"); //2、通过BoundValueOperations获取值 BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey"); String value2 = (String) hashKey.get("SmallKey"); //3、通过ValueOperations获取值 HashOperations hashOps = redisTemplate.opsForHash(); String value3 = (String) hashOps.get("HashKey", "SmallKey"); 8)、获取所有的键值对集合 //1、通过redisTemplate获取 Map entries = redisTemplate.boundHashOps("HashKey").entries(); //2、通过BoundValueOperations获取值 BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey"); Map entries1 = hashKey.entries(); //3、通过ValueOperations获取值 HashOperations hashOps = redisTemplate.opsForHash(); Map entries2 = hashOps.entries("HashKey"); 9)、删除 //删除小key redisTemplate.boundHashOps("HashKey").delete("SmallKey"); //删除大key redisTemplate.delete("HashKey"); 10)、判断Hash中是否含有该值 Boolean isEmpty = redisTemplate.boundHashOps("HashKey").hasKey("SmallKey"); 6、Set类型相关操作 1)、添加Set缓存(值可以是一个,也可是多个)(2/3是1的递进值) //1、通过redisTemplate设置值 redisTemplate.boundSetOps("setKey").add("setValue1", "setValue2", "setValue3"); //2、通过BoundValueOperations设置值 BoundSetOperations setKey = redisTemplate.boundSetOps("setKey"); setKey.add("setValue1", "setValue2", "setValue3"); //3、通过ValueOperations设置值 SetOperations setOps = redisTemplate.opsForSet(); setOps.add("setKey", "SetValue1", "setValue2", "setValue3"); 2)、设置过期时间(单独设置) redisTemplate.boundValueOps("setKey").expire(1,TimeUnit.MINUTES); redisTemplate.expire("setKey",1,TimeUnit.MINUTES); 3)、根据key获取Set中的所有值 //1、通过redisTemplate获取值 Set set1 = redisTemplate.boundSetOps("setKey").members(); //2、通过BoundValueOperations获取值 BoundSetOperations setKey = redisTemplate.boundSetOps("setKey"); Set set2 = setKey.members(); //3、通过ValueOperations获取值 SetOperations setOps = redisTemplate.opsForSet(); Set set3 = setOps.members("setKey"); 4)、根据value从一个set中查询,是否存在 Boolean isEmpty = redisTemplate.boundSetOps("setKey").isMember("setValue2"); 5)、获取Set缓存的长度 Long size = redisTemplate.boundSetOps("setKey").size(); 6)、移除指定的元素 Long result1 = redisTemplate.boundSetOps("setKey").remove("setValue1"); 7)、移除指定的key Boolean result2 = redisTemplate.delete("setKey"); 7、 LIST类型相关操作 1)、添加缓存(2/3是1的递进值) //1、通过redisTemplate设置值 redisTemplate.boundListOps("listKey").leftPush("listLeftValue1"); redisTemplate.boundListOps("listKey").rightPush("listRightValue2"); //2、通过BoundValueOperations设置值 BoundListOperations listKey = redisTemplate.boundListOps("listKey"); listKey.leftPush("listLeftValue3"); listKey.rightPush("listRightValue4"); //3、通过ValueOperations设置值 ListOperations opsList = redisTemplate.opsForList(); opsList.leftPush("listKey", "listLeftValue5"); opsList.rightPush("listKey", "listRightValue6"); 2)、将List放入缓存 ArrayList<String> list = new ArrayList<>(); redisTemplate.boundListOps("listKey").rightPushAll(list); redisTemplate.boundListOps("listKey").leftPushAll(list); 3)、设置过期时间(单独设置) redisTemplate.boundValueOps("listKey").expire(1,TimeUnit.MINUTES); redisTemplate.expire("listKey",1,TimeUnit.MINUTES); 4)、获取List缓存全部内容(起始索引,结束索引) List listKey1 = redisTemplate.boundListOps("listKey").range(0, 10); 5)、从左或从右弹出一个元素 String listKey2 = (String) redisTemplate.boundListOps("listKey").leftPop(); //从左侧弹出一个元素 String listKey3 = (String) redisTemplate.boundListOps("listKey").rightPop(); //从右侧弹出一个元素 6)、根据索引查询元素 String listKey4 = (String) redisTemplate.boundListOps("listKey").index(1); 7)、获取List缓存的长度 Long size = redisTemplate.boundListOps("listKey").size(); 8)、根据索引修改List中的某条数据(key,索引,值) redisTemplate.boundListOps("listKey").set(3L,"listLeftValue3"); 9)、移除N个值为value(key,移除个数,值) redisTemplate.boundListOps("listKey").remove(3L,"value"); 8、Zset类型的相关操作 1)、向集合中插入元素,并设置分数 //1、通过redisTemplate设置值 redisTemplate.boundZSetOps("zSetKey").add("zSetVaule", 100D); //2、通过BoundValueOperations设置值 BoundZSetOperations zSetKey = redisTemplate.boundZSetOps("zSetKey"); zSetKey.add("zSetVaule", 100D); //3、通过ValueOperations设置值 ZSetOperations zSetOps = redisTemplate.opsForZSet(); zSetOps.add("zSetKey", "zSetVaule", 100D); 2)、向集合中插入多个元素,并设置分数 DefaultTypedTuple<String> p1 = new DefaultTypedTuple<>("zSetVaule1", 2.1D); DefaultTypedTuple<String> p2 = new DefaultTypedTuple<>("zSetVaule2", 3.3D); redisTemplate.boundZSetOps("zSetKey").add(new HashSet<>(Arrays.asList(p1,p2))); 3)、按照排名先后(从小到大)打印指定区间内的元素, -1为打印全部 Set<String> range = redisTemplate.boundZSetOps("zSetKey").range(0, -1); 4)、获得指定元素的分数 Double score = redisTemplate.boundZSetOps("zSetKey").score("zSetVaule"); 5)、返回集合内的成员个数 Long size = redisTemplate.boundZSetOps("zSetKey").size(); 6)、返回集合内指定分数范围的成员个数(Double类型) Long COUNT = redisTemplate.boundZSetOps("zSetKey").count(0D, 2.2D); 7)、返回集合内元素在指定分数范围内的排名(从小到大) Set byScore = redisTemplate.boundZSetOps("zSetKey").rangeByScore(0D, 2.2D); 8)、带偏移量和个数,(key,起始分数,最大分数,偏移量,个数) Set<String> ranking2 = redisTemplate.opsForZSet().rangeByScore("zSetKey", 0D, 2.2D 1, 3); 9)、返回集合内元素的排名,以及分数(从小到大) Set<TypedTuple<String>> tuples = redisTemplate.boundZSetOps("zSetKey").rangeWithScores(0L, 3L); for (TypedTuple<String> tuple : tuples) { System.out.println(tuple.getValue() + " : " + tuple.getScore()); }ss 10)、返回指定成员的排名 //从小到大 Long startRank = redisTemplate.boundZSetOps("zSetKey").rank("zSetVaule"); //从大到小 Long endRank = redisTemplate.boundZSetOps("zSetKey").reverseRank("zSetVaule"); 11)、从集合中删除指定元素 redisTemplate.boundZSetOps("zSetKey").remove("zSetVaule"); 12)、删除指定索引范围的元素(Long类型) redisTemplate.boundZSetOps("zSetKey").removeRange(0L,3L); 13)、删除指定分数范围内的元素(Double类型) redisTemplate.boundZSetOps("zSetKey").removeRangeByScorssse(0D,2.2D); 14)、为指定元素加分(Double类型) Double score = redisTemplate.boundZSetOps("zSetKey").incrementScore("zSetVaule",1.1D); ———————————————— 原文链接:https://blog.csdn.net/lydms/article/details/105224210
-
随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。原始的使用缓存的方式如下:这样的缓存使用方式将数据读取后,主动对缓存进行更新操作,这样的方式使用方便,但是代码的耦合性高,代码侵入性强。 1 /** 2 * 使用缓存以id为字样,如果id所对应的缓存信息已经存在,则不会再读db 3 * @param id 4 * @return 5 */ 6 public UserInfo getUserInfoById(int id){ 7 UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(id+""); 8 if(userInfo != null){ 9 return userInfo; 10 } 11 System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功"); 12 UserInfo userInfo1 = userInfoDao.findById(id); 13 ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue(); 14 valueOperations.set(id+"", userInfo1); 15 return userInfo1; 16 } 17 18 @Transactional 19 public int saveUserInfo(UserInfo userInfo){ 20 //更新缓存 21 userInfoDao.saveUserInfo(userInfo); 22 int id = userInfo.getId(); 23 //userInfo 里面的id值已经发生了变化 24 System.out.println(userInfo.getId()); 25 ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue(); 26 valueOperations.set(id+"", userInfo); 27 redisTemplate.opsForValue().set(id+"", userInfo); 28 return id; 29 }Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。下面我们通过一个简单的例子来展示,我们是如何给一个既有应用增加缓存功能的。引入缓存<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>在application.preoperties中定义redis的配置# REDIS (RedisProperties) # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0自定义缓存,本文使用redis作为缓存,自定义缓存配置,继承CachingConfigurerSupport类 1 /** 2 * 自定义缓存配置文件,继承 CachingConfigurerSupport 3 * Created by huanl on 2017/8/22. 4 */ 5 @Configuration 6 @EnableCaching 7 public class RedisConfig extends CachingConfigurerSupport{ 8 public RedisConfig() { 9 super(); 10 } 11 12 /** 13 * 指定使用哪一种缓存 14 * @param redisTemplate 15 * @return 16 */ 17 @Bean 18 public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) { 19 RedisCacheManager rcm = new RedisCacheManager(redisTemplate); 20 return rcm; 21 } 22 23 /** 24 * 指定默认的key生成方式 25 * @return 26 */ 27 @Override 28 public KeyGenerator keyGenerator() { 29 KeyGenerator keyGenerator = new KeyGenerator() { 30 @Override 31 public Object generate(Object o, Method method, Object... objects) { 32 StringBuilder sb = new StringBuilder(); 33 sb.append(o.getClass().getName()); 34 sb.append(method.getName()); 35 for (Object obj : objects) { 36 sb.append(obj.toString()); 37 } 38 return sb.toString(); 39 } 40 }; 41 return keyGenerator; 42 } 43 44 @Override 45 public CacheResolver cacheResolver() { 46 return super.cacheResolver(); 47 } 48 49 @Override 50 public CacheErrorHandler errorHandler() { 51 return super.errorHandler(); 52 } 53 54 /** 55 * redis 序列化策略 ,通常情况下key值采用String序列化策略 56 * StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。StringRedisSerializer 57 * RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。JdkSerializationRedisSerializer 58 * @param factory 59 * @return 60 */ 61 @Bean 62 public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory){ 63 RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); 64 redisTemplate.setConnectionFactory(factory); 65 66 // // 使用Jackson2JsonRedisSerialize 替换默认序列化 67 // Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 68 // ObjectMapper om = new ObjectMapper(); 69 // om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 70 // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 71 // jackson2JsonRedisSerializer.setObjectMapper(om); 72 // 73 // 74 // //设置value的序列化方式 75 // redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 76 // //设置key的序列化方式 77 // redisTemplate.setKeySerializer(new StringRedisSerializer()); 78 // redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 79 // redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 80 81 //使用fastJson作为默认的序列化方式 82 GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer(); 83 redisTemplate.setDefaultSerializer(genericFastJsonRedisSerializer); 84 redisTemplate.setValueSerializer(genericFastJsonRedisSerializer); 85 redisTemplate.setKeySerializer(new StringRedisSerializer()); 86 redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer); 87 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 88 redisTemplate.afterPropertiesSet(); 89 90 return redisTemplate; 91 92 } 93 94 /** 95 * 转换返回的object为json 96 * @return 97 */ 98 @Bean 99 public HttpMessageConverters fastJsonHttpMessageConverters(){ 100 // 1、需要先定义一个converter 转换器 101 FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); 102 // 2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据 103 FastJsonConfig fastJsonConfig = new FastJsonConfig(); 104 fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); 105 // 3、在convert 中添加配置信息 106 fastConverter.setFastJsonConfig(fastJsonConfig); 107 // 4、将convert 添加到converters当中 108 HttpMessageConverter<?> converter = fastConverter; 109 return new HttpMessageConverters(converter); 110 } 111 112 113 }在Spring Boot主类中增加@EnableCaching注解开启缓存功能 1 @SpringBootApplication 2 @Import(RedisConfig.class) 3 @MapperScan("com.redistest.dao") 4 @EnableCaching 5 public class RedisApplication { 6 7 public static void main(String[] args) { 8 SpringApplication.run(RedisApplication.class, args); 9 } 10 }在Service类中使用缓存@Service public class UserInfoService { @Autowired private UserInfoDao userInfoDao; @Autowired private RedisTemplate redisTemplate; /** * 优先从缓存中获取数据,如果缓存中不存在,则从db中读取,读取后将结果按照key存入缓存 * @param id * @return */ @Cacheable(value = "user", key="#id + 'findById'") public UserInfo getUserInfoByIDNew(int id){ return userInfoDao.findById(id); } /** * 使用缓存以id为字样,如果id所对应的缓存信息已经存在,则不会再读db * @param id * @return */ public UserInfo getUserInfoById(int id){ UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(id+""); if(userInfo != null){ return userInfo; } System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功"); UserInfo userInfo1 = userInfoDao.findById(id); ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue(); valueOperations.set(id+"", userInfo1); return userInfo1; } @Transactional public int saveUserInfo(UserInfo userInfo){ //更新缓存 userInfoDao.saveUserInfo(userInfo); int id = userInfo.getId(); //userInfo 里面的id值已经发生了变化 System.out.println(userInfo.getId()); ValueOperations<String, UserInfo> valueOperations = redisTemplate.opsForValue(); valueOperations.set(id+"", userInfo); redisTemplate.opsForValue().set(id+"", userInfo); return id; } /** * * @param userInfo * @return */ @Transactional //更新后删除指定值的缓存,获取值得时候默认从db中获取 //@CacheEvict(value = "user", key="#userInfo.id+'findById'") //配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析 @CachePut(value = "user", key = "#userInfo.id+'findById'") public UserInfo updateUserInfo(UserInfo userInfo){ userInfoDao.updateUserInfo(userInfo); int id = userInfo.getId(); redisTemplate.opsForValue().set(id+"", userInfo); return userInfo; } }使用的MybatisDao@Mapper @Component public interface UserInfoDao { @Select(value = "select * from t_t_user where id=#{id}") public UserInfo findById(int id); public int saveUserInfo(UserInfo userInfo); public int updateUserInfo(UserInfo userInfo); }mapper文件,在application.properties文件中定义mapper文件的位置mybatis.mapper-locations=mapper.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--命名空间:分类管理sql隔离,方便管理--> <mapper namespace="com.redistest.dao.UserInfoDao"> <!--插入--> <insert id="saveUserInfo" useGeneratedKeys="true" keyProperty="id" keyColumn="id" parameterType="com.redistest.domain.UserInfo"> INSERT INTO t_t_user (name, password, salt, role) VALUES (#{name}, #{password}, #{salt}, #{role}) </insert> <!--更新--> <update id="updateUserInfo" parameterType="com.redistest.domain.UserInfo"> UPDATE t_t_user set name=#{name}, password=#{password}, salt=#{salt}, role=#{role} where id=#{id} </update> </mapper>Cache注解详解回过头来我们再来看,这里使用到的两个注解分别作了什么事情。@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = "users"):配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。@Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。除了这里用到的两个注解之外,还有下面几个核心注解:@CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析@CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:allEntries:非必需,默认为false。当为true时,会移除所有数据beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。缓存配置完成了上面的缓存实验之后,可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢?在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:GenericJCache (JSR-107)EhCache 2.xHazelcastInfinispanRedisGuavaSimple除了按顺序侦测外,我们也可以通过配置属性spring.cache.type来强制指定。我们可以通过debug调试查看cacheManager对象的实例来判断当前使用了什么缓存。本文中不对所有的缓存做详细介绍,下面以常用的EhCache为例,看看如何配置来使用EhCache进行缓存管理。在Spring Boot中开启EhCache非常简单,只需要在工程中加入ehcache.xml配置文件并在pom.xml中增加ehcache依赖,框架只要发现该文件,就会创建EhCache的缓存管理器。在src/main/resources目录下创建:ehcache.xml123456789<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="ehcache.xsd"><cache name="users"maxEntriesLocalHeap="200"timeToLiveSeconds="600"></cache></ehcache>在pom.xml中加入1234<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId></dependency>完成上面的配置之后,再通过debug模式运行单元测试,观察此时CacheManager已经是EhCacheManager实例,说明EhCache开启成功了。对于EhCache的配置文件也可以通过application.properties文件中使用spring.cache.ehcache.config属性来指定,比如:1spring.cache.ehcache.config=classpath:config/another-config.xml
-
添加依赖: <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring2.X集成redis所需common-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency> 创建RedisConfig @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解决乱码的问题),过期时间600秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } } 3、在接口中添加redis缓存 由于首页数据变化不是很频繁,而且首页访问量相对较大,所以我们有必要把首页接口数据缓存到redis缓存中,减少数据库压力和提高访问速度。 改造service-cms模块首页banner接口,首页课程与讲师接口类似 3.1 Spring Boot缓存注解 (1)缓存@Cacheable 根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。 查看源码,属性值如下: 属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与 value 差不多,二选一即可 key 可选属性,可以使用 SpEL 标签自定义缓存的key (2)缓存@CachePut 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。 查看源码,属性值如下: 属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与 value 差不多,二选一即可 key 可选属性,可以使用 SpEL 标签自定义缓存的key (3)缓存@CacheEvict 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上 查看源码,属性值如下: 属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与 value 差不多,二选一即可 key 可选属性,可以使用 SpEL 标签自定义缓存的key allEntries 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存 beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存 3.2 启动redis服务 3.3连接redis服务可能遇到的问题 (1)关闭liunx防火墙 (2)找到redis配置文件, 注释一行配置 (3)如果出现下面错误提示 修改 protected-mode yes 改为 protected-mode no 在application.properties添加redis配置信息 spring.redis.host=81.68.112.20 spring.redis.port=6379 spring.redis.database= 0 spring.redis.timeout=1800000 spring.redis.lettuce.pool.max-active=20 spring.redis.lettuce.pool.max-wait=-1 #最大阻塞等待时间(负数表示没限制) spring.redis.lettuce.pool.max-idle=5 spring.redis.lettuce.pool.min-idle=0 下面是一个列子 (2)修改CrmBannerServiceImpl,添加redis缓存注解 @Service public class CrmBannerServiceImpl extends ServiceImpl<CrmBannerMapper, CrmBanner> implements CrmBannerService { //查询所有banner @Cacheable(value = "banner", key = "'selectIndexList'") @Override public List<CrmBanner> selectAllBanner() { //根据id进行降序排列,显示排列之后前两条记录 QueryWrapper<CrmBanner> wrapper = new QueryWrapper<>(); wrapper.orderByDesc("id"); //last方法,拼接sql语句 wrapper.last("limit 2"); List<CrmBanner> list = baseMapper.selectList(null); return list; } @Override public void pageBanner(Page<CrmBanner> pageParam, Object o) { baseMapper.selectPage(pageParam, null); } @Override public CrmBanner getBannerById(String id) { return baseMapper.selectById(id); } @CacheEvict(value = "banner", allEntries = true) @Override public void saveBanner(CrmBanner banner) { baseMapper.insert(banner); } @CacheEvict(value = "banner", allEntries = true) @Override public void updateBannerById(CrmBanner banner) { baseMapper.updateById(banner); } @CacheEvict(value = "banner", allEntries = true) @Override public void removeBannerById(String id) { baseMapper.deleteById(id); } } ———————————————— 原文链接:https://blog.csdn.net/u014496893/article/details/113139275
-
Java操作Redis 一、Java使用Jedis 1.Java操作Redis的选择 需要redis的一个java客户端jedis。 jedis是客户端,而reids是服务器,使用jedis这个java客户端操作redis数据库。 2.9.0 jar 版本下载: jedis-2.9.0.jar 在你的 classpath 中包含该驱动包。 2.Jedis的使用 import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { String host ="127.0.0.1"; int port = 6379; int timeout = 1000;//超时时间,1秒超时 //连接本地的 Redis 服务 Jedis jedis = new Jedis(host,port,timeout); // 如果 Redis 服务设置了密码,需要下面这行,没有就不需要 // jedis.auth("123456"); System.out.println("连接成功"); //查看服务是否运行 System.out.println("服务正在运行: "+jedis.ping()); } } 运行以上 Java 程序 3.连接池的使用 连接池的作用: 限定了最大并发连接数,防止Redis服务由于连接过多而崩溃。 避免重复创建和关闭连接,节约内存资源,和时间资源。 连接池参数设置: 最小连接数(空闲最大连接数) 最大连接数(忙时最大连接数) 超时时间(创建连接超时时间) 设置连接畅通时机 import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import org.junit.Test; public class RedisTest { /* 使用连接池访问Redis 1.创建连接池配置对象 2.配置 忙时:最大连接数 闲时:最大连接数 最大等待时间 获取连接池检查配置 3.通过连接池配置对象创建连接池jedispool 4.通过连接池获取连接 5.执行操作 6.释放连接 7.摧毁连接池-如果是真正项目中它应该是一个受spring管理的单例 */ @Test public void testRedis() throws Exception{ //1.创建连接池配置对象 JedisPoolConfig config = new JedisPoolConfig(); //2.配置 config.setMaxIdle(8);//忙时:最大连接数 config.setMaxTotal(10);//闲时:最大连接数 config.setMaxWaitMillis(1*1000); //创建连接超时 config.setTestOnBorrow(true);//获取连接是测试连接是否畅通 //3.创建连接池 JedisPool pool = new JedisPool(config, "127.0.0.1",6379, 1000); //4.通过连接池获取连接 Jedis jedis = pool.getResource(); //5.执行操作 jedis.set("name","木研"); System.out.println("获取值:" + jedis.get("name")); //6.释放连接 jedis.close(); //底层做了兼容,如果是连接池操作就是释放,如果是连接操作就是关闭 //7.摧毁连接池 pool.destroy(); } } 运行以上 Java 程序 二、Jedis操作Redis 使用jedis来操作redis的key和value,而value有很多种类型,和命令操作一样。 1.String的操作 import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); System.out.println(jedis.set("name","zhangsan"));//新增 System.out.println(jedis.get("name"));//获取 System.out.println(jedis.set("name","zhangsan1"));//修改 System.out.println(jedis.get("name"));//获取 System.out.println(jedis.del("name"));//删除 } } 2.List的操作 import redis.clients.jedis.Jedis; import java.util.List; public class Main { public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); jedis.lpush("students1","1","2","3","5","6"); // 获取存储的数据并输出 List<String> list = jedis.lrange("students1", 0 ,3); for(int i=0; i<list.size(); i++) { System.out.println("列表项为: "+list.get(i)); } } } 3.Set的操作 import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); jedis.sadd("students2","a1","b1","c1","b1"); System.out.println(jedis.smembers("students2")); } } 4.ZSet的操作 import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); jedis.zadd("students3",166.3,"a1"); System.out.println(jedis.zrange("students3",0,-1)); } } 5.Hash的操作 import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); jedis.hset("students4","student","zhangsan"); System.out.println(jedis.hget("students3", "student")); } } 6.key的操作 import redis.clients.jedis.Jedis; import java.util.Iterator; import java.util.Set; public class Main { public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); // 获取数据并输出 Set<String> keys = jedis.keys("*"); Iterator<String> it=keys.iterator() ; while(it.hasNext()){ String key = it.next(); System.out.println(key); } } } 三、Jedis排序 import redis.clients.jedis.Jedis; import redis.clients.jedis.SortingParams; public class Main { /* * sort排序 * 数字不用加SortingParams * 字母需要加SortingParams,并且是指ASCII值排序 * */ public static void main(String[] args) { //连接本地的 Redis 服务 Jedis jedis = new Jedis("127.0.0.1",6379,1000); //清空当前数据库中的所有 key jedis.flushDB(); //对数字排序 jedis.lpush("nums","1","2","3","4","8","5","3","1"); System.out.println(jedis.sort("nums")); SortingParams sortingParams1 = new SortingParams(); sortingParams1.desc();// 按desc排序 System.out.println(jedis.sort("nums", sortingParams1)); //对字母排序 jedis.lpush("langues","java","php","c++","test","ui"); SortingParams sortingParams = new SortingParams(); sortingParams.alpha();//按照字母排序 sortingParams.desc(); System.out.println(jedis.sort("langues",sortingParams)); } } 四、Jedis事务 import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Transaction; import java.util.List; public class Main { /*redis的事务处理,多条命令中有失败也无法将所有指令回滚*/ public static void main(String[] args) { //创建连接池配置对象 JedisPoolConfig config = new JedisPoolConfig(); //配置 config.setMaxIdle(8);//忙时:最大连接数 config.setMaxTotal(10);//闲时:最大连接数 config.setMaxWaitMillis(1*1000); //创建连接超时 config.setTestOnBorrow(true);//获取连接是测试连接是否畅通 //创建连接池 JedisPool pool = new JedisPool(config, "127.0.0.1",6379, 1000); //通过连接池获取连接 Jedis jedis = pool.getResource(); //清空当前数据库中的所有 key jedis.flushDB(); jedis.set("name","ty"); jedis.set("age","18"); System.out.println("获取name、age的值:" + jedis.mget("name","age")); //开启事务 Transaction transaction = jedis.multi(); transaction.incr("name"); transaction.incr("age"); //提交事务 List<Object> exec = transaction.exec(); System.out.println(exec); System.out.println("获取name、age的值:" + jedis.mget("name","age")); //关闭连接 jedis.close(); //摧毁连接池 pool.destroy(); } } ———————————————— 原文链接:https://blog.csdn.net/qq_45988641/article/details/117768515
-
1、隐藏微信网页右上角的按钮 document.addEventListener('WeixinJSBridgeReady',function onBridgeReady() {` // 通过下面这个API隐藏右上角按钮` WeixinJSBridge.call('hideOptionMenu'); }); document.addEventListener('WeixinJSBridgeReady',function onBridgeReady() { // 通过下面这个API显示右上角按钮` WeixinJSBridge.call('showOptionMenu'); }); 2、隐藏微信网页底部的导航栏 document.addEventListener('WeixinJSBridgeReady',function onBridgeReady() {` // 通过下面这个API隐藏底部导航栏 WeixinJSBridge.call('hideToolbar'); });document.addEventListener('WeixinJSBridgeReady',function onBridgeReady() { // 通过下面这个API显示底部导航栏 WeixinJSBridge.call('showToolbar'); }); 3、在微信网页中获取用户的网络状态 WeixinJSBridge.invoke('getNetworkType',{},function (e){` // 在这里拿到e.err_msg,这里面就包含了所有的网络类型 alert(e.err_msg); }); network_type:wifi wifi网络 network_type:edge 非wifi,包含3G/2G network_type:fail 网络断开连接 network_type:wwan 2g或者3g ———————————————— 原文链接:https://blog.csdn.net/qq_26642611/article/details/104844973
-
ProviderManager.authenticate(Authentication) public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); //1、获取认证提供者AuthenticationProvider //这里获取的值只有AnonymousAuthenticationProvider for (AuthenticationProvider provider : getProviders()) { //2、由于AnonymousAuthenticationProvider不支持处理这个Authentication //继续下一个循环 if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { //2.1 如果支持,则调用认证方法 result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException | InternalAuthenticationServiceException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw e; } catch (AuthenticationException e) { lastException = e; } } //3、如果上面的认证结果为空,并且parent不为空,则调用parent.authenticate() if (result == null && parent != null) { // Allow the parent to try. try { /** 这里和上面唯一的区别就是getProviders()获取的值不一样, parent里面的AuthenticationProvider实现类有DaoAuthenticationProvider, 内部会调用UserDetailsService.loadUserByUsername()方法根据用户名加载用户, 我们之前编写的CustomUserDetailsService类就是在这里被调用的 */ result = parentResult = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request } catch (AuthenticationException e) { lastException = parentException = e; } } if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication ((CredentialsContainer) result).eraseCredentials(); } // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it if (parentResult == null) { eventPublisher.publishAuthenticationSuccess(result); } return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage( "ProviderManager.providerNotFound", new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}")); } // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it if (parentException == null) { prepareException(lastException, authentication); } throw lastException; } parent.authenticate(Authentication):这里和上面唯一的区别就是getProviders()获取的值不一样,parent里面的AuthenticationProvider实现类有DaoAuthenticationProvider,内部会调用UserDetailsService.loadUserByUsername()方法根据用户名加载用户,我们之前编写的CustomUserDetailsService类就是在这里被调用的 ———————————————— 原文链接:https://blog.csdn.net/u012153127/article/details/118195907
-
您好我这两篇文章为啥不让发布:Java流处理之序列化和打印流网络通信协议、协议分类和软件结构
-
随便打开一个Microsoft Visual Studio,新建一个WinForms项目,从下面列表中随便选择一个NET框架。net35;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;net48;net481;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net7.0;创建完窗口后,切换到【程序包管理控制台】,输入下面命令并回车:Install-Package PaddleSegSharp -Version 1.0.0完成PaddleSegSharp的依赖包安装。在窗口界面拖放一个按钮,并双击,在按钮的点击事件中,输入以下代码:添加命名空间:using PaddleSegSharp;OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png"; if (ofd.ShowDialog() != DialogResult.OK) return; string modelPath = null; //参数 MattingParameter parameter = new MattingParameter(); //初始化引擎 PaddleSegMattingEngine engine = new PaddleSegMattingEngine(); engine.Init(modelPath, parameter); //设置背景颜色 engine.Setbackground(45, 145, 255); //分割后的文件 string outfile = "C:\\" + Guid.NewGuid().ToString() + ".bmp"; engine.Seg(ofd.FileName, outfile);然后按F5运行,点击按钮,选择一个本地图片,进行抠图。结果存在C盘下,打开即可看到效果图。 项目简介:本项目是一个基于百度飞桨PaddleSeg项目的人像分割模块而开发的.NET的工具类库。本项目中PaddleSeg.dll文件是基于开源项目最新发布版本PaddleSeg 2.7PaddleSeg的版本修改而成的C++动态库,基于opencv的x64编译而成的。PaddleSeg是基于飞桨PaddlePaddle的端到端图像分割套件,内置45+模型算法及140+预训练模型,支持配置化驱动和API调用开发方式,打通数据标注、模型开发、训练、压缩、部署的全流程,提供语义分割、交互式分割、Matting、全景分割四大分割能力,助力算法在医疗、工业、遥感、娱乐等场景落地应用。本项目只能在X64的CPU上编译和使用,只能在avx指令集上的CPU上使用。本项目支持飞桨人像抠图的所有模型。PP-MattingV2-512、PP-Matting-512、PP-Matting-1024、PP-HumanMatting、MODNet-MobileNetV2。 默认使用MODNet-MobileNetV2模型。项目目前支持以下.NET框架,可以说,覆盖范围很广。net35;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;net48;net481;netstandard2.0;netcoreapp3.1;net5.0;net6.0;net7.0;项目开源地址:cid:link_1https://www.cnblogs.com/raoyutian/p/16983749.html 原文连接
-
package org.fh.util;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.util.Random;import java.util.regex.Matcher;import java.util.regex.Pattern;/** * 说明:常用工具 * 作者:FH Admin * from:fhadmin.cn */public class Tools { /** * 随机生成六位数验证码 * @return */ public static int getRandomNum(){ Random r = new Random(); return r.nextInt(900000)+100000;//(Math.random()*(999999-100000)+100000) } /** * 随机生成四位数验证码 * @return */ public static int getRandomNum4(){ Random r = new Random(); return r.nextInt(9000)+1000; } /** * 随机生成两位数验证码 * @return */ public static int getRandomNum2(){ Random r = new Random(); return r.nextInt(90)+10; } /** * 检测字符串是否不为空(null,"","null") * @param s * @return 不为空则返回true,否则返回false */ public static boolean notEmpty(String s){ return s!=null && !"".equals(s) && !"null".equals(s); } /** * 检测字符串是否为空(null,"","null") * @param s * @return 为空则返回true,不否则返回false */ public static boolean isEmpty(String s){ return s==null || "".equals(s) || "null".equals(s); } /** * 字符串转换为字符串数组 * @param str 字符串 * @param splitRegex 分隔符 * @return */ public static String[] str2StrArray(String str,String splitRegex){ if(isEmpty(str)){ return null; } return str.split(splitRegex); } /** * 用默认的分隔符(,)将字符串转换为字符串数组 * @param str 字符串 * @return */ public static String[] str2StrArray(String str){ return str2StrArray(str,",\\s*"); } /** * 往文件里的内容 * @param filePath 文件路径 * @param content 写入的内容 */ public static void writeFile(String fileP,String content){ String filePath = String.valueOf(Thread.currentThread().getContextClassLoader().getResource(""))+"../../"; //项目路径 filePath = filePath.replaceAll("file:/", ""); filePath = filePath.replaceAll("%20", " "); filePath = filePath.trim() + fileP.trim(); if(filePath.indexOf(":") != 1){ filePath = File.separator + filePath; } try { OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(filePath),"utf-8"); BufferedWriter writer=new BufferedWriter(write); writer.write(content); writer.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 往文件里的内容(Projectpath下) * @param filePath 文件路径 * @param content 写入的内容 */ public static void writeFileCR(String fileP,String content){ String filePath = PathUtil.getProjectpath() + fileP; try { OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(filePath),"utf-8"); BufferedWriter writer=new BufferedWriter(write); writer.write(content); writer.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 验证邮箱 * @param email * @return */ public static boolean checkEmail(String email){ boolean flag = false; try{ String check = "^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; Pattern regex = Pattern.compile(check); Matcher matcher = regex.matcher(email); flag = matcher.matches(); }catch(Exception e){ flag = false; } return flag; } /** * 验证手机号码 * @param mobiles * @return */ public static boolean checkMobileNumber(String mobileNumber){ boolean flag = false; try{ Pattern regex = Pattern.compile("^(((13[0-9])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8})|(0\\d{2}-\\d{8})|(0\\d{3}-\\d{7})$"); Matcher matcher = regex.matcher(mobileNumber); flag = matcher.matches(); }catch(Exception e){ flag = false; } return flag; } /** * 检测KEY是否正确 * @param paraname 传入参数 * @param FKEY 接收的 KEY * @return 为空则返回true,不否则返回false */ public static boolean checkKey(String paraname, String FKEY){ paraname = (null == paraname)? "":paraname; return MD5.md5(paraname+DateUtil.getDays()+",fh,").equals(FKEY); } /**读取txt里的全部内容 * @param fileP 文件路径 * @param encoding 编码 * @return */ public static String readTxtFileAll(String fileP, String encoding) { StringBuffer fileContent = new StringBuffer(); try { String filePath = String.valueOf(Thread.currentThread().getContextClassLoader().getResource(""))+"../../"; //项目路径 filePath = filePath.replaceAll("file:/", ""); filePath = filePath.replaceAll("%20", " "); filePath = filePath.trim() + fileP.trim(); if(filePath.indexOf(":") != 1){ filePath = File.separator + filePath; } File file = new File(filePath); if (file.isFile() && file.exists()) { // 判断文件是否存在 InputStreamReader read = new InputStreamReader( new FileInputStream(file), encoding); // 考虑到编码格式 BufferedReader bufferedReader = new BufferedReader(read); String lineTxt = null; while ((lineTxt = bufferedReader.readLine()) != null) { fileContent.append(lineTxt); fileContent.append("\n"); } read.close(); }else{ System.out.println("找不到指定的文件,查看此路径是否正确:"+filePath); } } catch (Exception e) { System.out.println("读取文件内容出错"); } return fileContent.toString(); } /** * 读取Projectpath某文件里的全部内容 * @param filePath 文件路径 */ public static String readFileAllContent(String fileP) { StringBuffer fileContent = new StringBuffer(); try { String encoding = "utf-8"; File file = new File(PathUtil.getProjectpath() + fileP);//文件路径 if (file.isFile() && file.exists()) { // 判断文件是否存在 InputStreamReader read = new InputStreamReader( new FileInputStream(file), encoding); // 考虑到编码格式 BufferedReader bufferedReader = new BufferedReader(read); String lineTxt = null; while ((lineTxt = bufferedReader.readLine()) != null) { fileContent.append(lineTxt); fileContent.append("\n"); } read.close(); }else{ System.out.println("找不到指定的文件,查看此路径是否正确:"+fileP); } } catch (Exception e) { System.out.println("读取文件内容出错"); } return fileContent.toString(); } public static void main(String[] args) { System.out.println(getRandomNum()); }}
-
背景 最近好几个项目在运行过程中客户都提出文件上传大小的限制能否设置的大一些,用户经常需要上传好几个 G 的资料文件,如图纸,视频等,并且需要在上传大文件过程中进行优化实时展现进度条,进行技术评估后针对框架文件上传进行扩展升级,扩展接口支持大文件分片上传处理,减少服务器瞬时的内存压力,同一个文件上传失败后可以从成功上传分片位置进行断点续传,文件上传成功后再次上传无需等待达到秒传的效果,优化用户交互体验,具体的实现流程如下图所示 (java fhadmin.cn) 文件 MD5 计算 对于文件 md5 的计算我们使用 spark-md5 第三方库,大文件我们可以分片分别计算再合并节省时间,但是经测试 1G 文件计算 MD5 需要 20s 左右的时间,所以经过优化我们抽取文件部分特征信息(文件第一片 + 文件最后一片 + 文件修改时间),来保证文件的相对唯一性,只需要 2s 左右,大大提高前端计算效率,对于前端文件内容块的读取我们需要使用 html5 的 api 中 fileReader.readAsArrayBuffer 方法,因为是异步触发,封装的方法提供一个回调函数进行使用 //java fhadmin.cn createSimpleFileMD5(file, chunkSize, finishCaculate) { var fileReader = new FileReader(); var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice; var chunks = Math.ceil(file.size / chunkSize); var currentChunk = 0; var spark = new SparkMD5.ArrayBuffer(); var startTime = new Date().getTime(); loadNext(); fileReader.onload = function() { spark.append(this.result); if (currentChunk == 0) { currentChunk = chunks - 1; loadNext(); } else { var fileMD5 = hpMD5(spark.end() + file.lastModifiedDate); finishCaculate(fileMD5) } }; function loadNext() { var start = currentChunk * chunkSize; var end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } } 文件分片切割 我们通过定义好文件分片大小,使用 blob 对象支持的 file.slice 方法切割文件,分片上传请求需要同步按顺序请求,因为使用了同步请求,前端 ui 会阻塞无法点击,需要开启 worker 线程进行操作,完成后通过 postMessage 方法传递消息给主页面通知 ui 进度条的更新,需要注意的是,worker 线程方法不支持 window 对象,所以尽量不要使用第三方库,使用原生的 XMLHttpRequest 对象发起请求,需要的参数通过 onmessage 方法传递获取 页面 upload 请求方法如下 //java fhadmin.cn upload() { var file = document.getElementById("file").files[0]; if (!file) { alert("请选择需要上传的文件"); return; } if (file.size < pageData.chunkSize) { alert("选择的文件请大于" + pageData.chunkSize / 1024 / 1024 + "M") } var filesize = file.size; var filename = file.name; pageData.chunkCount = Math.ceil(filesize / pageData.chunkSize); this.createSimpleFileMD5(file, pageData.chunkSize, function(fileMD5) { console.log("计算文件MD:" + fileMD5); pageData.showProgress = true; var worker = new Worker('worker.js'); var param = { token: GetTokenID(), uploadUrl: uploadUrl, filename: filename, filesize: filesize, fileMD5: fileMD5, groupguid: pageData.groupguid1, grouptype: pageData.grouptype1, chunkCount: pageData.chunkCount, chunkSize: pageData.chunkSize, file: file } worker.onmessage = function(event) { var workresult = event.data; if (workresult.code == 0) { pageData.percent = workresult.percent; if (workresult.percent == 100) { pageData.showProgress = false; worker.terminate(); } } else { pageData.showProgress = false; worker.terminate(); } } worker.postMessage(param); }) } worker.js 执行方法如下 //java fhadmin.cn function FormAjax_Sync(token, data, url, success) { var xmlHttp = new XMLHttpRequest(); xmlHttp.open("post", url, false); xmlHttp.setRequestHeader("token", token); xmlHttp.onreadystatechange = function() { if (xmlHttp.status == 200) { var result = JSON.parse(this.responseText); var status = this.status success(result, status); } }; xmlHttp.send(data); } onmessage = function(evt) { var data = evt.data; console.log(data) //传递的参数 var token = data.token var uploadUrl = data.uploadUrl var filename = data.filename var fileMD5 = data.fileMD5 var groupguid = data.groupguid var grouptype = data.grouptype var chunkCount = data.chunkCount var chunkSize = data.chunkSize var filesize = data.filesize var filename = data.filename var file = data.file var start = 0; var end; var index = 0; var startTime = new Date().getTime(); while (start < filesize) { end = start + chunkSize; if (end > filesize) { end = filesize; } var chunk = file.slice(start, end); //切割文件 var formData = new FormData(); formData.append("file", chunk, filename); formData.append("fileMD5", fileMD5); formData.append("chunkCount", chunkCount) formData.append("chunkIndex", index); formData.append("chunkSize", end - start); formData.append("groupguid", groupguid); formData.append("grouptype", grouptype); //上传文件 FormAjax_Sync(token, formData, uploadUrl, function(result, status) { var code = 0; var percent = 0; if (result.code == 0) { console.log("分片共" + chunkCount + "个" + ",已成功上传第" + index + "个") percent = parseInt((parseInt(formData.get("chunkIndex")) + 1) * 100 / chunkCount); } else { filesize = -1; code = -1 console.log("分片第" + index + "个上传失败") } self.postMessage({ code: code, percent: percent }); }) start = end; index++; } console.log("上传分片总时间:" + (new Date().getTime() - startTime)); console.log("分片完成"); } 文件分片接收 前端文件分片处理完毕后,接下来我们详细介绍下后端文件接受处理的方案,分片处理需要支持用户随时中断上传与文件重复上传,我们新建表 f_attachchunk 来记录文件分片的详细信息,表结构设计如下 CREATE TABLE `f_attachchunk` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ChunkGuid` varchar(50) NOT NULL, `FileMD5` varchar(100) DEFAULT NULL, `FileName` varchar(200) DEFAULT NULL, `ChunkSize` int(11) DEFAULT NULL, `ChunkCount` int(11) DEFAULT NULL, `ChunkIndex` int(11) DEFAULT NULL, `ChunkFilePath` varchar(500) DEFAULT NULL, `UploadUserGuid` varchar(50) DEFAULT NULL, `UploadUserName` varchar(100) DEFAULT NULL, `UploadDate` datetime DEFAULT NULL, `UploadOSSID` varchar(200) DEFAULT NULL, `UploadOSSChunkInfo` varchar(1000) DEFAULT NULL, `ChunkType` varchar(50) DEFAULT NULL, `MergeStatus` int(11) DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8mb4; FileMD5:文件 MD5 唯一标识文件 FileName:文件名称 ChunkSize:分片大小 ChunkCount:分片总数量 ChunkIndex:分片对应序号 ChunkFilePath:分片存储路径(本地存储文件方案使用) UploadUserGuid:上传人主键 UploadUserName:上传人姓名 UploadDate:上传人日期 UploadOSSID:分片上传批次 ID(云存储方案使用) UploadOSSChunkInfo:分片上传单片信息(云存储方案使用) ChunkType:分片存储方式(本地存储,阿里云,华为云,Minio 标识) MergeStatus:分片合并状态(未合并,已合并) 文件分片存储后端一共分为三步,检查分片 =》保存分片 =》合并分片,我们这里先以本地文件存储为例讲解,云存储思路一致,后续会提供对应使用的 api 方法 检查分片 检查分片以数据库文件分片记录的 FIleMD5 与 ChunkIndex 组合来确定分片的唯一性,因为本地分片 temp 文件是作为临时文件存储,可能会出现手动清除施放磁盘空间的问题,所以数据库存在记录我们还需要对应的检查实际文件情况 boolean existChunk = false; AttachChunkDO dbChunk = attachChunkService.checkExistChunk(fileMD5, chunkIndex, "Local"); if (dbChunk != null) { File chunkFile = new File(dbChunk.getChunkFilePath()); if (chunkFile.exists()) { if (chunkFile.length() == chunkSize) { existChunk = true; } else { //删除数据库记录 attachChunkService.delete(dbChunk.getChunkGuid()); } } else { //删除数据库记录 attachChunkService.delete(dbChunk.getChunkGuid()); } } 保存分片 保存分片分为两块,文件存储到本地,成功后数据库插入对应分片信息 //获取配置中附件上传文件夹 String filePath = frameConfig.getAttachChunkPath() + "/" + fileMD5 + "/"; //根据附件guid创建文件夹 File targetFile = new File(filePath); if (!targetFile.exists()) { targetFile.mkdirs(); } if (!existChunk) { //保存文件到文件夹 String chunkFileName = fileMD5 + "-" + chunkIndex + ".temp"; FileUtil.uploadFile(FileUtil.convertStreamToByte(fileContent), filePath, chunkFileName); //插入chunk表 AttachChunkDO attachChunkDO = new AttachChunkDO(fileMD5, fileName, chunkSize, chunkCount, chunkIndex, filePath + chunkFileName, "Local"); attachChunkService.insert(attachChunkDO); } 合并分片 在上传分片方法中,如果当前分片是最后一片,上传完毕后进行文件合并工作,同时进行数据库合并状态的更新,下一次同一个文件上传时我们可以直接拷贝之前合并过的文件作为新附件,减少合并这一步骤的 I/O 操作,合并文件我们采用 BufferedOutputStream 与 BufferedInputStream 两个对象,固定缓冲区大小 if (chunkIndex == chunkCount - 1) { //合并文件 String merageFileFolder = frameConfig.getAttachPath() + groupType + "/" + attachGuid; File attachFolder = new File(merageFileFolder); if (!attachFolder.exists()) { attachFolder.mkdirs(); } String merageFilePath = merageFileFolder + "/" + fileName; merageFile(fileMD5, merageFilePath); attachChunkService.updateMergeStatusToFinish(fileMD5); //插入到附件库 //设置附件唯一guid attachGuid = CommonUtil.getNewGuid(); attachmentDO.setAttguid(attachGuid); attachmentService.insert(attachmentDO); } 云文件分片上传 云文件上传与本地文件上传的区别就是,分片文件直接上传到云端,再调用云存储 api 进行文件合并与文件拷贝,数据库相关记录与检查差异不大 阿里云 OSS 上传分片前需要生成该文件的分片上传组标识 uploadid 上传分片时需要指定 uploadid,同时我们要将返回的分片信息 PartETag 序列化保存数据库,用于后续的文件合并 //java fhadmin.cn 合并分片时通过传递保存分片的 PartETag 对象数组进行操作,为了附件独立唯一性我们不直接使用合并后的文件,通过 api 进行文件拷贝副本使用 public boolean merageFile(String uploadId, List<PartETag> chunkInfoList,String key,AttachmentDO attachmentDO,boolean checkMerge){ key = "chunk/" + key + "/" + key; boolean result = true; try{ TenantParams.attach appConfig = getAttach(); OSSClient ossClient = InitOSS(appConfig); String bucketName = appConfig.getBucketname_auth(); if(!checkMerge){ CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, key, uploadId, chunkInfoList); CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest); } String attachKey = getKey(attachmentDO); ossClient.copyObject(bucketName,key,bucketName,attachKey); ossClient.shutdown(); }catch (Exception e){ e.printStackTrace(); logger.error("OSS合并文件失败:" + e.getMessage()); result = false; } return result; } 华为云 OBS 华为云 api 与阿里云 api 大致相同,只有个别参数名称不同,直接上代码 //java fhadmin.cn Minio 文件存储 Minio 应用比较广泛,框架也同时支持了自己独立部署的 Minio 文件存储系统,Minio 没有对应的分片上传 api 支持,我们可以在上传完分片文件后,使用 composeObject 方法进行文件的合并 ——————————————— 原文作者:FH-Admin 转自链接:https://learnku.com/articles/64010 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。
-
我们可以通过@TableId注解的 类型属性来设置主键id的增长策略,一共有几个多个主键策略,可根据情况自由配置。1,ASSIGN_ID(雪花算法) 如果不设置类型值,默认则使用IdType.ASSIGN_ID策略(自3.3.0起)。该策略会使用雪花算法自动生成主键ID,主键类型为长或字符串(分别对应的MySQL的表字段为BIGINT和VARCHAR)提示:该策略使用接口IdentifierGenerator的方法nextId(以实现类为DefaultIdentifierGenerator雪花算法),下面是雪花算法介绍:雪花算法(雪花)是微博开源的分布式ID生成算法其核心思想就是:使用一个64位的长型的数字作为全局唯一ID。在分布式系统中的应用十分广泛,且ID引入了时间戳,基本上保持自增的。1个2345678@Datapublic class UserInfo { //指定主键生成策略使用雪花算法(默认策略) @TableId(type = IdType.ASSIGN_ID) private Long id; private String userName; private String passWord;}2,ASSIGN_UUID(排除中划线的UUID) 如果使用IdType.ASSIGN_UUID策略,并重新自动生成排除中划线的UUID作为主键。主键类型为String,对应MySQL的表分段为VARCHAR(32)提示:该策略使用接口IdentifierGenerator的方法nextUUID1个2345678@Datapublic class UserInfo { //指定主键生成策略为不含中划线的UUID @TableId(type = IdType.ASSIGN_UUID) private String id; private String userName; private String passWord;}3,AUTO(数据库ID自增)对于像MySQL这样的支持主键自动递增的数据库,我们可以使用IdType.AUTO策略。1个2345678@Datapublic class UserInfo { //指定主键使用数据库ID自增策略 @TableId(type = IdType.AUTO) private Integer id; private String userName; private String passWord;}4,INPUT(插入前自行设置主键值)(1)针对有序列的数据库:Oracle,SQLServer等,当需要建立一个自增序列时,需要用到序列。提示:在Oracle 11g中,设置自增扩,需要先创建序列(SQUENCE)再创建一个触发器(TRIGGER)。在Oracle 12c中,只需要使用IDENTITY属性就可以了,和MySQL一样简单。(2)Mybatis -Plus已经定义好了常见的数据库主键序列,我们首先只需要在@Configuration类中定义好@Bean:Mybatis -Plus内置了如下数据库主键序列(如果内置支持不满足你的需求,可实现IKeyGenerator接口来进行扩展):DB2KeyGeneratorH2KeyGeneratorKingbaseKeyGeneratorOracle密钥生成器PostgreKeyGenerator1个234@Beanpublic OracleKeyGenerator oracleKeyGenerator(){ return new OracleKeyGenerator();}(3)然后实体类配置主键Sequence,指定主键策略为IdType.INPUT即可:提示:支持父类定义@KeySequence子类使用,这样就可以几个表共享一个Sequence1个234567@TableName("TEST_SEQUSER")@KeySequence("SEQ_TEST")//类注解public class TestSequser{ @TableId(value = "ID", type = IdType.INPUT) private Long id;}(4)如果主键是String类型的,也可以使用:如何使用序列作为主键,但是实体主键类型是字符串开头,表的主键是varchar2,但是需要从序列中取值实体定义@KeySequence注解clazz指定类型String.class实体定义主键的类型字符串注意:oracle的序列返回的是Long类型,如果主键类型是Integer,可能会引起ClassCastException1个234567@KeySequence(value = "SEQ_ORACLE_STRING_KEY", clazz = String.class)public class YourEntity{ @TableId(value = "ID_STR", type = IdType.INPUT) private String idStr; ...}5,无(无状态)如果使用IdType.NONE策略,表示未设置主键类型(注解里等于跟随上下,左右里约等于INPUT)附:变量策略配置 假设我们希望完全全部都使用AUTO策略(数据库ID自增),那么可以在 application.properties中添加如下配置进行修改:1个mybatis-plus.global-config.db-config.id-type=auto原文出自:www.hangge.com 转载请保留原文链接:cid:link_0
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签