diff --git a/.vscode/java.code-snippets b/.vscode/java.code-snippets
index 3abd9b7..4c23945 100644
--- a/.vscode/java.code-snippets
+++ b/.vscode/java.code-snippets
@@ -23,8 +23,8 @@
" * @author ${1:}",
" * @version V1.0",
" * @title ${TM_FILENAME_BASE}",
- " * @description ${2:}",
" * @date ${CURRENT_YEAR}/${CURRENT_MONTH}/${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}",
+ " * @description ${2:}",
" */",
""
],
@@ -38,8 +38,8 @@
" * @author ${1:}",
" * @version V1.0",
" * @title ${TM_FILENAME_BASE}",
- " * @description ${2:}",
" * @date ${CURRENT_YEAR}/${CURRENT_MONTH}/${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}",
+ " * @description ${2:}",
" */",
""
],
@@ -53,8 +53,8 @@
" * @author ${1:}",
" * @version V1.0",
" * @title ${TM_FILENAME_BASE}",
- " * @description ${2:}",
" * @date ${CURRENT_YEAR}/${CURRENT_MONTH}/${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}",
+ " * @description ${2:}",
" */",
""
],
diff --git a/pom.xml b/pom.xml
index 5fbbcc2..f515d32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,9 @@
1.5.0
1.18
1.38.0
+ 33.0.0-jre
+ 5.8.26
+ 3.12.0
@@ -146,6 +149,25 @@
jansi
${jansi.version}
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/constant/RedisKeyConstants.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/constant/RedisKeyConstants.java
new file mode 100644
index 0000000..d797d30
--- /dev/null
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/constant/RedisKeyConstants.java
@@ -0,0 +1,29 @@
+package com.jy.xiaoyishu.auth.constant;
+
+/**
+ *
+ * @author ayi
+ * @version V1.0
+ * @title RedisKeyConstants
+ * @description Redis key 常量
+ * @date 2026/01/15 18:45
+ */
+
+public class RedisKeyConstants {
+
+ /**
+ * 验证码 KEY 前缀
+ */
+ private static final String VERIFICATION_CODE_KEY_PREFIX = "verification_code:";
+
+ /**
+ * 构建验证码 KEY
+ *
+ * @param phone 手机号
+ * @return 验证码 KEY
+ */
+ public static String buildVerificationCodeKey(String phone) {
+ return VERIFICATION_CODE_KEY_PREFIX + phone;
+ }
+
+}
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/TestController.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/TestController.java
index 747b9e4..0160916 100644
--- a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/TestController.java
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/TestController.java
@@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.jy.framework.biz.operationlog.aspect.ApiOperationLog;
import com.jy.framework.common.response.Response;
-import com.jy.xiaoyishu.auth.vo.User;
+import com.jy.xiaoyishu.auth.model.vo.User;
import cn.dev33.satoken.stp.StpUtil;
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/VerificationCodeController.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/VerificationCodeController.java
new file mode 100644
index 0000000..16582d5
--- /dev/null
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/controller/VerificationCodeController.java
@@ -0,0 +1,33 @@
+package com.jy.xiaoyishu.auth.controller;
+
+import com.jy.framework.biz.operationlog.aspect.ApiOperationLog;
+import com.jy.framework.common.response.Response;
+import com.jy.xiaoyishu.auth.model.vo.verificationcode.SendVerificationCodeReqVO;
+import com.jy.xiaoyishu.auth.service.VerificationCodeService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author ayi
+ * @version V1.0
+ * @title VerificationCodeController
+ * @date 2026/01/15
+ * @description 验证码控制器
+ */
+
+@Slf4j
+@RestController
+public class VerificationCodeController {
+ @Resource
+ private VerificationCodeService verificationCodeService;
+
+ @PostMapping("/verification/code/send")
+ @ApiOperationLog(description = "发送短信验证码")
+ public Response> send(@Validated @RequestBody SendVerificationCodeReqVO sendVerificationCodeReqVO) {
+ return verificationCodeService.send(sendVerificationCodeReqVO);
+ }
+}
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/enums/ResponseCodeEnum.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/enums/ResponseCodeEnum.java
index e6b6096..f462bfb 100644
--- a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/enums/ResponseCodeEnum.java
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/enums/ResponseCodeEnum.java
@@ -18,7 +18,9 @@ import com.jy.framework.common.exception.BaseExceptionInterface;
@AllArgsConstructor
public enum ResponseCodeEnum implements BaseExceptionInterface {
// -------- 通用异常状态码 --------
- SYSTEM_ERROR("AUTH-10000", "系统错误"), PARAM_NOT_VALID("AUTH-10001", "参数错误");
+ SYSTEM_ERROR("AUTH-10000", "系统错误"),
+ PARAM_NOT_VALID("AUTH-10001", "参数错误"),
+ VERIFICATION_CODE_SEND_FREQUENTLY("AUTH-20000", "请求太频繁,请3分钟后再试");
/**
* 错误码
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/exception/GlobalExceptionHandler.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/exception/GlobalExceptionHandler.java
index 34111b1..aba80d9 100644
--- a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/exception/GlobalExceptionHandler.java
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/exception/GlobalExceptionHandler.java
@@ -15,6 +15,15 @@ import com.jy.xiaoyishu.auth.enums.ResponseCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
+/**
+ *
+ * @author ayi
+ * @version V1.0
+ * @title GlobalExceptionHandler
+ * @description 全局异常处理器
+ * @date 2026/01/15 18:40
+ */
+
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/vo/User.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/model/vo/User.java
similarity index 94%
rename from xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/vo/User.java
rename to xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/model/vo/User.java
index b6ec884..02d92df 100644
--- a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/vo/User.java
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/model/vo/User.java
@@ -1,4 +1,4 @@
-package com.jy.xiaoyishu.auth.vo;
+package com.jy.xiaoyishu.auth.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/model/vo/verificationcode/SendVerificationCodeReqVO.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/model/vo/verificationcode/SendVerificationCodeReqVO.java
new file mode 100644
index 0000000..b705cf9
--- /dev/null
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/model/vo/verificationcode/SendVerificationCodeReqVO.java
@@ -0,0 +1,30 @@
+package com.jy.xiaoyishu.auth.model.vo.verificationcode;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ *
+ * @author ayi
+ * @version V1.0
+ * @title SendVerificationCodeReqVO
+ * @description 发送验证码请求参数
+ * @date 2026/01/15 18:39
+ */
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class SendVerificationCodeReqVO {
+
+ /**
+ * 手机号
+ */
+ @NotBlank(message = "手机号不能为空")
+ private String phone;
+
+}
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/service/VerificationCodeService.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/service/VerificationCodeService.java
new file mode 100644
index 0000000..27446b7
--- /dev/null
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/service/VerificationCodeService.java
@@ -0,0 +1,25 @@
+package com.jy.xiaoyishu.auth.service;
+
+import com.jy.framework.common.response.Response;
+import com.jy.xiaoyishu.auth.model.vo.verificationcode.SendVerificationCodeReqVO;
+
+/**
+ *
+ * @author ayi
+ * @version V1.0
+ * @title VerificationCodeService
+ * @description 验证码服务
+ * @date 2026/01/15 18:48
+ */
+
+public interface VerificationCodeService {
+
+ /**
+ * 发送验证码
+ *
+ * @param sendVerificationCodeReqVO 发送验证码请求参数
+ * @return 响应结果
+ */
+ Response> send(SendVerificationCodeReqVO sendVerificationCodeReqVO);
+
+}
diff --git a/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java
new file mode 100644
index 0000000..5f53cdc
--- /dev/null
+++ b/xiaoyi-auth/src/main/java/com/jy/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java
@@ -0,0 +1,51 @@
+package com.jy.xiaoyishu.auth.service.impl;
+import cn.hutool.core.util.RandomUtil;
+import com.jy.framework.common.exception.BizException;
+import com.jy.framework.common.response.Response;
+import com.jy.xiaoyishu.auth.constant.RedisKeyConstants;
+import com.jy.xiaoyishu.auth.enums.ResponseCodeEnum;
+import com.jy.xiaoyishu.auth.model.vo.verificationcode.SendVerificationCodeReqVO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import com.jy.xiaoyishu.auth.service.VerificationCodeService;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+public class VerificationCodeServiceImpl implements VerificationCodeService {
+
+ @Resource
+ private RedisTemplate redisTemplate;
+
+ @Override
+ public Response> send(SendVerificationCodeReqVO sendVerificationCodeReqVO) {
+ // 手机号
+ String phone = sendVerificationCodeReqVO.getPhone();
+
+ // 构建验证码 redis key
+ String key = RedisKeyConstants.buildVerificationCodeKey(phone);
+
+ // 判断是否已发送验证码
+ boolean isSent = redisTemplate.hasKey(key);
+ if (isSent) {
+ // 若之前发送的验证码未过期,则提示发送频繁
+ throw new BizException(ResponseCodeEnum.VERIFICATION_CODE_SEND_FREQUENTLY);
+ }
+
+ // 生成 6 位随机数字验证码
+ String verificationCode = RandomUtil.randomNumbers(6);
+
+ // todo: 调用第三方短信发送服务
+
+ log.info("==> 手机号: {}, 已发送验证码:【{}】", phone, verificationCode);
+
+ // 存储验证码到 redis, 并设置过期时间为 3 分钟
+ redisTemplate.opsForValue().set(key, verificationCode, 3, TimeUnit.MINUTES);
+
+ return Response.success();
+ }
+}
diff --git a/xiaoyi-auth/src/main/resources/config/application-dev.yml b/xiaoyi-auth/src/main/resources/config/application-dev.yml
index 1bc8e41..175eb32 100644
--- a/xiaoyi-auth/src/main/resources/config/application-dev.yml
+++ b/xiaoyi-auth/src/main/resources/config/application-dev.yml
@@ -55,9 +55,9 @@ spring:
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
max-idle: 10 # 连接池中的最大空闲连接
-# output:
-# ansi:
-# enabled: always
+ output:
+ ansi:
+ enabled: always
application:
name: xiaoyishu-auth
diff --git a/xiaoyi-framework/xiaoyi-common/pom.xml b/xiaoyi-framework/xiaoyi-common/pom.xml
index b2aaa50..ed09ea0 100644
--- a/xiaoyi-framework/xiaoyi-common/pom.xml
+++ b/xiaoyi-framework/xiaoyi-common/pom.xml
@@ -57,6 +57,22 @@
org.hibernate.validator
hibernate-validator
+
+
+
+ com.google.guava
+ guava
+
+
+
+ cn.hutool
+ hutool-all
+
+
+
+ org.apache.commons
+ commons-lang3
+
\ No newline at end of file