diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/public_/PublicOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/public_/PublicOrderController.java new file mode 100644 index 0000000..a2eb751 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/public_/PublicOrderController.java @@ -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 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 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; + } +} + diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 511842b..0c287a1 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -112,6 +112,8 @@ public class SecurityConfig permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); // 对于登录login 注册register 验证码captchaImage 允许匿名访问 requests.antMatchers("/login", "/register", "/captchaImage").permitAll() + // 公开接口,允许匿名访问 + .antMatchers("/public/**").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java index 2b57133..ae045f2 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java @@ -413,11 +413,11 @@ public class InstructionServiceImpl implements IInstructionService { static { /* - 18539187615 - 15738558087 + 13243039070 15639125541 - 17530176250 */ + 17530176250 + */ phoneWithTF.add("13243039070"); phoneWithTF.add("15639125541");