后端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 层(API接口) 无论是传统的三层架构还是现在的COLA架构,Controller 层依旧有一席之地,说明他的必要性;说它是配角是因为 Controller 层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收…...
网络安全法 -网络信息安全
第四章 网络信息安全 第四十条 网络运营者应当对其收集的用户信息严格保密,并建立健全用户信息保护制度。 第四十一条 网络运营者收集、使用个人信息,应当遵循合法、正当、必要的原则,公开收集、使用规则,明示收集、使用信息的…...
matlab figure函数 single 数据类型
1.matlab figure函数详细介绍 在MATLAB中,figure函数用于创建新的图形窗口或激活现有的图形窗口。以下是figure函数的详细介绍和用法: 基本用法 创建新图形窗口:不带任何参数调用figure会创建一个新的图形窗口,并将其设为当前活…...
endroid/qr-code生成二维码,中文乱码的解决方案
endroid/qr-code version:6.0.3 默认不支持中文; 1、https://fonts.google.com/noto/fonts,从这里下载字体; 2、下载简体中文:Noto Sans Simplified Chinese 3、下载后,把压缩包解压,把NotoSansSC-Regul…...
深度和法线纹理
屏幕后期处理效果的基本原理就是当游戏画面渲染完毕后通过获取到该画面的信息进行额外的效果处理 之前的边缘检测、高斯模糊、Bloom、运动模糊等效果都是基于获取当前屏幕图像中的像素信息进行后期处理的 如果仅仅根据像素信息来进行一些效果处理,存在以下问题&…...
监听H5页面在微信浏览器异常退出
参考文章 onBeforeUnmount(() > {unNormalExit(); });//---------------------------异常退出---------------------- function unNormalExit() {enterOrExitRoom({type: 37,roomId: roomId.value,userId: userId.value,nickName: name.value,loginUserType: 2, //0 专家 1…...
Linux 串口编程
目录 前言一、tty体系二、串口硬件基础知识三、Linux下的串口编程3.1 打开串口3.2 从串口读写数据,问题1、2的诞生3.3 关闭串口3.4 串口配置3.4.1 获取/设置串口的参数3.4.2 设置波特率3.4.3 设置控制模式标志3.4.4 设置本地模式标志3.4.5 设置输入模式标志3.4.6 设置输出模式标…...
Adminer源码编译 精简语言中英文和基本使用方法
Adminer是一个小而强悍的基于web的数据库管理工具, 官方默认支持几十种语言,但是对于中国的用户而言只需要有中文和英文就够了,其他语言基本无用。这就需要我们下载Adminer源码自己编译 Adminer.php , 如下图所示 adminer 中英文语言精简版本…...
go 中线程安全map
在 Go 语言中,官方包 sync.Map 确实提供了线程安全的映射数据结构。然而,正如你所提到的,使用 sync.Map 时,有时需要进行类型断言,这可能会让代码显得冗长或不直观。 如果你希望使用一个更加易用的线程安全映射&#…...
eslint 安装与使用-基础教程
中文官网 官方规则解析 规则参考 - ESLint - 插件化的 JavaScript 代码检查工具 eslint ESlint 是一个检查 JS,TS 语法的工具.能够与常用开发工具,例如 VS Code,进行集成并提供错误提示,和可能的修正方法 安装 安装eslint npm init esli…...
自然语言处理的未来愿景
自然语言处理的未来愿景 在这个信息爆炸的时代,计算机如何理解和生成我们日常使用的语言,已经成为一个引人注目的问题。你有没有想过,为什么智能助手能理解你的指令?又或者,为什么社交媒体上的推荐引擎能够精准地推荐你喜爱的内容?这背后,正是自然语言处理(NLP)在发挥…...
等保2.0三级测评华为华三交换机路由器
在使用本博客提供的学习笔记及相关内容时,请注意以下免责声明: 信息准确性:本博客的内容是基于作者的个人理解和经验,尽力确保信息的准确性和时效性,但不保证所有信息都完全正确或最新。 非专业建议:博客中的内容仅供参考,不能替代专业人士的意见和建议。在做出任何重要…...
BA和CS算法中的Levy飞行策略
Levy飞行策略通过模拟自然界中动物的长距离迁徙行为,指导粒子进行更大范围的搜索,有助于算法快速找到全局最优解。它是一种具有独特优势的随机行为策略,模拟随机游走或搜索过程中的步长和方向,其步长的概率分布为重尾分布…...
PHP:实现两张无关联表数据的联合分页处理方案
前言 在现代软件开发中,高效地处理数据是至关重要的环节。尤其是在使用 PHP 进行开发时,常常会遇到各种复杂的数据处理需求。其中,实现两张无关联表数据的联合分页处理就是一个具有挑战性的任务。这种需求在很多实际应用场景中都可能出现&am…...
【单元测试】单元测试介绍
1 单元测试基础 1.单元测试:单元测试又称模块测试,属于白盒测试,是最小单位的测试。模块分为程序模块和功能模块。功能模块指实现了一个完整功能的模块(单元),一个完整的程序单元具备输入、加工和输出三个…...
PyQt事件机制及其应用
一、实例前置 一个小闹钟应用 创建主窗口类 首先我们创建了一个名为AlarmClock的类,它继承自QMainWindow。这个类将包含我们的GUI组件和逻辑。 from Alarm_clock import Ui_MainWindowclass AlarmClock(QMainWindow):def __init__(self):super().__init__()# 初始化…...
厦门凯酷全科技有限公司抖音电商服务的卓越典范
在短视频和直播带货迅速崛起的时代,厦门凯酷全科技有限公司(以下简称“凯酷全科技”)以其专业的服务、创新的精神以及对市场的深刻理解,在抖音电商领域中脱颖而出,成为众多品牌商家信赖的选择。本文将深入探讨凯酷全科…...
vue3水波柱状图 ,实现
效果图 //引用页面 <div style"height: 60px;background-color: #fff;border-radius: 5px;width: 40px;"><WavePercentage:percentage"progress"primary-color"#ffcb7c"secondary-color"#ffcb7c"/></div>import Wa…...
如何在两台 PostgreSQL 服务器之间使用逻辑复制槽进行数据复制
如何在两台 PostgreSQL 服务器之间使用逻辑复制槽进行数据复制 如何在两台 PostgreSQL 服务器之间使用逻辑复制槽进行数据复制环境准备1. 配置主服务器(Publisher)1.1 修改 postgresql.conf1.2 修改 pg_hba.conf1.3 重启 PostgreSQL 服务1.4 创建逻辑复制…...
System.Data.OracleClient 需要 Oracle 客户端软件 version 8.1.7 或更高版本
问题1:“/”应用程序中的服务器错误。 System.Data.OracleClient 需要 Oracle 客户端软件 version 8.1.7 或更高版本。 说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
【Java】Ajax 技术详解
文章目录 1. Filter 过滤器1.1 Filter 概述1.2 Filter 快速入门开发步骤:1.3 Filter 执行流程1.4 Filter 拦截路径配置1.5 过滤器链2. Listener 监听器2.1 Listener 概述2.2 ServletContextListener3. Ajax 技术3.1 Ajax 概述3.2 Ajax 快速入门服务端实现:客户端实现:4. Axi…...
使用homeassistant 插件将tasmota 接入到米家
我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能,利用了巴法接入小爱的功能,将本地mqtt转发给巴法以实现小爱控制的功能,前提条件。1需要tasmota 设备, 2.在本地搭建了mqtt服务可, 3.搭建了ha 4.在h…...
LangChain + LangSmith + DeepSeek 入门实战:构建代码生成助手
本文基于 Jupyter Notebook 实践代码,结合 LangChain、LangSmith 和 DeepSeek 大模型,手把手演示如何构建一个代码生成助手,并实现全流程追踪与优化。 一、环境准备与配置 1. 安装依赖 pip install langchain langchain_openai2. 设置环境变…...
Linux——TCP和UDP
一、TCP协议 1.特点 TCP提供的是面向连接、可靠的、字节流服务。 2.编程流程 (1)服务器端的编程流程 ①socket() 方法创建套接字 ②bind()方法指定套接字使用的IP地址和端口。 ③listen()方法用来创建监听队列。 ④accept()方法处理客户端的连接…...
