《SpringBoot项目实战》第五篇—接口发生异常如何统一处理

系列文章导航
第一篇—接口参数的一些弯弯绕绕
第二篇—接口用户上下文的设计与实现
第三篇—留下用户调用接口的痕迹
第四篇—接口的权限控制
第五篇—接口发生异常如何统一处理

本文参考项目源码地址:summo-springboot-interface-demo

前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。

BUG对于程序员来说实在是不陌生,当代码出现BUG时,异常也会随之出现,但BUG并不等于异常,BUG只是导致异常出现的一个原因。导致异常发生的原因非常多,本篇文章我也主要只讲一下接口相关的异常怎么处理。

一、接口异常的分类

在接口设计中,应该尽量避免使用异常来进行控制流程。接口应该尽可能返回明确的错误码和错误信息,而不是直接抛出异常。

1. 业务异常(Business Exception)

这是接口处理过程中可能出现的业务逻辑错误,例如参数校验失败、权限不足等。这些异常通常是预期的,并且可以提供相应的错误码和错误信息给调用方。

2. 系统异常(System Exception)

这是接口处理过程中可能出现的非预期错误,例如数据库异常、网络异常等。这些异常通常是未知的,并且可能导致接口无法正常响应。这种错误不仅需要记录异常信息通知系统管理员处理,还需要封装起来做好提示,不能直接把错误返回给用户。

3. 客户端异常(Client Exception)

这是调用方在使用接口时可能出现的错误,例如请求参数错误、请求超时等。这些异常通常是由于调用方的错误导致的,接口本身没有问题。可以根据具体情况选择是否返回错误信息给调用方。

二、接口异常的常见处理办法

1. 异常捕获和处理

在接口的实现代码中,可以使用try-catch语句捕获异常,并进行相应的处理。可以选择将异常转化为合适的错误码和错误信息,然后返回给调用方。或者根据具体情况选择是否记录异常日志,并通知系统管理员进行处理。

2. 统一异常处理器

可以使用统一的异常处理器来统一处理接口异常。在Spring Boot中,可以使用@ControllerAdvice和@ExceptionHandler注解来定义一个全局的异常处理器。这样可以将所有接口抛出的异常统一处理,例如转化为特定的错误码和错误信息,并返回给调用方。

3. 抛出自定义异常

可以根据业务需求定义一些自定义的异常类,继承RuntimeException或其他合适的异常类,并在接口中抛出这些异常。这样可以在异常发生时,直接抛出异常,由上层调用方进行捕获和处理。

4. 返回错误码和错误信息

可以在接口中定义一套错误码和错误信息的规范,当发生异常时,返回对应的错误码和错误信息给调用方。这样调用方可以根据错误码进行相应的处理,例如展示错误信息给用户或者进行相应的逻辑处理。
例如这样的弹窗提示

5. 跳转到指定错误页

比如遇到401、404、500等错误时,SpringBoot框架会返回自带的错误页,在这里我们其实可以自己重写一些更美观、更友好的错误提示页,最好还能引导用户回到正确的操作上来,例如这样

而不是下面这样

三、接口异常的统一处理

通过前面两段我们可以发现,造成异常的原因很多,出现异常的地方很多,异常的处理手段也很多。基于以上三多的情况,我们需要一个地方来统一接收异常、统一处理异常,上面提到SpringBoot的@ControllerAdvice注解作为一个全局的异常处理器来统一处理异常。但@ControllerAdvice注解不是万能的,它有一个问题:

对于@ControllerAdvice注解来说,它主要用于处理Controller层的异常情况,即在控制器方法中发生的异常。因为它是基于Spring MVC的控制器层的异常处理机制。
而Filter层是位于控制器之前的一层过滤器,它可以用于对请求进行预处理和后处理。当请求进入Filter时,还没有进入到Controller层,所以@ControllerAdvice注解无法直接处理Filter层中的异常。
所以对于Filter中的异常,我们需要单独处理。

1. @ControllerAdvice全局异常处理器的使用

(1)自定义业务异常

由于SpringBoot框架并没有定义业务相关的错误码,所以我们需要自定义业务错误码。该错误码可以根据业务复杂程度进行分类,每个错误码对应一个具体的异常情况。这样前后端统一处理异常时可以根据错误码进行具体的处理逻辑,提高异常处理的准确性和效率。同时,定义错误码还可以方便进行异常监控和日志记录,便于排查和修复问题。

a、定义常见的异常状态码

ResponseCodeEnum.java

package com.summo.demo.model.response;public enum ResponseCodeEnum {/*** 请求成功*/SUCCESS("0000", ErrorLevels.DEFAULT, ErrorTypes.SYSTEM, "请求成功"),/*** 登录相关异常*/LOGIN_USER_INFO_CHECK("LOGIN-0001", ErrorLevels.INFO, ErrorTypes.BIZ, "用户信息错误"),/*** 权限相关异常*/NO_PERMISSIONS("PERM-0001", ErrorLevels.INFO, ErrorTypes.BIZ, "用户无权限"),/*** 业务相关异常*/BIZ_CHECK_FAIL("BIZ-0001", ErrorLevels.INFO, ErrorTypes.BIZ, "业务检查异常"),BIZ_STATUS_ILLEGAL("BIZ-0002", ErrorLevels.INFO, ErrorTypes.BIZ, "业务状态非法"),BIZ_QUERY_EMPTY("BIZ-0003", ErrorLevels.INFO, ErrorTypes.BIZ, "查询信息为空"),/*** 系统出错*/SYSTEM_EXCEPTION("SYS-0001", ErrorLevels.ERROR, ErrorTypes.SYSTEM, "系统出错啦,请稍后重试"),;/*** 枚举编码*/private final String code;/*** 错误级别*/private final String errorLevel;/*** 错误类型*/private final String errorType;/*** 描述说明*/private final String description;ResponseCodeEnum(String code, String errorLevel, String errorType, String description) {this.code = code;this.errorLevel = errorLevel;this.errorType = errorType;this.description = description;}public String getCode() {return code;}public String getErrorLevel() {return errorLevel;}public String getErrorType() {return errorType;}public String getDescription() {return description;}public static ResponseCodeEnum getByCode(Integer code) {for (ResponseCodeEnum value : values()) {if (value.getCode().equals(code)) {return value;}}return SYSTEM_EXCEPTION;}}
b、自定义业务异常类

BizException.java

package com.summo.demo.exception.biz;import com.summo.demo.model.response.ResponseCodeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BizException extends RuntimeException {/*** 错误码*/private ResponseCodeEnum errorCode;/*** 自定义错误信息*/private String errorMsg;}

(2) 全局异常处理器

BizGlobalExceptionHandler

package com.summo.demo.exception.handler;import javax.servlet.http.HttpServletResponse;import com.summo.demo.exception.biz.BizException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;@RestControllerAdvice(basePackages = {"com.summo.demo.controller", "com.summo.demo.service"})
public class BizGlobalExceptionHandler {@ExceptionHandler(BizException.class)public ModelAndView handler(BizException ex, HttpServletResponse response) {ModelAndView modelAndView = new ModelAndView();switch (ex.getErrorCode()) {case LOGIN_USER_INFO_CHECK:// 重定向到登录页modelAndView.setViewName("redirect:/login");break;case NO_PERMISSIONS:// 设置错误信息和错误码modelAndView.addObject("errorMsg", ex.getErrorMsg());modelAndView.addObject("errorCode", ex.getErrorCode().getCode());modelAndView.setViewName("403");break;case BIZ_CHECK_FAIL:case BIZ_STATUS_ILLEGAL:case BIZ_QUERY_EMPTY:case SYSTEM_EXCEPTION:default:// 设置错误信息和错误码modelAndView.addObject("errorMsg", ex.getErrorMsg());modelAndView.addObject("errorCode", ex.getErrorCode().getCode());modelAndView.setViewName("error");}return modelAndView;}
}

(3) 测试效果

@RestControllerAdvice和@ExceptionHandler使用起来很简单,下面我们来测试一下(由于不写界面截图是在太丑,我麻烦ChatGPT帮我写了一套简单的界面)。

a、普通业务异常捕获
第一步、打开登录页

访问链接:http://localhost:8080/login
输入账号、密码,点击登录进入首页

第二步、登录进入首页

第三步、调用一个会报错的接口

再服务启动之前我写了一个根据用户名查询用户的方法,如果查询不到用户的话我会抛出一个异常,代码如下:

public ResponseEntity<String> query(String userName) {//根据名称查询用户List<UserDO> list = userRepository.list(new QueryWrapper<UserDO>().lambda().like(UserDO::getUserName, userName));if (CollectionUtils.isEmpty(list)) {throw new BizException(ResponseCodeEnum.BIZ_QUERY_EMPTY, "根据用户名称查询用户为空!");}//返回数据return ResponseEntity.ok(JSONObject.toJSONString(list));
}

这时,我们查询一个不存在的用户
访问接口:http://localhost:8080/user/query?userName=sss
因为数据库中没有用户名为sss的这个用户,会抛出一个异常

b、403权限不足异常捕获
第一步、打开登录页

访问链接:http://localhost:8080/login
登录界面使用小B的账号登录

第二步、登录进入首页

第三步、调用删除用户的接口

调用接口:http://localhost:8080/user/delete?userId=2
由于小B的账号只有查询权限,没有删除权限,所以返回403错误页

注意👉🏻:在调试之前需要在application.yml或application.properties配置文件中增加一个配置:server.error.whitelabel.enabled=false
这个配置的意思是是否启用默认的错误页面,这里我们自己写了一套错误页,所以不需要框架自带的配置了。

2. 自定义Filter中异常的处理

由于@ControllerAdvice注解无法捕获自定义Filter中抛出的异常,这里我们就需要使用另外一种方法进行处理:ErrorController接口。

(1) 原理解释

Spring Boot的ErrorController是一个接口,用于定义处理应用程序中发生的错误的自定义逻辑。它允许开发人员以更灵活的方式处理和响应异常,而不是依赖于默认的错误处理机制。:

  • 定制错误页面:通过实现ErrorController接口,可以自定义应用程序的错误页面,以提供更好的用户体验。可以根据不同的异常类型和HTTP状态码提供不同的错误页面或错误信息。
  • 记录错误日志:ErrorController可以用于捕获和记录应用程序中的异常,并将其记录到日志中。这对于问题追踪和排查非常有帮助,可以了解应用程序中发生的错误和异常的详细信息。
  • 重定向或转发请求:通过ErrorController,可以根据错误的类型或其他条件,将请求重定向到不同的URL或转发到其他控制器方法。这对于根据错误情况做出不同的处理非常有用,例如重定向到自定义的错误页面或执行特定的错误处理逻辑。

(2) 使用方法

使用方法直接看看我的代码就知道了。
CustomErrorController.java

package com.summo.demo.controller;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class CustomErrorController implements ErrorController {@RequestMapping("/error")public ModelAndView handleError(HttpServletRequest request, HttpServletResponse response) {//获取当前响应返回的状态码int statusCode = response.getStatus();//如果响应头中存在statusCode,则默认使用这个statusCodeif (StringUtils.isNotBlank(response.getHeader("statusCode"))) {statusCode = Integer.valueOf(response.getHeader("statusCode"));}if (statusCode == HttpServletResponse.SC_FOUND) {// 获取Location响应头的值,进行重定向String redirectLocation = response.getHeader("Location");return new ModelAndView("redirect:" + redirectLocation);} else if (statusCode == HttpServletResponse.SC_UNAUTHORIZED) {// 重定向到登录页return new ModelAndView("redirect:/login");} else if (statusCode == HttpServletResponse.SC_FORBIDDEN) {// 返回403页面return new ModelAndView("403");} else if (statusCode == HttpServletResponse.SC_NOT_FOUND) {// 返回404页面return new ModelAndView("404");} else if (statusCode == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {// 返回500页面,并传递errorMsg和errorCode到模板ModelAndView modelAndView = new ModelAndView("500");modelAndView.addObject("errorMsg", response.getHeader("errorMsg"));modelAndView.addObject("errorCode", response.getHeader("errorCode"));return modelAndView;} else {// 返回其他错误页面return new ModelAndView("error");}}}

细心的读者可能会看到,statusCode来自于两个地方,第一个是response.getStatus();第二个是response.getHeader(“statusCode”)。这两者的区别是第一个是框架自动设置的,第二个则是我根据业务逻辑设置的。
原因是在WebFilter中一旦抛出了异常,response.getStatus()一定会是500,即使这个异常是因为用户身份失效导致的。但异常又不得不抛出,所以我通过自定义response的header的方式设置了错误码,传递到/error接口。

(3) 测试效果

a、404错误页,接口找不到
第一步、打开登录页

访问链接:http://localhost:8080/login
输入账号、密码,点击登录进入首页

第二步、登录进入首页

第三步、访问一个不存在的页面

访问链接:http://localhost:8080/xxxx
由于xxxx接口没有被定义过,界面会返回404

b、401错误,用户身份标识为空或无效

这里我做的处理是,如果用户身份标识为空或无效那么我会默认跳转到登录页。
测试方法是打开一个无痕界面,随便输入一个链接:http://localhost:8080/user/query
由于Cookie中token不存在,所以我不管访问的是哪个链接,直接将状态码改为401,而CustomErrorController遇到401的错误,会默认重定向到登录页。

四、优化无痕窗口下的重新登录体验

Filter异常的全局处理除了ErrorController之外,还可以通过自定义拦截器的方式实现,这两个东西会一个就行了。这里我再说一个高级一点的东西,举个例子:
我在一个无痕窗口调用接口:http://localhost:8080/user/query?userName=小B
因为当前窗口的Cookie中是没有token的,按照401错误的处理方式,我会重定向到登录页去。
但这个有一个问题:重新登录之后,进入的是首页,不是调用user/query接口,我还得重新去找这个接口,重新输入参数。而且这要是一个分享页那就尴尬了,登陆完不知道对方分享了啥,用户体验会很差,那么有办法优化这个问题吗?答案是有,如何做,继续看。

1. 在WebFilter中获取当前请求的全路径

所谓全路径就是“http://localhost:8080/user/query?userName=小B” ,如何获取,可以用我这个方法

/*** 获取完整的路径URL,包括参数** @param httpServletRequest* @return 路径URL
*/
private String getRequestURL(HttpServletRequest httpServletRequest) {String url = httpServletRequest.getRequestURL().toString();String query = httpServletRequest.getQueryString();if (query != null) {url += "?" + query;}return url;
}

2. 在WebFilter抛出401错误的地方设置httpServletResponse的header

如下

httpServletResponse.setHeader("redirectURL",URLEncoder.encode(getRequestURL(httpServletRequest), "utf-8"));

因为参数有可能是中文,这里需要用URLEncoder转下义。

3. 在CustomErrorController中获取到这个跳转链接

// 重定向到登录页或指定页面if (StringUtils.isNotBlank(response.getHeader("redirectURL"))) {return new ModelAndView("redirect:/login?redirectURL=" + response.getHeader("redirectURL"));}

效果如下

可以看到我们在login后面携带了一个redirectURL参数

4. 登录提交时将redirectURL参数一并提交

 @PostMapping("/login")
public void userLogin(@RequestParam(required = true) String userName,@RequestParam(required = true) String password,@RequestParam(required = false) String redirectURL,HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) {userService.login(userName, password, redirectURL, httpServletRequest, httpServletResponse);
}

5. 验证通过后重定向到redirectURL

 try {//如果跳转路径不为空,则直接重定向到跳转路径if (StringUtils.isNotBlank(redirectURL)) {httpServletResponse.sendRedirect(redirectURL);return;}//跳转到登录页httpServletResponse.sendRedirect("/index");} catch (IOException e) {log.error("重定向发生异常", e);
}

以上就是这个问题的解决方案了,具体代码大家可以看我的demo:summo-springboot-interface-demo

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

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

相关文章

漏洞复现-jquery-picture-cut 任意文件上传_(CVE-2018-9208)

jquery-picture-cut 任意文件上传_&#xff08;CVE-2018-9208&#xff09; 漏洞信息 jQuery Picture Cut v1.1以下版本中存在安全漏洞CVE-2018-9208文件上传漏洞 描述 ​ picture cut是一个jquery插件&#xff0c;以友好和简单的方式处理图像&#xff0c;具有基于bootstrap…

V3Det大规模词汇视觉检测数据集与LaRS海上全景障碍物检测数据集

V3Det与LaRS是ICCV2023上发表的数据集工作&#xff0c;规模都比较大&#xff0c;后续有可能会用到&#xff0c;因此记录下来。 V3Det: Vast Vocabulary Visual Detection Dataset Paper: https://arxiv.org/abs/2304.03752 URL: https://v3det.openxlab.org.cn/ 在现实世界中…

出租屋智能视频监控系统方案:全面保卫租客安全

除了我们常见的家庭、社区、园区等智能监控&#xff0c;出租房作为很多人的暂住所也极易发生盗窃等事件&#xff0c;为保障大众租户的财产安全&#xff0c;旭帆科技特地针对出租屋制定了智能监控系统方案。 1、安装智能安防摄像头 高清晰度、夜视功能良好的智能摄像头&#xf…

Java数据类型,变量与运算符

1.字面常量 常量是在程序运行期间&#xff0c;固定不变的量称为常量。 public class HelloWorld{public static void main(String[] args){System.out.println("Hello,world");} } 在以上程序中&#xff0c;输出的Hello Word&#xff0c;其中的“Hello Word”就是…

工业自动化产品抗干扰笔记

工业自动化产品抗干扰笔记 名词解释&#xff1a; 耦合矢量控制寄生振荡热噪点闪变噪点尖峰噪点反电势集肤效应交流电阻感抗容抗寄生电容共模与差模电场和磁场漏电流浪涌电压电流传输与电压传输各种地线解析 一、干扰来源&#xff1a; 任何一个电磁干扰现象都具有电磁干扰源…

前端开发技术栈(工具篇):2023深入了解webpack的安装和使用以及核心概念和启动流程(详细) 63.3k stars

目录 Webpack简介 Entry Module Chunk Loader Plugin Output Webpack的启动流程 Webpack的优缺点 Webpack的使用 1. 安装Webpack 2. 创建Webpack配置文件 3. 编写代码 4. 运行Webpack 5. 在HTML中引入打包后的文件 6. 执行编译命令 Webpack其他功能介绍 1. 使…

单片机为什么一直用C语言,不用其他编程语言?

单片机为什么一直用C语言&#xff0c;不用其他编程语言&#xff1f; 51 单片机规模小得拮据&#xff0c;C 的优势几乎看不到。放个类型信息进去都费劲&#xff0c;你还想用虚函数&#xff1f;还想模板展开&#xff1f;程序轻松破 10k。最近很多小伙伴找我&#xff0c;说想要一些…

大促期间的窜货低价怎么处理

产品窜货往往会伴随低价&#xff0c;所以监测窜货就是要先监测低价&#xff0c;当然有些产品卖价不低价也窜货&#xff0c;但这些正价或者高价卖的链接对渠道影响并不太大&#xff0c;所以治理低价窜货更重要。 力维网络专业为品牌提供窜货治理&#xff0c;低价监测的需求&…

面试必考精华版Leetcode215. 数组中的第K个最大元素

题目&#xff1a; 代码&#xff08;2023年10月27日首刷看解析&#xff09;&#xff1a; class Solution { public:int findKthLargest(vector<int>& nums, int k) {return quickselect(nums,k);}int quickselect(vector<int>& nums,int k){vector<int&…

[UDS] --- WriteDataByIdentifier 0x2E

1 0x2E功能描述 根据ISO14119-1标准中所述&#xff0c;诊断服务2E主要用于Client向Server(ECU)通过DID的方式写入相关的数据。 2 0x2E应用场景 一般而言&#xff0c;对于2E诊断服务&#xff0c;主要应用场景为以下场合&#xff1a; 在整车下线的过程中写入相关配置信息&…

【速看】如何通过合理的封装,让你的自动化脚本更上一层楼!

1. 前言 上一篇推文利用一个在图片范围内实现随机坐标点击的例子&#xff0c;去教会大家如何将自己想要的效果实现出来&#xff0c;受到大家的热情反响&#xff0c;在我们官方讨论群中&#xff0c;还有大佬对我们的示例代码进行优化改进&#xff0c;做了很多合理的函数封装&…

蓝桥杯每日一题2023.10.26

测试次数 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 对于本题我们可以使用动态规划来分析 dp[i][j]代表剩余i层&#xff0c;j台手机的情况下对应的测试数量 分类进行讨论一下&#xff1a; 1.当只剩下一台手机的时候&#xff0c;只能从第一层一层一层往上尝试&#xff0c…

时序预测 | Python实现ARIMA-LSTM自回归移动差分模型结合长短期记忆神经网络时间序列预测

时序预测 | Python实现ARIMA-LSTM自回归移动差分模型结合长短期记忆神经网络时间序列预测 目录 时序预测 | Python实现ARIMA-LSTM自回归移动差分模型结合长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Python实现ARIMA-LSTM自…

颈肩肌筋膜炎做什么检查

颈肩肌筋膜炎症状 颈肩背部广泛疼痛酸胀沉重感、麻木感&#xff0c;僵硬、活动受限&#xff0c;可向后头部及上臂放散。疼痛呈持续性&#xff0c;可因感染、疲劳、受凉、受潮等因素而加重。查体见颈部肌紧张&#xff0c;压痛点常在棘突及棘突旁斜方肌、菱形肌等&#xff0c;压…

基于卷积神经网络的乳腺癌分类 深度学习 医学图像 计算机竞赛

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

死锁是什么?如何避免?如何排查?为什么这样排查 详细总结

1.死锁是什么 多个线程访问资源 线程加锁不当 会造成死锁。导致所有线程被阻塞&#xff0c;且无法解开 2.死锁的产生原因 1.加锁后忘记解锁 2.重复加锁&#xff0c;造成死锁 3.B锁内部调用函数A &#xff0c;A运行是又加锁 导致A,B均无法运行 3.如何避免死锁 多检查…

C++中invoke与function的区别

C invoke invoke是C17标准引入的一个函数模板&#xff0c;用来调用可调用对象&#xff08;Callable Object&#xff0c;如函数指针、函数对象、成员函数指针等&#xff09;并返回结果。 invoke提供了统一的调用语法&#xff0c;无论可调用对象的类型是什么&#xff0c;都可以…

一文讲明白阿里云ace认证的含金量!

《中国云计算行业洞察与人才分析》显示&#xff0c;云计算领域人才月均薪酬在1W元以上的占比高达93.7%&#xff0c;3万元以上占比仍达24.7%。 相比之下&#xff0c;云计算人才远超互联网人才薪酬平均线&#xff0c;反映出市场对于其专业技术人才的需求。 云计算架构师、云计算…

2016年亚太杯APMCM数学建模大赛B题化学元素对变形钢筋性能的影响求解全过程文档及程序

2016年亚太杯APMCM数学建模大赛 B题 化学元素对变形钢筋性能的影响 原题再现 热轧带肋钢筋通常被称为变形钢筋&#xff0c;它主要用于钢筋混凝土构件的骨架&#xff0c;在使用中需要一定的机械强度、弯曲和变形性能、制造焊接性。钢中的化学成分是影响热轧钢最终组织性能的基…

Go 命令大全:全面解析与实践

一、Go命令全列表 在这部分&#xff0c;我们将通过一个表格来快速浏览Go语言的所有内建命令及其基本功能。这些命令涵盖了从代码构建、测试&#xff0c;到依赖管理和其他工具等方面。 命令功能描述go build编译Go源文件go run编译并运行Go程序go get下载并安装依赖或项目go m…