当前位置: 首页 > article >正文

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

  1. 文件上传与下载工具类
    用于项目 文件上传和下载,这个工具类 是基于 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格式
一般我们返回 都会有常用几个参数

  1. 状态码 code
  2. 提示语 msg
  3. 数据 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 写出来一个音乐播放器项目。


http://www.kler.cn/a/500599.html

相关文章:

  • Gitlab-Runner配置
  • windows及linux 安装 Yarn 4.x 版本
  • 潜力巨大但道路曲折的量子计算
  • 计算机网络(二)——物理层和数据链路层
  • Notepad++上NppFTP插件的安装和使用教程
  • springBoot整合ELK Windowsb版本 (elasticsearch+logstash+kibana)
  • golang中的eval,goeval,govaluate
  • 智能风控/数据分析 聚合 分组 连接
  • LeetCode-找出字符串中第一个匹配项的下标(028)
  • WPF控件Grid的布局和C1FlexGrid的多选应用
  • Golang笔记——channel
  • 软件系统分析与设计综合实践-家庭维修服务系统小程序(代码见附录,私发)
  • Xcode 正则表达式实现查找替换
  • JVM之垃圾回收器概述(续)的详细解析
  • 【机器学习】零售行业的智慧升级:机器学习驱动的精准营销与库存管理
  • 【Spring Boot 应用开发】-04 自动配置-数据源
  • 【优选算法篇】:深入浅出位运算--性能优化的利器
  • EFCore HasDefaultValueSql (续1 ValueGeneratedOnAdd)
  • 金融项目实战 04|JMeter实现自动化脚本接口测试及持续集成
  • PHP语言的软件工程
  • VSCode配置php开发环境
  • Microsoft Sql Server 2019 视图
  • 第六届土木建筑及灾害防控国际学术会议暨第三届智慧城市建筑与基础设施耐久性国际学术会议(CADPC DuraBI 2025)
  • 33_操作Redis分片集群
  • 用C语言实现推箱子小游戏
  • windows C#-泛型方法