寿险业务系统异常处理方案

news2024/11/24 11:12:30

我们的系统使用的java语言开发,基于Spring框架搭建的业务中台,在讨论业务系统异常处理策略之前,先把java的异常机制进行简单说明。

一、Java的异常机制

1.Java异常分类

在这里插入图片描述

【Error】是系统错误或者编译错误,常见的虚拟机运行错误、栈溢出错误、内存溢出错误都是属于error,这种程序无法处理,发生后会导致jvm终止线程

【Exception】是程序中产生的错误,程序本身可以捕获并且处理。通常会分为运行时异常(非受检异常)和非运行时异常(受检异常),受检异常程序必须要处理(try-catch 或者继续抛出),非受检异常程序可以不处理,会自动向上抛出,直至main方法或者Thread.run方法,终止该线程。

2. Java的异常处理方式

(1)调用方通过try - catch - finally处理,示例代码如下:

try
{
  可能会发生的异常
}catch(异常类型 异常名(变量)){
  针对异常进行处理的代码
}catch(异常类型 异常名(变量)){
  针对异常进行处理的代码
}...
[finally{
  释放资源代码;
}]

(2)throws 调用方补处理,直接将异常在方法声明中抛出,交由上层处理,示例代码如下:

public void testExceptionThrow throws NullPointerException{
	 throw new NullPointerException();
}

二、Spring的异常机制

Spring有一套自带的视图错误处理机制,借助SpringMVC的视图控制能力,通过一些异常的处理Resolver来进行错误页面的跳转。而在中台系统建设中,则需要用到Spring提供的自定义异常的处理能力,后端使用的异常处理机制主要以下两种

1. @ControllerAdvice+@ExceptionHandler处理全局异常

实现方式是自定义一个异常处理类,只需要在该类上标记@ControllerAdvice即可。
同时要在执行异常处理的方法上标记@ExceptionHandler。这样在发生了指定异常时可以找到响应的异常处理方法进行处理。此种模式的底层是 ExceptionHandlerExceptionResolver 支持的,代码示例如下:

/**
 * 处理整个web controller的异常
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class}) // 可以处理多种异常
    public String handleArithmeticException() {
        log.info("处理异常");
        // 也可以返回ModelAndView类型对象,因为在处理异常相关源码中最后也会渲染视图转化为ModelAndView
        return "error";
    }

    @ExceptionHandler({NullPointerException.class})
    public String handleNullPointerException(Exception e) {
        log.info("处理异常");
        return "error";
    }
}

@ControllerAdvice 注解的原理是SpringAOP提供的,是将Controller层的方法作为切面,从而对Controller层方法进行拦截处理,如果是前后端分离的项目也可以使用 @RestControllerAdvice 注解。

2. 自定义实现 HandlerExceptionResolver 处理异常

可以作为默认的全局异常处理规则(注意:设置为最高优先级会顶替掉SpringBoot原生的异常处理规则)

// SpringBoot底层会优先调用SpringBoot定义的异常处理器(ExceptionResolver)
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            response.sendError(511, "xxx错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        ModelAndView modelAndView = new ModelAndView();
        return modelAndView;
    }
}

三、业务系统中的异常处理

异常是可以继承的,业务系统中都会通过继承异常实现自己的自定义异常,而为了简化开发,减少程序员的工作量,自定义异常都会实现RuntimeException ,这样程序中就不需要显性处理,如有需要自己捕获处理该异常,但这种潜规则会导致很多程序员不知道异常该如何使用如何设计。下面我就分享下寿险中台中的异常处理机制,可以给大家提供一种异常处理的参考方案。

1.设计异常机制前的知识准备

【为什么要自定义异常】

Java是面向对象的语言,区别于面向过程的语言,很多处理结果和信息不仅可以像面向过程的语言的返回值传递,还可以通过参数对象、上下文变量传递,而java提供的异常机制就是在不影响方法定义的出参、入参的情况下定义错误信息的传递机制。

【系统中的错误码和异常的关系】

异常是一种快捷方便的错误传递方式,而外部服务接口交互不能通过异常的方式传递。

(1)错误码 : 接口交互的错误传递方式,一般都是由一个Result的DTO 通过errorCode和errorMessage来承载。
(2)异常 : 应用内部的错误传递方式,可以由框架统一处理,调用方也可以根据自己的需要去差异化catch进行业务处理。

【为什么会选择集成RuntimeException】

RuntimeException不需要程序处理,可以交由框架自动处理,而且Spring的异常机制就是针对RuntimeException进行的,更加方便了程序员的开发工作。由于RuntimeException的无感也会带来一些问题,如果不在方法中声明或者注释中说明异常情况,调用方很容易忽略了异常情况的逻辑和应对,致使系统的健壮性降低。

【系统异常还有没有必要自定义,有没有必要继承RuntimeException】

先说结论系统异常自定义还是有必要的,因为很多系统异常都是受检异常,尤其使用一些组件或者功能时,数组越界、ClassNotFound等,针对这类异常程序必须要catch或者throw 处理,会给开发带来很大的处理工作。而系统异常的处理往往逻辑比较统一,完全可以交由框架在拦截层统一处理,所以在业务系统中将系统异常统一定义转换,可以极大降低对开发人员的要求,同时也可以满足资深程序员对各类异常单独处理的需求。

【异常中需要注意的事项】

(1)主动throw异常,异常中需要带有上下文信息;
(2)程序中尽量不要产生error,error是无法处理的,会导致线程终止,exp. 方法参数中使用原生类型int,而请求方传的参数是Integer,当参数为null的时候就会触发error,导致线程终止;

2.寿险中台的异常规范

【寿险中台异常的分类】

常见的业务系统中都会定义自己的业务异常、系统异常,寿险中台也是使用了众安的common包中的异常定义的BizException和ServiceException作为寿险中台异常的父类,这样做的好处时即使寿险中台自定义了异常,但整体异常框架仍然是在众安技术部的规范内,实现自己的自定义需求的同时,不会影响技术框架的能力。

业务系统通常是自定义业务异常,但针对特殊的系统异常有特殊处理的话也可以扩展ServiceException(极少数,比如需要识别一些中间件的特殊异常进行中间件调用方式的替换等)。所以业务系统自定义的异常归类如下:
在这里插入图片描述

【系统异常规范】

每个系统必须有一个统一的Error枚举类,以理赔为例会在common包中定义ClaimErrorEnum;自定义异常构造方法参数中必须要有此枚举类。
在说明业务系统错误枚举类之前,在提下业务错误枚举的父类接口BaseResultCode。由于枚举无法继承,所以一些通用的内容通过接口的方式让业务枚举实现,赋能给枚举类。业务错误枚举类通过接口实现继承了错误码的组装方法,业务系统只需要关注自己的3位错误码即可,同时也规范了业务枚举类的方法行为,可以统一拦截标准化处理。

接口BaseResultCode代码如下:

/**
 * 错误码枚举的实现基础接口 <br/>
 * 各业务系统的自定义枚举类需要实现此接口 <br/>
 *
 * @author guosenlin
 * @date 2022/2/21 11:12
 */
public interface BaseResultCode {

    /**
     * 部门编码:技术服务中心编码
     */
    String DEPARTMENT_CODE = "10";
    /**
     * 应用编码:未知的应用编码
     */
    String UNKNOWN_APP_CODE = "00";

    /**
     * 返回错误编码,定义为三位,自定义 2 是业务错误码 9 是系统错误码
     *
     *
     * @return
     */
    String getCode();

    /**
     * 返回错误描述
     * 
     * @return
     */
    String getMsg();

    /**
     * 应用编码
     * 
     * @return
     */
    String getAppCode();

    /**
     * 返回错误编码,由部门编码+应用编码+自定义编码拼接完成 <br/>
     * 1、部门编码:取默认值DEPARTMENT_CODE <br/>
     * 2、应用编码:子类实现的getAppCode()方法返回,一个应用是固定值 <br/>
     * 3、自定义编码:子类枚举实现getCode() <br/>
     *
     * @return
     */
    default String errorCode() {
        String appCode = getAppCode();
        String code = getCode();
        StringBuilder sb = new StringBuilder();
        sb.append(DEPARTMENT_CODE);
        // 00代表未知
        sb.append(appCode != null ? appCode : UNKNOWN_APP_CODE);
        sb.append(code);
        return sb.toString();
    }

    /**
     * 获得错误描述
     * 
     * @return
     */
    default String errorDesc() {
        return getMsg();
    }

}

理赔服务业务错误枚举ClaimErrorEnum代码如下:

/**
 * 理赔错误码枚举类 <br/>
 * code:错误代码 <br/>
 * msg:错误描述 <br/>
 * 
 * @author nidazhang
 * @date 2022-11-14
 */
@Getter
@AllArgsConstructor
public enum ClaimErrorCodeEnum implements BaseResultCode {

    /**  201:理赔报案失败 */
    REPORT_FAILED_ERROR("201", "理赔报案失败"),
    /** 202:未查询到该报案号对应的案件信息 */
    NOT_QUERY_REPORT_INFO_ERROR("202", "未查询到该报案号对应的案件信息"),
    /**  203:案件状态非法  */
    ILLEGAL_REPORT_STATUS_ERROR("203", "案件状态非法"),
    /** 204:案件存在分支业务  */
    REPORT_HAS_BRANCH_BUSINESS_ERROR("204", "案件存在分支业务"),
    /** 205:调用保单锁单并抄单返回为空 */
    COPYING_POLICY_RETURN_NULL_ERROR("205", "调用保单锁单并抄单返回为空"),
    /**  206:批量解锁失败 */
    BATCH_UNLOCK_FAILED_ERROR("206", "批量解锁失败"),
    /** 207:案件已注销或者已结案,不允许对案件进行操作  */
    REPORT_STATUS_NOT_ALLOW_OPERATE_ERROR("207", "案件已注销或者已结案,不允许对案件进行操作"),
    /** 208:未查找到该分案或者材料已齐全,禁止补传  */
    PROHIBIT_MATERIAL_UPLOAD_ERROR("208", "未查找到该分案或者材料已齐全,禁止补传"),

    /** 901:调用保单锁单并抄单异常 */
    CALL_LOCK_AND_QUERY_POLICY_ERROR("901", "调用保单锁单并抄单异常"),
    /** 902:调用批量解锁系统异常 */
    CALL_BATCH_UNLOCK_ERROR("902", "调用批量解锁系统异常"),;

    private String code;
    private String msg;

    /**
     * 获得理赔系统的项目编码,为固定常量值
     * 
     * @return
     */
    @Override
    public String getAppCode() {
        return ClaimReportConstants.CLAIM_APP_CODE;
    }
}

自定义异常代码如下:

异常基础类:

/**
 * 异常基础类
 *
 * @author guosenlin
 * @data 2021/7/30 14:36
 */
public abstract class BasicException extends RuntimeException {
    /**
     * 异常错误码枚举
     */
    protected BaseResultCode errorCodeEnum;
    /**
     * 异常错误信息描述
     */
    protected String errorMsg;
    /**
     * 异常上下文信息
     */
    protected Map<String,Object> exceptionContext;

    /**
     * 获得错误码枚举信息
     * 
     * @return
     */
    public BaseResultCode getErrorCodeEnum() {
        return errorCodeEnum;
    }

    /**
     * 获得错误信息
     * 
     * @return
     */
    public String getErrorMsg() {
        if (errorMsg != null) {
            return errorMsg;
        }
        if (errorCodeEnum != null) {
            return errorCodeEnum.errorDesc();
        }
        return "";
    }

    /**
     * 获得错误码信息
     * 
     * @return
     */
    public String getErrorCode() {
        if (errorCodeEnum != null) {
            return errorCodeEnum.errorCode();
        }
        return "";
    }

    /**
     * 获得异常上下文信息
     * 
     * @return
     */
    public Map<String,Object> getExceptionContext() {
        return exceptionContext;
    }

    public BasicException() {
        super();
    }

    public BasicException(String message) {
        super(message);
    }

    public BasicException(Throwable cause) {
        super(cause);
    }

    public BasicException(String message, Throwable cause) {
        super(message, cause);
    }

    @Override
    public String getMessage() {
        StringBuilder sb = new StringBuilder();
        sb.append(getErrorCode());
        sb.append(":");
        sb.append(getErrorMsg());
        sb.append(",");
        String message = super.getMessage();
        sb.append(message != null ? message : "");
        return sb.toString();
    }
}

理赔服务自定义异常示例:

/**
 * 理赔抄单异常
 *
 * @author guosenlin
 * @data 2021/7/30 14:37
 */
public class ClaimCopyPolicyException extends BasicException {
    private static final long serialVersionUID = 2237743543787228870L;

    public ClaimCopyPolicyException(String errMsg, Object... contextInfo) {
        this(errMsg, null, contextInfo);
    }

    public ClaimCopyPolicyException(Throwable e, Object... contextInfo) {
        this(null, e, contextInfo);
    }

    public ClaimCopyPolicyException(String errMsg, Throwable e, Object... contextInfo) {
        super(e);
        this.errorMsg = errMsg;
        this.contextInfo = contextInfo;
    }
}

【业务异常处理机制】

(1)系统框架异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseBean<?> methodArgumentNotValidErrorHandler(HttpServletRequest req,
        MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        StringBuilder errorMsg = new StringBuilder();
        if (result.hasErrors()) {
            List<ObjectError> allErrors = result.getAllErrors();
            allErrors.forEach(error -> errorMsg.append(error.getDefaultMessage()).append("!"));
        }
        log.info("参数校验失败,reqMethod:{},URI:{},错误信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(), errorMsg,
            ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(ResultCodeEnum.PARAM_EXCEPTION, errorMsg.toString());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseBean<?> illegalArgumentExceptionHandler(HttpServletRequest req, IllegalArgumentException e) {
        String message = e.getMessage();
        log.info("参数校验失败,reqMethod:{},URI:{},错误信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(), message,
            ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(ResultCodeEnum.PARAM_EXCEPTION, message);
    }

    @ExceptionHandler(BindException.class)
    public ResponseBean<?> bindErrorHandler(HttpServletRequest req, BindException e) {
        BindingResult result = e.getBindingResult();
        StringBuilder errorMsg = new StringBuilder();
        if (result.hasErrors()) {
            List<ObjectError> allErrors = result.getAllErrors();
            allErrors.forEach(error -> errorMsg.append(error.getDefaultMessage()).append("!"));
        }
        log.info("参数校验失败,reqMethod:{},URI:{},错误信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(), errorMsg,
            ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(ResultCodeEnum.PARAM_EXCEPTION, errorMsg.toString());
    }

    @ExceptionHandler(value = BizException.class)
    public ResponseBean<?> bizErrorHandler(HttpServletRequest req, BizException e) {
        log.info("业务异常,reqMethod:{},URI:{},上下文信息:{},异常信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(),
            LogUtils.toJsonOrElseToString(e.getContextInfo()), e.getErrorMsg(), ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(e.getErrorCodeEnum(), e.getErrorMsg());
    }

    @ExceptionHandler(value = ServiceException.class)
    public ResponseBean<?> serviceErrorHandler(HttpServletRequest req, ServiceException e) {
        log.warn("服务异常,reqMethod:{},URI:{},上下文信息:{},异常信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(),
            LogUtils.toJsonOrElseToString(e.getContextInfo()), e.getErrorMsg(), ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(e.getErrorCodeEnum(), e.getErrorMsg());
    }

    @ExceptionHandler(value = BasicException.class)
    public ResponseBean<?> basicExceptionHandler(HttpServletRequest req, BasicException e) {
        log.warn("服务异常,reqMethod:{},URI:{},上下文信息:{},异常信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(),
            LogUtils.toJsonOrElseToString(e.getContextInfo()), e.getErrorMsg(), ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(e.getErrorCodeEnum(), e.getErrorMsg());
    }

    @ExceptionHandler(value = Throwable.class)
    public ResponseBean<?> defaultErrorHandler(HttpServletRequest req, Throwable e) {
        log.error("系统异常,reqMethod:{},URI:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(),
            ExceptionUtils.getStackTrace(e));
        return ResponseBean.fail(ResultCodeEnum.UNKNOWN_EXCEPTION, e.getMessage());
    }
}
(2)主动捕获特殊处理
//抄单并锁单
List<ClaimPolicyBO> claimPolicyList = multiplePolicyLockIntegration.queryPolicySnapAndLock(claimReportBO);
try {
//生成立案号
String registerNo = bizNoGenarateManager.generateRegistNo();
claimReportBO.setRegisterNo(registerNo);
claimReportBO.setRegisterTime(LocalDateTime.now());
claimReportBO.setStatus(CaseStatusEnum.FINISHED_REGIST);
//抄单填充报案号以及分案号以及锁单标记
for (ClaimCaseBO claimCaseBO : claimReportBO.getSubCaseList()) {
for (ClaimReportPolicyBO casePolicyRelaBO : claimCaseBO.getCasePolicyRelationList()) {
setPolicyLevelReportNo(claimReportBO, claimPolicyList, claimCaseBO, casePolicyRelaBO);
}
}
//入库保存
reportRepositoryService.saveClaimRegist(claimReportBO);
} catch (Exception exception) {
//入库或者生成号码等异常时候,保单解锁掉
multiplePolicyLockIntegration.batchUnlockPolicy(claimReportBO);
throw new ServiceException(ClaimErrorCodeEnum.CLAIM_REGISTER_SYS_002_ERROR, exception, claimReportBO.getReportNo());
}
(3)异常信息在接口层的转换处理
1)针对系统异常处理:通过异常切面,将异常转换为错误码
@ExceptionHandler(BindException.class)
public ResponseBean<?> bindErrorHandler(HttpServletRequest req, BindException e) {
BindingResult result = e.getBindingResult();
StringBuilder errorMsg = new StringBuilder();
if (result.hasErrors()) {
List<ObjectError> allErrors = result.getAllErrors();
allErrors.forEach(error -> errorMsg.append(error.getDefaultMessage()).append("!"));
}
log.info("参数校验失败,reqMethod:{},URI:{},错误信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(), errorMsg,
ExceptionUtils.getStackTrace(e));
return ResponseBean.fail(ResultCodeEnum.PARAM_EXCEPTION, errorMsg.toString());
}
2)针对业务异常处理:获取异常的错误码,通过统一的包装类进行返回
@ExceptionHandler(value = BasicException.class)
public ResponseBean<?> basicExceptionHandler(HttpServletRequest req, BasicException e) {
log.warn("服务异常,reqMethod:{},URI:{},上下文信息:{},异常信息:{},异常堆栈:{}", req.getMethod(), req.getRequestURI(),
LogUtils.toJsonOrElseToString(e.getContextInfo()), e.getErrorMsg(), ExceptionUtils.getStackTrace(e));
return ResponseBean.fail(e.getErrorCodeEnum(), e.getErrorMsg());
}

【寿险中台异常处理方案】

在这里插入图片描述

【寿险中台自定义异常使用规范】

(1)主动封装抛出自定义异常, 必须带有环境上下文信息,上下文信息要包含发生异常点的具体信息;比如在遍历保单险种的时候发生数据不合法系统异常,上下文中应该包含遍历的保单险种号,而不只是保单号信息;

(2)主动抛出异常无需在打印相关异常日志,因为异常中已包含堆栈信息和上下文信息,最终会在捕获处或者系统拦截器处打印;此时打印属于重复打印,会无谓增加日志量;

(3)只允许主动抛出自定义业务异常,不允许主动抛出java原生异常;可以将其他系统异常转化为自定义系统异常,原则上不允许主动抛出自定义的系统异常,比如以下代码使用的是java原生的IllegalStateException来封装业务枚举不存在异常,会导致异常捕获处理的复杂度和困难增加。
反例错误代码
在这里插入图片描述

(4)捕获异常必须进行有效业务处理,否则不允许catch自定义异常。有效业务处理包括打印error日志触发告警,保存异常记录数据,调用其他业务方法或者将受检异常转为自定义系统异常等;不允许捕获异常后只是做runtime类的异常转换或者打印日志等无用行为;

(5)程序员主动抛出异常必须清晰区分系统异常、业务异常和正确的错误枚举,业务异常和系统异常会导致处理逻辑的差异,影响业务处理结果;

(6)定义方法时如果会抛出自定义异常,必须在方法声明中声明异常信息,并在注释中说明不同错误枚举的产生业务场景,以便调用方根据情况自行决定异常处理策略;方法中调用其他服务产生的自定义异常也应在方法中声明;

(7)寿险中台的默认事务处理是在Facade层,当没有明确的事务代码或注解,事务会在facade层统一提交,异常传递至平台默认拦截器层时会导致事务回滚;
在这里插入图片描述

(8)非必要不允许自定义异常,必要场景为需要针对某类业务错误进行特殊业务处理,这种情况下通过自定义异常方便系统通过catch异常的方式实现。禁止只是为了某一通用异常业务概念进行自定义封装,比如核保不通过属于行业内比较通用的业务规则错误,但如果没有针对核保不通过的异常进行捕获处理需求,只需要使用通用的BizException+核保不通过错误枚举承载即可。

(9)寿险中台通用错误码规范
寿险中台错误码由 三部分组成 系统编码 + 应用编码 + 具体错误码,具体错误码包含通用错误码和自定义错误码,通用错误码包含常见的业务参数不合法、权限不足、不符合业务、系统未知错误等通用错误码。以寿险中台新契约核保不通过为例,寿险中台系统编码为SX,新契约编码为NCS,核保不通过的错误码排位201 ,则此最终接口错误码为 SX-NCS-001。

(10)应用自定义错误码需要遵守通用的号段规则
2XX代表业务错误码,应用依次编排自定义业务错误码;
9XX代表系统错误码,应用依次编排自定义系统错误码。

参考文章:
https://blog.csdn.net/qq_51628741/article/details/125873733

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023,考个软考中级证书稳妥深圳入户,5月考试8月办入户

最新消息&#xff01;最新消息&#xff01;最新消息&#xff01; 2023年2月8日&#xff0c;深圳市发展和改革委员会深圳市公安局深圳市人力资源和社会保障局关于印发《深圳市积分入户办法》的最新通知↓ 来源《深圳市发展和改革委员会》 该积分入户将于2023年2月15日正式实施&…

C++与Lua交互实例 -- 矩阵的加减乘除(版本一)

C与Lua交互实例 – 矩阵的加减乘除&#xff08;版本一&#xff09; 关于lua中封装的类模板以及相关知识可参考以下链接&#xff1a; https://ufgnix0802.blog.csdn.net/article/details/128854786 https://ufgnix0802.blog.csdn.net/article/details/128827673 https://ufgnix0…

Python深度学习实战PyQt5窗口切换的堆叠布局示例详解

本文以堆叠窗口控件为例&#xff0c;详细介绍堆叠布局的界面设计和程序实现过程&#xff0c;通过案例带小白创建一个典型的堆叠布局多窗口切换程序软件项目中经常需要多种不同的图形界面&#xff0c;以适应不同的任务场景。选项卡控件&#xff08;QTackedWidget&#xff09;通过…

[Java 进阶] synchronized 与 ReentrantLock的区别

祝一切顺利~ 文章目录synchronized 与 ReentrantLock的区别synchronized 与 ReentrantLock的区别 1.ReentrantLock的锁是公平锁,synchronized是非公平锁.(公平锁是指当锁被释放时,由等待时间最长的线程获取锁.非公平锁是锁被释放时,所有线程不论等待时间长短,都一起去竞争) 2.…

小白系列Vite-Vue3-TypeScript:009-屏幕适配

上一篇我们介绍了ViteVue3TypeScript项目中mockjs的安装和配置。本篇我们来介绍屏幕适配方案&#xff0c;简单说来就是要最大程度上保证我们的界面在各种各样的终端设备上显示正常。通用的屏幕适配方案有两种&#xff1a;① 基于rem 适配&#xff08;推荐&#xff0c;也是本篇要…

线下沙龙|从VUCA到BANI,找到你的“第二曲线”

冷战之后&#xff0c;VUCA盛行&#xff0c;世界已复杂到无可言表&#xff1b;然而&#xff0c;我们正在拼命地理解和适应“VUCA时代”&#xff0c;却又被迷迷糊糊、跌跌撞撞地推进了“BANI时代”。 未来之路在何方&#xff1f;在脚下&#xff0c;却不知该如何去迈&#xff1f; …

elasticsearch使用painless的一些简单例子

文章目录1、背景2、准备数据2.1 mapping2.2 插入数据3、例子3.1 (update)更新文档 id1 的文档&#xff0c;将 age 加 2岁3.2 (update_by_query)如果 province 是北京的话&#xff0c;就将 age 减少1岁3.3 (ctx.op)如果张三的年龄小于20岁就不处理&#xff0c;否则就删除这个文档…

MLX90316KGO-BDG-100-RE传感器 旋转位置 角度测量

介绍MLX90316是Tria⊗is旋转位置传感器&#xff0c;提供在设备表面旋转的小偶极磁铁(轴端磁铁)的绝对角位置。得益于其表面的集成磁集中器(IMC)&#xff0c;单片设备以非接触式方式感知应用磁通量密度的水平分量。这种独特的传感原理应用于旋转位置传感器&#xff0c;可在机械(…

(1分钟速通面试) 矩阵分解相关内容

矩阵分解算法--总结QR分解 LU分解本篇博客总结一下QR分解和LU分解&#xff0c;这些都是矩阵加速的操作&#xff0c;在slam里面还算是比较常用的内容&#xff0c;这个地方在isam的部分出现过。(当然isam也是一个坑&#xff0c;想要出点创新成果的话 可能是不太现实的 短期来讲 哈…

【电商】订单系统--售后的简易流程与系统关系

用户进行了订单签收并不意味着终结&#xff0c;这只是一个新的开始&#xff0c;因为商品送达后可能会由于运输过程包装或商品有破损&#xff0c;商品本质量并非商品详情中所描述的那样等各种原因使用户进行退货或换货&#xff1b;还有一种场景是用户签收后发现有的商品漏发、少…

线性卡尔曼滤波详解

自动驾驶面临的第一个问题&#xff1a;从哪里来&#xff0c;到哪里去&#xff1f;要解决这个问题&#xff0c;自动驾驶汽车首先需要准确的知道自己在地图上的位置。理所当然的我们可以想到通过GPS来进行定位&#xff0c;但获取GPS信号需要跟卫星进行通信&#xff0c;这就导致它…

简单的洗牌(数据结构系列4)

目录 前言&#xff1a; 1.思想 1.1基本框架的搭建 1.2洗牌操作 1.3揭牌 2.代码运行结果截图 结束语&#xff1a; 前言&#xff1a; 在上一次的博客中小编与大家分享了ArrayList的模拟实现和一些具体的使用方法&#xff0c;那么接下来我们通过一个简单的洗牌练习来巩固一…

Java创建枚举类并定义使用枚举项

前言 在项目开发中&#xff0c;常量和枚举都是我们经常使用的技巧。都是为了提高开发效率。常量我们一般都会创建一个常量类&#xff0c;里面放一些经常使用的变量&#xff0c;例如&#xff1a; 枚举的定义和使用相比于常量&#xff0c;也是有异曲同工之巧的&#xff0c;下面就…

梅开二度的 axios 源码阅读,三千字详细分享功能函数,帮助扩展开发思维

前言 第一遍看 axios 源码&#xff0c;更多的是带着日常开发的习惯&#xff0c;时不时产生出点联想。 第二遍再看 axios 源码&#xff0c;目标明确&#xff0c;就是奔着函数来的。 当有了明确清晰的目标&#xff0c;阅读速度上来了&#xff0c;思绪也转的飞快。 按图索骥&a…

day 15 第六章二叉树

层序遍历 102.二叉树的层序遍历107.二叉树的层次遍历II199.二叉树的右视图637.二叉树的层平均值429.N叉树的层序遍历515.在每个树行中找最大值116.填充每个节点的下一个右侧节点指针117.填充每个节点的下一个右侧节点指针II104.二叉树的最大深度111.二叉树的最小深度 226.翻转二…

速腾rshelios 5515惯导时间同步

目前接触过两种雷达和惯导同步的方式&#xff1a; 1.惯导输出gprms和pps信号给米文系统&#xff0c;雷达驱动从系统里读取时间。 2.惯导输出gprms和pps信号给雷达&#xff0c;雷达驱动从雷达数据读取时间。 GPRMS和PPS的内容参考&#xff1a;STM32模拟GPS输出PPS、GPRMC与VLP…

多传感器融合定位九-基于滤波的融合方法Ⅰ其一

多传感器融合定位九-基于滤波的融合方法Ⅰ其一1. 滤波器的作用2. 概率基础知识2.1 概率、概率密度2.2 联合概率密度2.3 条件概率密度2.4 贝叶斯公式2.5 贝叶斯推断2.6 高斯概率密度函数2.7 联合高斯概率密度函数2.8 高斯随机变量的线性分布1. 滤波器的作用 滤波器的本质&#…

编译与链接------《程序员的自我修养》

本篇整理于《程序员的自我修养》一书中编译与链接相关知识&#xff0c;整理的目的是为了更加深入的了解编译于链接的更多底层知识&#xff0c;面对程序运行时种种性能瓶颈我们束手无策。我们看到的是这些问题的现象,但是却很难看清本质&#xff0c;所有这些问题的本质就是软件运…

Allegro如何使用Vertext命令修改丝印线段的形状操作指导

Allegro如何使用Vertext命令修改丝印线段的形状操作指导 在用Allegro画丝印线段的时候,如果画了一段不是自己需要形状的线段,无需删除重画,可以用Vertext命令直接编辑 如下图 修改前 修改后 具体操作如下 选择Edit

Java笔记-JUC基础

1、什么是JUC JUC指的是java.util三个并发编程工具包 java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks 2、线程的几种状态 public enum State{NEW,//新建RUNNABLE,//准备就绪&#xff0c;等待资源BLOCKED,//阻塞WAITING,//一直等待TIMED_WAITI…