从来都是在controller层进行参数校验,便对吗?

大家好,这里是小奏,觉得文章不错可以关注公众号小奏技术

传统的参数校验

相信现在的大部分web项目进行参数校验都是通过如下方式

  1. 添加依赖
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
  1. 在 controller进行参数校验
  • ValidationController
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class ValidationController {

    private final XiaoZouService xiaoZouService;

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody XiaoZouDTO xiaoZouDTO) {
        xiaoZouService.createUser(xiaoZouDTO);
        return ResponseEntity.ok("用户创建成功");
    }
}
  • XiaoZouDTO
@Data
public class XiaoZouDTO {

    @NotNull(message = "用户ID不能为空")
    private Long id;

    @NotBlank(message = "用户名不能为空")
    @Length(min = 2, max = 20, message = "用户名长度必须在2-20之间")
    private String username;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

实际的业务逻辑处理被封装在xiaoZouService

异常处理器

我们再随便加一个全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        StringBuilder errors = new StringBuilder();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.append(fieldName).append(": ").append(errorMessage).append("; ");
        });
        return ResponseEntity.badRequest().body(errors.toString());
    }
}

测试

然后我们进行接口请求

POST http://localhost:8091/api/xiao-zou
Accept: application/json
Content-Type: application/json

{
  "name": "xiao-zou",
  "age": 18
}

参数我们随便乱传,就会出现如下错误信息提示

email: 邮箱不能为空; id: 用户ID不能为空; username: 用户名不能为空; 

结果看似满足我们的预期。

但是目前我们会注意到一个问题

  1. 我们的参数实际使用是在service,但是我们的参数校验是在controller
  2. 如果我们存在自定义aop切面进行参数修改,aop的切面执行顺序在参数校验之后,那么我们的参数校验就失效了
  3. 我们的service方法并不单单仅在controller中调用,我们的service方法可能会在其他地方调用,比如jobmqRPC等等,这种情况下我们的参数校验就失效了

我们来实际举例说明

AOP切面修改参数导致的参数校验失败

假设我们的的邮箱为了数据安全之类的业务背景,统一通过请求头获取

那么我们可能定义如下一个切面

@Aspect
@Slf4j
@RequiredArgsConstructor
public class XiaoZouHttpAspect {

    @Pointcut("@annotation(com.spring.boot.base.annotation.XiaoZouResponse)")
    public void controllerMethodAspect() {

    }
    
    @GetMapping
    @Before("controllerMethodAspect()")
    public void doBefore(JoinPoint joinPoint) {

        Object[] args = joinPoint.getArgs();
        if (Objects.isNull(args) || args.length == 0) {
            return;
        }
        for (Object argObj : args) {
            if (argObj instanceof XiaoZouDTO) {
                XiaoZouDTO xiaoZouDTO = (XiaoZouDTO) argObj;
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest httpServletRequest = requestAttributes.getRequest();
                xiaoZouDTO.setEmail(httpServletRequest.getHeader("x-xiaozou-email"));
            }
        }
        
    }
    
}

此时如果请求头里面没有x-xiaozou-email,但是我们的body里面传了email

这时候的参数校验也会失败,失败步骤如下

  1. XiaoZouDTO接收到body的email参数进行参数校验通过了参数校验
  2. XiaoZouHttpAspect切面修改了email参数,但是这个修改是在参数校验之后的,email为空进入到service层,所以参数校验失败

dubbo调用参数校验失效

dubbo例的测试用就很简单了。我们的service可能既给controller调用,也可能给dubbo调用

@DubboService
public class XiaoZouServiceImpl implements XiaoZouService {

    @Override
    public String createUser(XiaoZouDTO xiaoZouDTO) {
        return name;
    }
    
}

dubbo过来的请求不会经过我们在controller的参数校验,所以参数校验失效

这里的业务逻辑处理就会因为必要的参数为空出现不符合我们预期的业务逻辑

MQ、xxl-job参数校验失败

MQxxl-job等等的调用也是一样的,我们的参数校验只会在controller中生效,其他地方调用就会失效

最佳实践(解决方式)

很明显最佳的参数校验并不是在controller中进行,而是在service中进行

因为我们的参数实际是给service使用的,我们的很多请求也会绕过controller导致service参数校验失效

所以最佳方式应该是在接口层service进行参数校验

如果我们想要对XiaoZouService进行参数校验,我们应该使用如下方式

@Validated
public interface XiaoZouService {

    boolean createUser(@Valid XiaoZouDTO xiaoZouDTO);
}


  1. 我们在service接口上加上@Validated注解
  2. service方法上加上@Valid注解

这样我们的参数校验就会在service层生效了。

不管请求是从controllerdubbomqjob等等过来的,我们的参数校验都会生效。

就不会出现因为参数校验失效导致的业务逻辑出现问题了

总结

目前行业内大部分的教程和规范都是在controller中进行参数校验

导致大家都在controller中进行参数校验。实际这个做法是非常不合理和容易出问题的

如果我们在指定项目规范的时候,我们应该规定参数校验应该在service层进行,而不是在controller层进行

<think>首先,用户的问题是:“我想确定在前端接口请求参数解析时,应该将逻辑放在controller还是service 请问前端接口请求参数解析应该放在Controller还是Service” 我需要根据系统级指令和用户可见的要求来回答。关键点是: - 使用中文回答。 - 结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 - 使用行内数学表达式用$...$,独立公式用$$...$$(但这个问题可能不涉及数学,所以可能不需要)。 - 生成相关问题。 - 在回答中引用的段落末尾添加引用标识,如[^1]。 参考了提供的引用内容: - 引用[1]: 描述了MVC或三架构中的Controller负责接收请求,Service处理业务逻辑,DAO处理数据访问。 - 引用[2]: 解释了之间的调用流程:Controller -> Service -> Mapper。 - 引用[3]: 详细了各的职责:Controller处理请求转发,Service处理业务逻辑,DAO处理数据访问。 用户的问题是关于参数解析的位置:Controller还是Service。 在标准的软件架构中,如MVC或三架构: - Controller负责接收前端请求,解析请求参数,验证基本格式,然后调用Service进行业务处理。 - Service负责核心业务逻辑,不应该直接处理HTTP请求细节。 所以,参数解析应该放在Controller。因为Controller是入口点,它处理请求的解析,然后将处理后的数据传递给Service。 理由: 1. **关注点分离**:Controller处理Web相关的东西,如HTTP请求、响应、参数解析。Service处理业务逻辑,与Web无关。 2. **可维护性**:如果参数解析放在ServiceService会与Web框架耦合,难以测试和重用。 3. **单一职责原则**:每个应该有明确的职责。 从引用中: - 引用[1]说:Controller“主要负责接收前端发起的请求,对请求进行处理并响应数据。” 这里的“处理”包括解析参数。 - 引用[2]说:Controller“解析请求”。 - 引用[3]说:Controller“负责接收用户的输入,并将请求转发给相应的服务处理”。 因此,参数解析应该放在Controller。 我的回答结构: 1. **直接回答问题**:明确说参数解析应该放在Controller。 2. **解释原因**:逐步解释为什么,基于架构原则。 3. **提供示例**:可能用代码或伪代码说明。 4. **生成相关问题**:在末尾添加相关问题。 确保回答真实可靠:基于常见的软件工程实践。 最后,添加引用标识:既然引用了提供的文本,在相关句子末尾加[^1], [^2], [^3]。 相关问题生成:基于用户问题,生成2-3个相关问题。</think>### 前端接口请求参数解析的位置:Controller还是Service? 在标准的软件架构中(如MVC或三架构),前端接口请求参数解析的逻辑应该放在**Controller**。下面我将逐步解释原因,并提供详细说明,确保回答基于可靠的软件工程实践。 #### 1. **Controller的职责** Controller(控制)是系统与前端交互的入口点,主要负责接收HTTP请求、解析请求参数、进行基本验证(如参数格式、类型检查),并将处理后的数据转发给Service进行业务逻辑处理。参数解析属于请求处理的一部分,因此应该由Controller负责[^1]。 例如,在Spring框架中,Controller通常使用注解(如`@RequestParam`或`@RequestBody`)来解析请求参数: ```java @RestController public class UserController { @Autowired private UserService userService; @PostMapping("/user") public ResponseEntity<?> createUser(@RequestBody UserRequest request) { // 参数解析:将JSON请求体解析为UserRequest对象 if (request == null || request.getName() == null) { return ResponseEntity.badRequest().body("参数错误"); } // 调用Service处理业务逻辑 userService.createUser(request); return ResponseEntity.ok("用户创建成功"); } } ``` 在这里,`@RequestBody`注解在Controller完成了参数解析,然后将`UserRequest`对象传递给Service。 #### 2. **为什么不应该放在Service?** - **关注点分离原则**:Service(业务逻辑)的核心职责是处理业务规则、计算和协调数据访问(DAO)。如果将参数解析放在Service,会导致业务逻辑与Web请求细节(如HTTP协议、参数格式)耦合,降低代码的可维护性和可测试性[^2]。 - 例如,Service方法如果直接处理原始请求对象(如HttpServletRequest),会变得难以独立测试(需模拟Web环境),而Controller可以通过单元测试轻松覆盖参数解析逻辑。 - **单一职责原则**:每个应专注于其核心任务。Controller处理请求/响应,Service处理业务逻辑。参数解析是请求处理的一部分,属于Controller的范畴[^3]。 - **架构清晰性**:在请求流程中,Controller先解析参数,然后调用Service接口(而非实现类),确保业务逻辑保持纯净和可重用[^2]。如果Service涉及参数解析,它可能违反分架构的初衷。 #### 3. **最佳实践建议** - **Controller处理**:在Controller中完成所有请求相关的解析,包括: - 参数提取(如查询参数、路径参数、请求体)。 - 基本验证(如非空检查、类型转换)。 - 错误处理(如返回400 Bad Request响应)。 然后,将解析后的数据(如DTO对象)传递给Service。 - **Service聚焦业务**:Service应接收简单、干净的参数(如领域对象或基本类型),专注于业务规则。例如: ```java @Service public class UserService { public void createUser(UserRequest request) { // 业务逻辑:如数据校验、计算、调用DAO if (isValidUser(request)) { userDao.save(request); } } } ``` - **优势**:这种分离提高了代码的可读性、可测试性,并支持模块化开发。例如,修改参数格式时,只需调整Controller,而不影响业务逻辑。 #### 4. **总结** 参数解析应放在Controller,以保持架构的清晰和高效。Controller作为“守门人”,处理所有与Web相关的细节,而Service专注于核心业务,确保系统易于维护和扩展[^1][^2][^3]。如果您使用的框架(如Spring、Express.js或Flask)有特定机制,遵循其约定即可。 如果您有更多上下文(如具体框架或代码示例),我可以提供更针对性的建议!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值