feat: 添加图形验证码接口和扣分项排序功能
All checks were successful
continuous-integration/drone/push Build is passing

新增功能:
- 添加图形验证码接口 GET /oauth/captcha,返回验证码图片和key
- 添加扣分项排序接口 POST /update-order
- 新增数据库完整备份 martial_db.sql

技术细节:
- CaptchaController: 新增 getCaptcha() 方法,生成4位验证码,Redis缓存5分钟
- MartialDeductionItemController: 新增 updateOrder() 批量更新排序
- IMartialDeductionItemService/Impl: 新增排序服务方法
This commit is contained in:
2025-12-18 11:55:35 +08:00
parent f6c019e520
commit 4e487b76b7
5 changed files with 9226 additions and 1 deletions

File diff suppressed because one or more lines are too long

View File

@@ -28,11 +28,39 @@ public class CaptchaController {
private final BladeRedis bladeRedis;
/**
* 获取图形验证码
*/
@GetMapping("/oauth/captcha")
@ApiOperationSupport(order = 1)
@Operation(summary = "获取图形验证码", description = "返回验证码图片和key")
public R<java.util.Map<String, String>> getCaptcha() {
// 生成唯一key
String key = java.util.UUID.randomUUID().toString().replace("-", "");
// 生成4位随机验证码
String code = generateCode(4);
// 存储验证码到Redis有效期5分钟
String cacheKey = CacheNames.CAPTCHA_KEY + key;
bladeRedis.setEx(cacheKey, code.toLowerCase(), Duration.ofMinutes(5));
// 生成验证码图片简单的base64图片
String image = generateCaptchaImage(code);
// 返回结果
java.util.Map<String, String> result = new java.util.HashMap<>();
result.put("key", key);
result.put("image", image);
return R.data(result);
}
/**
* 发送短信验证码
*/
@PostMapping("/send")
@ApiOperationSupport(order = 1)
@ApiOperationSupport(order = 2)
@Operation(summary = "发送短信验证码", description = "传入手机号")
public R send(@Parameter(description = "手机号", required = true) @RequestParam String phone) {
// 验证手机号格式
@@ -78,4 +106,62 @@ public class CaptchaController {
}
return code.toString();
}
/**
* 生成验证码图片Base64格式
* 使用 SVG 格式生成验证码,避免字体依赖问题
*
* @param code 验证码文本
* @return Base64编码的图片
*/
private String generateCaptchaImage(String code) {
Random random = new Random();
StringBuilder svg = new StringBuilder();
svg.append("<svg xmlns='http://www.w3.org/2000/svg' width='120' height='40'>");
// 背景
svg.append("<rect width='120' height='40' fill='#f8f9fa'/>");
// 绘制验证码字符
for (int i = 0; i < code.length(); i++) {
char c = code.charAt(i);
int x = 15 + i * 25;
int y = 25 + random.nextInt(5) - 2;
int rotate = random.nextInt(30) - 15;
// 随机颜色
String color = String.format("#%02x%02x%02x",
random.nextInt(100),
random.nextInt(100),
random.nextInt(100));
svg.append(String.format(
"<text x='%d' y='%d' font-size='28' font-weight='bold' fill='%s' transform='rotate(%d %d %d)'>%c</text>",
x, y, color, rotate, x, y, c
));
}
// 添加干扰线
for (int i = 0; i < 3; i++) {
int x1 = random.nextInt(120);
int y1 = random.nextInt(40);
int x2 = random.nextInt(120);
int y2 = random.nextInt(40);
String color = String.format("#%02x%02x%02x",
random.nextInt(200) + 50,
random.nextInt(200) + 50,
random.nextInt(200) + 50);
svg.append(String.format(
"<line x1='%d' y1='%d' x2='%d' y2='%d' stroke='%s' stroke-width='1'/>",
x1, y1, x2, y2, color
));
}
svg.append("</svg>");
// 转换为 Base64
return "data:image/svg+xml;base64," + java.util.Base64.getEncoder().encodeToString(svg.toString().getBytes());
}
}

View File

@@ -75,4 +75,13 @@ public class MartialDeductionItemController extends BladeController {
return R.status(deductionItemService.removeByIds(Func.toLongList(ids)));
}
/**
* 更新排序
*/
@PostMapping("/update-order")
@Operation(summary = "更新排序", description = "传入排序数据")
public R updateOrder(@RequestBody List<MartialDeductionItem> sortData) {
return R.status(deductionItemService.updateOrder(sortData));
}
}

View File

@@ -3,6 +3,8 @@ package org.springblade.modules.martial.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.martial.pojo.entity.MartialDeductionItem;
import java.util.List;
/**
* DeductionItem 服务类
*
@@ -10,4 +12,12 @@ import org.springblade.modules.martial.pojo.entity.MartialDeductionItem;
*/
public interface IMartialDeductionItemService extends IService<MartialDeductionItem> {
/**
* 批量更新排序
*
* @param sortData 排序数据
* @return 是否成功
*/
boolean updateOrder(List<MartialDeductionItem> sortData);
}

View File

@@ -6,6 +6,8 @@ import org.springblade.modules.martial.mapper.MartialDeductionItemMapper;
import org.springblade.modules.martial.service.IMartialDeductionItemService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* DeductionItem 服务实现类
*
@@ -14,4 +16,23 @@ import org.springframework.stereotype.Service;
@Service
public class MartialDeductionItemServiceImpl extends ServiceImpl<MartialDeductionItemMapper, MartialDeductionItem> implements IMartialDeductionItemService {
@Override
public boolean updateOrder(List<MartialDeductionItem> sortData) {
if (sortData == null || sortData.isEmpty()) {
return false;
}
// 批量更新排序
for (MartialDeductionItem item : sortData) {
if (item.getId() != null && item.getSortOrder() != null) {
MartialDeductionItem updateItem = new MartialDeductionItem();
updateItem.setId(item.getId());
updateItem.setSortOrder(item.getSortOrder());
this.updateById(updateItem);
}
}
return true;
}
}