diff --git a/docs/video-common模块实现步骤和代码.md b/docs/video-common模块实现步骤和代码.md deleted file mode 100644 index 722810f..0000000 --- a/docs/video-common模块实现步骤和代码.md +++ /dev/null @@ -1,1798 +0,0 @@ -# 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 implements Serializable { - private static final long serialVersionUID = 1L; - - /** - * 状态码 - */ - private Integer code; - - /** - * 返回消息 - */ - private String message; - - /** - * 返回数据 - */ - private T data; - - /** - * 成功结果 - */ - public static Result success() { - return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null); - } - - /** - * 成功结果(带数据) - */ - public static Result success(T data) { - return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); - } - - /** - * 成功结果(自定义消息和数据) - */ - public static Result success(String message, T data) { - return new Result<>(ResultCode.SUCCESS.getCode(), message, data); - } - - /** - * 失败结果 - */ - public static Result error() { - return new Result<>(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMessage(), null); - } - - /** - * 失败结果(自定义消息) - */ - public static Result error(String message) { - return new Result<>(ResultCode.ERROR.getCode(), message, null); - } - - /** - * 失败结果(自定义状态码和消息) - */ - public static Result error(Integer code, String message) { - return new Result<>(code, message, null); - } - - /** - * 自定义结果 - */ - public static Result 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 handleBusinessException(BusinessException e) { - log.error("业务异常:{}", e.getMessage()); - return Result.error(e.getCode(), e.getMessage()); - } - - /** - * 处理参数校验异常 (JSR303) - */ - @ExceptionHandler(ConstraintViolationException.class) - public Result handleConstraintViolationException(ConstraintViolationException e) { - Set> 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 handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - List 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 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 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 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 getClaimFromToken(String token, Function 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 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 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) 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 hmget(String key) { - return redisTemplate.opsForHash().entries(key); - } - - /** - * 设置Hash结构数据 - */ - public boolean hmset(String key, Map 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 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 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 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 listBuckets() { - try { - return minioClient.listBuckets(); - } catch (Exception e) { - log.error("列出所有存储桶失败:", e); - throw new RuntimeException("列出所有存储桶失败", e); - } - } - - /** - * 列出存储桶中的所有对象 - */ - public Iterable> 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 redisTemplate(RedisConnectionFactory factory) { - RedisTemplate 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 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 implements Serializable { - private static final long serialVersionUID = 1L; - - /** - * 总记录数 - */ - private long total; - - /** - * 每页记录数 - */ - private long size; - - /** - * 当前页数 - */ - private long current; - - /** - * 总页数 - */ - private long pages; - - /** - * 列表数据 - */ - private List records; - - /** - * 是否有前一页 - */ - private boolean hasPrevious; - - /** - * 是否有下一页 - */ - private boolean hasNext; - - /** - * 构造方法 - */ - public PageUtils(IPage 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 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 -``` diff --git a/docs/video-common模块目录结构.md b/docs/video-common模块目录结构.md new file mode 100644 index 0000000..d5c6872 --- /dev/null +++ b/docs/video-common模块目录结构.md @@ -0,0 +1,34 @@ +# video-common模块实现步骤和代码 + +基于Spring Boot 3.2.4、Spring Cloud 2023.0.1和Spring Cloud Alibaba 2023.0.1.0技术栈,完整实现video-common模块。 + +## 目录结构设计 + +首先,创建以下目录结构: + +``` +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 +``` diff --git a/docs/video-gateway模块目录结构.md b/docs/video-gateway模块目录结构.md new file mode 100644 index 0000000..028725d --- /dev/null +++ b/docs/video-gateway模块目录结构.md @@ -0,0 +1,38 @@ +# video-gateway模块实现步骤和代码 + +API网关模块(video-gateway)实现,这是微服务架构中的入口点,负责请求路由、限流、鉴权等功能。 + +## 目录结构设计 + +为video-gateway模块创建以下目录结构: + +``` +video-gateway/ +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/ +│ │ │ └── nuaa/ +│ │ │ └── video/ +│ │ │ └── gateway/ +│ │ │ ├── config/ # 配置类 +│ │ │ ├── filter/ # 过滤器 +│ │ │ ├── handler/ # 处理器 +│ │ │ └── GatewayApplication.java # 启动类 +│ │ └── resources/ +│ │ ├── application.yml # 主配置文件 +│ │ └── application-dev.yml # 开发环境配置 +└── pom.xml +``` + +gateway模块具备以下功能: + +1. 基于Spring Cloud Gateway的路由功能 +2. JWT认证鉴权 +3. 基于Sentinel的流量控制和服务熔断 +4. CORS跨域支持 +5. 请求日志记录 +6. 全局异常处理 +7. 动态路由配置 + +这个网关将作为整个微服务架构的入口点,为各个微服务提供统一的API访问层。 \ No newline at end of file