上一章文章 我们做了 音乐播放器这个项目的 框架搭建和一些基础配置文件。 参考网址:
https://blog.csdn.net/Drug_/article/details/145044096
这篇文章我们来分享一些 项目中用到的一些工具类。
一般项目里 我会创建一个 utils 文件夹 来存放 项目中常用的工具类
1. JWT 工具类。
用于用户登录后生成token 的一个工具类
package com.music.base.utils;/*** User:Json* Date: 2024/3/28**/import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;import com.music.base.exception.JsonlRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;import java.util.Date;
import java.util.HashMap;
import java.util.Map;//@Component
@Slf4j
public class JwtHelper {//过期时间 24 小时private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000 ;//私钥private static final String TOKEN_SECRET="6666";/*** 生成签名*/public static String sign(Integer userId) {try {// 设置过期时间Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);// 私钥和加密算法Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);// 设置头部信息Map<String, Object> header = new HashMap<>(2);header.put("Type", "Jwt");header.put("alg", "HS256");// 返回token字符串return JWT.create().withHeader(header).withClaim("userId", userId).withExpiresAt(date).sign(algorithm);} catch (Exception e) {log.error("生成签名失败:", e);return null;}}/*** 生成签名,15分钟过期** @param **username*** @param **password*** @return*/public static String sign(String userId,String platFrom) {try {// 设置过期时间Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);// 私钥和加密算法Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);// 设置头部信息Map<String, Object> header = new HashMap<>(2);header.put("Type", "Jwt");header.put("alg", "HS256");// 返回token字符串return JWT.create().withHeader(header)//.withClaim("username", ysUser.getUsername()) // 用户名.withExpiresAt(date).sign(algorithm);} catch (Exception e) {log.error("生成签名失败:", e);return null;}}/*** 检验token是否正确 并获取所有数据*/public static Map<String, Object> verifyAll(String token) {if (StringUtils.isEmpty(token)) {return new HashMap<>();}try {Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);JWTVerifier verifier = JWT.require(algorithm).build();DecodedJWT jwt = verifier.verify(token);Map<String, Object> map = new HashMap<>();// map.put("data", jwt.getClaim("userId").asMap());map.put("userId", jwt.getClaim("userId").asInt());// map.put("jti", jwt.getClaim("jti").asString());return map;} catch (Exception e) {log.error("非法token:", e);throw new JsonlRuntimeException("token验证失败:"+e.getMessage());}}}
2. MD5工具类
用于用户登录的 密码 加密。
package com.music.base.utils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class MD5 {/*** 给指定字符串按照md5算法去加密** @param psd 需要加密的密码 加盐处理** @return md5后的字符串*/public static String encoder(String psd) {try {//加盐处理psd = psd + "666json";//1,指定加密算法类型MessageDigest digest = MessageDigest.getInstance("MD5");//2,将需要加密的字符串中转换成byte类型的数组,然后进行随机哈希过程byte[] bs = digest.digest(psd.getBytes());
// System.out.println(bs.length);//3,循环遍历bs,然后让其生成32位字符串,固定写法//4,拼接字符串过程StringBuffer stringBuffer = new StringBuffer();for (byte b : bs) {int i = b & 0xff;//int类型的i需要转换成16机制字符String hexString = Integer.toHexString(i);
// System.out.println(hexString);if (hexString.length() < 2) {hexString = "0" + hexString;}stringBuffer.append(hexString);}return stringBuffer.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return "";}/*** 给指定字符串按照md5算法去加密** @param psd 需要加密的密码 加盐处理** @return md5后的字符串*/public static String encoderNoCelite(String psd) {try {//1,指定加密算法类型MessageDigest digest = MessageDigest.getInstance("MD5");//2,将需要加密的字符串中转换成byte类型的数组,然后进行随机哈希过程byte[] bs = digest.digest(psd.getBytes());
// System.out.println(bs.length);//3,循环遍历bs,然后让其生成32位字符串,固定写法//4,拼接字符串过程StringBuffer stringBuffer = new StringBuffer();for (byte b : bs) {int i = b & 0xff;//int类型的i需要转换成16机制字符String hexString = Integer.toHexString(i);
// System.out.println(hexString);if (hexString.length() < 2) {hexString = "0" + hexString;}stringBuffer.append(hexString);}return stringBuffer.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return "";}public static String anyTimeEncoder(String psd, int time){String encoderString = psd;if(time <= 0){return encoderString;}for(int i = 0; i < time; i++){encoderString = MD5.encoder(encoderString);}return encoderString;};public static void main(String[] args) {System.out.println(encoder("nwadmin"));}
}
3. redis工具类
用于项目中 操作redis缓存。
package com.music.base.utils;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;@Component
@Slf4j
public class RedisUtils {@Value("${spring.application.name}")private String sername;private static String serverName;private static RedisTemplate redisTemplate;private static RedisSerializer<String> stringSerializer = new StringRedisSerializer();private static DefaultRedisScript<String> lockScript= new DefaultRedisScript<String>();;private static DefaultRedisScript<String> unlockScript=new DefaultRedisScript<String>();private static final Long EXEC_RESULT = 1L;@PostConstructpublic void init() {serverName = sername;getStringRedisTemplate();}private static void getStringRedisTemplate() {redisTemplate = AppContextUtil.getBean(StringRedisTemplate.class);}public static <T> T getString(String key, Class<T> valueType) {String value = (String) redisTemplate.opsForValue().get(key);if (StringUtils.isEmpty(value)) {return null;}return JSONObject.parseObject(value, valueType);}public static void delString(String key) {redisTemplate.delete(key);}public static <T> Boolean setString(String key, T value, long time, TimeUnit unit) {String valueStr = JSONObject.toJSONString(value);redisTemplate.opsForValue().set(key, valueStr);if (time > 0) {redisTemplate.expire(key, time, unit);}return true;}public static <T> Boolean setString(String key, T value, long time) {String valueStr = JSONObject.toJSONString(value);redisTemplate.opsForValue().set(key, valueStr);if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;}public static <T> List<T> getMapListValue(String mapKey, String key, Class<T> clazz) {String value = (String) redisTemplate.opsForHash().get(mapKey, key);return JSONObject.parseArray(value, clazz);}public static <T> T getMapValue(String mapKey, String key, Class<T> clazz) {String value = (String) redisTemplate.opsForHash().get(mapKey, key);return (T) JSONObject.parseObject(value, clazz);}public static <T> void putHashValue(String mapKey, String key, T value, long time) {redisTemplate.opsForHash().put(mapKey, key, JSON.toJSON(value));if (time > 0) {redisTemplate.expire(mapKey, time, TimeUnit.SECONDS);}}/** -------------------key相关操作--------------------- *//*** 删除key** @param key*/public static void delete(String key) {redisTemplate.delete(key);}/*** 批量删除key** @param keys*/public void delete(Collection<String> keys) {redisTemplate.delete(keys);}/*** 是否存在key** @param key* @return*/public static Boolean hasKey(String key) {return redisTemplate.hasKey(key);}/*** 是否存在key** @param key* @return*/public Boolean hasKeyPub(String key) {return redisTemplate.hasKey(key);}/*** 设置过期时间** @param key* @param timeout* @param unit* @return*/public static Boolean expirePub(String key, long timeout, TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}/*** 设置过期时间** @param key* @param timeout* @param unit* @return*/public Boolean expire(String key, long timeout, TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}/*** 设置过期时间** @param key* @param date* @return*/public Boolean expireAt(String key, Date date) {return redisTemplate.expireAt(key, date);}/*** 查找匹配的key** @param pattern* @return*/public Set<String> keys(String pattern) {return redisTemplate.keys(pattern);}/*** 返回 key 的剩余的过期时间** @param key* @param unit* @return*/public Long getExpire(String key, TimeUnit unit) {return redisTemplate.getExpire(key, unit);}/*** 返回 key 的剩余的过期时间** @param key* @return*/public Long getExpire(String key) {return redisTemplate.getExpire(key);}/*** 从当前数据库中随机返回一个 key** @return*/public String randomKey() {return (String) redisTemplate.randomKey();}/*** 修改 key 的名称** @param oldKey* @param newKey*/public void rename(String oldKey, String newKey) {redisTemplate.rename(oldKey, newKey);}/*** 仅当 newkey 不存在时,将 oldKey 改名为 newkey** @param oldKey* @param newKey* @return*/public Boolean renameIfAbsent(String oldKey, String newKey) {return redisTemplate.renameIfAbsent(oldKey, newKey);}/*** 返回 key 所储存的值的类型** @param key* @return*/public DataType type(String key) {return redisTemplate.type(key);}/** -------------------string相关操作--------------------- *//*** 设置指定 key 的值** @param key* @param value*/public void set(String key, String value) {redisTemplate.opsForValue().set(key, value);}/*** 获取指定 key 的值** @param key* @return*/public String get(String key) {return (String) redisTemplate.opsForValue().get(key);}/*** 返回 key 中字符串值的子字符** @param key* @param start* @param end* @return*/public String getRange(String key, long start, long end) {return redisTemplate.opsForValue().get(key, start, end);}/*** 将给定 key 的值设为 value ,并返回 key 的旧值(old value)** @param key* @param value* @return*/public String getAndSet(String key, String value) {return (String) redisTemplate.opsForValue().getAndSet(key, value);}/*** 对 key 所储存的字符串值,获取指定偏移量上的位(bit)** @param key* @param offset* @return*/public Boolean getBit(String key, long offset) {return redisTemplate.opsForValue().getBit(key, offset);}/*** 批量获取** @param keys* @return*/public List<String> multiGet(Collection<String> keys) {return redisTemplate.opsForValue().multiGet(keys);}/*** 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value** @param key 位置* @param value 值,true为1, false为0* @return*/public boolean setBit(String key, long offset, boolean value) {return redisTemplate.opsForValue().setBit(key, offset, value);}/*** 将值 value 关联到 key ,并将 key 的过期时间设为 timeout** @param key* @param value* @param timeout 过期时间* @param unit 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES* 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS*/public void setEx(String key, String value, long timeout, TimeUnit unit) {redisTemplate.opsForValue().set(key, value, timeout, unit);}/*** 只有在 key 不存在时设置 key 的值** @param key* @param value* @return 之前已经存在返回false, 不存在返回true*/public boolean setIfAbsent(String key, String value) {return redisTemplate.opsForValue().setIfAbsent(key, value);}/**** 只有在 key 不存在时设置 key 的值* @param key* @param value* @param time 单位 秒* @return 之前已经存在返回false, 不存在返回true* **/public static <T> Boolean setIfAbsent(String key, T value, long time) {String valueStr = JSONObject.toJSONString(value);return redisTemplate.opsForValue().setIfAbsent(key, valueStr, time,TimeUnit.SECONDS);}/*** 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始** @param key* @param value* @param offset 从指定位置开始覆写*/public void setRange(String key, String value, long offset) {redisTemplate.opsForValue().set(key, value, offset);}/*** 获取字符串的长度** @param key* @return*/public Long size(String key) {return redisTemplate.opsForValue().size(key);}/*** 批量添加** @param maps*/public void multiSet(Map<String, String> maps) {redisTemplate.opsForValue().multiSet(maps);}/*** 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在** @param maps* @return 之前已经存在返回false, 不存在返回true*/public boolean multiSetIfAbsent(Map<String, String> maps) {return redisTemplate.opsForValue().multiSetIfAbsent(maps);}/*** 增加(自增长), 负数则为自减** @param key* @return*/public Long incrBy(String key, long increment) {return redisTemplate.opsForValue().increment(key, increment);}/*** @param key* @return*/public Double incrByFloat(String key, double increment) {return redisTemplate.opsForValue().increment(key, increment);}/*** 追加到末尾** @param key* @param value* @return*/public Integer append(String key, String value) {return redisTemplate.opsForValue().append(key, value);}/** -------------------hash相关操作------------------------- *//*** 获取存储在哈希表中指定字段的值** @param key* @param field* @return*/public Object hGet(String key, String field) {return redisTemplate.opsForHash().get(key, field);}/*** 获取所有给定字段的值** @param key* @return*/public Map<Object, Object> hGetAll(String key) {return redisTemplate.opsForHash().entries(key);}/*** 获取所有给定字段的值** @param key* @param fields* @return*/public List<Object> hMultiGet(String key, Collection<Object> fields) {return redisTemplate.opsForHash().multiGet(key, fields);}public static <T> void hPut(String key, String hashKey, T value) {// 将value转换为JSON字符串String jsonValue = JSON.toJSONString(value);redisTemplate.opsForHash().put(key, hashKey, jsonValue);}public static <T> void hPutAll(String key, Map<String, T> maps) {Map<String, String> values = new HashMap<>();maps.keySet().forEach(mapKey -> {values.put(mapKey, JSONObject.toJSONString(maps.get(mapKey)));});redisTemplate.opsForHash().putAll(key, values);}public static <T> void hPutAll_Integer(String key, Map<Integer, T> maps) {Map<Integer, String> values = new HashMap<>();maps.keySet().forEach(mapKey -> {values.put(mapKey, JSONObject.toJSONString(maps.get(mapKey)));});redisTemplate.opsForHash().putAll(key, values);}/*** 仅当hashKey不存在时才设置** @param key* @param hashKey* @param value* @return*/public Boolean hPutIfAbsent(String key, String hashKey, String value) {return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);}/*** 删除一个或多个哈希表字段** @param key* @param fields* @return*/public Long hDelete(String key, Object... fields) {return redisTemplate.opsForHash().delete(key, fields);}/*** 查看哈希表 key 中,指定的字段是否存在** @param key* @param field* @return*/public boolean hExists(String key, String field) {return redisTemplate.opsForHash().hasKey(key, field);}/*** 为哈希表 key 中的指定字段的整数值加上增量 increment** @param key* @param field* @param increment* @return*/public Long hIncrBy(String key, Object field, long increment) {return redisTemplate.opsForHash().increment(key, field, increment);}/*** 为哈希表 key 中的指定字段的整数值加上增量 increment** @param key* @param field* @param delta* @return*/public Double hIncrByFloat(String key, Object field, double delta) {return redisTemplate.opsForHash().increment(key, field, delta);}/*** 获取所有哈希表中的字段** @param key* @return*/public Set<Object> hKeys(String key) {return redisTemplate.opsForHash().keys(key);}/*** 获取哈希表中字段的数量** @param key* @return*/public Long hSize(String key) {return redisTemplate.opsForHash().size(key);}/*** 获取哈希表中所有值** @param key* @return*/public List<Object> hValues(String key) {return redisTemplate.opsForHash().values(key);}/*** 迭代哈希表中的键值对** @param key* @param options* @return*/public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {return redisTemplate.opsForHash().scan(key, options);}/** ------------------------list相关操作---------------------------- *//*** 通过索引获取列表中的元素** @param key* @param index* @return*/public String lIndex(String key, long index) {return (String) redisTemplate.opsForList().index(key, index);}/*** 获取列表指定范围内的元素** @param key* @param start 开始位置, 0是开始位置* @param end 结束位置, -1返回所有* @return*/public List<String> lRange(String key, long start, long end) {return redisTemplate.opsForList().range(key, start, end);}/*** 存储在list头部** @param key* @param value* @return*/public Long lLeftPush(String key, String value) {return redisTemplate.opsForList().leftPush(key, value);}/*** @param key* @param value* @return*/public Long lLeftPushAll(String key, String... value) {return redisTemplate.opsForList().leftPushAll(key, value);}/*** @param key* @param value* @return*/public Long lLeftPushAll(String key, Collection<String> value) {return redisTemplate.opsForList().leftPushAll(key, value);}/*** 当list存在的时候才加入** @param key* @param value* @return*/public Long lLeftPushIfPresent(String key, String value) {return redisTemplate.opsForList().leftPushIfPresent(key, value);}/*** 如果pivot存在,再pivot前面添加** @param key* @param pivot* @param value* @return*/public Long lLeftPush(String key, String pivot, String value) {return redisTemplate.opsForList().leftPush(key, pivot, value);}/*** @param key* @param value* @return*/public Long lRightPush(String key, String value) {return redisTemplate.opsForList().rightPush(key, value);}/*** @param key* @param value* @return*/public Long lRightPushAll(String key, String... value) {return redisTemplate.opsForList().rightPushAll(key, value);}/*** @param key* @param value* @return*/public Long lRightPushAll(String key, Collection<String> value) {return redisTemplate.opsForList().rightPushAll(key, value);}/*** 为已存在的列表添加值** @param key* @param value* @return*/public Long lRightPushIfPresent(String key, String value) {return redisTemplate.opsForList().rightPushIfPresent(key, value);}/*** 在pivot元素的右边添加值** @param key* @param pivot* @param value* @return*/public Long lRightPush(String key, String pivot, String value) {return redisTemplate.opsForList().rightPush(key, pivot, value);}/*** 通过索引设置列表元素的值** @param key* @param index 位置* @param value*/public void lSet(String key, long index, String value) {redisTemplate.opsForList().set(key, index, value);}/*** 移出并获取列表的第一个元素** @param key* @return 删除的元素*/public String lLeftPop(String key) {return (String) redisTemplate.opsForList().leftPop(key);}/*** 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止** @param key* @param timeout 等待时间* @param unit 时间单位* @return*/public String lBLeftPop(String key, long timeout, TimeUnit unit) {return (String) redisTemplate.opsForList().leftPop(key, timeout, unit);}/*** 移除并获取列表最后一个元素** @param key* @return 删除的元素*/public String lRightPop(String key) {return (String) redisTemplate.opsForList().rightPop(key);}/*** 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止** @param key* @param timeout 等待时间* @param unit 时间单位* @return*/public String lBRightPop(String key, long timeout, TimeUnit unit) {return (String) redisTemplate.opsForList().rightPop(key, timeout, unit);}/*** 移除列表的最后一个元素,并将该元素添加到另一个列表并返回** @param sourceKey* @param destinationKey* @return*/public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {return (String) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);}/*** 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止** @param sourceKey* @param destinationKey* @param timeout* @param unit* @return*/public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,long timeout, TimeUnit unit) {return (String) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);}/*** 删除集合中值等于value得元素** @param key* @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;* index<0, 从尾部开始删除第一个值等于value的元素;* @param value* @return*/public Long lRemove(String key, long index, String value) {return redisTemplate.opsForList().remove(key, index, value);}/*** 裁剪list** @param key* @param start* @param end*/public void lTrim(String key, long start, long end) {redisTemplate.opsForList().trim(key, start, end);}/*** 获取列表长度** @param key* @return*/public Long lLen(String key) {return redisTemplate.opsForList().size(key);}/** --------------------set相关操作-------------------------- *//*** set添加元素** @param key* @param values* @return*/public Long sAdd(String key, String... values) {return redisTemplate.opsForSet().add(key, values);}/*** set移除元素** @param key* @param values* @return*/public Long sRemove(String key, Object... values) {return redisTemplate.opsForSet().remove(key, values);}/*** 移除并返回集合的一个随机元素** @param key* @return*/public String sPop(String key) {return (String) redisTemplate.opsForSet().pop(key);}/*** 将元素value从一个集合移到另一个集合** @param key* @param value* @param destKey* @return*/public Boolean sMove(String key, String value, String destKey) {return redisTemplate.opsForSet().move(key, value, destKey);}/*** 获取集合的大小** @param key* @return*/public Long sSize(String key) {return redisTemplate.opsForSet().size(key);}/*** 判断集合是否包含value** @param key* @param value* @return*/public Boolean sIsMember(String key, Object value) {return redisTemplate.opsForSet().isMember(key, value);}/*** 获取两个集合的交集** @param key* @param otherKey* @return*/public Set<String> sIntersect(String key, String otherKey) {return redisTemplate.opsForSet().intersect(key, otherKey);}/*** 获取key集合与多个集合的交集** @param key* @param otherKeys* @return*/public Set<String> sIntersect(String key, Collection<String> otherKeys) {return redisTemplate.opsForSet().intersect(key, otherKeys);}/*** key集合与otherKey集合的交集存储到destKey集合中** @param key* @param otherKey* @param destKey* @return*/public Long sIntersectAndStore(String key, String otherKey, String destKey) {return redisTemplate.opsForSet().intersectAndStore(key, otherKey,destKey);}/*** key集合与多个集合的交集存储到destKey集合中** @param key* @param otherKeys* @param destKey* @return*/public Long sIntersectAndStore(String key, Collection<String> otherKeys,String destKey) {return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,destKey);}/*** 获取两个集合的并集** @param key* @param otherKeys* @return*/public Set<String> sUnion(String key, String otherKeys) {return redisTemplate.opsForSet().union(key, otherKeys);}/*** 获取key集合与多个集合的并集** @param key* @param otherKeys* @return*/public Set<String> sUnion(String key, Collection<String> otherKeys) {return redisTemplate.opsForSet().union(key, otherKeys);}/*** key集合与otherKey集合的并集存储到destKey中** @param key* @param otherKey* @param destKey* @return*/public Long sUnionAndStore(String key, String otherKey, String destKey) {return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);}/*** key集合与多个集合的并集存储到destKey中** @param key* @param otherKeys* @param destKey* @return*/public Long sUnionAndStore(String key, Collection<String> otherKeys,String destKey) {return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);}/*** 获取两个集合的差集** @param key* @param otherKey* @return*/public Set<String> sDifference(String key, String otherKey) {return redisTemplate.opsForSet().difference(key, otherKey);}/*** 获取key集合与多个集合的差集** @param key* @param otherKeys* @return*/public Set<String> sDifference(String key, Collection<String> otherKeys) {return redisTemplate.opsForSet().difference(key, otherKeys);}/*** key集合与otherKey集合的差集存储到destKey中** @param key* @param otherKey* @param destKey* @return*/public Long sDifference(String key, String otherKey, String destKey) {return redisTemplate.opsForSet().differenceAndStore(key, otherKey,destKey);}/*** key集合与多个集合的差集存储到destKey中** @param key* @param otherKeys* @param destKey* @return*/public Long sDifference(String key, Collection<String> otherKeys,String destKey) {return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,destKey);}/*** 获取集合所有元素** @param key* @return*/public Set<String> setMembers(String key) {return redisTemplate.opsForSet().members(key);}/*** 随机获取集合中的一个元素** @param key* @return*/public String sRandomMember(String key) {return (String) redisTemplate.opsForSet().randomMember(key);}/*** 随机获取集合中count个元素** @param key* @param count* @return*/public List<String> sRandomMembers(String key, long count) {return redisTemplate.opsForSet().randomMembers(key, count);}/*** 随机获取集合中count个元素并且去除重复的** @param key* @param count* @return*/public Set<String> sDistinctRandomMembers(String key, long count) {return redisTemplate.opsForSet().distinctRandomMembers(key, count);}/*** @param key* @param options* @return*/public Cursor<String> sScan(String key, ScanOptions options) {return redisTemplate.opsForSet().scan(key, options);}/**------------------zSet相关操作--------------------------------*//*** 添加元素,有序集合是按照元素的score值由小到大排列** @param key* @param value* @param score* @return*/public Boolean zAdd(String key, String value, double score) {return redisTemplate.opsForZSet().add(key, value, score);}/*** @param key* @param values* @return*/public Long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> values) {return redisTemplate.opsForZSet().add(key, values);}/*** @param key* @param values* @return*/public Long zRemove(String key, Object... values) {return redisTemplate.opsForZSet().remove(key, values);}/*** 增加元素的score值,并返回增加后的值** @param key* @param value* @param delta* @return*/public Double zIncrementScore(String key, String value, double delta) {return redisTemplate.opsForZSet().incrementScore(key, value, delta);}/*** 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列** @param key* @param value* @return 0表示第一位*/public Long zRank(String key, Object value) {return redisTemplate.opsForZSet().rank(key, value);}/*** 返回元素在集合的排名,按元素的score值由大到小排列** @param key* @param value* @return*/public Long zReverseRank(String key, Object value) {return redisTemplate.opsForZSet().reverseRank(key, value);}/*** 获取集合的元素, 从小到大排序** @param key* @param start 开始位置* @param end 结束位置, -1查询所有* @return*/public Set<String> zRange(String key, long start, long end) {return redisTemplate.opsForZSet().range(key, start, end);}/*** 获取集合元素, 并且把score值也获取** @param key* @param start* @param end* @return*/public Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start,long end) {return redisTemplate.opsForZSet().rangeWithScores(key, start, end);}/*** 根据Score值查询集合元素** @param key* @param min 最小值* @param max 最大值* @return*/public Set<String> zRangeByScore(String key, double min, double max) {return redisTemplate.opsForZSet().rangeByScore(key, min, max);}/*** 根据Score值查询集合元素, 从小到大排序** @param key* @param min 最小值* @param max 最大值* @return*/public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,double min, double max) {return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);}/*** @param key* @param min* @param max* @param start* @param end* @return*/public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key,double min, double max, long start, long end) {return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,start, end);}/*** 获取集合的元素, 从大到小排序** @param key* @param start* @param end* @return*/public Set<String> zReverseRange(String key, long start, long end) {return redisTemplate.opsForZSet().reverseRange(key, start, end);}/*** 获取集合的元素, 从大到小排序, 并返回score值** @param key* @param start* @param end* @return*/public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key,long start, long end) {return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,end);}/*** 根据Score值查询集合元素, 从大到小排序** @param key* @param min* @param max* @return*/public Set<String> zReverseRangeByScore(String key, double min,double max) {return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);}/*** 根据Score值查询集合元素, 从大到小排序** @param key* @param min* @param max* @return*/public Set<ZSetOperations.TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double min, double max) {return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,min, max);}/*** @param key* @param min* @param max* @param start* @param end* @return*/public Set<String> zReverseRangeByScore(String key, double min,double max, long start, long end) {return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,start, end);}/*** 根据score值获取集合元素数量** @param key* @param min* @param max* @return*/public Long zCount(String key, double min, double max) {return redisTemplate.opsForZSet().count(key, min, max);}/*** 获取集合大小** @param key* @return*/public Long zSize(String key) {return redisTemplate.opsForZSet().size(key);}/*** 获取集合大小** @param key* @return*/public Long zZCard(String key) {return redisTemplate.opsForZSet().zCard(key);}/*** 获取集合中value元素的score值** @param key* @param value* @return*/public Double zScore(String key, Object value) {return redisTemplate.opsForZSet().score(key, value);}/*** 移除指定索引位置的成员** @param key* @param start* @param end* @return*/public Long zRemoveRange(String key, long start, long end) {return redisTemplate.opsForZSet().removeRange(key, start, end);}/*** 根据指定的score值的范围来移除成员** @param key* @param min* @param max* @return*/public Long zRemoveRangeByScore(String key, double min, double max) {return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);}/*** 获取key和otherKey的并集并存储在destKey中** @param key* @param otherKey* @param destKey* @return*/public Long zUnionAndStore(String key, String otherKey, String destKey) {return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);}/*** @param key* @param otherKeys* @param destKey* @return*/public Long zUnionAndStore(String key, Collection<String> otherKeys,String destKey) {return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);}/*** 交集** @param key* @param otherKey* @param destKey* @return*/public Long zIntersectAndStore(String key, String otherKey,String destKey) {return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,destKey);}/*** 交集** @param key* @param otherKeys* @param destKey* @return*/public Long zIntersectAndStore(String key, Collection<String> otherKeys,String destKey) {return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey);}/*** @param key* @param options* @return*/public Cursor<ZSetOperations.TypedTuple<String>> zScan(String key, ScanOptions options) {return redisTemplate.opsForZSet().scan(key, options);}public static boolean lock(String lockKey, String lockSign, long expireTime) {String script ="if redis.call('setNx',KEYS[1],ARGV[1]) then " +" if redis.call('get',KEYS[1])==ARGV[1] then " +" return redis.call('expire',KEYS[1],ARGV[2]) " +" else " +" return '0' " +" end " +"end";lockScript.setScriptText(script);lockScript.setResultType(String.class);Object result = redisTemplate.execute(lockScript, stringSerializer, stringSerializer, Collections.singletonList(lockKey),lockSign, expireTime + "");return EXEC_RESULT.equals(result);}public static boolean unlock(String lockKey, String lockSign) {String script="if redis.call('get',KEYS[1]) == ARGV[1] then " +" return redis.call('del',KEYS[1]) " +"else " +" return '0' " +"end";unlockScript.setScriptText(script);unlockScript.setResultType(String.class);Object result = redisTemplate.execute(unlockScript, stringSerializer, stringSerializer, Collections.singletonList(lockKey),lockSign);return EXEC_RESULT.equals(result);}public static boolean tryLock(String lockKey, String lockSign, long expireTime, int tryTimes, long interval) {if (tryTimes < 1) {tryTimes = 1;}int count = 0;while (true) {boolean success = lock(lockKey, lockSign, expireTime);if (success) {return true;} else {count++;if (count >= tryTimes) {log.error("{}次加锁失败,key={},lockSign={},", count, lockKey, lockSign);return false;}log.error("第{}次尝试加锁失败,key={},lockSign={},", count, lockKey, lockSign);if (interval > 0) {try {Thread.sleep(interval);} catch (InterruptedException e) {log.error("尝试加锁线程出现异常", e);Thread.currentThread().interrupt();}}}}}public static boolean tryUnlock(String lockKey, String lockSign) {return unlock(lockKey, lockSign);}
}
- 文件上传与下载工具类
用于项目 文件上传和下载,这个工具类 是基于 x-file-storage 依赖 来实现的。
这里我只分享了一个 工具类 实际对于这个 依赖 能用于到 springBoot框架中 还需要很多配置 对于这些配置这篇文章就不一一介绍了。具体搭建可以参考 下面的这个博客 来完整这个依赖的完整配置。
如果对于这个依赖不太熟悉的小伙伴可以去参考一下 这个博客
https://blog.csdn.net/Drug_/article/details/143402973
package com.music.base.utils;import com.music.base.exception.JsonlRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.Tika;
import org.dromara.x.file.storage.core.Downloader;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.*;@Slf4j
@Component
public class UploadHelper {@Autowiredprivate FileStorageService fileStorageService;//上传文件public FileInfo uploadFile(MultipartFile file, String fileDir) {if (StringUtils.isEmpty(fileDir)) {fileDir = "files";}if (file.isEmpty()) {throw new JsonlRuntimeException("请上传文件!");}// 获取文件的MIME类型String mimeType = getMimeType(file);// 检查是否允许MIME类型if (!isValidMimeType(mimeType,true)) {throw new JsonlRuntimeException("文件类型不合法!");}FileInfo fileInfo= fileStorageService.of(file).setPath(generateFilePath(fileDir)).setSaveFilename(getFileName() + "." + getFileExtensionWithDot(Objects.requireNonNull(file.getOriginalFilename()))).upload();return fileInfo;}private String getMimeType(MultipartFile file) {try {Tika tika = new Tika();return tika.detect(file.getInputStream());} catch (IOException e) {return "";}}//文件验证 isAll true 全部验证 false 只验证图片private boolean isValidMimeType(String mimeType,boolean isAll) {if(isAll){// 允许的MIME类型列表String[] allowedMimeTypes = {"image/png", "image/jpeg", "image/gif","audio/mp3","audio/mpeg"};for (String allowedMimeType : allowedMimeTypes) {if (allowedMimeType.equalsIgnoreCase(mimeType)) {return true;}}return false;}else{// 允许的MIME类型列表String[] allowedMimeTypes = {"image/png", "image/jpeg", "image/gif"};for (String allowedMimeType : allowedMimeTypes) {if (allowedMimeType.equalsIgnoreCase(mimeType)) {return true;}}return false;}}//定义文件路径private String generateFilePath(String fileDir) {// 'yyyyMMdd'String currentDate = new java.text.SimpleDateFormat("yyyyMMdd").format(new Date());// file nameString fileName = "upload/" + fileDir + "/" + currentDate + "/";return fileName;}//随机文件名private String getFileName() {// unique IDString uniqueID = UUID.randomUUID().toString();// 10000 and 99999int randomNum = (int) (Math.random() * (99999 - 10000 + 1)) + 10000;return uniqueID + randomNum;}//获取文件后缀private String getFileExtensionWithDot(String fileName) {int dotIndex = fileName.lastIndexOf('.');if (dotIndex > 0 && dotIndex < fileName.length() - 1) {return fileName.substring(dotIndex + 1);}return "";}public Downloader downLoadFile(String id) {FileInfo fileInfoByUrl = fileStorageService.getFileInfoByUrl(id);return fileStorageService.download(fileInfoByUrl);}/*** 从给定的路径字符串中提取文件名和文件所在的目录路径* @param filePath 完整的文件路径* @return 一个字符串数组,第一个元素是文件名,第二个元素是目录路径*/public static String[] extractFileNameAndPath(String filePath) {// 获取最后一个 '/' 之后的文件名String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);// 获取最后一个 '/' 之前的路径String directoryPath = filePath.substring(0, filePath.lastIndexOf("/"));return new String[] { fileName, directoryPath };}}
目前我们只简单的做了一些工具类,如果后续项目中 有用到新的工具类 我们在封装使用。
下面再分享一下 返回值的封装。
就是 返回给前端的数据结构 json格式
一般我们返回 都会有常用几个参数
- 状态码 code
- 提示语 msg
- 数据 data
返回值封装 文件一
package com.music.base.out;import java.io.Serializable;/*** User:Json* Date: 2024/3/22**/
public interface IResultCode extends Serializable {/** 错误级别,notice、warning、error。默认 warning*/public final static String ERROR_LEVEL_WARNING = "warning";public final static String ERROR_LEVEL_NOTICE = "notice";public final static String ERROR_LEVEL_ERROR = "error";/*** 默认为空消息*/public static final String DEFAULT_NULL_MESSAGE = "暂无承载数据";/*** 消息** @return String*/String getMessage();/*** 状态码** @return int*/int getCode();}
文件二:
package com.music.base.out;import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;import java.io.Serializable;
import java.util.Optional;import static org.springframework.util.ObjectUtils.nullSafeEquals;/*** User:Json* Date: 2024/3/22**/
@Getter
@Setter
@ToString
@NoArgsConstructor
@Slf4j
public class R<T> implements Serializable {private static final long serialVersionUID = 1L;private int code;private String errorLevel;private T data;private String msg;public R(ResultCode resultCode) {this(resultCode, null, resultCode.getMessage());}public R(ResultCode resultCode, String msg) {this(resultCode, null, msg);}public R(ResultCode resultCode, T data) {this(resultCode, data, resultCode.getMessage());}public R(ResultCode resultCode, T data, String msg) {this(resultCode.code, data, msg, null);}private R(int errorCode, T data, String msg, String errorLevel) {this.data = data;this.msg = msg;this.code = errorCode;this.errorLevel = errorLevel;}public R(Integer code, String message) {this(code, null, message, null);}public R(Integer code, String message, String errorLevel) {this(code, null, message, errorLevel);}/*** 判断返回是否为成功** @param result Result* @return 是否成功*/public static boolean isSuccess(@Nullable R<?> result) {return Optional.ofNullable(result).map(x -> nullSafeEquals(ResultCode.SUCCESS.code, x.code)).orElse(Boolean.FALSE);}/*** 判断返回是否为成功** @param result Result* @return 是否成功*/public static boolean isNotSuccess(@Nullable R<?> result) {return !R.isSuccess(result);}/*** 返回R** @param data 数据* @param <T> T 泛型标记* @return R*/public static <T> R<T> data(T data) {return data(data, ResultCode.SUCCESS.getMessage());}/*** 返回R** @param data 数据* @param msg 消息* @param <T> T 泛型标记* @return R*/public static <T> R<T> data(T data, String msg) {return data(ResultCode.SUCCESS.code, data, msg);}/*** 返回R** @param code 状态码* @param data 数据* @param msg 消息* @param <T> T 泛型标记* @return R*/public static <T> R<T> data(int code, T data, String msg) {return new R<>(code, data, data == null ? IResultCode.DEFAULT_NULL_MESSAGE : msg, null);}/*** 返回R** @param <T> T 泛型标记* @return R*/public static <T> R<T> success() {return new R<>(ResultCode.SUCCESS);}/*** 返回R** @param msg 消息* @param <T> T 泛型标记* @return R*/public static <T> R<T> success(String msg) {return new R<>(ResultCode.SUCCESS, msg);}/*** 返回R** @param resultCode 业务代码* @param <T> T 泛型标记* @return R*/public static <T> R<T> success(ResultCode resultCode) {return new R(resultCode);}/*** 返回R** @param resultCode 业务代码* @param msg 消息* @param <T> T 泛型标记* @return R*/public static <T> R<T> success(ResultCode resultCode, String msg) {return new R<>(resultCode, msg);}/*** 返回R** @return R*/public static <T> R<T> fail() {return new R<>(ResultCode.NORMAL_ERROR.getCode(), ResultCode.NORMAL_ERROR.getMessage(), IResultCode.ERROR_LEVEL_WARNING);}/*** 返回R** @param msg 消息* @param <T> T 泛型标记* @return R*/public static <T> R<T> fail(String msg) {return new R<>(ResultCode.NORMAL_ERROR.getCode(), msg, IResultCode.ERROR_LEVEL_WARNING);}/*** 返回R** @param code 状态码* @param msg 消息* @param <T> T 泛型标记* @return R*/public static <T> R<T> fail(int code, String msg) {return new R<>(code, null, msg, IResultCode.ERROR_LEVEL_WARNING);}/*** 返回R** @param r 返回对象* @param <T> T 泛型标记* @return R*/public static <T> R<T> fail(R r) {return fail(r.getCode(), r.getMsg());}/*** 返回R** @param resultCode 业务代码* @param <T> T 泛型标记* @return R*/public static <T> R<T> fail(ResultCode resultCode) {return fail(resultCode.getCode(), resultCode.getMessage());}/*** 返回R** @param resultCode 业务代码* @param <T> T 泛型标记* @return R*/public static <T> R<T> fail(ResultCode resultCode, String msg) {return fail(resultCode.getCode(), msg);}}
文件三:
package com.music.base.out;import lombok.AllArgsConstructor;
import lombok.Getter;/*** User:Json* Date: 2024/3/22**/
@Getter
@AllArgsConstructor
public enum ResultCode implements IResultCode {/** 接口正常响应.*/SUCCESS("success", 1000),/** 服务通信发生的错误(致命),需要存储信息并且及时检查.*/SERVICE_ERROR("服务通信发生的错误(致命),需要存储信息并且及时检查!", 1010),/** 普通业务错误,前端弹层提示.*/NORMAL_ERROR("普通业务错误,操作失败!", 1001),/** 登录超时,前端弹层提示,且做登录操作.*/LOGIN_ERROR("登录超时!", 1002),/** 权限错误.*/AUTH_ERROR("权限错误!", 1003),AUTH_ROLE_ERROR("账号异常!未绑定角色身份!", 1003),AUTH_NO_ERROR("无权访问!", 1003),/** 验证码错误.*/VERIFY_ERROR("验证码错误!", 1004),;/*** 信息描述*/final String message;/*** code编码*/final int code;}
接下来 我再分享 两个自定义注解 配合AOP 切面编程 来实现的 封装。
注解一: NoLogin
应用场景 这个注解 我们会在拦截器里使用 如果哪些接口不需要 登录 可以被用户访问,那就可以在控制里的方法上 打上这个注解 即可。
使用方式:
第一步:创建声明 NoLogin 文件
package com.music.base.aop.noLogin;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/**** Json* **/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoLogin {String name() default "";
}
第二步:我们声明一个拦截器
主要实现 判断请求的接口是否需要登录
如果不需要登录 直接 放行
如果需要登录 验证 前端是否传 token
验证token 是否正确
正确后 就把当前用户信息 存到 当前线程缓存中,请求完毕后 移除缓存
代码还是比较简单的 。
LoginInterceptor 对于这个类 第二种在定义 initConfig 文件的时候 提到过。
package com.music.base.webFilterIntercept;import com.alibaba.fastjson.JSON;
import com.music.base.aop.noLogin.NoLogin;
import com.music.base.enums.HttpHeaderEnums;
import com.music.base.out.R;
import com.music.base.out.ResultCode;
import com.music.base.utils.JwtHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Map;@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("============== 登录 ============== ");if (handler instanceof ResourceHttpRequestHandler) return true;if (!(handler instanceof HandlerMethod)) {return false;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();if (method.isAnnotationPresent(NoLogin.class)) {NoLogin noLogin = method.getAnnotation(NoLogin.class);if (noLogin != null) {if(StringUtils.isEmpty(noLogin.name())){return true;}}}//1.获取请求参数access-tokenString token = request.getHeader(HttpHeaderEnums.ACCESS_TOKEN.getKey());if (token == null) token = request.getParameter(HttpHeaderEnums.ACCESS_TOKEN.getKey());Map<String, Object> loginUserInfo = JwtHelper.verifyAll(token);if (StringUtils.isEmpty(token) || CollectionUtils.isEmpty(loginUserInfo)) {response.setContentType("application/json;charset=utf-8");PrintWriter out = response.getWriter();out.println(JSON.toJSONString(R.fail(ResultCode.LOGIN_ERROR)));out.flush();out.close();return false;}LoginUserInfo.setLoginUserId((Integer) loginUserInfo.get("userId"));return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//会话结束移除线程缓存LoginUserInfo.removeLoginUserId();}}
第三步:定义一个 当前登录存储
package com.music.base.webFilterIntercept;//当前用户信息
public class LoginUserInfo {private static ThreadLocal<Integer> loginUserId = new ThreadLocal<Integer>();public static void setLoginUserId(Integer userId) {loginUserId.set(userId);}public static Integer getLoginUserId() {return loginUserId.get();}public static void removeLoginUserId() {loginUserId.remove();}}
使用方式:比如登录接口不需要验证用户登录就可以访问。
那就在控制器方法上打上这个注解即可
@NoLogin
举例:
//登录@PostMapping("login")@NoLoginpublic R login(@RequestBody MuUser muUser){return userService.login(muUser);}
注解二:@RateRequest
这个注解主要用于 防止用户重复请求。允许多少秒后 才能再次请求
举例应用场景:
比如用户在网站上下单,为了防止用户重复提交表单,可以在控制器上打上这种注解,一个请求到达控制器后,会先通过这个注解来验证这个请求是否请求过
如果请求过 就会给用户提示,您已提交,不能重复提交等提示语。
package com.music.base.aop.rateRequest.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** User:Json* Date: 2024/4/19**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateRequest {/*** 单位:秒* 多久可以再次请求 默认 6秒* @return*/int expireTime() default 6;}
当然只定义注解也是不行的,我们需要在根据注解 来做一个 AOP 来使用这个注解完成 多次请求拦截。
package com.music.base.aop.rateRequest;import com.music.base.aop.rateRequest.annotation.RateRequest;
import com.music.base.exception.JsonlRuntimeException;
import com.music.base.utils.MD5;
import com.music.base.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;/*** User:Json* Date: 2024/4/19**/
@Aspect
@Component
@Slf4j
public class RateRequestAop {@Pointcut("@annotation(com.music.base.aop.rateRequest.annotation.RateRequest)")public void duplicate() {}/*** @param pjp* @return* @throws Throwable*/@Around("duplicate()")public Object aroundRateRequest(ProceedingJoinPoint pjp) throws Throwable {MethodSignature msig = (MethodSignature) pjp.getSignature();Method currentMethod = pjp.getTarget().getClass().getMethod(msig.getName(), msig.getParameterTypes());RateRequest notDuplicate = currentMethod.getAnnotation(RateRequest.class);int expireTime = notDuplicate.expireTime();String className = pjp.getTarget().getClass().getName();String methodName = currentMethod.getName();StringBuilder handleArr = new StringBuilder(className);handleArr.append("-" + methodName);Object[] args = pjp.getArgs();for (Object object : args) {if (object != null && !(object instanceof HttpServletRequest)) {handleArr.append(object.toString());}}String info = "rate_request:" + MD5.encoder(handleArr.toString());//当key 不存在 setBoolean result= RedisUtils.setIfAbsent(info,handleArr,expireTime);if (!result) {log.warn("【多次请求拦截 RateRequest:】" + handleArr);throw new JsonlRuntimeException("请求正在处理,请稍后...");}try {return pjp.proceed();} finally {RedisUtils.delete(info);}}}
使用方式也是 哪个 控制器里的方法需要 就打在哪个控制器上即可。
最后再分享一个 日志的配置 。对于日志的配置 一般我们会在这个文件夹下
创建一个 logback-spring.xml 来配置项目中的日志 ,让我们更好的来管理 我们的项目日志
内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds"><!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --><!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --><!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --><!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --><springProperty scope="context" name="serviceName" source="spring.application.name" /><contextName>logback</contextName><!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --><!-- 日志输出位置--><property name="log.path" value="/home/temp/${serviceName}/logs" />
<!-- 按照日期一个文件夹--><timestamp key="datetime" datePattern="yyyy-MM-dd"/><!-- 彩色日志 --><!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 --><!-- magenta:洋红 --><!-- boldMagenta:粗红--><!-- cyan:青色 --><!-- white:白色 --><!-- magenta:洋红 --><property name="CONSOLE_LOG_PATTERN"value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%magenta(%msg%n)"/><!--输出到控制台--><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--><!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 --><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUG</level></filter><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!-- 设置字符集 --><charset>UTF-8</charset></encoder></appender><!--输出到文件--><!-- 时间滚动输出 level为 INFO 日志 --><appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/${datetime}/log_info.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 每天日志归档路径以及格式 --><fileNamePattern>${log.path}/${datetime}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>7</maxHistory></rollingPolicy><!-- 此日志文件只记录info级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 时间滚动输出 level为 WARN 日志 --><appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/${datetime}/log_warn.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/${datetime}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>7</maxHistory></rollingPolicy><!-- 此日志文件只记录warn级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>warn</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 时间滚动输出 level为 ERROR 日志 --><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/${datetime}/log_error.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/${datetime}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>7</maxHistory></rollingPolicy><!-- 此日志文件只记录ERROR级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 时间滚动输出 level为 mybatis debug 日志 只有debug模式mybatis 日志才会 所以必须开启debug --><appender name="MYBATIS_DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/${datetime}/log_mybatis_sql.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/${datetime}/mybatisSql/log-mybatis-sql-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>7</maxHistory></rollingPolicy><!-- 此日志文件只记录DEBUG级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>DEBUG</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。--><!--使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:--><!--开发环境:打印控制台--><springProfile name="dev"><!--可以输出项目中的,mybatis的sql debug日志 别的debug日志不打印, additivity="false" 不传播到根logger --><logger name="com.music.base.mapper" level="DEBUG" ><appender-ref ref="MYBATIS_DEBUG_FILE"/></logger><logger name="com.music.base.service" level="DEBUG" ><appender-ref ref="MYBATIS_DEBUG_FILE"/></logger><logger name="com.baomidou.mybatisplus" level="DEBUG" ><appender-ref ref="MYBATIS_DEBUG_FILE"/></logger><!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG可以包含零个或多个appender元素。--><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="WARN_FILE" /><appender-ref ref="ERROR_FILE" /></root></springProfile><!--生产环境:输出到文件--><springProfile name="prd"><logger name="com.music.*" level="DEBUG" additivity="false" ><appender-ref ref="MYBATIS_DEBUG_FILE"/></logger><logger name="com.music.mybatisplus" level="DEBUG" additivity="false"><appender-ref ref="MYBATIS_DEBUG_FILE"/></logger>
<!-- <logger name="com.baomidou.*" level="DEBUG" additivity="false" >-->
<!-- <appender-ref ref="MYBATIS_DEBUG_FILE"/>-->
<!-- </logger>--><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="ERROR_FILE" /><appender-ref ref="WARN_FILE" /></root></springProfile></configuration>
对于spring boot 日志 我以前也分享过很多篇 。有兴趣的小伙伴可以去看一下。
比如这一篇
https://blog.csdn.net/Drug_/article/details/139143307
实际上面的这些工具类 和一些 拦截器 注解的 封装 不仅仅会在 音乐播放器这个项目中 使用。基本上我们再开发任何项目的时候都有可能用到。所以 以上代码的分享 大家可以不限用于 音乐播放器这个项目中。因为在框架搭建的前期 ,我习惯性先封装一些 公共的通用性一些 工具类。还比如 时间处理的工具类 等等。 后续如果有业务需求的公共封装,我们可以再根据业务需求进行封装。对于前期的项目开发准备,我们只考虑一些通用性的 封装。
如果你是 刚刚开始学习编程 或者刚学习 springboot 框架。也可以尝试着先来封装一些 后续用的一些方法,我们把这些提炼出来封装成公共的。方便后续项目中使用。所以当我们拿到一个需求的时候。不要着急上来写业务逻辑代码。而是先整理思路,做好前期准备。再来写业务逻辑。这样后续才能写起来很轻松。
所以做一个 软件系统,如果要是做的很强大。我们的前期规划,思路整理这些环节是必不可少的。
好了 对于这个音乐播放器这个项目 java 后端分享就到这里了。下一章 应该就差不多可以创建数据库来开始写业务逻辑啦。
说明:
因核心分享 的是java编程。后续文章中 基本分享的都是java语言的代码。
对于前端的代码,在文章中就不分享了。只会分享一下页面效果。
当然项目完结后,我会把前后端项目源码打包好放到最后一篇文章中。
如果有小伙伴有兴趣可以订阅此专栏,后续会持续更新直到音乐播放器这个软件完成。
我会尽可能详细的以文字的形式分享出来 从0到1 写出来一个音乐播放器项目。