发送验证码接口 #1

Merged
duww merged 1 commits from refactor/rename-literature-to-document into master 2026-01-29 10:37:12 +08:00
20 changed files with 276 additions and 50 deletions

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(git mv:*)"
]
}
}

View File

@@ -0,0 +1,29 @@
package com.bicloud.common.utils;
public class RedisConstants {
public static final String LOGIN_CODE_KEY = "login:code:";
public static final String GET_CODE_LOCK = "getCodeLock:phone:";
public static final String GET_CODE_BLACKLIST_PHONE = "getCodeBlacklist:phone:";
public static final String GET_CODE_BLACKLIST_IP_ADDR = "getCodeBlacklist:ipAddr:";
public static final String GET_CODE_BLACKLIST_MAC_ADDR = "getCodeBlacklist:macAddr:";
public static final Long GET_CODE_LOCK_TTL = 1L;
public static final Long REFRESH_BLACKLIST_TTL = 24L;
public static final Long LOGIN_CODE_TTL = 2L;
public static final String LOGIN_USER_KEY = "login:token:";
public static final Long LOGIN_USER_TTL = 36000L;
public static final Long CACHE_NULL_TTL = 2L;
public static final Long CACHE_SHOP_TTL = 30L;
public static final String CACHE_SHOP_KEY = "cache:shop:";
public static final String LOCK_SHOP_KEY = "lock:shop:";
public static final Long LOCK_SHOP_TTL = 10L;
public static final String SECKILL_STOCK_KEY = "seckill:stock:";
public static final String SECKILL_ORDER_KEY = "seckill:order";
public static final String BLOG_LIKED_KEY = "blog:liked:";
public static final String FEED_KEY = "feed:";
public static final String SHOP_GEO_KEY = "shop:geo:";
public static final String USER_SIGN_KEY = "sign:";
}

View File

@@ -0,0 +1,20 @@
package com.bicloud.common.utils;
public class RegexPatterns {
/**
* 手机号正则
*/
public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
/**
* 邮箱正则
*/
public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
/**
* 密码正则。4~32位的字母、数字、下划线
*/
public static final String PASSWORD_REGEX = "^\\w{4,32}$";
/**
* 验证码正则, 6位数字或字母
*/
public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";
}

View File

@@ -0,0 +1,39 @@
package com.bicloud.common.utils;
import com.github.xiaoymin.knife4j.core.util.StrUtil;
public class RegexUtils {
/**
* 是否是无效手机格式
* @param phone 要校验的手机号
* @return true:符合false不符合
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexPatterns.PHONE_REGEX);
}
/**
* 是否是无效邮箱格式
* @param email 要校验的邮箱
* @return true:符合false不符合
*/
public static boolean isEmailInvalid(String email){
return mismatch(email, RegexPatterns.EMAIL_REGEX);
}
/**
* 是否是无效验证码格式
* @param code 要校验的验证码
* @return true:符合false不符合
*/
public static boolean isCodeInvalid(String code){
return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
}
// 校验是否不符合正则格式
private static boolean mismatch(String str, String regex){
if (StrUtil.isBlank(str)) {
return true;
}
return !str.matches(regex);
}
}

View File

@@ -31,7 +31,7 @@ public class OpenApiConfig {
// 分组1文献管理接口
@Bean
public GroupedOpenApi literatureApi() {
public GroupedOpenApi documentApi() {
return GroupedOpenApi.builder()
.group("bicloud接口") // 分组名称
.packagesToScan("com.bicloud.controller") // 匹配的接口路径

View File

@@ -2,11 +2,11 @@ package com.bicloud.controller;
import com.bicloud.common.result.PageResult;
import com.bicloud.common.result.Result;
import com.bicloud.pojo.dto.LiteratureCreateDto;
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
import com.bicloud.pojo.dto.DocumentCreateDto;
import com.bicloud.pojo.dto.DocumentPageQueryDto;
import com.bicloud.pojo.vo.DocumentTypeVo;
import com.bicloud.pojo.vo.LiteratureListVo;
import com.bicloud.service.LiteratureService;
import com.bicloud.pojo.vo.DocumentListVo;
import com.bicloud.service.DocumentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@@ -17,28 +17,28 @@ import java.util.List;
@CrossOrigin
@RestController
@RequestMapping("/literature")
@RequestMapping("/document")
@Tag(name = "文献管理", description = "文献相关接口")
@Slf4j
public class LiteratureController {
public class DocumentController {
@Autowired
private LiteratureService literatureService;
private DocumentService documentService;
// 新增文献数据
@PostMapping("/add")
@Operation(summary = "新增文献")
public Result<String> addLiterature(@RequestBody LiteratureCreateDto dto) {
literatureService.addLiterature(dto);
public Result<String> addDocument(@RequestBody DocumentCreateDto dto) {
documentService.addDocument(dto);
return Result.success("新增文献成功");
}
// 删除文献
@DeleteMapping("/delete/{id}") // 通过路径变量接收文献ID
@Operation(summary = "删除文献")
public Result<String> deleteLiterature(@PathVariable Long id) { // @PathVariable绑定路径中的id参数
public Result<String> deleteDocument(@PathVariable Long id) { // @PathVariable绑定路径中的id参数
try {
literatureService.deleteLiterature(id);
documentService.deleteDocument(id);
return Result.success("删除文献成功");
} catch (Exception e) {
e.printStackTrace();
@@ -49,8 +49,8 @@ public class LiteratureController {
//分页查询
@Operation(summary = "分页查询文献")
@GetMapping("/page")
public Result<PageResult<LiteratureListVo>> page(LiteraturePageQueryDto queryDTO) {
PageResult<LiteratureListVo> result = literatureService.pageQuery(queryDTO);
public Result<PageResult<DocumentListVo>> page(DocumentPageQueryDto queryDTO) {
PageResult<DocumentListVo> result = documentService.pageQuery(queryDTO);
return Result.success(result);
}
@@ -58,7 +58,7 @@ public class LiteratureController {
@GetMapping("/types")
@Operation(summary = "获取文献分类下拉列表")
public Result<List<DocumentTypeVo>> types() {
return Result.success(literatureService.listTypes());
return Result.success(documentService.listTypes());
}
}

View File

@@ -0,0 +1,33 @@
package com.bicloud.controller;
import com.bicloud.common.result.Result;
import com.bicloud.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@CrossOrigin
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 发送手机验证码
*/
@PostMapping("code")
@Operation(summary = "发送验证码")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session, HttpServletRequest request) {
// 发送短信验证码并保存验证码
return userService.sendCode(phone, session, request);
}
}

View File

@@ -1,25 +1,25 @@
package com.bicloud.mapper;
import com.bicloud.pojo.dto.LiteratureCreateDto;
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
import com.bicloud.pojo.dto.DocumentCreateDto;
import com.bicloud.pojo.dto.DocumentPageQueryDto;
import com.bicloud.pojo.vo.DocumentTypeVo;
import com.bicloud.pojo.vo.LiteratureListVo;
import com.bicloud.pojo.vo.DocumentListVo;
import com.github.pagehelper.Page;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface LiteratureMapper {
public interface DocumentMapper {
// 新增插入 biology_document_info
int insertDocument(LiteratureCreateDto dto);
int insertDocument(DocumentCreateDto dto);
// 删除软删除 removed=1
int softDeleteById(Long id);
// 分页查询返回列表VOPageHelper自动分页
Page<LiteratureListVo> pageQuery(LiteraturePageQueryDto queryDTO);
Page<DocumentListVo> pageQuery(DocumentPageQueryDto queryDTO);
// 获取文献分类
List<DocumentTypeVo> listTypes();

View File

@@ -8,7 +8,7 @@ import java.time.LocalDateTime;
* 新增文献写入 biology_document_info的入参
*/
@Data
public class LiteratureCreateDto {
public class DocumentCreateDto {
private Integer docType; // 对应 biology_document_info.doc_type类型ID
private String docLabel; // 对应 doc_label目前是varchar先按原样存

View File

@@ -4,7 +4,7 @@ import lombok.Data;
import java.math.BigDecimal;
@Data
public class LiteratureDto {
public class DocumentDto {
// 字段名要和前端提交的参数名一致
private Long id;
private Integer year;

View File

@@ -8,7 +8,7 @@
* 列表页筛选 + 分页参数
*/
@Data
public class LiteraturePageQueryDto {
public class DocumentPageQueryDto {
private Integer page;
private Integer pageSize;

View File

@@ -4,7 +4,7 @@ import lombok.Data;
import java.math.BigDecimal;
@Data // Lombok注解自动生成get/set/toString等方法
public class LiteratureInfo {
public class DocumentInfo {
private Long id;
private Integer year;
private String articleTitle; // 对应表中article_title

View File

@@ -6,7 +6,7 @@ import lombok.Data;
* 列表页返回对象
*/
@Data
public class LiteratureListVo {
public class DocumentListVo {
private Long id; // 隐藏id
private Integer year; // 年份

View File

@@ -1,23 +1,23 @@
package com.bicloud.service;
import com.bicloud.common.result.PageResult;
import com.bicloud.pojo.dto.LiteratureCreateDto;
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
import com.bicloud.pojo.dto.DocumentCreateDto;
import com.bicloud.pojo.dto.DocumentPageQueryDto;
import com.bicloud.pojo.vo.DocumentTypeVo;
import com.bicloud.pojo.vo.LiteratureListVo;
import com.bicloud.pojo.vo.DocumentListVo;
import java.util.List;
public interface LiteratureService {
public interface DocumentService {
// 新增文献写入 biology_document_info
void addLiterature(LiteratureCreateDto dto);
void addDocument(DocumentCreateDto dto);
// 删除文献软删除removed=1
void deleteLiterature(Long id);
void deleteDocument(Long id);
// 分页查询返回列表VO
PageResult<LiteratureListVo> pageQuery(LiteraturePageQueryDto queryDTO);
PageResult<DocumentListVo> pageQuery(DocumentPageQueryDto queryDTO);
// 获取文献分类下拉列表
List<DocumentTypeVo> listTypes();

View File

@@ -0,0 +1,9 @@
package com.bicloud.service;
import com.bicloud.common.result.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
public interface UserService {
Result sendCode(String phone, HttpSession session, HttpServletRequest request);
}

View File

@@ -1,12 +1,12 @@
package com.bicloud.service.impl;
import com.bicloud.common.result.PageResult;
import com.bicloud.mapper.LiteratureMapper;
import com.bicloud.pojo.dto.LiteratureCreateDto;
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
import com.bicloud.mapper.DocumentMapper;
import com.bicloud.pojo.dto.DocumentCreateDto;
import com.bicloud.pojo.dto.DocumentPageQueryDto;
import com.bicloud.pojo.vo.DocumentTypeVo;
import com.bicloud.pojo.vo.LiteratureListVo;
import com.bicloud.service.LiteratureService;
import com.bicloud.pojo.vo.DocumentListVo;
import com.bicloud.service.DocumentService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,25 +15,25 @@ import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class LiteratureServiceImpl implements LiteratureService {
public class DocumentServiceImpl implements DocumentService {
@Autowired
private LiteratureMapper literatureMapper;
private DocumentMapper documentMapper;
@Override
public void addLiterature(LiteratureCreateDto dto) {
public void addDocument(DocumentCreateDto dto) {
// 新库直接写入 biology_document_infoSQL XML
literatureMapper.insertDocument(dto);
documentMapper.insertDocument(dto);
}
@Override
public void deleteLiterature(Long id) {
public void deleteDocument(Long id) {
// 新库软删除 removed=1
literatureMapper.softDeleteById(id);
documentMapper.softDeleteById(id);
}
@Override
public PageResult<LiteratureListVo> pageQuery(LiteraturePageQueryDto queryDTO) {
public PageResult<DocumentListVo> pageQuery(DocumentPageQueryDto queryDTO) {
// 1) 兜底默认值避免空指针/非法分页参数
int page = (queryDTO.getPage() == null || queryDTO.getPage() < 1) ? 1 : queryDTO.getPage();
@@ -43,7 +43,7 @@ public class LiteratureServiceImpl implements LiteratureService {
PageHelper.startPage(page, pageSize);
// 3) 执行 mapper 查询PageHelper 自动分页 + 自动 count
Page<LiteratureListVo> p = literatureMapper.pageQuery(queryDTO);
Page<DocumentListVo> p = documentMapper.pageQuery(queryDTO);
// 4) total + records
return new PageResult<>(p.getTotal(), p.getResult());
@@ -51,7 +51,7 @@ public class LiteratureServiceImpl implements LiteratureService {
@Override
public List<DocumentTypeVo> listTypes() {
return literatureMapper.listTypes();
return documentMapper.listTypes();
}
}

View File

@@ -0,0 +1,72 @@
package com.bicloud.service.impl;
import com.bicloud.common.result.Result;
import com.bicloud.common.utils.RegexUtils;
import com.bicloud.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import static com.bicloud.common.utils.RedisConstants.*;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session, HttpServletRequest request) {
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 2.如果不符合,返回错误信息
return Result.error("手机号格式错误!");
}
// 3. 获取客户端IP地址
String clientIpAddr = request.getRemoteAddr();
// 4.如果存在锁,直接返回失败
String phoneGetCodeLock = stringRedisTemplate.opsForValue().get(GET_CODE_LOCK + phone);
if (phoneGetCodeLock != null) {
return Result.error("获取验证码过快,请稍后重试!");
}
// 5.检查两个黑名单中的次数
String blacklistPhoneCount = stringRedisTemplate.opsForValue().get(GET_CODE_BLACKLIST_PHONE + phone);
int getCodePhoneCount = (blacklistPhoneCount != null) ? Integer.parseInt(blacklistPhoneCount) : 0;
if (getCodePhoneCount >= 400) {
return Result.error("获取验证码次数过多,您的手机号已被限制!");
}
String blacklistIpAddrCount = stringRedisTemplate.opsForValue().get(GET_CODE_BLACKLIST_IP_ADDR + clientIpAddr);
int getCodeIpAddrCount = (blacklistIpAddrCount != null) ? Integer.parseInt(blacklistIpAddrCount) : 0;
if (getCodeIpAddrCount >= 300) {
return Result.error("获取验证码次数过多您的IP已被限制");
}
// 6.更新锁和黑名单
stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_PHONE + phone, String.valueOf(getCodePhoneCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS);
stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_IP_ADDR + clientIpAddr, String.valueOf(getCodeIpAddrCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS);
stringRedisTemplate.opsForValue().set(GET_CODE_LOCK + phone, "1", GET_CODE_LOCK_TTL, TimeUnit.MINUTES);
// 7.校验通过,生成验证码
// String code = RandomUtil.randomNumbers(6);
String code = "123123"; //为了方便测试,改成固定值
// 8.保存验证码到 redis并上锁
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
// 9.发送验证码
log.debug("发送短信验证码成功,验证码:{}", code);
// 10.返回ok
return Result.success("发送短信验证码成功,验证码:"+code);
}
}

View File

@@ -16,6 +16,18 @@ spring:
connection-timeout: 30000
maximum-pool-size: 10
minimum-idle: 5
data:
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 2
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s
mybatis:
mapper-locations: classpath*:mapper/*.xml

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bicloud.mapper.LiteratureMapper">
<mapper namespace="com.bicloud.mapper.DocumentMapper">
<!-- 新增文献:写入 biology_document_info -->
<insert id="insertDocument" parameterType="com.bicloud.pojo.dto.LiteratureCreateDto" useGeneratedKeys="true" keyProperty="id">
<insert id="insertDocument" parameterType="com.bicloud.pojo.dto.DocumentCreateDto" useGeneratedKeys="true" keyProperty="id">
INSERT INTO biology_document_info
(doc_type, doc_label, doc_title, doc_icon, doc_desp, doc_info,
doc_collect, position, author, publication, impact_factor,
@@ -28,7 +28,7 @@
2) 文献分类名来自 biology_document_type.namejoin
3) IF范围impact_factor 是varchar先用 REGEXP 过滤“纯数字/小数”,再 CAST 比较
-->
<select id="pageQuery" resultType="com.bicloud.pojo.vo.LiteratureListVo">
<select id="pageQuery" resultType="com.bicloud.pojo.vo.DocumentListVo">
SELECT
d.id,
YEAR(d.publish_time) AS year,

View File

@@ -1,13 +1,18 @@
package com.bicloud;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
@SpringBootTest
class BicloudApplicationTests {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
void contextLoads() {
}
}