您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

Controller层代码这么写,简洁又优雅

时间:2022-08-02 11:56:44  来源:微信公众号  作者:程序员小文

一个优秀的Controller层逻辑

说到 Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,我认为是「不可或缺的配角」,说它不可或缺是因为无论是传统的三层架构还是现在的COLA架构,Controller 层依旧有一席之地,说明他的必要性;说它是配角是因为 Controller 层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求

从现状看问题

Controller 主要的工作有以下几项

  • 接收请求并解析参数
  • 调用 Service 执行具体的业务代码(可能包含参数校验)
  • 捕获业务逻辑异常做出反馈
  • 业务逻辑执行成功做出响应
//DTO@Datapublic class TestDTO {    private Integer num;    private String type;}//Service@Servicepublic class TestService {    public Double service(TestDTO testDTO) throws Exception {        if (testDTO.getNum() <= 0) {            throw new Exception("输入的数字需要大于0");        }        if (testDTO.getType().equals("square")) {            return Math.pow(testDTO.getNum(), 2);        }        if (testDTO.getType().equals("factorial")) {            double result = 1;            int num = testDTO.getNum();            while (num > 1) {                result = result * num;                num -= 1;            }            return result;        }        throw new Exception("未识别的算法");    }}//Controller@RestControllerpublic class TestController {    private TestService testService;    @PostMApping("/test")    public Double test(@RequestBody TestDTO testDTO) {        try {            Double result = this.testService.service(testDTO);            return result;        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Autowired    public DTOid setTestService(TestService testService) {        this.testService = testService;    }}

如果真的按照上面所列的工作项来开发 Controller 代码会有几个问题

  • 参数校验过多地耦合了业务代码,违背单一职责原则
  • 可能在多个业务中都抛出同一个异常,导致代码重复
  • 各种异常反馈和成功响应格式不统一,接口对接不友好

改造 Controller 层逻辑

统一返回结构

统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为 null 就判断成功与否,因为有些接口的设计就是如此),使用一个状态码、状态信息就能清楚地了解接口调用情况

//定义返回数据结构public interface IResult {    Integer getCode();    String getMessage();}//常用结果的枚举public enum ResultEnum implements IResult {    SUCCESS(2001, "接口调用成功"),    VALIDATE_FAILED(2002, "参数校验失败"),    COMMON_FAILED(2003, "接口调用失败"),    FORBIDDEN(2004, "没有权限访问资源");    private Integer code;    private String message;    //省略get、set方法和构造方法}//统一返回数据结构@Data@NoArgsConstructor@AllArgsConstructorpublic class Result<T> {    private Integer code;    private String message;    private T data;    public static <T> Result<T> success(T data) {        return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);    }    public static <T> Result<T> success(String message, T data) {        return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);    }    public static Result<?> failed() {        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);    }    public static Result<?> failed(String message) {        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);    }    public static Result<?> failed(IResult errorResult) {        return new Result<>(errorResult.getCode(), errorResult.getMessage(), null);    }    public static <T> Result<T> instance(Integer code, String message, T data) {        Result<T> result = new Result<>();        result.setCode(code);        result.setMessage(message);        result.setData(data);        return result;    }}

统一返回结构后,在 Controller 中就可以使用了,但是每一个 Controller 都写这么一段最终封装的逻辑,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构

统一包装处理

Spring 中提供了一个类 ResponseBodyAdvice ,能帮助我们实现上述需求

ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。那这样就可以把统一包装的工作放到这个类里面。

public interface ResponseBodyAdvice<T> {    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);    @Nullable    T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);}
  • supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
  • beforeBodyWrite:对 response 进行具体的处理
// 如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成@RestControllerAdvice(basePackages = "com.example.demo")public class ResponseAdvice implements ResponseBodyAdvice<Object> {    @Override    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {        // 如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解        return true;    }      @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {        // 提供一定的灵活度,如果body已经被包装了,就不进行包装        if (body instanceof Result) {            return body;        }        return Result.success(body);    }}

经过这样改造,既能实现对 Controller 返回的数据进行统一包装,又不需要对原有代码进行大量的改动

参数校验

JAVA API 的规范 JSR303 定义了校验的标准 validation-api ,其中一个比较出名的实现是 hibernate validation ,spring validation 是对其的二次封装,常用于 SpringMVC 的参数自动校验,参数校验的代码就不需要再与业务逻辑代码进行耦合了

@PathVariable 和 @RequestParam 参数校验

Get 请求的参数接收一般依赖这两个注解,但是处于 url 有长度限制和代码的可维护性,超过 5 个参数尽量用实体来传参 对 @PathVariable 和 @RequestParam 参数进行校验需要在入参声明约束的注解

如果校验失败,会抛出
MethodArgumentNotValidException 异常

@RestController(value = "prettyTestController")@RequestMapping("/pretty")@Validatedpublic class TestController {    private TestService testService;    @GetMapping("/{num}")    public Integer detail(@PathVariable("num") @Min(1) @Max(20) Integer num) {        return num * num;    }    @GetMapping("/getByEmail")    public TestDTO getByAccount(@RequestParam @NotBlank @Email String email) {        TestDTO testDTO = new TestDTO();        testDTO.setEmail(email);        return testDTO;    }    @Autowired    public void setTestService(TestService prettyTestService) {        this.testService = prettyTestService;    }}

校验原理

在 SpringMVC 中,有一个类是
RequestResponseBodyMethodProcessor ,这个类有两个作用(实际上可以从名字上得到一点启发)

  • 用于解析 @RequestBody 标注的参数
  • 处理 @ResponseBody 标注方法的返回值

解析 @RequestBoyd 标注参数的方法是 resolveArgument

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {      /**     * Throws MethodArgumentNotValidException if validation fails.     * @throws HttpMessageNotReadableException if {@link RequestBody#required()}     * is {@code true} and there is no body content or if there is no suitable     * converter to read the content with.     */    @Override    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {      parameter = parameter.nestedIfOptional();      //把请求数据封装成标注的DTO对象      Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());      String name = Conventions.getVariableNameForParameter(parameter);      if (binderFactory != null) {        WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);        if (arg != null) {          //执行数据校验          validateIfApplicable(binder, parameter);          //如果校验不通过,就抛出MethodArgumentNotValidException异常          //如果我们不自己捕获,那么最终会由DefaultHandlerExceptionResolver捕获处理          if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());          }        }        if (mavContainer != null) {          mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());        }      }      return adaptArgumentIfNecessary(arg, parameter);    }}public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {  /**    * Validate the binding target if applicable.    * <p>The default implementation checks for {@code @javax.validation.Valid},    * Spring's {@link org.springframework.validation.annotation.Validated},    * and custom annotations whose name starts with "Valid".    * @param binder the DataBinder to be used    * @param parameter the method parameter descriptor    * @since 4.1.5    * @see #isBindExceptionRequired    */   protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {    //获取参数上的所有注解      Annotation[] annotations = parameter.getParameterAnnotations();      for (Annotation ann : annotations) {      //如果注解中包含了@Valid、@Validated或者是名字以Valid开头的注解就进行参数校验         Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);         if (validationHints != null) {        //实际校验逻辑,最终会调用Hibernate Validator执行真正的校验        //所以Spring Validation是对Hibernate Validation的二次封装            binder.validate(validationHints);            break;         }      }   }}

@RequestBody 参数校验

Post、Put 请求的参数推荐使用 @RequestBody 请求体参数

对 @RequestBody 参数进行校验需要在 DTO 对象中加入校验条件后,再搭配 @Validated 即可完成自动校验 如果校验失败,会抛出
ConstraintViolationException 异常

//DTO@Datapublic class TestDTO {    @NotBlank    private String userName;    @NotBlank    @Length(min = 6, max = 20)    private String password;    @NotNull    @Email    private String email;}//Controller@RestController(value = "prettyTestController")@RequestMapping("/pretty")public class TestController {    private TestService testService;    @PostMapping("/test-validation")    public void testValidation(@RequestBody @Validated TestDTO testDTO) {        this.testService.save(testDTO);    }    @Autowired    public void setTestService(TestService testService) {        this.testService = testService;    }}

校验原理

声明约束的方式,注解加到了参数上面,可以比较容易猜测到是使用了 AOP 对方法进行增强

而实际上 Spring 也是通过
MethodValidationPostProcessor 动态注册 AOP 切面,然后使用
MethodValidationInterceptor 对切点方法进行织入增强

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {      //指定了创建切面的Bean的注解   private Class<? extends Annotation> validatedAnnotationType = Validated.class;      @Override    public void afterPropertiesSet() {        //为所有@Validated标注的Bean创建切面        Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);        //创建Advisor进行增强        this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));    }    //创建Advice,本质就是一个方法拦截器    protected Advice createMethodValidationAdvice(@Nullable Validator validator) {        return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());    }}public class MethodValidationInterceptor implements MethodInterceptor {    @Override    public Object invoke(MethodInvocation invocation) throws Throwable {        //无需增强的方法,直接跳过        if (isFactoryBeanMetadataMethod(invocation.getMethod())) {            return invocation.proceed();        }              Class<?>[] groups = determineValidationGroups(invocation);        ExecutableValidator execVal = this.validator.forExecutables();        Method methodToValidate = invocation.getMethod();        Set<ConstraintViolation<Object>> result;        try {            //方法入参校验,最终还是委托给Hibernate Validator来校验             //所以Spring Validation是对Hibernate Validation的二次封装            result = execVal.validateParameters(                invocation.getThis(), methodToValidate, invocation.getArguments(), groups);        }        catch (IllegalArgumentException ex) {            ...        }        //校验不通过抛出ConstraintViolationException异常        if (!result.isEmpty()) {            throw new ConstraintViolationException(result);        }        //Controller方法调用        Object returnValue = invocation.proceed();        //下面是对返回值做校验,流程和上面大概一样        result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);        if (!result.isEmpty()) {            throw new ConstraintViolationException(result);        }        return returnValue;    }}

自定义校验规则

有些时候 JSR303 标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则

自定义校验规则需要做两件事情

  • 自定义注解类,定义错误信息和一些其他需要的内容
  • 注解校验器,定义判定规则
//自定义注解类@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documented@Constraint(validatedBy = MobileValidator.class)public @interface Mobile {    /**     * 是否允许为空     */    boolean required() default true;    /**     * 校验不通过返回的提示信息     */    String message() default "不是一个手机号码格式";    /**     * Constraint要求的属性,用于分组校验和扩展,留空就好     */    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};}//注解校验器public class MobileValidator implements ConstraintValidator<Mobile, CharSequence> {    private boolean required = false;    private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$"); // 验证手机号    /**     * 在验证开始前调用注解里的方法,从而获取到一些注解里的参数     *     * @param constraintAnnotation annotation instance for a given constraint declaration     */    @Override    public void initialize(Mobile constraintAnnotation) {        this.required = constraintAnnotation.required();    }    /**     * 判断参数是否合法     *     * @param value   object to validate     * @param context context in which the constraint is evaluated     */    @Override    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {        if (this.required) {            // 验证            return isMobile(value);        }        if (StringUtils.hasText(value)) {            // 验证            return isMobile(value);        }        return true;    }    private boolean isMobile(final CharSequence str) {        Matcher m = pattern.matcher(str);        return m.matches();    }}

自动校验参数真的是一项非常必要、非常有意义的工作。JSR303 提供了丰富的参数校验规则,再加上复杂业务的自定义校验规则,完全把参数校验和业务逻辑解耦开,代码更加简洁,符合单一职责原则。

更多关于 Spring 参数校验请参考:

https://juejin.cn/post/6856541106626363399

自定义异常与统一拦截异常

原来的代码中可以看到有几个问题

  • 抛出的异常不够具体,只是简单地把错误信息放到了 Exception 中
  • 抛出异常后,Controller 不能具体地根据异常做出反馈
  • 虽然做了参数自动校验,但是异常返回结构和正常返回结构不一致

自定义异常是为了后面统一拦截异常时,对业务中的异常有更加细颗粒度的区分,拦截时针对不同的异常作出不同的响应

而统一拦截异常的目的一个是为了可以与前面定义下来的统一包装返回结构能对应上,另一个是我们希望无论系统发生什么异常,Http 的状态码都要是 200 ,尽可能由业务来区分系统的异常

//自定义异常public class ForbiddenException extends RuntimeException {    public ForbiddenException(String message) {        super(message);    }}//自定义异常public class BusinessException extends RuntimeException {    public BusinessException(String message) {        super(message);    }}//统一拦截异常@RestControllerAdvice(basePackages = "com.example.demo")public class ExceptionAdvice {    /**     * 捕获 {@code BusinessException} 异常     */    @ExceptionHandler({BusinessException.class})    public Result<?> handleBusinessException(BusinessException ex) {        return Result.failed(ex.getMessage());    }    /**     * 捕获 {@code ForbiddenException} 异常     */    @ExceptionHandler({ForbiddenException.class})    public Result<?> handleForbiddenException(ForbiddenException ex) {        return Result.failed(ResultEnum.FORBIDDEN);    }    /**     * {@code @RequestBody} 参数校验不通过时抛出的异常处理     */    @ExceptionHandler({MethodArgumentNotValidException.class})    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {        BindingResult bindingResult = ex.getBindingResult();        StringBuilder sb = new StringBuilder("校验失败:");        for (FieldError fieldError : bindingResult.getFieldErrors()) {            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");        }        String msg = sb.toString();        if (StringUtils.hasText(msg)) {            return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), msg);        }        return Result.failed(ResultEnum.VALIDATE_FAILED);    }    /**     * {@code @PathVariable} 和 {@code @RequestParam} 参数校验不通过时抛出的异常处理     */    @ExceptionHandler({ConstraintViolationException.class})    public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {        if (StringUtils.hasText(ex.getMessage())) {            return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), ex.getMessage());        }        return Result.failed(ResultEnum.VALIDATE_FAILED);    }    /**     * 顶级异常捕获并统一处理,当其他异常无法处理时候选择使用     */    @ExceptionHandler({Exception.class})    public Result<?> handle(Exception ex) {        return Result.failed(ex.getMessage());    }}

总结

做好了这一切改动后,可以发现 Controller 的代码变得非常简洁,可以很清楚地知道每一个参数、每一个 DTO 的校验规则,可以很明确地看到每一个 Controller 方法返回的是什么数据,也可以方便每一个异常应该如何进行反馈

这一套操作下来后,我们能更加专注于业务逻辑的开发,代码简洁、功能完善,何乐而不为呢?

原文链接:
https://mp.weixin.qq.com/s/KA1gwlup0uybaJR0PbGFyw



Tags:Controller   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java的Controller中无法获取到真正的客户端IP地址问题
Java web中的Controller如何获取客户端的IP地址?我们通常的做法都是通过 HttpServletRequest.getRemoteHost方法,但是这个简单的方法却可能你无法获取到真实的客户端IP。我们...【详细内容】
2023-11-18  Search: Controller  点击:(53)  评论:(0)  加入收藏
五分钟搞懂Ingress /IngressController/IngressClass的区别
先来个一句话总结:Ingress由Ingress规则、IngressController、IngressClass这3部分组成。Ingress资源只是一系列路由转发配置,必须使用IngressController才能让路由规则生效,而...【详细内容】
2023-09-17  Search: Controller  点击:(100)  评论:(0)  加入收藏
Spring定义Controller接口的这些方式你肯定不知道
在这个程序中我们还可以让这个程序成为一个受应用服务管理的Servlet程序。可以将注解改成@WebServlet("/others/servlet")。只是换成这个注解还并不能生效,还需要在启动类(任...【详细内容】
2023-09-08  Search: Controller  点击:(252)  评论:(0)  加入收藏
还不懂分布系统,速看Kafka Controller选举过程
上篇文章讲了Kafka架构,详细介绍了Kafka中不同组件之间是怎样协调工作的。了解到Kafka集群包含多个Broker节点,但是这些Broker节点的具体作用是什么?是怎么进行通信的?某个Broke...【详细内容】
2023-05-15  Search: Controller  点击:(339)  评论:(0)  加入收藏
Controller层代码这么写,简洁又优雅
一个优秀的Controller层逻辑说到 Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,我认为是「不可或缺的配角」,说它不可或缺是因为无论是传统的三层架...【详细内容】
2022-08-02  Search: Controller  点击:(339)  评论:(0)  加入收藏
k8s二进制安装kube-controller-manager
利用 kube-controller-manager-csr.json请求文件,创建 kube-controller-manager 证书和私钥[root@FNSHB109 k8s]# cat kube-controller-manager-csr.json{"CN": "system:kube...【详细内容】
2022-05-17  Search: Controller  点击:(724)  评论:(0)  加入收藏
SpringBoot中的Controller详解
SpringBoot中的Controller注册本篇将会以Servlet为切入点,通过源码来看web容器中的Controller是如何注册到HandlerMapping中。请求来了之后,web容器是如何根据请求路径找到对...【详细内容】
2021-11-04  Search: Controller  点击:(2240)  评论:(0)  加入收藏
我也没想到,Java开发 API接口可以不用写 Controller了
今天介绍我正在用的一款高效敏捷开发工具magic-api,顺便分享一点工作中使用它的心得缘起先说一下我为什么会使用这个工具?最近新启动一个项目,业务并不算复杂,那种典型的管理系...【详细内容】
2021-06-01  Search: Controller  点击:(491)  评论:(0)  加入收藏
ajax请求controller ajax跨域报错处理
报错:Access to XMLHttpRequest at &#39;http://localhost:8080/SpringBootServer/testfile&#39; from origin &#39;null&#39; has been blocked by CORS policy: No &#39;Ac...【详细内容】
2020-09-03  Search: Controller  点击:(219)  评论:(0)  加入收藏
▌简易百科推荐
Meta如何将缓存一致性提高到99.99999999%
介绍缓存是一种强大的技术,广泛应用于计算机系统的各个方面,从硬件缓存到操作系统、网络浏览器,尤其是后端开发。对于Meta这样的公司来说,缓存尤为重要,因为它有助于减少延迟、扩...【详细内容】
2024-04-15    dbaplus社群  Tags:Meta   点击:(3)  评论:(0)  加入收藏
SELECT COUNT(*) 会造成全表扫描?回去等通知吧
前言SELECT COUNT(*)会不会导致全表扫描引起慢查询呢?SELECT COUNT(*) FROM SomeTable网上有一种说法,针对无 where_clause 的 COUNT(*),MySQL 是有优化的,优化器会选择成本最小...【详细内容】
2024-04-11  dbaplus社群    Tags:SELECT   点击:(3)  评论:(0)  加入收藏
10年架构师感悟:从问题出发,而非技术
这些感悟并非来自于具体的技术实现,而是关于我在架构设计和实施过程中所体会到的一些软性经验和领悟。我希望通过这些分享,能够激发大家对于架构设计和技术实践的思考,帮助大家...【详细内容】
2024-04-11  dbaplus社群    Tags:架构师   点击:(2)  评论:(0)  加入收藏
Netflix 是如何管理 2.38 亿会员的
作者 | Surabhi Diwan译者 | 明知山策划 | TinaNetflix 高级软件工程师 Surabhi Diwan 在 2023 年旧金山 QCon 大会上发表了题为管理 Netflix 的 2.38 亿会员 的演讲。她在...【详细内容】
2024-04-08    InfoQ  Tags:Netflix   点击:(5)  评论:(0)  加入收藏
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(9)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(16)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(14)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(9)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(16)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(10)  评论:(0)  加入收藏
站内最新
站内热门
站内头条