3. 使用springboot做一个音乐播放器软件项目【封装项目使用的工具类】
上一章文章 我们做了 音乐播放器这个项目的 框架搭建和一些基础配置文件。 参考网址:
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;
@PostConstruct
public 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 {
@Autowired
private 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 name
String fileName = "upload/" + fileDir + "/" + currentDate + "/";
return fileName;
}
//随机文件名
private String getFileName() {
// unique ID
String uniqueID = UUID.randomUUID().toString();
// 10000 and 99999
int 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 {
@Override
public 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-token
String 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;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public 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")
@NoLogin
public 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 不存在 set
Boolean 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 写出来一个音乐播放器项目。