1799 lines
47 KiB
Markdown
1799 lines
47 KiB
Markdown
# 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.ConditionalOnBean;
|
||
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配置类
|
||
* 只有在存在RedisOperations类且没有redisTemplate bean时才会创建
|
||
*/
|
||
@Configuration
|
||
@EnableCaching
|
||
@ConditionalOnClass(RedisOperations.class)
|
||
@EnableConfigurationProperties(RedisProperties.class)
|
||
public class RedisConfig {
|
||
|
||
@Bean
|
||
@ConditionalOnMissingBean(name = "redisTemplate")
|
||
@ConditionalOnBean(RedisConnectionFactory.class) // 只有当存在RedisConnectionFactory时才创建
|
||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||
template.setConnectionFactory(factory);
|
||
|
||
// 创建配置好的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.boot.autoconfigure.condition.ConditionalOnClass;
|
||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||
|
||
import javax.sql.DataSource;
|
||
|
||
/**
|
||
* MyBatis Plus配置类
|
||
* 只有在存在DataSource且启用mybatis-plus时才生效
|
||
*/
|
||
@Configuration
|
||
@EnableTransactionManagement
|
||
@ConditionalOnClass({DataSource.class})
|
||
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
|
||
@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
|
||
```
|