This commit is contained in:
2025-10-21 23:27:18 +08:00
parent c7d38daf32
commit b035ff7f7f
3 changed files with 165 additions and 3 deletions

View File

@@ -0,0 +1,160 @@
package com.ruoyi.web.controller.public_;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.LimitType;
import com.ruoyi.jarvis.service.IInstructionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* 公开订单提交控制器
* 用于接收外部提交的订单信息
* 特点:
* 1. 无需登录认证
* 2. 带接口限流保护
* 3. 详细的日志记录
* 4. 只允许使用"单"指令提交订单
*/
@RestController
@RequestMapping("/public/order")
public class PublicOrderController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(PublicOrderController.class);
private final IInstructionService instructionService;
public PublicOrderController(IInstructionService instructionService) {
this.instructionService = instructionService;
}
/**
* 提交订单
*
* 限流策略:
* - 每个IP每分钟最多3次请求
* - 防止恶意刷单和攻击
*
* @param body 请求体包含command字段
* @param request HTTP请求对象用于获取客户端信息
* @return 执行结果
*/
@PostMapping("/submit")
@RateLimiter(
key = CacheConstants.RATE_LIMIT_KEY,
time = 60,
count = 3,
limitType = LimitType.IP
)
public AjaxResult submit(@RequestBody Map<String, String> body, HttpServletRequest request) {
// 获取客户端信息用于日志记录
String clientIp = getClientIp(request);
String userAgent = request.getHeader("User-Agent");
// 获取指令内容
String cmd = body != null ? body.get("command") : null;
// 记录请求日志
log.info("======================================");
log.info("公开订单提交 - 开始");
log.info("客户端IP: {}", clientIp);
log.info("User-Agent: {}", userAgent);
log.info("请求时间: {}", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
// 参数校验
if (cmd == null || cmd.trim().isEmpty()) {
log.warn("参数校验失败: 指令内容为空");
log.info("公开订单提交 - 结束(失败)");
log.info("======================================");
return AjaxResult.error("请输入订单信息");
}
String trimmedCmd = cmd.trim();
log.info("指令内容长度: {} 字符", trimmedCmd.length());
log.info("指令内容预览: {}", trimmedCmd.length() > 100 ? trimmedCmd.substring(0, 100) + "..." : trimmedCmd);
// 安全检查:只允许"单"开头的指令
if (!trimmedCmd.startsWith("单:") && !trimmedCmd.startsWith("单:") && !trimmedCmd.startsWith("")) {
log.warn("安全检查失败: 指令不是以'单'开头");
log.info("公开订单提交 - 结束(拒绝)");
log.info("======================================");
return AjaxResult.error("只允许提交订单信息,指令必须以'单:'开头");
}
// 执行指令
List<String> result;
try {
log.info("开始执行订单指令...");
result = instructionService.execute(trimmedCmd);
log.info("订单指令执行完成");
// 记录执行结果
if (result != null && !result.isEmpty()) {
log.info("执行结果条数: {}", result.size());
for (int i = 0; i < result.size(); i++) {
String item = result.get(i);
if (item != null) {
// 检查是否包含警告标记
if (item.contains("[炸弹]")) {
log.warn("执行结果[{}]包含警告: {}", i, item);
} else if (item.contains("成功")) {
log.info("执行结果[{}]: 成功", i);
} else {
log.info("执行结果[{}]长度: {} 字符", i, item.length());
}
}
}
} else {
log.warn("执行结果为空");
}
log.info("公开订单提交 - 结束(成功)");
log.info("======================================");
return AjaxResult.success(result);
} catch (Exception e) {
log.error("执行订单指令时发生异常", e);
log.error("异常类型: {}", e.getClass().getName());
log.error("异常消息: {}", e.getMessage());
log.info("公开订单提交 - 结束(异常)");
log.info("======================================");
return AjaxResult.error("订单提交失败: " + e.getMessage());
}
}
/**
* 获取客户端真实IP地址
* 考虑代理和负载均衡的情况
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 对于通过多个代理的情况第一个IP为客户端真实IP
if (ip != null && ip.contains(",")) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
}

View File

@@ -112,6 +112,8 @@ public class SecurityConfig
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage").permitAll() requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 公开接口,允许匿名访问
.antMatchers("/public/**").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

View File

@@ -413,11 +413,11 @@ public class InstructionServiceImpl implements IInstructionService {
static { static {
/* /*
18539187615
15738558087
13243039070 13243039070
15639125541 15639125541
17530176250 */ 17530176250
*/
phoneWithTF.add("13243039070"); phoneWithTF.add("13243039070");
phoneWithTF.add("15639125541"); phoneWithTF.add("15639125541");