Files
HAOVP/docs/video-common模块实现步骤和代码.md
T

1789 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# video-common模块实现步骤和代码
基于Spring Boot 3.2.4、Spring Cloud 2023.0.1和Spring Cloud Alibaba 2023.0.1.0技术栈,完整实现video-common模块。
## 1. 目录结构设计
首先,创建以下目录结构:
```
video-common/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── nuaa/
│ │ │ └── video/
│ │ │ └── common/
│ │ │ ├── annotation/ # 自定义注解
│ │ │ ├── config/ # 配置类
│ │ │ ├── constants/ # 常量定义
│ │ │ ├── exception/ # 异常处理
│ │ │ ├── model/ # 通用数据模型
│ │ │ │ ├── dto/ # 数据传输对象
│ │ │ │ ├── entity/ # 实体类
│ │ │ │ ├── enums/ # 枚举类
│ │ │ │ └── vo/ # 视图对象
│ │ │ ├── utils/ # 工具类
│ │ │ └── feign/ # Feign接口
│ │ └── resources/
│ │ └── META-INF/
│ │ └── spring.factories # 自动配置
│ └── test/
└── pom.xml
```
## 2. 实现通用响应模型
创建统一的响应对象:
```java
// com.nuaa.video.common.model.vo.Result.java
package com.nuaa.video.common.model.vo;
import com.nuaa.video.common.model.enums.ResultCode;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.io.Serializable;
/**
* 统一响应结果类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
private Integer code;
/**
* 返回消息
*/
private String message;
/**
* 返回数据
*/
private T data;
/**
* 成功结果
*/
public static <T> Result<T> success() {
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
}
/**
* 成功结果(带数据)
*/
public static <T> Result<T> success(T data) {
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 成功结果(自定义消息和数据)
*/
public static <T> Result<T> success(String message, T data) {
return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 失败结果
*/
public static <T> Result<T> error() {
return new Result<>(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMessage(), null);
}
/**
* 失败结果(自定义消息)
*/
public static <T> Result<T> error(String message) {
return new Result<>(ResultCode.ERROR.getCode(), message, null);
}
/**
* 失败结果(自定义状态码和消息)
*/
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
/**
* 自定义结果
*/
public static <T> Result<T> result(ResultCode resultCode, T data) {
return new Result<>(resultCode.getCode(), resultCode.getMessage(), data);
}
}
```
## 3. 创建响应状态码枚举
```java
// com.nuaa.video.common.model.enums.ResultCode.java
package com.nuaa.video.common.model.enums;
import lombok.Getter;
/**
* 响应状态码枚举
*/
@Getter
public enum ResultCode {
/**
* 成功
*/
SUCCESS(200, "操作成功"),
/**
* 参数错误
*/
PARAM_ERROR(400, "参数错误"),
/**
* 未授权
*/
UNAUTHORIZED(401, "未授权"),
/**
* 禁止访问
*/
FORBIDDEN(403, "禁止访问"),
/**
* 资源不存在
*/
NOT_FOUND(404, "资源不存在"),
/**
* 服务器内部错误
*/
ERROR(500, "服务器内部错误"),
/**
* 服务不可用
*/
SERVICE_UNAVAILABLE(503, "服务不可用"),
/**
* 用户不存在
*/
USER_NOT_FOUND(1001, "用户不存在"),
/**
* 用户已存在
*/
USER_ALREADY_EXISTS(1002, "用户已存在"),
/**
* 密码错误
*/
PASSWORD_ERROR(1003, "密码错误"),
/**
* 视频不存在
*/
VIDEO_NOT_FOUND(2001, "视频不存在"),
/**
* 视频上传失败
*/
VIDEO_UPLOAD_FAILED(2002, "视频上传失败"),
/**
* 评论不存在
*/
COMMENT_NOT_FOUND(3001, "评论不存在");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
```
## 4. 实现全局异常处理
```java
// com.nuaa.video.common.exception.BusinessException.java
package com.nuaa.video.common.exception;
import com.nuaa.video.common.model.enums.ResultCode;
import lombok.Getter;
/**
* 业务异常类
*/
@Getter
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private final Integer code;
/**
* 错误消息
*/
private final String message;
public BusinessException(String message) {
this.code = ResultCode.ERROR.getCode();
this.message = message;
}
public BusinessException(Integer code, String message) {
this.code = code;
this.message = message;
}
public BusinessException(ResultCode resultCode) {
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
}
public BusinessException(ResultCode resultCode, String message) {
this.code = resultCode.getCode();
this.message = message;
}
}
```
```java
// com.nuaa.video.common.exception.GlobalExceptionHandler.java
package com.nuaa.video.common.exception;
import com.nuaa.video.common.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.error("业务异常:{}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常 (JSR303)
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
String message = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验异常:{}", message);
return Result.error(400, message);
}
/**
* 处理方法参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
String message = fieldErrors.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("方法参数校验异常:{}", message);
return Result.error(400, message);
}
/**
* 处理绑定异常
*/
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
String message = e.getBindingResult().getAllErrors().stream()
.map(error -> error.getDefaultMessage())
.collect(Collectors.joining(", "));
log.warn("参数绑定异常:{}", message);
return Result.error(400, message);
}
/**
* 处理未知异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常:", e);
return Result.error("系统繁忙,请稍后再试");
}
}
```
## 5. 实现JWT工具类
```java
// com.nuaa.video.common.utils.JwtUtils.java
package com.nuaa.video.common.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* JWT工具类
*/
@Slf4j
@Component
public class JwtUtils {
@Value("${jwt.secret:video-platform-secret-key-2024-nuaa}")
private String secret;
@Value("${jwt.expiration:86400}")
private long expiration;
@Value("${jwt.token-header:Authorization}")
private String tokenHeader;
/**
* 根据用户ID生成JWT
*/
public String generateToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
return createToken(claims, userId.toString());
}
/**
* 从JWT中获取用户ID
*/
public Long getUserIdFromToken(String token) {
return Long.valueOf(getClaimFromToken(token, Claims::getSubject));
}
/**
* 判断JWT是否过期
*/
public boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 验证JWT
*/
public boolean validateToken(String token, String username) {
try {
final String tokenUsername = getUsernameFromToken(token);
return (tokenUsername.equals(username) && !isTokenExpired(token));
} catch (Exception e) {
log.error("JWT验证失败:", e);
return false;
}
}
/**
* 从JWT中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, claims -> claims.get("username", String.class));
}
/**
* 获取JWT过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从JWT中获取指定声明
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 从JWT中获取所有声明
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
/**
* 创建JWT
*/
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.claims(claims)
.subject(subject)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(getSigningKey())
.compact();
}
/**
* 获取签名密钥 - 适配JJWT 0.12.3
*/
private SecretKey getSigningKey() {
byte[] keyBytes = secret.getBytes();
return Keys.hmacShaKeyFor(keyBytes);
}
}
```
## 6. 实现Redis工具类
```java
// com.nuaa.video.common.utils.RedisUtils.java
package com.nuaa.video.common.utils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*/
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnBean(RedisTemplate.class)
public class RedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存过期时间
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
log.error("Redis设置过期时间失败:", e);
return false;
}
}
/**
* 获取缓存过期时间
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*/
public boolean hasKey(String key) {
try {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
} catch (Exception e) {
log.error("Redis判断key是否存在失败:", e);
return false;
}
}
/**
* 删除缓存
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) List.of(key));
}
}
}
/**
* 获取普通缓存
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 设置普通缓存
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
log.error("Redis设置缓存失败:", e);
return false;
}
}
/**
* 设置普通缓存并设置过期时间
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
log.error("Redis设置缓存并设置过期时间失败:", e);
return false;
}
}
/**
* 递增
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().decrement(key, delta);
}
/**
* 获取Hash结构数据
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取Hash结构中的所有键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 设置Hash结构数据
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
log.error("Redis设置Hash结构数据失败:", e);
return false;
}
}
/**
* 设置Hash结构数据并设置过期时间
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("Redis设置Hash结构数据并设置过期时间失败:", e);
return false;
}
}
/**
* 向Hash结构中放入一个数据项
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
log.error("Redis向Hash结构中放入一个数据项失败:", e);
return false;
}
}
/**
* 向Hash结构中放入一个数据项并设置过期时间
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("Redis向Hash结构中放入一个数据项并设置过期时间失败:", e);
return false;
}
}
/**
* 删除Hash结构中的值
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断Hash结构中是否有该项的值
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* Hash结构中递增
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* Hash结构中递减
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
/**
* 获取Set结构数据
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
log.error("Redis获取Set结构数据失败:", e);
return null;
}
}
/**
* 向Set结构中添加值
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
log.error("Redis向Set结构中添加值失败:", e);
return 0;
}
}
/**
* 获取List结构数据
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
log.error("Redis获取List结构数据失败:", e);
return null;
}
}
/**
* 向List结构中添加值
*/
public long lSet(String key, Object value) {
try {
return redisTemplate.opsForList().rightPush(key, value);
} catch (Exception e) {
log.error("Redis向List结构中添加值失败:", e);
return 0;
}
}
}
```
## 7. 实现MinIO工具类
```java
// com.nuaa.video.common.utils.MinioUtils.java
package com.nuaa.video.common.utils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* MinIO工具类
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtils {
private final MinioClient minioClient;
@Value("${minio.bucketName:video-platform}")
private String bucketName;
/**
* 检查存储桶是否存在
*/
public boolean bucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("检查存储桶是否存在失败:", e);
return false;
}
}
/**
* 创建存储桶
*/
public void createBucket(String bucketName) {
try {
boolean exists = bucketExists(bucketName);
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
log.error("创建存储桶失败:", e);
throw new RuntimeException("创建存储桶失败", e);
}
}
/**
* 列出所有存储桶
*/
public List<Bucket> listBuckets() {
try {
return minioClient.listBuckets();
} catch (Exception e) {
log.error("列出所有存储桶失败:", e);
throw new RuntimeException("列出所有存储桶失败", e);
}
}
/**
* 列出存储桶中的所有对象
*/
public Iterable<Result<Item>> listObjects(String bucketName) {
return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).recursive(true).build());
}
/**
* 上传文件
*/
public String uploadFile(MultipartFile file, String fileType) {
try {
// 检查存储桶是否存在
createBucket(bucketName);
// 生成文件名
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
String objectName = fileType + "/" + fileName;
// 上传文件
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
return objectName;
} catch (Exception e) {
log.error("上传文件失败:", e);
throw new RuntimeException("上传文件失败", e);
}
}
/**
* 上传视频文件
*/
public String uploadVideo(MultipartFile file) {
return uploadFile(file, "videos");
}
/**
* 上传图片文件
*/
public String uploadImage(MultipartFile file) {
return uploadFile(file, "images");
}
/**
* 上传字节数组
*/
public String uploadBytes(byte[] bytes, String fileName, String contentType, String fileType) {
try {
// 检查存储桶是否存在
createBucket(bucketName);
// 生成文件名
String objectName = fileType + "/" + System.currentTimeMillis() + "_" + fileName;
// 上传字节数组
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(bais, bytes.length, -1)
.contentType(contentType)
.build()
);
return objectName;
} catch (Exception e) {
log.error("上传字节数组失败:", e);
throw new RuntimeException("上传字节数组失败", e);
}
}
/**
* 获取文件
*/
public InputStream getObject(String objectName) {
try {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
log.error("获取文件失败:", e);
throw new RuntimeException("获取文件失败", e);
}
}
/**
* 删除文件
*/
public void removeObject(String objectName) {
try {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
log.error("删除文件失败:", e);
throw new RuntimeException("删除文件失败", e);
}
}
/**
* 获取文件外链
*/
public String getPreSignedUrl(String objectName, int expiry) {
try {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(objectName)
.method(Method.GET)
.expiry(expiry, TimeUnit.SECONDS)
.build()
);
} catch (Exception e) {
log.error("获取文件外链失败:", e);
throw new RuntimeException("获取文件外链失败", e);
}
}
/**
* 获取文件外链(使用默认过期时间7天)
*/
public String getPreSignedUrl(String objectName) {
return getPreSignedUrl(objectName, 7 * 24 * 60 * 60);
}
/**
* 获取文件基础URL
*/
public String getBaseUrl(String objectName) {
try {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(objectName)
.method(Method.GET)
.build()
).split("\\?")[0];
} catch (Exception e) {
log.error("获取文件基础URL失败:", e);
throw new RuntimeException("获取文件基础URL失败", e);
}
}
}
```
## 8. 创建Redis配置类
```java
// com.nuaa.video.common.config.RedisConfig.java
package com.nuaa.video.common.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
/**
* Redis配置类
*/
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 创建配置好的ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
```
## 9. 创建MinIO配置类
```java
// com.nuaa.video.common.config.MinioConfig.java
package com.nuaa.video.common.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MinIO配置类
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
/**
* 服务地址
*/
private String endpoint;
/**
* 访问密钥
*/
private String accessKey;
/**
* 密钥
*/
private String secretKey;
/**
* 存储桶名称
*/
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
```
## 10. 创建MyBatis Plus配置类
```java
// com.nuaa.video.common.config.MybatisPlusConfig.java
package com.nuaa.video.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* MyBatis Plus配置类
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.nuaa.video.**.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 防止全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
```
## 11. 创建Web配置类
```java
// com.nuaa.video.common.config.WebConfig.java
package com.nuaa.video.common.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 配置跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
```
## 12. 创建分页工具类
```java
// com.nuaa.video.common.utils.PageUtils.java
package com.nuaa.video.common.utils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页工具类
*/
@Data
public class PageUtils<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 每页记录数
*/
private long size;
/**
* 当前页数
*/
private long current;
/**
* 总页数
*/
private long pages;
/**
* 列表数据
*/
private List<T> records;
/**
* 是否有前一页
*/
private boolean hasPrevious;
/**
* 是否有下一页
*/
private boolean hasNext;
/**
* 构造方法
*/
public PageUtils(IPage<T> page) {
this.total = page.getTotal();
this.size = page.getSize();
this.current = page.getCurrent();
this.pages = page.getPages();
this.records = page.getRecords();
this.hasPrevious = current > 1;
this.hasNext = current < pages;
}
}
```
## 13. 创建字符串工具类
```java
// com.nuaa.video.common.utils.StringUtils.java
package com.nuaa.video.common.utils;
import java.util.UUID;
/**
* 字符串工具类
*/
public class StringUtils {
/**
* 判断字符串是否为空
*/
public static boolean isEmpty(String str) {
return str == null || str.trim().length() == 0;
}
/**
* 判断字符串是否不为空
*/
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* 生成UUID
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成固定长度的随机字符串
*/
public static String randomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = (int) (Math.random() * str.length());
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 驼峰转下划线
*/
public static String camelToUnderscore(String str) {
if (isEmpty(str)) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isUpperCase(c)) {
sb.append("_").append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 下划线转驼峰
*/
public static String underscoreToCamel(String str) {
if (isEmpty(str)) {
return "";
}
StringBuilder sb = new StringBuilder();
boolean underscoreFlag = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '_') {
underscoreFlag = true;
} else {
if (underscoreFlag) {
sb.append(Character.toUpperCase(c));
underscoreFlag = false;
} else {
sb.append(c);
}
}
}
return sb.toString();
}
}
```
## 14. 创建基础实体类
```java
// com.nuaa.video.common.model.entity.BaseEntity.java
package com.nuaa.video.common.model.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体类
*/
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/**
* 更新人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
```
## 15. 创建常量类
```java
// com.nuaa.video.common.constants.CommonConstants.java
package com.nuaa.video.common.constants;
/**
* 通用常量
*/
public interface CommonConstants {
/**
* 成功标记
*/
Integer SUCCESS = 200;
/**
* 失败标记
*/
Integer FAIL = 500;
/**
* 已删除
*/
Integer DELETED = 1;
/**
* 未删除
*/
Integer NOT_DELETED = 0;
/**
* 正常状态
*/
Integer STATUS_NORMAL = 1;
/**
* 禁用状态
*/
Integer STATUS_DISABLED = 0;
/**
* 锁定状态
*/
Integer STATUS_LOCKED = 2;
/**
* 系统默认用户
*/
String SYSTEM_USER = "system";
/**
* 匿名用户
*/
String ANONYMOUS_USER = "anonymous";
}
```
```java
// com.nuaa.video.common.constants.SecurityConstants.java
package com.nuaa.video.common.constants;
/**
* 安全相关常量
*/
public interface SecurityConstants {
/**
* 认证请求头
*/
String AUTHORIZATION_HEADER = "Authorization";
/**
* 授权token前缀
*/
String BEARER_PREFIX = "Bearer ";
/**
* Basic认证前缀
*/
String BASIC_PREFIX = "Basic ";
/**
* JWT存储权限前缀
*/
String AUTHORITY_PREFIX = "ROLE_";
/**
* JWT存储权限属性
*/
String JWT_AUTHORITIES_KEY = "authorities";
/**
* 刷新token
*/
String REFRESH_TOKEN = "refresh_token";
/**
* 默认登录URL
*/
String LOGIN_URL = "/auth/login";
/**
* 验证码缓存前缀
*/
String CAPTCHA_PREFIX = "captcha:";
/**
* 验证码有效期(秒)
*/
long CAPTCHA_EXPIRATION = 60;
/**
* Token过期时间(秒)
*/
long TOKEN_EXPIRATION = 86400;
}
```
## 16. 创建元数据处理器
```java
// com.nuaa.video.common.config.MybatisPlusMetaObjectHandler.java
package com.nuaa.video.common.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.nuaa.video.common.constants.CommonConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* MyBatis Plus元数据处理器
*/
@Slf4j
@Component
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.debug("自动填充插入字段");
// 创建时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 更新时间
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// 创建人
this.strictInsertFill(metaObject, "createdBy", String.class, CommonConstants.SYSTEM_USER);
// 更新人
this.strictInsertFill(metaObject, "updatedBy", String.class, CommonConstants.SYSTEM_USER);
}
@Override
public void updateFill(MetaObject metaObject) {
log.debug("自动填充更新字段");
// 更新时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// 更新人
this.strictUpdateFill(metaObject, "updatedBy", String.class, CommonConstants.SYSTEM_USER);
}
}
```
## 17. 创建分页请求DTO
```java
// com.nuaa.video.common.model.dto.PageRequestDTO.java
package com.nuaa.video.common.model.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 分页请求DTO
*/
@Data
@Accessors(chain = true)
public class PageRequestDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 当前页码
*/
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小值为1")
private Integer current = 1;
/**
* 每页记录数
*/
@NotNull(message = "每页记录数不能为空")
@Min(value = 1, message = "每页记录数最小值为1")
@Max(value = 100, message = "每页记录数最大值为100")
private Integer size = 10;
/**
* 排序字段
*/
private String orderField;
/**
* 排序方式(asc/desc)
*/
private String orderType;
}
```
## 18. 创建Feign配置类
```java
// com.nuaa.video.common.config.FeignConfig.java
package com.nuaa.video.common.config;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Enumeration;
import jakarta.servlet.http.HttpServletRequest;
/**
* Feign配置类
*/
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
// 传递认证信息
if ("Authorization".equalsIgnoreCase(name) ||
"Content-Type".equalsIgnoreCase(name)) {
requestTemplate.header(name, value);
}
}
}
}
};
}
}
```
## 19. 创建日期工具类
```java
// com.nuaa.video.common.utils.DateUtils.java
package com.nuaa.video.common.utils;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
* 日期工具类
*/
public class DateUtils {
/**
* 日期格式化
*/
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String SIMPLE_DATE_FORMAT = "yyyyMMdd";
public static final String SIMPLE_DATE_TIME_FORMAT = "yyyyMMddHHmmss";
/**
* 日期对象格式化
*/
public static String format(LocalDateTime localDateTime, String pattern) {
if (localDateTime == null) {
return null;
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return formatter.format(localDateTime);
}
/**
* 日期对象格式化(默认格式)
*/
public static String format(LocalDateTime localDateTime) {
return format(localDateTime, DEFAULT_DATE_TIME_FORMAT);
}
/**
* 字符串转日期
*/
public static LocalDateTime parse(String dateTimeStr, String pattern) {
if (StringUtils.isEmpty(dateTimeStr)) {
return null;
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.parse(dateTimeStr, formatter);
}
/**
* 字符串转日期(默认格式)
*/
public static LocalDateTime parse(String dateTimeStr) {
return parse(dateTimeStr, DEFAULT_DATE_TIME_FORMAT);
}
/**
* LocalDateTime转Date
*/
public static Date toDate(LocalDateTime localDateTime) {
if (localDateTime == null) {
return null;
}
ZoneId zoneId = ZoneId.systemDefault();
return Date.from(localDateTime.atZone(zoneId).toInstant());
}
/**
* Date转LocalDateTime
*/
public static LocalDateTime toLocalDateTime(Date date) {
if (date == null) {
return null;
}
ZoneId zoneId = ZoneId.systemDefault();
return date.toInstant().atZone(zoneId).toLocalDateTime();
}
/**
* 获取当前日期时间
*/
public static LocalDateTime now() {
return LocalDateTime.now();
}
/**
* 计算两个日期之间的天数
*/
public static long daysBetween(LocalDateTime start, LocalDateTime end) {
if (start == null || end == null) {
return 0;
}
return ChronoUnit.DAYS.between(start, end);
}
/**
* 计算两个日期之间的小时数
*/
public static long hoursBetween(LocalDateTime start, LocalDateTime end) {
if (start == null || end == null) {
return 0;
}
return ChronoUnit.HOURS.between(start, end);
}
/**
* 计算两个日期之间的分钟数
*/
public static long minutesBetween(LocalDateTime start, LocalDateTime end) {
if (start == null || end == null) {
return 0;
}
return ChronoUnit.MINUTES.between(start, end);
}
}
```
## 20. 创建spring.factories文件
用于自动配置加载Bean。
```
# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.nuaa.video.common.config.RedisConfig,\
com.nuaa.video.common.config.MybatisPlusConfig,\
com.nuaa.video.common.config.MinioConfig,\
com.nuaa.video.common.config.WebConfig,\
com.nuaa.video.common.config.FeignConfig,\
com.nuaa.video.common.config.MybatisPlusMetaObjectHandler,\
com.nuaa.video.common.exception.GlobalExceptionHandler
```