diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..4ff5223
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,113 @@
+
+
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/video-comment/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://5.180.253.115:3306/video_comment?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ $PROJECT_DIR$/video-content/src/main/resources/application.yml
+ jdbc.RedisDriver
+ jdbc:redis://5.180.253.115:6379/2
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/video-admin/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://5.180.253.115:3306/video_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/video-auth/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://5.180.253.115:3306/video_auth?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ $PROJECT_DIR$/video-user/src/main/resources/application.yml
+ jdbc.RedisDriver
+ jdbc:redis://5.180.253.115:6379/1
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/video-recommend/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://5.180.253.115:3306/video_recommend?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ $PROJECT_DIR$/video-recommend/src/main/resources/application.yml
+ jdbc.RedisDriver
+ jdbc:redis://5.180.253.115:6379/4
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ $PROJECT_DIR$/video-comment/src/main/resources/application.yml
+ jdbc.RedisDriver
+ jdbc:redis://5.180.253.115:6379/3
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ $PROJECT_DIR$/video-auth/src/main/resources/application.yml
+ jdbc.RedisDriver
+ jdbc:redis://5.180.253.115:6379/0
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/video-content/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://5.180.253.115:3306/video_content?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ $PROJECT_DIR$/video-user/src/main/resources/application.yml
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://5.180.253.115:3306/video_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ $PROJECT_DIR$/video-admin/src/main/resources/application.yml
+ jdbc.RedisDriver
+ jdbc:redis://5.180.253.115:6379/5
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 0000000..f6d2542
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/video-common模块实现步骤和代码.md b/docs/video-common模块实现步骤和代码.md
new file mode 100644
index 0000000..3170741
--- /dev/null
+++ b/docs/video-common模块实现步骤和代码.md
@@ -0,0 +1,1788 @@
+# 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