如何优雅简单地写 Controller 层代码?

本篇就来介绍一下,如何写好一个 controller ,让你的接口变的更加优雅!

一个完整的后端请求由 4 部分组成:

  • 接口地址(也就是 URL 地址)

  • 请求方式(一般就是 get、set,当然还有 put、delete)

  • 请求数据(request,有 head 跟 body)

  • 响应数据(response)

本篇将解决以下 3 个问题:

  • 当接收到请求时,如何优雅的校验参数

  • 返回响应数据该如何统一的进行处理

  • 接收到请求,处理业务逻辑时抛出了异常又该如何处理

1Controller 层参数接收

太基础了,可以跳过...

常见的请求就分为 get 跟 post 两种:

@RestController
@RequestMapping("/product/product-info")
public class ProductInfoController {

    @Autowired
    ProductInfoService productInfoService;

    @GetMapping("/findById")
    public ProductInfoQueryVo findById(Integer id) {
        ...
    }

    @PostMapping("/page")
    public IPage findPage(Page page, ProductInfoQueryVo vo) {
        ...
    }
}
  • RestController:

    之前解释过,@RestController=@Controller+ResponseBody。

    加上这个注解,springboot 就会吧这个类当成 controller 进行处理,然后把所有返回的参数放到 ResponseBody 中。

  • @RequestMapping:

    请求的前缀,也就是所有该 Controller 下的请求都需要加上 /product/product-info 的前缀。

  • @GetMapping("/findById"):

    标志这是一个 get 请求,并且需要通过 /findById 地址才可以访问到。

  • @PostMapping("/page"):

    同理,表示是个 post 请求。

    参数:至于参数部分,只需要写上 ProductInfoQueryVo,前端过来的 json 请求便会通过映射赋值到对应的对象中,例如请求这么写,productId 就会自动被映射到 vo 对应的属性当中。

    size : 1
    current : 1
    
    productId : 1
    productName : 泡脚
    

2统一状态码

返回格式

为了跟前端妹妹打好关系,我们通常需要对后端返回的数据进行包装一下,增加一下状态码,状态信息,这样前端妹妹接收到数据就可以根据不同的状态码,判断响应数据状态,是否成功是否异常进行不同的显示。

当然这让你拥有了更多跟前端妹妹的交流机会,假设我们约定了 1000 就是成功的意思。

如果你不封装,那么返回的数据是这样子的:

{
  "productId": 1,
  "productName": "泡脚",
  "productPrice": 100.00,
  "productDescription": "中药泡脚加按摩",
  "productStatus": 0,
}

经过封装以后时这样子的:

{
  "code": 1000,
  "msg": "请求成功",
  "data": {
    "productId": 1,
    "productName": "泡脚",
    "productPrice": 100.00,
    "productDescription": "中药泡脚加按摩",
    "productStatus": 0,
  }
}

封装 ResultVo

这些状态码肯定都是要预先编好的,怎么编呢?写个常量 1000?还是直接写死 1000?

要这么写就真的书白读的了,写状态码当然是用枚举拉:

  1. 首先先定义一个状态码的接口,所有状态码都需要实现它,有了标准才好做事:

    public interface StatusCode {
        public int getCode();
        public String getMsg();
    }
    
  2. 然后去找前端妹妹,跟他约定好状态码(这可能是你们唯一的约定了)枚举类嘛,当然不能有 setter 方法了,因此我们不能在用 @Data 注解了,我们要用 @Getter。

    @Getter
    public enum ResultCode implements StatusCode{
        SUCCESS(1000, "请求成功"),
        FAILED(1001, "请求失败"),
        VALIDATE_ERROR(1002, "参数校验失败"),
        RESPONSE_PACK_ERROR(1003, "response返回包装失败");
    
        private int code;
        private String msg;
    
        ResultCode(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    }
    
  3. 写好枚举类,就开始写 ResultVo 包装类了,我们预设了几种默认的方法,比如成功的话就默认传入 object 就可以了,我们自动包装成 success。

    @Data
    public class ResultVo {
        // 状态码
        private int code;
    
        // 状态信息
        private String msg;
    
        // 返回对象
        private Object data;
    
        // 手动设置返回vo
        public ResultVo(int code, String msg, Object data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
    
        // 默认返回成功状态码,数据对象
        public ResultVo(Object data) {
            this.code = ResultCode.SUCCESS.getCode();
            this.msg = ResultCode.SUCCESS.getMsg();
            this.data = data;
        }
    
        // 返回指定状态码,数据对象
        public ResultVo(StatusCode statusCode, Object data) {
            this.code = statusCode.getCode();
            this.msg = statusCode.getMsg();
            this.data = data;
        }
    
        // 只返回状态码
        public ResultVo(StatusCode statusCode) {
            this.code = statusCode.getCode();
            this.msg = statusCode.getMsg();
            this.data = null;
        }
    }
    
  4. 使用,现在的返回肯定就不是 return data;这么简单了,而是需要 new ResultVo(data);

    @PostMapping("/findByVo")
    public ResultVo findByVo(@Validated ProductInfoVo vo) {
        ProductInfo productInfo = new ProductInfo();
        BeanUtils.copyProperties(vo, productInfo);
        return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
    }
    

最后返回就会是上面带了状态码的数据了。

3统一校验

原始做法

假设有一个添加 ProductInfo 的接口,在没有统一校验时,我们需要这么做。

@Data
public class ProductInfoVo {
    // 商品名称
    private String productName;
    // 商品价格
    private BigDecimal productPrice;
    // 上架状态
    private Integer productStatus;
}
@PostMapping("/findByVo")
public ProductInfo findByVo(ProductInfoVo vo) {
    if (StringUtils.isNotBlank(vo.getProductName())) {
        throw new APIException("商品名称不能为空");
    }
    if (null != vo.getProductPrice() && vo.getProductPrice().compareTo(new BigDecimal(0)) < 0) {
        throw new APIException("商品价格不能为负数");
    }
    ...

    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}

这 if 写的人都傻了,能忍吗?肯定不能忍啊。

@Validated 参数校验

好在有 @Validated,又是一个校验参数必备良药了。有了 @Validated 我们只需要再 vo 上面加一点小小的注解,便可以完成校验功能。

@Data
public class ProductInfoVo {
    @NotNull(message = "商品名称不允许为空")
    private String productName;

    @Min(value = 0, message = "商品价格不允许为负数")
    private BigDecimal productPrice;

    private Integer productStatus;
}
@PostMapping("/findByVo")
public ProductInfo findByVo(@Validated ProductInfoVo vo) {
    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}

运行看看,如果参数不对会发生什么?

我们故意传一个价格为 -1 的参数过去:

productName : 泡脚
productPrice : -1
productStatus : 1
{
  "timestamp": "2020-04-19T03:06:37.268+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "Min.productInfoVo.productPrice",
        "Min.productPrice",
        "Min.java.math.BigDecimal",
        "Min"
      ],
      "arguments": [
        {
          "codes": [
            "productInfoVo.productPrice",
            "productPrice"
          ],
          "defaultMessage": "productPrice",
          "code": "productPrice"
        },
        0
      ],
      "defaultMessage": "商品价格不允许为负数",
      "objectName": "productInfoVo",
      "field": "productPrice",
      "rejectedValue": -1,
      "bindingFailure": false,
      "code": "Min"
    }
  ],
  "message": "Validation failed for object\u003d\u0027productInfoVo\u0027. Error count: 1",
  "trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object \u0027productInfoVo\u0027 on field \u0027productPrice\u0027: rejected value [-1]; codes [Min.productInfoVo.productPrice,Min.productPrice,Min.java.math.BigDecimal,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [productInfoVo.productPrice,productPrice]; arguments []; default message [productPrice],0]; default message [商品价格不允许为负数]\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:830)\n",
  "path": "/leilema/product/product-info/findByVo"
}

大功告成了吗?虽然成功校验了参数,也返回了异常,并且带上"商品价格不允许为负数"的信息。

但是你要是这样返回给前端,前端妹妹就提刀过来了,当年约定好的状态码,你个负心人说忘就忘?

用户体验小于等于 0 啊!所以我们要进行优化一下,每次出现异常的时候,自动把状态码写好,不负妹妹之约!

优化异常处理

首先我们先看看校验参数抛出了什么异常:

Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors

我们看到代码抛出了 org.springframework.validation.BindException 的绑定异常,因此我们的思路就是 AOP 拦截所有 controller,然后异常的时候统一拦截起来,进行封装!完美!

图片

玩你个头啊完美,这么呆瓜的操作 springboot 不知道吗?spring mvc 当然知道拉,所以给我们提供了一个 @RestControllerAdvice 来增强所有 @RestController,然后使用 @ExceptionHandler 注解,就可以拦截到对应的异常。

这里我们就拦截 BindException.class 就好了。最后在返回之前,我们对异常信息进行包装一下,包装成 ResultVo,当然要跟上 ResultCode.VALIDATE_ERROR 的异常状态码。

这样前端妹妹看到 VALIDATE_ERROR 的状态码,就会调用数据校验异常的弹窗提示用户哪里没填好。

@RestControllerAdvice
public class ControllerExceptionAdvice {

    @ExceptionHandler({BindException.class})
    public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
    }
}

来看看效果,完美。1002 与前端妹妹约定好的状态码:

{
  "code": 1002,
  "msg": "参数校验失败",
  "data": "商品价格不允许为负数"
}

4统一响应

统一包装响应

再回头看一下 controller 层的返回:

return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));

开发小哥肯定不乐意了,谁有空天天写 new ResultVo(data) 啊,我就想返回一个实体!怎么实现我不管!

好把,那就是 AOP 拦截所有 Controller,再 @After 的时候统一帮你封装一下咯。

图片

怕是上一次脸打的不够疼,springboot 能不知道这么个操作吗?

@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        // response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装
        return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class);
    }

    @Override
    public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
        // String类型不能直接包装
        if (returnType.getGenericParameterType().equals(String.class)) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                // 将数据包装在ResultVo里后转换为json串进行返回
                return objectMapper.writeValueAsString(new ResultVo(data));
            } catch (JsonProcessingException e) {
                throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
            }
        }
        // 否则直接包装成ResultVo返回
        return new ResultVo(data);
    }
}
  1. @RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) 自动扫描了所有指定包下的 controller,在 Response 时进行统一处理。

  2. 重写 supports 方法,也就是说,当返回类型已经是 ResultVo 了,那就不需要封装了,当不等与 ResultVo 时才进行调用 beforeBodyWrite 方法,跟过滤器的效果是一样的。

  3. 最后重写我们的封装方法 beforeBodyWrite,注意除了 String 的返回值有点特殊,无法直接封装成 json,我们需要进行特殊处理,其他的直接 new ResultVo(data); 就 ok 了。

打完收工,看看效果:

@PostMapping("/findByVo")
public ProductInfo findByVo(@Validated ProductInfoVo vo) {
    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return productInfoService.getOne(new QueryWrapper(productInfo));
}

此时就算我们返回的是 po,接收到的返回就是标准格式了,开发小哥露出了欣慰的笑容。

{
  "code": 1000,
  "msg": "请求成功",
  "data": {
    "productId": 1,
    "productName": "泡脚",
    "productPrice": 100.00,
    "productDescription": "中药泡脚加按摩",
    "productStatus": 0,
    ...
  }
}

NOT 统一响应

不开启统一响应原因:开发小哥是开心了,可是其他系统就不开心了。举个例子:我们项目中集成了一个健康检测的功能,也就是这货。

@RestController
public class HealthController {
    @GetMapping("/health")
    public String health() {
        return "success";
    }
}

公司部署了一套校验所有系统存活状态的工具,这工具就定时发送 get 请求给我们系统:

“兄弟,你死了吗?” “我没死,滚” “兄弟,你死了吗?” “我没死,滚”

是的,web 项目的本质就是复读机。一旦发送的请求没响应,就会给负责人发信息(企业微信或者短信之类的),你的系统死啦!赶紧回来排查 bug 吧!

让大家感受一下。每次看到我都射射发抖,早上 6 点!我 tm!!!!!

图片

好吧,没办法,人家是老大,人家要的返回不是:

{
  "code": 1000,
  "msg": "请求成功",
  "data": "success"
}

人家要的返回只要一个 success,人家定的标准不可能因为你一个系统改。俗话说的好,如果你改变不了环境,那你就只能我****

新增不进行封装注解:因为百分之 99 的请求还是需要包装的,只有个别不需要,写在包装的过滤器吧?

又不是很好维护,那就加个注解好了。所有不需要包装的就加上这个注解。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}

然后在我们的增强过滤方法上过滤包含这个注解的方法:

@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        // response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装
        return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
                || methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
    }
    ...

最后就在不需要包装的方法上加上注解:

@RestController
public class HealthController {

    @GetMapping("/health")
    @NotControllerResponseAdvice
    public String health() {
        return "success";
    }
}

这时候就不会自动封装了,而其他没加注解的则依旧自动包装:

图片

5统一异常

每个系统都会有自己的业务异常,比如库存不能小于 0 子类的,这种异常并非程序异常,而是业务操作引发的异常,我们也需要进行规范的编排业务异常状态码,并且写一个专门处理的异常类,最后通过刚刚学习过的异常拦截统一进行处理,以及打日志。

  1. 异常状态码枚举,既然是状态码,那就肯定要实现我们的标准接口 StatusCode。

    @Getter
    public enum  AppCode implements StatusCode {
    
        APP_ERROR(2000, "业务异常"),
        PRICE_ERROR(2001, "价格异常");
    
        private int code;
        private String msg;
    
        AppCode(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    }
    
  2. 异常类,这里需要强调一下,code 代表 AppCode 的异常状态码,也就是 2000;msg 代表业务异常,这只是一个大类,一般前端会放到弹窗 title 上;最后 super(message); 这才是抛出的详细信息,在前端显示在弹窗体中,在 ResultVo 则保存在 data 中。

    @Getter
    public class APIException extends RuntimeException {
        private int code;
        private String msg;
    
        // 手动设置异常
        public APIException(StatusCode statusCode, String message) {
            // message用于用户设置抛出错误详情,例如:当前价格-5,小于0
            super(message);
            // 状态码
            this.code = statusCode.getCode();
            // 状态码配套的msg
            this.msg = statusCode.getMsg();
        }
    
        // 默认异常使用APP_ERROR状态码
        public APIException(String message) {
            super(message);
            this.code = AppCode.APP_ERROR.getCode();
            this.msg = AppCode.APP_ERROR.getMsg();
        }
    
    }
    
  3. 最后进行统一异常的拦截,这样无论在 service 层还是 controller 层,开发人员只管抛出 API 异常,不需要关系怎么返回给前端,更不需要关心日志的打印。

    @RestControllerAdvice
    public class ControllerExceptionAdvice {
    
        @ExceptionHandler({BindException.class})
        public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
            // 从异常对象中拿到ObjectError对象
            ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
            return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
        }
    
        @ExceptionHandler(APIException.class)
        public ResultVo APIExceptionHandler(APIException e) {
            // log.error(e.getMessage(), e); 由于还没集成日志框架,暂且放着,写上TODO
            return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
        }
    }
    
  4. 最后使用,我们的代码只需要这么写。

    if (null == orderMaster) {
       throw new APIException(AppCode.ORDER_NOT_EXIST, "订单号不存在:" + orderId);
    }
    

    效果:

    {
      "code": 2003,
      "msg": "订单不存在",
      "data": "订单号不存在:1998"
    }
    

就会自动抛出 AppCode.ORDER_NOT_EXIST 状态码的响应,并且带上异常详细信息订单号不存在:xxxx。

图片

后端小哥开发有效率,前端妹妹获取到 2003 状态码,调用对应警告弹窗,title 写上订单不存在,body 详细信息记载"订单号不存在:1998"。同时日志还自动打上去了!

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/616850.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HDFS HA 修改nameservice

本例中修改将原来的hdfs-ha 修改为 hdfs-ns 停止HDFS, 防止新的业务操作 等待停止结束 KDE中需要调整的配置项如下图所示 a.搜索栏找到fs.defaultFS&#xff0c;将hdfs://hdfs-ha改为hdfs://hdfs-ns b.搜索栏找到dfs.nameservices&#xff0c;将hdfs-ha改为hdfs-ns c.搜索栏找…

DE2-115开发板基于verilog和nioⅡ的流水灯实现

目录 一、 内容概要二、 实现2.1 基于Nios II软核的流水灯2.1.1 准备工作2.1.2 工程搭建2.1.3 硬件代码设计Ⅰ 连接IP核Ⅱ 编写代码Ⅲ 各种配置 2.1.4 软件代码设计Ⅰ 环境构建Ⅱ 编写代码 2.1.5 代码下载Ⅰ 硬件下载Ⅱ 软件下载 2.1.6 运行结果 2.2 Verilog流水灯 三、 心得体…

5.10.4 Vision Transformer的条件位置编码(CPE)

用于视觉 Transformer 的条件位置编码&#xff08;CPE&#xff09;方案与之前预定义且独立于输入标记的固定或可学习位置编码不同&#xff0c;CPE 是动态生成的&#xff0c;并以输入标记的局部邻域为条件。 CPE 可以轻松泛化到比模型在训练期间见过的输入序列更长的输入序列。…

Mysql8.0修改配置文件my.ini的坑

出现的问题&#xff1a;一般直接双击打开my.ini文件默认会用系统自带的记事本打开&#xff0c;如果打开后修改了其中的内容并通过记事本直接保存的话&#xff0c;下次重启就会导致mysql无法启动。 原因是mysql会以ANSI编码读取my.ini文件。 解决办法&#xff1a;使用notepad打…

Imitation Learning学习记录(理论例程)

前言 最近还是衔接着之前的学习记录&#xff0c;这次打算开始学习模仿学习的相关原理&#xff0c;参考的开源资料为 TeaPearce/Counter-Strike_Behavioural_Cloning: IEEE CoG & NeurIPS workshop paper ‘Counter-Strike Deathmatch with Large-Scale Behavioural Clonin…

现代制造之3D打印技术进行零件加工

现代制造 有现代技术支撑的制造业&#xff0c;即无论是制造还是服务行业&#xff0c;添了现代两个字不过是因为有了现代科学技术的支撑&#xff0c;如发达的通信方式&#xff0c;不断发展的互联网&#xff0c;信息化程度加强了&#xff0c;因此可以为这两个行业增加了不少优势…

简单易懂的Java Queue入门教程!

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

600/天,海外项目值班,接不接?

朋友介绍了一个海外项目&#xff0c;广告系统短期维护&#xff0c;刚上线需要维护14天也就是2个星期&#xff0c;费用单价600/天&#xff0c;主要工作内容&#xff1a;北京晚上12点-早上8点值班&#xff0c;如果有问题及时响应并修复。 如果我年轻10岁&#xff0c;这个项目我倒…

【网站项目】SpringBoot803房屋租赁管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

C++入门指南(上)

目录 ​编辑 一、祖师爷画像 二、什么是C 三、C发展史 四、C在工作领域的应用 1. 操作系统以及大型系统软件开发 2. 服务器端开发 3. 游戏开发 4. 嵌入式和物联网领域 5. 数字图像处理 6. 人工智能 7. 分布式应用 五、如何快速上手C 一、祖师爷画像 本贾尼斯特劳斯…

docker修改默认安装路径

docker安装之后默认在 /etc/docker 在/etc/docker 文件下有一个daemon -json 没有就新增 {"registry-mirrors": ["https://kfwkfulq.mirror.aliyuncs.com","https://2lqq34jg.mirror.aliyuncs.com","https://pee6w651.mirror.aliyuncs.c…

续篇——源码部署LAMP环境上线项目——禅道项目

上篇:LNMP环境部署WordPress——使用源码包安装方式部署环境-CSDN博客 目录 一.前提准备 1. 名词区别 2. 下载项目软件包 3. 上传项目源码到虚拟机并解压 二.安装Apache 1. 环境清理 2.关闭Nginx 3. 下载Apache 4. 下载APR组件 4.1 安装apr 4.2 安装apr-util组件 5…

算法学习012-不同路径 c++动态规划算法实现 中小学算法思维学习 信奥算法解析

目录 C不同路径 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 六、推荐资料 C不同路径 一、题目要求 1、编程实现 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09…

德国储能项目锂电池储能集装箱突发火灾:安全挑战再引关注

2024年4月27日&#xff0c;德国尼尔莫尔商业区的一起锂电池储能集装箱火灾事件引起了全球关注。这起事故不仅导致两名消防员在救援过程中受伤&#xff0c;更暴露了储能系统在安全领域亟待解决的重要问题。 根据德国消防队的出警记录&#xff0c;火灾发生在晚上9点前不久。消防人…

在Linux操作系统中LVM逻辑券管理指令

1.PV物理券相关指令 1.查看机器中的PV pvscan 命令 这个叫做/dev/sda2 的PV&#xff0c;被加入到了名叫centos的卷组中&#xff0c;并且这个券组的大小是小于19.51GB 2.创建物理券 pvcreate 磁盘/分区名称 pvcreate /dev/sdc 3.删除物理券 pvremove 磁盘/分区名称 2.…

5.10.3 使用 Transformer 进行端到端对象检测(DETR)

框架的主要成分称为 DEtection TRansformer 或 DETR&#xff0c;是基于集合的全局损失&#xff0c;它通过二分匹配强制进行独特的预测&#xff0c;以及 Transformer 编码器-解码器架构。 DETR 会推理对象与全局图像上下文的关系&#xff0c;以直接并行输出最终的预测集。 1. …

欢乐钓鱼大师自动钓鱼,游戏辅助!

在探索《欢乐钓鱼大师》的世界时&#xff0c;一项备受关注的功能是陀螺仪模式。这是一种利用手机陀螺仪传感器来增强游戏体验的功能&#xff0c;通过模拟真实的钓鱼动作&#xff0c;让玩家更深入地沉浸在游戏的世界中&#xff0c;感受到更加逼真的钓鱼体验。在本篇攻略中&#…

【全开源】JAVA同城组局同城找搭子系统源码支持微信小程序微信公众号H5 APP

让你周末不孤单 发布活动&#xff1a;用户可以发布自己想要进行的活动&#xff0c;包括活动类型、时间、地点等信息&#xff0c;方便其他用户查找和参与。搜索搭档&#xff1a;用户可以根据活动类型、时间、地点等信息&#xff0c;搜索附近的搭档&#xff0c;快速找到志同道合…

Github 2024-05-12 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Filament: 加速Laravel开发的完美起点 创建周期:1410 天开发语言:PHP协议类型:MIT LicenseStar数量:12228 个Fork数量:1990 次关…

工程师工具箱系列(2)hasor

文章目录 工程师工具箱系列(2)hasor简介特点环境准备引入依赖数据库脚本文件配置Hasor配置 运行测试小结 工程师工具箱系列(2)hasor 简介 Hasor有着自己的独立的生命周期与Spring的不同&#xff0c;是一套完整的体系&#xff0c;提供了注入DataQL、Dataway、hasor-web等等&am…