refactor(project): Reorganize frontend and backend directories
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
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";
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
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;
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
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("系统繁忙,请稍后再试");
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
public String uploadObject(String fileType, String fileName, InputStream inputStream, long size, String contentType) throws Exception {
|
||||
// 检查存储桶是否存在
|
||||
createBucket(bucketName);
|
||||
|
||||
// 生成对象名称
|
||||
String objectName = fileType + "/" + fileName;
|
||||
|
||||
// 上传文件
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.stream(inputStream, size, -1)
|
||||
.contentType(contentType)
|
||||
.build()
|
||||
);
|
||||
|
||||
return objectName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package com.nuaa.video.common.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# src/main/resources/META-INF/spring.factories
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
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
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.nuaa.video.common.test;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
/**
|
||||
* Controller层测试基类
|
||||
* 提供MockMvc和其他测试通用配置
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ActiveProfiles("test")
|
||||
public abstract class BaseControllerTest {
|
||||
|
||||
@Autowired
|
||||
protected MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
protected ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 将对象转换为JSON字符串
|
||||
*/
|
||||
protected String toJson(Object obj) throws Exception {
|
||||
return objectMapper.writeValueAsString(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nuaa.video.common.test;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Service层测试基类
|
||||
* 提供通用测试工具方法
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ActiveProfiles("test")
|
||||
public abstract class BaseServiceTest {
|
||||
|
||||
/**
|
||||
* 断言结果成功
|
||||
*/
|
||||
protected <T> void assertSuccess(com.nuaa.video.common.model.vo.Result<T> result) {
|
||||
assertNotNull(result);
|
||||
assertEquals(200, result.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言结果失败
|
||||
*/
|
||||
protected <T> void assertError(com.nuaa.video.common.model.vo.Result<T> result) {
|
||||
assertNotNull(result);
|
||||
assertNotEquals(200, result.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言结果失败并包含特定消息
|
||||
*/
|
||||
protected <T> void assertErrorWithMessage(com.nuaa.video.common.model.vo.Result<T> result, String message) {
|
||||
assertNotNull(result);
|
||||
assertNotEquals(200, result.getCode());
|
||||
assertTrue(result.getMessage().contains(message));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user