当前位置: 首页 > news >正文

后端API接口设计标准(Java)

Controller 层(API接口)

无论是传统的三层架构还是现在的COLA架构,Controller 层依旧有一席之地,说明他的必要性;说它是配角是因为 Controller 层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求

从现状看问题
Controller 主要的工作有以下几项:

  • 接收请求并解析参数
  • 调用 Service 执行具体的业务代码(可能包含参数校验)
  • 捕获业务逻辑异常做出反馈
  • 业务逻辑执行成功做出响应

//DTO

@Data
public class TestDTO {private Integer num;private String type;
}

//Service

@Service
public 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

@RestController
public 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);}}@Autowiredpublic 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
@AllArgsConstructor
public 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);@NullableT 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> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解return true;}@Overridepublic 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 返回的数据进行统一包装,又不需要对原有代码进行大量的改动

处理 cannot be cast to java.lang.String 问题
如果直接使用 ResponseBodyAdvice,对于一般的类型都没有问题,当处理字符串类型时,会抛出 xxx.包装类 cannot be cast to java.lang.String 的类型转换的异常

在 ResponseBodyAdvice 实现类中 debug 发现,只有 String 类型的 selectedConverterType 参数值是 org.springframework.http.converter.StringHttpMessageConverter,而其他数据类型的值是 org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

String 类型
在这里插入图片描述

其他类型 (如 Integer 类型)
在这里插入图片描述

现在问题已经较为清晰了,因为我们需要返回一个 Result 对象

所以使用 MappingJackson2HttpMessageConverter 是可以正常转换的

而使用 StringHttpMessageConverter 字符串转换器会导致类型转换失败

现在处理这个问题有两种方式

1、在 beforeBodyWrite 方法处进行判断,如果返回值是 String 类型就对 Result 对象手动进行转换成 JSON 字符串,另外方便前端使用,最好在 @RequestMapping 中指定 ContentType

@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {...@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 提供一定的灵活度,如果body已经被包装了,就不进行包装if (body instanceof Result) {return body;}// 如果返回值是String类型,那就手动把Result对象转换成JSON字符串if (body instanceof String) {try {return this.objectMapper.writeValueAsString(Result.success(body));} catch (JsonProcessingException e) {throw new RuntimeException(e);}}return Result.success(body);}...
}@GetMapping(value = "/returnString", produces = "application/json; charset=UTF-8")
public String returnString() {return "success";
}

2、修改 HttpMessageConverter 实例集合中 MappingJackson2HttpMessageConverter 的顺序。因为发生上述问题的根源所在是集合中 StringHttpMessageConverter 的顺序先于 MappingJackson2HttpMessageConverter 的,调整顺序后即可从根源上解决这个问题
网上有不少做法是直接在集合中第一位添加 MappingJackson2HttpMessageConverter

@Configuration
public class WebConfiguration implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(0, new MappingJackson2HttpMessageConverter());}
}

诚然,这种方式可以解决问题,但其实问题的根源不是集合中缺少这一个转换器,而是转换器的顺序导致的,所以最合理的做法应该是调整 MappingJackson2HttpMessageConverter 在集合中的顺序

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {/*** 交换MappingJackson2HttpMessageConverter与第一位元素* 让返回值类型为String的接口能正常返回包装结果** @param converters initially an empty list of converters*/@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {for (int i = 0; i < converters.size(); i++) {if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converters.get(i);converters.set(i, converters.get(0));converters.set(0, mappingJackson2HttpMessageConverter);break;}}}
}

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

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

对 @PathVariable 和 @RequestParam 参数进行校验需要在入参声明约束的注解

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

@RestController(value = "prettyTestController")
@RequestMapping("/pretty")
@Validated
public 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;}@Autowiredpublic void setTestService(TestService prettyTestService) {this.testService = prettyTestService;}
}

校验原理
在 SpringMVC 中,有一个类是 RequestResponseBodyMethodProcessor ,这个类有两个作用

1、用于解析 @RequestBody 标注的参数
2、处理 @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.*/@Overridepublic 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

@Data
public class TestDTO {@NotBlankprivate String userName;@NotBlank@Length(min = 6, max = 20)private String password;@NotNull@Emailprivate 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);}@Autowiredpublic 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;@Overridepublic 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 {@Overridepublic 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 标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则

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

1、自定义注解类,定义错误信息和一些其他需要的内容
2、注解校验器,定义判定规则

//自定义注解类

@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*/@Overridepublic void initialize(Mobile constraintAnnotation) {this.required = constraintAnnotation.required();}/*** 判断参数是否合法** @param value   object to validate* @param context context in which the constraint is evaluated*/@Overridepublic 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 提供了丰富的参数校验规则,再加上复杂业务的自定义校验规则,完全把参数校验和业务逻辑解耦开,代码更加简洁,符合单一职责原则。

自定义异常与统一拦截异常
原来的代码中可以看到有几个问题

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

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

而统一拦截异常的目的一个是为了可以与前面定义下来的统一包装返回结构能对应上,另一个是我们希望无论系统发生什么异常,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 方法返回的是什么数据,也可以方便每一个异常应该如何进行反馈。

相关文章:

后端API接口设计标准(Java)

Controller 层&#xff08;API接口&#xff09; 无论是传统的三层架构还是现在的COLA架构&#xff0c;Controller 层依旧有一席之地&#xff0c;说明他的必要性&#xff1b;说它是配角是因为 Controller 层的代码一般是不负责具体的逻辑业务逻辑实现&#xff0c;但是它负责接收…...

Oracle Recovery Tools工具一键解决ORA-00376 ORA-01110故障(文件offline)---惜分飞

客户在win上面迁移数据文件,由于原库非归档,结果导致有两个文件scn不一致,无法打开库,结果他们选择offline文件,然后打开数据库 Wed Dec 04 14:06:04 2024 alter database open Errors in file d:\app\administrator\diag\rdbms\orcl\orcl\trace\orcl_ora_6056.trc: ORA-01113:…...

Python制做一个简易PDF编辑器——关于PDF文字编辑实现的思路

在Python零基础快速入门最后一篇&#xff0c;我们一起做了一个PDF编辑小工具&#xff0c;里面只实现的PDF翻页浏览等&#xff0c;并没有实现PDF的文字在线编辑&#xff0c;是因为在PDF编辑器中实现文字编辑功能是一个相对复杂的过程&#xff0c;因为PDF格式本质上是一个用于呈现…...

RabbitMQ如何保证消息不被重复消费

前言&#xff1a; 正常情况下&#xff0c;消费者在消费消息后&#xff0c;会给消息队列发送一个确认&#xff0c;消息队列接收后就知道消息已经被成功消费了&#xff0c;然后就从队列中删除该消息&#xff0c;也就不会将该消息再发送给其他消费者了。不同消息队列发出的确认消…...

Windows Terminal ssh到linux

1. windows store安装 Windows Terminal 2. 打开json文件配置 {"$help": "https://aka.ms/terminal-documentation","$schema": "https://aka.ms/terminal-profiles-schema","actions": [{"command": {"ac…...

vue实现页面自动滚动,鼠标悬浮暂停,移开继续

1、给div一个id <div class"kb_nei_new_left" id"chartsContainer">2、定义一个自动滚动的方法 autoSroll(Id) {// flag 为true时停止滚动var flag false;// 定时器var timer;function roll() {var h -1;timer setInterval(function () {flag …...

第3章:文本样式 --[CSS零基础入门]

CSS(层叠样式表)允许你以多种方式定制文本的外观。以下是一些常用的文本和字体相关的CSS属性: 1.字体 字体系列 当然,下面是两个使用不同字体系列的CSS示例。每个示例都展示了如何指定一个字体系列,并提供备用字体以确保在用户的系统中找不到首选字体时仍有合适的字体可…...

从视觉到雷达:多模态感知如何引领自动驾驶安全革命

文章目录 摘要引言多模态感知融合的原理与架构感知技术的特点多模态感知融合的目标 数据融合实现示例代码结构与主要组件模型定义 MultimodalFusionModel前向传播&#xff08;forward 方法&#xff09;模型细节剖析实践应用 QA环节总结参考资料 摘要 本文探讨了多模态感知技术…...

若依集成更好用的easyexcel

背景 若依使用的是apach poi并在此基础上进行封装apach poi的原生的api是很复杂的&#xff0c;若依简化了了此操作apach poi的上传速率和下载速率都是没有优化的&#xff0c;依赖于文件大小的限制在此前提下&#xff0c;如果没法满足客户的需求&#xff08;超大型文件的上传&am…...

大数据新视界 -- 大数据大厂之 Hive 数据导入:多源数据集成的策略与实战(上)(3/ 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

线段树模板

单点修改 #include <bits/stdc.h> using namespace std; #define IOS ios::sync_with_stdio(false),cin.tie(nullptr); #define rep(i, x, y) for(int i(x), _(y);i<_;i) #define rrep(i, x, y) for(int i(x), _(y);i>_;i--) #define all(x) x.begin(),x.end() #d…...

算法刷题Day15: BM37 二叉搜索树的最近公共祖先

题目链接 描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q&#xff0c;最近公共祖先LCA(T,p,q)表示一个节点x&#xff0c;满足x是p和q的祖先且x的深度尽可能大。在这里&#xff0c;一个节点也可以…...

正则表达式去除文本中括号()<>[]里的内容

一行文本中包含有各种括号&#xff0c;如()、<>、[]&#xff0c;我们希望把括号及括号内的内容0去除&#xff0c;可以通过正则表达式来实现。 匹配() pattern r\([^)]*\) # 匹配()匹配一个左括号(&#xff0c;然后匹配0个或多个不是右括号的任意字符[^)]*&#xff0c…...

Environment Modules安装配置

Environment Modules安装配置 Environment Modules是一款用来管理计算机软件环境的软件&#xff0c;通过简单的命令来控制计算机环境变量。本文接受该软件的安装和配置方法 系统&#xff1a; Linux OpenSUSE 15.6 软件版本&#xff1a; modules 5.5 依赖&#xff1a; gcc 7.5…...

constexpr、const和 #define 的比较

constexpr、const 和 #define 的比较 一、定义常量 constexpr 定义&#xff1a;constexpr用于定义在编译期可求值的常量表达式。示例&#xff1a;constexpr int x 5;这里&#xff0c;x的值在编译期就确定为5。 const 定义&#xff1a;const表示变量在运行期间不能被修改&…...

STM32串口接收与发送(关于为什么接收不需要中断而发生需要以及HAL_UART_Transmit和HAL_UART_Transmit_IT的区别)

一、HAL_UART_Transmit和HAL_UART_Transmit_IT的区别 1. HAL_UART_Transmit_IT&#xff08;非阻塞模式&#xff09;&#xff1a; HAL_UART_Transmit_IT 是非阻塞的传输函数&#xff0c;也就是说&#xff0c;当你调用 HAL_UART_Transmit_IT 时&#xff0c;它不会等到数据完全发…...

如何制作“优美”PPT

目录 1.免费PPT模板网站&#xff1a; 2.免费有较好质量的图片网站&#xff1a; 免费图片资源 免费透明PNG图片资源&#xff1a; 免费icon图片资源&#xff1a; 3.选择好的图片&#xff1a; 图片底色 4.要与不要 千万不要&#xff1a; 一定要&#xff1a; 6.一些建议…...

5G模组AT命令脚本-控制模组进入飞行模式

控制模组进入飞行模式 控制模组进入飞行模式 控制模组进入飞行模式 控制模组进入飞行模式 #!/bin/bash ## 5G模组采用USB3.0与上位机连接&#xff0c;usb接口在上位机上虚拟出多个port,其中一个可用于发送AT命令&#xff0c;控制模组 ## 本脚本控制模组进入飞行模式## flyin …...

计算机网络-Wireshark探索ARP

使用工具 Wiresharkarp: To inspect and clear the cache used by the ARP protocol on your computer.curl(MacOS)ifconfig(MacOS or Linux): to inspect the state of your computer’s network interface.route/netstat: To inspect the routes used by your computer.Brows…...

Vue 2 生命周期函数详解

Vue 2 生命周期函数详解 引言 Vue.js 是一个渐进式的 JavaScript 框架&#xff0c;用于构建用户界面。理解 Vue 的生命周期函数&#xff08;Lifecycle Hooks&#xff09;对于开发高效的 Vue 应用至关重要。本文将详细介绍 Vue 2 的生命周期钩子、每个阶段的作用及其代码示例&…...

Vue的路由实现模式:hash模式和history模式

Vue 路由的两种模式&#xff1a; hash 模式&#xff1a; 类似于住在一个大房子里&#xff0c;你的地址很长&#xff0c;但用一个 “门牌号”&#xff08;# 后面的部分&#xff09;来标识你住哪间房间。 例如&#xff1a; bash http://example.com/#/home 这就好比 “example.…...

R语言 | 峰峦图 / 山脊图

目的&#xff1a;为展示不同数据分布的差异。 1. ggplot2 实现 # 准备数据 datmtcars[, c("mpg", "cyl")] colnames(dat)c("value", "type") head(dat) # value type #Mazda RX4 21.0 6 #Mazda RX4 Wag …...

Kubernetes(K8s)

头条&#xff1a;参考资料 Kubernetes 入门指南&#xff1a;从基础到实践_kubernetes 从入门到实践-CSDN博客 Kubernetes&#xff08;k8s&#xff09;与docker的区别 Docker、Kubernetes之间的区别_docker和kubernetes区别-CSDN博客 Docker部署SpringBoot项目&#xff08;镜…...

【代码随想录|贪心算法05】

56.合并区间 题目链接56. 合并区间 - 力扣&#xff08;LeetCode&#xff09; 这道题思路跟前两道也很像&#xff0c;就是更新把相同的区间合并而已。 class Solution { public: static bool cmp(const vector<int>& a,const vector<int>& b){return a[0…...

QQ聊天室--C++基础项目--QT+Socket网络编程

目录 一、项目概述 二、项目成果 1、QQ基础界面展示&#xff1a; 2、群聊界面展示&#xff1a; 3、聊天功能展示 三、项目代码 1、登录头文件&#xff08;denglu.h&#xff09; 2、登录源文件&#xff08;denglu.cpp&#xff09; 3、聊天界面头文件&#xff08;widget.…...

分布式搜索引擎之elasticsearch基本使用2

分布式搜索引擎之elasticsearch基本使用2 在分布式搜索引擎之elasticsearch基本使用1中&#xff0c;我们已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。 所以j接下来&#xff0c;我们研究下…...

今日商协丨商协会在“双循环”新发展格局中的作用

在当今全球经济环境中&#xff0c;世界格局正在经历深刻变化&#xff0c;中国正在全面构建“双循环”新发展格局&#xff0c;以实现更高质量、更可持续的发展。在这一过程中&#xff0c;商协会发挥着不可或缺的作用。 商协会在国内大循环中扮演促进者的角色&#xff0c;不仅活…...

前端项目安装node-sass

这个依赖比较难装&#xff0c;因为这个依赖需要安装的版本是和node版本绑定的&#xff0c;所以你需要去sass的官网找到对应关系&#xff0c;下面是我的版本信息&#xff1a; node 16.14.2 node-sass:^6.0.1 sass-loader:^10.2.0 "sass": "^1.82.0", 你…...

算法-字符串-678.有效的括号字符串

一、题目 二、思路解析 1.思路&#xff1a; 用leftMin变量来记录存在的“&#xff08;”&#xff0c; 用leftMax变量记录字符串中最多的“&#xff08;” 2.常用方法&#xff1a; 无 3.核心逻辑&#xff1a; 1.遍历字符串&#xff1a; a.当前字符为"("&#xff0c;le…...

linux 压缩文件为zip

在 Linux 系统中&#xff0c;可以使用 zip 命令来压缩文件或目录 打开终端&#xff08;Terminal&#xff09;。 使用 cd 命令导航到要压缩的文件或目录所在的路径。 运行以下命令来压缩文件或目录&#xff1a; 压缩单个文件&#xff1a; zip output.zip file.txt这里&#xf…...

Cisco Packet Tracer | Cisco Packet Tracer - VLAN 实验 - 交换机的 VLAN 划分

关注这个工具的其它相关笔记&#xff1a;Cisco Packet Tracer —— 使用教程合集-CSDN博客 0x01&#xff1a;VLAN 划分 - 单个交换机 0x0101&#xff1a;拓扑搭建流程 从软件底部拖出一台交换机&#xff08;笔者选择的型号是 2960 IOS15&#xff09;&#xff1a; 然后再拖出四…...

《计算机网络》(408大题)

2009 路由转发和静态路由的计算 子网划分、路由聚合的计算 注&#xff1a;CIDR中的子网号可以全为0或1&#xff0c;但是其主机号不允许。 注&#xff1a; 这里其实是把到互联网的路由当做了一个默认路由&#xff08;当一个目的网络地址与路由表中其他都不匹配时&#xff0c;…...

二叉树概述

目录 一、二叉树的基本结构 二、二叉树的遍历 1.前序 2.中序 3.后序 4.层序遍历 三.计算二叉树的相关参数 1.计算节点总个数 2.计算叶子节点的个数 3.计算树的高度 4.计算第k层的子树个数 5.查找树中val为x的节点 四.刷题 1.单值二叉树 2.检查两棵树是否相同 3.一…...

qiankun学习记录

什么是微前端 微前端是指存在于浏览器中的微服务&#xff0c;其借鉴了微服务的架构理念&#xff0c;将微服务的概念扩展到了前端。 如果对微服务的概念比较陌生的话&#xff0c;可以简单的理解为微前端就是将一个大型的前端应用拆分成多个模块&#xff0c;每个微前端模块可以…...

【C++ 20进阶(2):初始化 Initializer

【C 20进阶&#xff08;2&#xff09;&#xff1a;初始化 Initializer】 原文&#xff1a;https://blog.csdn.net/weixin_44259356/article/details/144377955 引言 本篇文章为系列文章将着重介绍C20新特性&#xff0c;一是希望可以和大家交流分享&#xff0c;二是也便于自己…...

vue3学习——事件监听(v-on)

我们可以使用 v-on 指令监听 DOM 事件&#xff1a; <button v-on:click"increment">{{ count }}</button> 因为其经常使用&#xff0c;v-on 也有一个简写语法&#xff1a; <button click"increment">{{ count }}</button> 此处…...

java全栈day13-后端Web实战2

接上述查询部门实现&#xff0c;完成后续要求 一、统一响应结果 1.1步骤 资料如下 对一开始的代码修改如下 结果如下 1.2测试 指定请求方式 结果 小结 二、前后端联调测试 资料如下&#xff1a; (不行&#xff0c;一定要不带空格和不带中文&#xff0c;要不然启动不了试了半天…...

C++/CX,一个智能的 C++/Windows 平台开发库!

以下是一篇关于C/CX的C学习文章&#xff1a; 开篇 嘿&#xff0c;大家好呀&#xff01;我是一行。今天咱们来一起探索一个超棒的C开发库——C/CX&#xff0c;它可是在Windows平台开发中非常智能且强大的工具哦&#xff0c;能让我们的开发变得更加高效便捷。让我们一起开启今天的…...

分布式 分布式事务 总结

前言 相关系列 《分布式 & 目录》《分布式 & 分布式事务 & 总结》《分布式 & 分布式事务 & 问题》 分布式事务 所谓分布式事务是指操作范围笼罩多个不同节点的事务。例如对于订单节点&库存节点而言&#xff0c;一次完整的交易需要同时调动两个节…...

数据结构(3)单链表的模拟实现

上一节我们进行了数据结构中的顺序表的模拟式现&#xff0c;今天我们来实现一下另外一个数据结构&#xff1a;单链表。 我们在实现顺序表之后一定会引发一些问题和思考&#xff1a; 1.顺序表在头部和中间插入数据会用到循环&#xff0c;时间复杂O&#xff08;N&#xff09; …...

HBU深度学习实验14.5-循环神经网络(1.5)

梯度爆炸实验 造成简单循环网络较难建模长程依赖问题的原因有两个&#xff1a;梯度爆炸和梯度消失。一般来讲&#xff0c;循环网络的梯度爆炸问题比较容易解决&#xff0c;一般通过权重衰减或梯度截断可以较好地来避免&#xff1b;对于梯度消失问题&#xff0c;更加有效的方式…...

Redis01

springbootredis 特点 1.高效性 2.支持多种数据结构 String,list,set,hash.zset 3.稳定性&#xff1a;持久化&#xff0c;主从复制&#xff08;集群&#xff09; 4.其他特性&#xff1a;支持过期时间&#xff0c;支持事务&#xff0c;消息订阅。 安装 1.下载安装包 redis官…...

数据库中decimal、float 和 double区别

在计算机科学中&#xff0c;decimal、float 和 double 是用于表示和处理数值的不同数据类型。 - decimal 是一种精确的十进制浮点数表示&#xff0c;通常用于需要高精度计算的场景&#xff0c;比如财务应用。它能够精确表示小数&#xff0c;并且不会出现浮点数运算误差。 - flo…...

HDR视频技术之五:HDR生产流程

在介绍 HDR 的生产流程之前&#xff0c;我们先介绍下视频制作与传输的一些基本知识。 内容类型&#xff1a; 直播内容&#xff08; live content&#xff09; ——所谓的直播内容即没有后处理过程以及创作者意图。分发给用户的信息是实时产生并且实时制作并派发的。常见的应用…...

CTFshow-爆破(Web21-28)

CTFshow-爆破(Web21-28) Web21 抓包 选则dic.zip里的字典爆破&#xff0c;记得添加前缀admin: 答案admin:shark63 burp里有一个自定义迭代器&#xff0c;可以设置前几部分&#xff0c;很好用 Web22 题目失效了直接看wp吧 360quake 使用空间搜索引擎—>360quake 搜索语法…...

C++重点和练习

作业题目&#xff1a; #include <iostream> using namespace std; class Rec {const int length;int width; public:void set_length(int l);void set_width(int w);int get_length();int get_width();void show(); };#include <iostream> using namespace std; c…...

UnityShaderLab-实现溶解效果

实现思路&#xff1a; 使用一张噪声图&#xff0c;与一个Cut值计算&#xff08;加或减&#xff09;&#xff0c;将计算后的值赋值给Alpha,然后小于0的片段就被丢弃掉了。 ShaderGraph实现&#xff1a; ShaderLab实现&#xff1a; Shader "Dissolve" {Properties{_…...

SQLite 数据库学习

1.install sudo apt update sudo apt install sqlitebrowser这是一个开源的图形用户界面工具&#xff0c;专门用于开发、管理和分析 SQLite 数据库。它支持创建或导入导出表、编辑数据、执行 SQL 查询等功能。 2.python 操作数据库 Python 内置了 sqlite3 模块&#xff0c;使…...

【LeetCode: 463. 岛屿的周长 + bfs】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…...

Bean的注入、单例和多例

目录 注入Bean对象 属性注入 构造注入 属性注入专题 注入集合/数组 级联简单类型赋值 Bean的单例和多例 注入Bean对象 简单类型使用value(除Date)&#xff0c;非简单类型使用ref 属性注入 name规则&#xff1a;必须提供set方法&#xff0c;去掉set&#xff0c;第一个字…...