如何设计一个“正确”的后端接口

2023-11-11

一个后端接口正常情况下会包含①接口地址url②接口的请求方式(get/post)③请求数据④相应数据

在此记录一下如何构建一个完整的后端接口的过程
无论一个简单还是复杂的接口,无论是对外开放的接口还是http接口,参数校验是比不可少的,因为调用者每次调用传入的参数是未知的,那如果他传进来的参数是不符合规则的,我们就没必要去执行后面的业务逻辑,只有在客户(调用者)传入的参数是合法且有效的,才必要去执行业务逻辑,这样在一定程度上也减少了程序的压力。

参数校验

1、最基础的直接在业务层进行参数校验
比如下面一个新增用户的接口,直接在业务层进行参数校验:

@PostMapping("add")
    public String  inserSysUser(SysUser sysUser){
        if(StringUtils.isBlank(sysUser.getName()) ){
            return "用户名不能为空";
        }
        if(sysUser.getName().length()<8 || sysUser.getName().length()>12){
            return "账号必须是8~11个字符";
        }
        if(!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", sysUser.getEmail())){
            return "邮箱格式不正确";
        }
        sysUserService.insert(sysUser);
        log.info("添加用户成功");
        return "success";
    }

当我们输入可预知的参数时,运行结果图下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样做的弊端在于,太过繁琐,当接口数量少且每个接口的请求参数数量少的时候,假如有100个接口并且每个接口有三四十个字段需要校验,如果用这种校验方法的话,会有很对的还重复代码,还没写业务逻辑呢就一大片的参数校验代码,使得我们的接口很繁琐。那有什么方法可以简化我们的代码呢。请看下面的Spring Validator和Hibernate Validator这两套Validator来进行参数校验

2、Validator + BindingResult进行校验
Validator可以自定义校验规则并且自动完成参数校验,想要它帮我们进行参数校验的前提是在需要被校验的字段上加上注解,当然了每个注解所对应的校验规则是不同的,与此同时,我们可以自定义验证失败后的提示信息
在使用它之前,先了解一下在使用它做参数校验的时候常用的注解有哪些:

@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

知道了常用的校验注解,下面是具体的使用方法:

@Data
public class SysUser implements Serializable {
    private static final long serialVersionUID = 854274168569296410L;

    private Long id;

    @NotNull(message = "用户名不能为空")
    @Size(min = 8,max =11,message ="用户名长度必须为8~11字符")
    private String name;

    @NotNull(message = "年龄不能为空")
    private Integer age;

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

}

校验规则和错误信息提示配置完成后,需要在接口中需要校验的参数上加上@Valid注解,并添加BindingResult参数就可完成参数的校验:

    @PostMapping("addSysUser")
    public String  inserSysUser(@Valid  SysUser sysUser, BindingResult bindingResult){

        // 有参数校验失败  将错误信息封装在BindingResult对象中返回
        for(ObjectError error:bindingResult.getAllErrors()){
            return error.getDefaultMessage();
        }
        sysUserService.insert(sysUser);
        log.info("添加用户成功");
        return "success";
    }

这样当请求数据传递到接口的时候Validator就自动完成校验了,校验的结果就会封装到BindingResult中去,如果有错误信息我们就直接返回给前端,业务逻辑代码也根本没有执行下去。
测试传一个错误的参数(邮箱给一个错误的格式)
运行效果:
在这里插入图片描述

同直接在业务代码中做参数校验相比,利用Validator进行参数校验有以下好处:
1.使用方便,只需要一个注解就可以完成参数校验并指定校验失败的提示信息
2.简化代码,有眼可见的简洁,避免了一大堆的if代码块
3.减少耦合度,使用Validator让业务层的代码只关注与业务逻辑,剔除基本的参数校验

使用Validator + BindingResult大部分情况下已经可以满足我们的需求了,不过这样做的话,需要在每个接口中后都添加一个BindingResult参数,
然后通过BindingResult把错误的信息提示给前端。这样你会发现,每个接口都有这个参数,又有一大堆的重复代码,那可否想一种解决办法去掉每个接口中的BindingResult这个参数呢?请往下看!!

3、Validator+自动抛出异常
在上面的代码中去掉BindingResult参数,去掉之后会发生什么事儿呢?还是上面的校验方法,给邮箱一个错误的格式,运行代码,效果如下:

  @PostMapping("addSysUser")
    public String  insert(@Valid  SysUser sysUser){
        sysUserService.insert(sysUser);
        log.info("添加用户成功");
        return "success";
    }

在这里插入图片描述
这样就已经达到我们想要的效果了,参数校验不通过自然就不执行接下来的业务逻辑,去掉BindingResult后会自动引发异常,异常发生了自然而然就不会执行业务逻辑。也就是说,我们完全没必要添加相关BindingResult相关操作。不过事情还没有完,异常是引发了,可我们并没有编写返回错误信息的代码,那参数校验失败了会响应什么数据给前端呢?

我们来看一下刚才异常发生后接口响应的数据:

{
    "timestamp": "2020-04-08T06:26:30.305+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Email.sysUser.email",
                "Email.email",
                "Email.java.lang.String",
                "Email"
            ],
            "arguments": [
                {
                    "codes": [
                        "sysUser.email",
                        "email"
                    ],
                    "arguments": null,
                    "defaultMessage": "email",
                    "code": "email"
                },
                [],
                {
                    "arguments": null,
                    "defaultMessage": ".*",
                    "codes": [
                        ".*"
                    ]
                }
            ],
            "defaultMessage": "邮箱格式不正确",
            "objectName": "sysUser",
            "field": "email",
            "rejectedValue": "1111",
            "bindingFailure": false,
            "code": "Email"
        }
    ],
    "message": "Validation failed for object='sysUser'. Error count: 1",
    "trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'sysUser' on field 'email': rejected value [1111]; codes [Email.sysUser.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [sysUser.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@fe2742e,.*]; default message [邮箱格式不正确]\r\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:109)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:745)\r\n",
    "path": "/sysUser/addSysUser"
}

直接将整个错误对象相关信息都响应给前端了!这样就很难受,不过解决这个问题也很简单,就是我们接下来要讲的全局异常处理!

全局异常处理

参数校验失败会自动引发异常,我们当然不可能再去手动捕捉异常进行处理,不然还不如用之前BindingResult方式呢。又不想手动捕捉这个异常,又要对这个异常进行处理,那正好使用SpringBoot全局异常处理来达到一劳永逸的效果!

首先需要创建一个新的类,在类上添加@ControllerAdvice或者@RestControllerAdvice注解,该类就配置成了全局处理类了
然后再类中新建方法,给方法加上@ExceptionHandler注解并指定我们想要处理的异常类型,接着在方法内编写对该异常的操作逻辑,就完成了对该异常的全局处理。

@RestControllerAdvice
@Slf4j
public class ExceptionControllerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return objectError.getDefaultMessage();
    }

}

自定义异常

在很多情况下,我们需要手动抛出异常,比如在业务层当有些条件不符合业务逻辑的时候,我们就可以手动抛出异常。
自定义一个异常类:

@Slf4j
@Getter
public class BizException extends RuntimeException {

    private String code;
    private String desc;

    public BizException(String code,String desc){

        this.code = code;
        this.desc = desc;
    }

}

在上面的全局异常处理类中添加对自定义异常类的处理:

@ExceptionHandler(BizException.class)
    public String BizException(BizException e){
        return e.getMessage();
    }

数据统一响应

现在我们规范好了参数校验方式和异常处理方式,然而还没有规范响应数据!比如我要获取一个分页信息数据,获取成功了呢自然就返回的数据列表,获取失败了后台就会响应异常信息,即一个字符串,就是说前端开发者压根就不知道后端响应过来的数据会是啥样的!所以,统一响应数据是前后端规范中必须要做的!

1、自定义统一响应体
统一数据响应第一步肯定要做的就是我们自己自定义一个响应体类,无论后台是运行正常还是发生异常,响应给前端的数据格式是不变的!那么如何定义响应体呢?关于异常的设计:如何更优雅的设计异常可以参考我们自定义异常类,也来一个响应信息代码code和响应信息说明desc。

@Getter
public class ResultVO<T> {
    private String code;
    private String desc;
    //响应数据
    private T data;
    public ResultVO(T data){
        this("1000","success",data);
    }
    public ResultVO(String code, String desc, T data) {
        this.code = code;
        this.desc = desc;
        this.data = data;
    }
}

有了上面的统一响应数据,只需要把之前的全局异常中的返回值做如下修改即可:

 @ExceptionHandler(BizException.class)
    public ResultVO BizException(BizException e){
        return new ResultVO(e.getCode(),"响应失败",e.getDesc());
    }

OK,这个异常信息响应就非常好了,状态码和响应说明还有错误提示数据都返给了前端,并且是所有异常都会返回相同的格式!异常这里搞定了,别忘了我们到接口那也要修改返回类型,我们新增一个接口好来看看效果:

 @PostMapping("addSysUser")
    public ResultVO<SysUser> insert(@Valid  SysUser sysUser){
        sysUserService.insert(sysUser);
        log.info("添加用户成功");
        return new ResultVO<>(sysUser);
    }

在这里插入图片描述
这样无论是正确响应还是发生异常,响应数据的格式都是统一的,十分规范!
数据格式是规范了,不过响应码code和响应信息desc还没有规范呀!大家发现没有,无论是正确响应,还是异常响应,响应码和响应信息是想怎么设置就怎么设置,要是10个开发人员对同一个类型的响应写10个不同的响应码,那这个统一响应体的格式规范就毫无意义!所以,必须要将响应码和响应信息给规范起来。那接下来就该请出响应码枚举了!

响应码枚举

@Getter
public enum ResultEnum {
    SUCCESS("1000","请求成功"),
    FAILED("1001","请求失败"),
    PARAM_FAIL("1002","参数校验失败"),
    ERROR("1003","请求异常"),
    ;
    private String code;
    private String desc;
    ResultEnum(String code,String desc){
        this.code = code;
        this.desc = desc;
    }
}

然后同时修改全局异常处理的响应码设置方式:

    @ExceptionHandler(BizException.class)
    public ResultVO BizException(BizException e){
        return new ResultVO(ResultEnum.PARAM_FAIL,e.getDesc());
    }

同时需要修改统一响应方法如下:

public ResultVO(ResultEnum resultEnum,T data){
        this.code = resultEnum.getCode();
        this.desc = resultEnum.getDesc();
        this.data = data;
    }
	

这样响应码和响应信息只能是枚举规定的那几个,就真正做到了响应数据格式、响应码和响应信息规范化、统一化!这些可以参考:Java项目构建基础:统一结果,统一异常,统一日志

全局处理响应数据

接口返回统一响应体 + 异常也返回统一响应体,其实这样已经很好了,但还是有可以优化的地方。要知道一个项目下来定义的接口搞个几百个太正常不过了,要是每一个接口返回数据时都要用响应体来包装一下好像有点麻烦,有没有办法省去这个包装过程呢?当然是有滴,还是要用到全局处理。
首先,先创建一个类加上注解使其成为全局处理类。然后继承ResponseBodyAdvice接口重写其中的方法,即可对我们的controller进行增强操作,具体看代码和注释:

@RestControllerAdvice(basePackages = {"com.kyriemtx.easycode.controller"})
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        // 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false
        return !methodParameter.getGenericParameterType().equals(ResultVO.class);
    }

    @Override
    public Object beforeBodyWrite(Object data, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        // String类型不能直接包装,所以要进行些特别的处理
        if (methodParameter.getGenericParameterType().equals(String.class)) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                // 将数据包装在ResultVO里后,再转换为json字符串响应给前端
                return objectMapper.writeValueAsString(new ResultVO<>(data));
            } catch (JsonProcessingException e) {
                throw new BizException("返回String类型错误");
            }
        }
        // 将原本的数据包装在ResultVO里
        return new ResultVO<>(data);
    }
}

重写的这两个方法是用来在controller将数据进行返回前进行增强操作,supports方法要返回为true才会执行beforeBodyWrite方法,所以如果有些情况不需要进行增强操作可以在supports方法里进行判断。对返回数据进行真正的操作还是在beforeBodyWrite方法中,我们可以直接在该方法里包装数据,这样就不需要每个接口都进行数据包装了,省去了很多麻烦。
我们可以现在去掉接口的数据包装来看下效果:

   @GetMapping("selectOne")
    public SysUser selectOne(Long id) {
        return this.sysUserService.queryById(id);
    }

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何设计一个“正确”的后端接口 的相关文章

随机推荐

  • Centos7的iso everything与DVD以及Live的区别

    DVD ISO 可以用安装程序安装的所有安装包 推荐镜像 Netinstall iso 从网络安装或者救援系统 Everything iso 包含centos7的一套完整的软件包 可以用来安装系统或者本地镜像 GnomeLive iso G
  • Mac下github的基本使用(有详细过程)

    一 github准备 1 注册github账号 https github com 按照提示进行注册 2 查看git版本 由于macOS默认安装了git 在终端输入 git v 3 设置username和email username随便输入一
  • 如何防止uniswap和pancakeswap夹子机器人

    被机器人夹是通俗说法 实际就是 front running 抢先提前交易 具体就是机器人在链上嗅探到你有买入行为的时候 他立刻买 gas给的比你高 快你一步确认 这样你成交价就高了 因为交易所有滑点 所以你依旧会以高一点的价格成交并且再将价
  • 区块链简单实现之p2p网络多节点同步

    区块链简单实现之p2p网络多节点同步 将区块保存为json文件 节点 不确定性 区块里保存节点信息 并未向所有节点广播 简单模拟 广播的代码 实现效果 完整的代码 承接上文 区块链的简单实现 我们已经实现了一个简单的区块链数据结构 现状 区
  • wasm + ffmpeg实现前端截取视频帧功能

    有没有那么一种可能 在前端页面处理音视频 例如用户选择一个视频 然后支持他设置视频的任意一帧作为封面 就不用把整一个视频上传到后端处理了 经过笔者的一番摸索 基本实现了这个功能 一个完整的demo ffmpeg wasm截取视频帧功能 支持
  • paddle-pytorch API对应表

    PyTorch API名称 对应Paddle API torch set default dtype paddle set default dtype torch get default dtype paddle get default d
  • linux下mysql-connector-c++连接远程服务器失败

    最近在将windows项目移植到linux下 碰到诸多问题 先谈mysql connector c 连接远程服务器失败问题 在windows下 sql Driver driver sql mysql get driver instance
  • 因果推理相关的图神经网络研究

    本文介绍两篇因果推理相关的图神经网络研究工作 一 OOD推荐系统下的因果表征学习 本文介绍了什么是推荐系统中的Out of Distribution OOD 问题 并从因果的角度提出了一种解决OOD问题的表示学习方式 文章链接 https
  • 关于xinput1_3.dll丢失的详细解决方法

    xinput1 3 dll是电脑文件中的dll文件 动态链接库文件 如果计算机中丢失了某个dll文件 可能会导致某些软件和游戏等程序无法正常启动运行 并且导致电脑系统弹窗报错 在我们打开软件或者游戏的时候 电脑提示xinput1 3 dll
  • Windows10下安装Linux子系统

    Windows10下安装Linux子系统 版本说明 版本 作者 日期 备注 0 1 ZY 2019 7 9 初稿 目录 文章目录 Windows10下安装Linux子系统 版本说明 目录 一 初衷 二 资料收集 三 官方安装说明 1 准备
  • 5.0结构型模式—概述

    结构型模式描述如何将类或对象按某种布局组成更大的结构 它分为类结构型模式和对象结构型模式 前者采用继承机制来组织接口和类 后者釆用组合或聚合来组合对象 由于组合关系或聚合关系比继承关系耦合度低 满足 合成复用原则 所以对象结构型模式比类结构
  • 国庆假期将至,拓世AI智能规划行程,让您轻松游遍全球热门景点!

    卡夫卡曾说 人不是活几年 几月 几天 几小时 而只活几个瞬间 亲赴一场与美景的邂逅 便是去找寻人生里的瞬间之美 转眼已是九月 正是人间好时节 挥别工作和生活的烦闷 奔向辽阔的天地中 即将到来的国庆长假 你需要来一场说走就走的旅行 将所有烦恼
  • 动态数据源配置druid+mybatis

    本方案不限数据库数量完全动态配置 支持不同的数据库部署在不同的服务器上 mybatis plus没测试 下个版本用oracle配的时候尝试plus 一 这次我们使用Mysql 本地现在有两个个数据库用于测试 如图 二 下一步我们看一下Dru
  • LintCode入门题目

    37 反转一个3位整数 反转一个只有3位数的整数 样例 样例 1 输入 number 123 输出 321 样例 2 输入 number 900 输出 9 注意事项 你可以假设输入一定是一个只有三位数的整数 这个整数大于等于100 小于10
  • 表空间的操作

    1 创建表空间 create tablespace tablespace name datafile filepath size filesize autoextend on next autosize maxsize filemaxsiz
  • rule34服务器不稳定,rule34网站

    rule34网站 内容精选 换一换 网站后台数据录入完成后 您需要为您的网站设置便于客户浏览和操作的前台显示界面 本章节主要通过已安装的网站模板指导您完成PC版 手机版网页的制作 以及网站数据的备份 已完成网站后台的设置 并且成功绑定域名
  • 用PyCharm打开已有代码

    一 代码的打开 1 在当前环境 打开新的项目 2 点open 打开文件存放的位置 3 trust Project 4 this window or new window 一般选this window 5 解决代码中的问题 1 一定要解决 2
  • python3 scrapy爬取微信公众号及历史信息V1.0

    环境 python3 scrapy 目的 写这篇文章主要是做一下纪念 毕竟是搞了快两天的东西了 今天加大了量 使用scrapy爬取100多个微信公众号 然后出现IP被封的情况下 当然了 这种情况并不是没有办法解决 只需要在scrapy中进行
  • Java SE学习笔记(五)——数组

    1 包装类 Wrapper Class 针对原生数据类型的包装 所有的包装类 8个 都位于java lang包下 对应8个包装类分别是 Byte Short Integer Long Float Double Character Boole
  • 如何设计一个“正确”的后端接口

    一个后端接口正常情况下会包含 接口地址url 接口的请求方式 get post 请求数据 相应数据 在此记录一下如何构建一个完整的后端接口的过程 无论一个简单还是复杂的接口 无论是对外开放的接口还是http接口 参数校验是比不可少的 因为调