Spring Boot 统一功能处理

目录

1.用户登录权限效验

1.1 Spring AOP 用户统一登录验证的问题

1.2 Spring 拦截器

1.2.1 自定义拦截器

1.2.2 将自定义拦截器加入到系统配置

1.3 拦截器实现原理

1.3.1 实现原理源码分析

2. 统一异常处理

2.1 创建一个异常处理类

2.2 创建异常检测的类和处理业务方法

3. 统一数据返回格式

3.1 统一数据返回的实现

3.2 返回String报错问题

问题解决


1.用户登录权限效验

1.1 Spring AOP 用户统一登录验证的问题

说到统一的用户登录验证,我们想到的第一个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:

@Aspect
@Component
public class UserAspect {// 定义切点方法 controller 包下、子孙包下所有类的所有方法@Pointcut("execution(* com.example.demo.controller..*.*(..))")public void pointcut() {}// 前置方法@Before("pointcut()")public void doBefore() {}// 环绕方法@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) {System.out.println("Around 方法开始执行");Object obj = joinPoint.proceed();System.out.println("Around 方法结束执行");return obj;}
}

如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:

  1. 没办法获取到 HttpSession 对象。
  2. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。

那这样如何解决呢?

1.2 Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器: Handlerinterceptor,拦截器的实现分为以下两个步骤:

  1. 创建自定义拦截器,实现 Handlerlnterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
  2. 将自定义拦截器配置到系统配置项, 并且设置合理的拦截规则, 也就是将自定义拦截器加入 WebMvcConfigureraddlnterceptors 方法中。具体实现如下.
Spring拦截器能够拿到参数并方便设置拦截规则, 也不需要AspectJ表达式.

1.2.1 自定义拦截器

新建一个普通的Spring Boot项目.

接下来使用代码来实现一个用户登录的权限效验,自定义拦截器是一个普通类,具体实现代码如下:

@Component
public class LoginInterceptor implements HandlerInterceptor {// 调用目标方法之前执行的方法// 此方法返回 boolean 类型的值,//      如果返回 true , 表示(拦截器)验证成功, 继续走后续的流程, 执行目标方法;//      如果返回 false , 这表示拦截器执行失败, 验证未通过, 后续的流程和目标方法不要执行了.@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户登录判断业务HttpSession session = request.getSession(false);if (session != null && session.getAttribute("session_userinfo") != null) {// 用户已经登录return true;}
//        response.sendRedirect("http://www.baidu.com");
//        response.setStatus(401);return false;}
}

1.2.2 将自定义拦截器加入到系统配置

要实现接口WebMvcConfigurer, 它里面有大量的方法, 其中addInterceptors方法, 需要我们实现.

也就是说当我们实现WebMvcConfigurer这个类, 那么这个类里面它内置了一个API, 那么我们去重写这个API就可以实现将我们自定义的拦截器写在项目当中.

可以看到, 这个是将registry注册器交给框架, 那么我们在重写的时候就拿这个registry去设置相应的规则即可.

具体实现代码如下:

@Configuration
public class MyConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor)   // 自定义的拦截器添加到系统配置项中.addPathPatterns("/**")     // 拦截所有URL.excludePathPatterns("/user/login")     // 排除 url /user/login 不拦截.excludePathPatterns("/user/reg").excludePathPatterns("/image/**")    // 排除 image 文件夹下的所有文件;}
}

其中:

  • addPathPatterns: 表示需要拦截的 URL,“**”表示拦截任意方法 (多级的全部方法), "*"则表示一级目录的所有.
  • excludePathPatterns: 表示需要排除的 URL。

说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件 (图片文件、JS 和 CSS 等文件)排除所有的静态资源


我们来看下代码是否能够实现拦截的目标.

@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public String login() {return "login";}@RequestMapping("/index")public String index() {return "index";}@RequestMapping("/reg")public String reg() {return "reg";}
}

启动项目后访问验证一下我们这个拦截器:

访问reg, reg是被排除故可访问.

访问index, 被拦截, 无响应信息.

通过F12可以看到index被拦截器拦截了, 这个目标方法没有被调用, 所以没有响应信息.

经过自定义拦截器中的设置可以看到响应:


如果拦截器执行失败了false, 那么后面的代码也不会走, 这个时候当我们返回false的时候, 前端人员如何拿到相关信息以知道是拦截器出错还是代码出错, 还是其他的问题出错?

LoginInterceptor中的return false前添加相关代码, 那么我们使用Servlet的方式打印给前端就可以解决.:

response.setContentType("application/json;charset=utf8");
response.getWriter().println("{\"code\":-1,\"msg\":\"登录失败\",\"data\":\"\"}");

启动项目, 访问index, 可以看到通过response打印了相关错误信息.


1.3 拦截器实现原理

对于一个标准的后端程序来说, 正常情况下的调用顺序:

用户访问后端程序, 那么访问的时候无论是用户还是前端程序员, 都是会把请求发送给控制器, 控制器进行参数的校验, 如果校验没问题之后会把请求发送给服务层(也就是调用服务层), 然后服务层再去决定要调用几个Mapper, 然后Mapper会去调用数据库, 数据库会把结果返回给Mapper, 然后按着 来时的路 回去给前端用户.

然而有了拦截器之后,会在调用 Controller 之前先进行相应的业务处理,执行的流程如下图所示:

1.3.1 实现原理源码分析

所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现,这一点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

而所有方法都会执行 DispatcherServlet 中的 doDispatch调度方法。doDispatch源码如下:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Object dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}// 调用预处理(重点) [执行我们拦截器的代码; 拦截器方法为false就直接返回否则调用Controller]if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 执行 Controller 中的业务 [执行我们自己方法的代码, 过了拦截器之后的方法]mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}

从上述源码可以看出在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle,而applyPreHandle 方法的实现源码如下:

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {// 获取项⽬中使⽤的拦截器 HandlerInterceptorHandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;}

从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 Handlerlinterceptor 并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:

2. 统一异常处理

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知也就是执行某个方法事件

2.1 创建一个异常处理类

@ControllerAdvice
public class MyExceptionAdvice {
}
@ControllerAdvice是针对于 controller的通知, 是针对于controller的增强方法.
当加了这个注解之后, 它会去监测控制器的异常, 如果控制器发生异常了, 那么底下的类就能感知的到, 感知到之后就能根据写的业务代码将相应的代码返回给前端.

2.2 创建异常检测的类和处理业务方法

拦截方法可以针对不同的拦截去写相应的处理代码.
@ControllerAdvice
@ResponseBody    // 加在类上表示类中所有方法都可以返回一个 JSON 的数据
public class MyExceptionAdvice {// 处理空指针异常// 如果出现了异常就返回给前端一个 HashMap 的对象@ExceptionHandler(NullPointerException.class)public HashMap<String, Object> doNullPointerException(NullPointerException e) {HashMap<String, Object> result = new HashMap<>();result.put("code", -1);result.put("msg", "空指针: " + e.getMessage());result.put("data", null);return result;}// 默认的异常处理(当具体的异常匹配不到时, 会执行此方法)@ExceptionHandler(Exception.class)public HashMap<String, Object> doException(Exception e) {HashMap<String, Object> result = new HashMap<>();result.put("code", -300);result.put("msg", "Exception: " + e.getMessage());result.put("data", null);return result;}
}
PS: 方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public int login() {Object obj = null;System.out.println(obj.hashCode());return 1;}
}

启动以访问:

可以看到, 前端没有报错, 说明通知到了前端.

改为:

    @RequestMapping("/login")public int login() {
//        Object obj = null;
//        System.out.println(obj.hashCode());int num = 10 / 0;return 1;}

再次访问user/login,

说明上面的Advice只是处理了空指针异常.

解决: 在MyExceptionAdvice中加入默认异常处理:

    // 默认的异常处理(当具体的异常匹配不到时, 会执行此方法)@ExceptionHandler(Exception.class)public HashMap<String, Object> doException(Exception e) {HashMap<String, Object> result = new HashMap<>();result.put("code", -300);result.put("msg", "Exception: " + e.getMessage());result.put("data", null);return result;}

再次访问user/login之后, 可以看到返回了算数异常. 所以说明当子类没有找到相应的异常处理之后, 就会找父类的Exception.

异常较多的时候, 交给前端后, 前端如何处理?

如果异常会走到 MyExceptionAdvice,就说明这个异常是我们后端程序员不可知的, 所以这个异常检测类是用于意外异常的拦截. 因为正常的业务异常在业务代码中会直接报出, 而意外的异常里面, 重要的是以正常的格式返回给前端的状态码, 至于具体的内容是什么, 大概率前端用不到.

3. 统一数据返回格式

统一数据返回格式的优点有很多,比如以下几个:
1. 方便前端程序员更好的接收和解析后端数据接口返回的数据
2. 降低前端程序员和后端程序员的沟通成本,任何时候返回的都是状态, 状态描述符, 数据, 按照某个格式实现就行了,因为所有接口都是这样返回的。
3. 有利于项目统一数据的维护和修改
4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容

3.1 统一数据返回的实现

统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,当写下面这些代码的时候, 就相当于是在返回之前做了一个拦截操作, 所有的返回之前都会走这里面的两个重写的方法(即在返回之前进行数据重写), 具体实现代码如下:

@ControllerAdvice   // 第一步
public class ResponseAdvice implements ResponseBodyAdvice {     // 第二步, 实现ResponseBodyAdvice接口并重写supports()与beforeBodyWrite()// 是否执行 beforeBodyWrite 方法, true=执行, 重写返回结果@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}// 返回数据之前进行数据重写, body是业务代码的返回结果, 即原始返回值@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String){// 返回一个 将对象转换成 JSON String 字符串//            objectMapper.writeValueAsString(request);return "{\"code\":200,\"msg\":\"\",\"data\":\"" + body +"\"}";}// 假定标准的返回值为 HashMap<String,Object>// 相关属性为 code,msg,data// 判断返回类型是否符合假定的标准返回值if (body instanceof HashMap) {return body;    // 符合假定的标准返回值则直接返回body}// 不符合假定的标准返回值则 重写返回结果, 让其返回一个统一的数据格式HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("data", body);result.put("msg", "");return result;}
}
    // 返回 int 类型    @RequestMapping("/login")public int login() {return 1;}// 返回假定的标准数据格式@RequestMapping("/reg")public HashMap<String, Object> reg() {HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("data", 1);result.put("msg", "");return result;}

可以看到, 返回的都是标准的数据格式.


3.2 返回String报错问题

但是, 统一异常处理在遇到String的时候返回会报错.

    @RequestMapping("/sayHi")public String sayHi() {return "say hi";}

异常日志:

2023-08-19 17:17:19.398 WARN 9316 --- [nio-8080-exec-8] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String]
注: 这里是由于之前设置了拦截器所以报了异常状态码是-300, 如果没有拦截器会直接报错500, 报错如下图.

没有拦截器的500报错图:

在异常状态码为-300的日志中可以看到, 显示了HashMap不能转换为String.

那么为什么我们明明在重写beforeBodyWrite()的时候已经是将String转换成HashMap了, 为什么又报错HashMap不能转换为String?

首先, String转换的执行流程是分为以下三步:

  1. 方法返回了String
  2. 统一数据返回之前的处理是: 将String转换成HashMap.
  3. 最终将HashMap转换成application/json字符串返回给前端(接口).

所以HashMap转换成String出错是在第三步发生的, 也就是异常日志的内容.

那么出错的原因就是在第三步时程序会去判断原body的类型是什么, 根据body的类型来选择相应的消息转换器进行转换. 也就是下面两种情况:

  • 如果是String, 那么它就会使用一个叫做StringHttpMessageConverter的转换器进行类型的转换.
  • 如果不是String, 那么它就会使用HttpMessageConverter的转换器进行类型转换.

正是因为上面选择转换器进行类型转换的动作, 所以就会触发bug, 在判断的时候是使用原类型进行判断的, 但是在转换的时候是拿HashMap进行转换的, 这个时候使用StringHttpMessageConverter去转换HashMap的时候这个转换器试图将HashMap转换成String JSON字符串, 但会发现无法转换, 此时就会直接报错. (可以认为这是Spring MVC在设计上的问题)

问题解决

  1. 将StringHttpMessageConverter这个转换器从项目中去掉.

我们可以通过修改当前项目的配置文件, 然后把StringHttpMessageConverter去掉, 这个时候就只能使用HttpMessageConverter来进行转换, 这个时候它就不会出错.

新建MyConfig类, 进行相关配置.

// 移除StringHttpMessageConverter
@Configuration  // 第一步, 加入 Spring 中
public class MyConfig implements WebMvcConfigurer {     // 实现 WebMvcConfigurer, 这样当前类才是一个系统配置项@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);}
}

  1. 在统一数据返回格式代码重写时, 单独处理String类型, 让其返回一个String字符串,而非HashMap.
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;// ..此处省略 supports重写的代码, 节省篇幅, 具体代码同前文@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// ..此处省略 body类型判断 以及 重写HashMap 代码, 见前文if (body instanceof String){// 返回一个 将对象转换成 JSON String 字符串return objectMapper.writeValueAsString(result);
//            return "{\"code\":200,\"msg\":\"\",\"data\":\"" + body +"\"}";}return result;}
}

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

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

相关文章

【Spring系列篇--关于IOC的详解】

目录 面试经典题目&#xff1a; 1. 什么是spring&#xff1f;你对Spring的理解&#xff1f;简单来说&#xff0c;Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 2.什么是IoC&#xff1f;你对IoC的理解&#xff1f;IoC的重要性?将实例化对象的权利从程序员…

Centos 8 网卡connect: Network is unreachable错误解决办法

现象1、ifconfig没有ens160配置 [testlocalhost ~]$ ifconfig lo: flags73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (Local Loopba…

基于深度学习的指针式仪表倾斜校正方法——论文解读

中文论文题目:基于深度学习的指针式仪表倾斜校正方法 英文论文题目&#xff1a;Tilt Correction Method of Pointer Meter Based on Deep Learning 周登科、杨颖、朱杰、王库.基于深度学习的指针式仪表倾斜校正方法[J].计算机辅助设计与图形学学报, 2020, 32(12):9.DOI:10.3724…

【Java】智慧工地SaaS平台源码:AI/云计算/物联网/智慧监管

智慧工地是指运用信息化手段&#xff0c;围绕施工过程管理&#xff0c;建立互联协同、智能生产、科学管理的施工项目信息化生态圈&#xff0c;并将此数据在虚拟现实环境下与物联网采集到的工程信息进行数据挖掘分析&#xff0c;提供过程趋势预测及专家预案&#xff0c;实现工程…

《强化学习:原理与Python实战》——可曾听闻RLHF

前言&#xff1a; RLHF&#xff08;Reinforcement Learning with Human Feedback&#xff0c;人类反馈强化学习&#xff09;是一种基于强化学习的算法&#xff0c;通过结合人类专家的知识和经验来优化智能体的学习效果。它不仅考虑智能体的行为奖励&#xff0c;还融合了人类专家…

kafka安装说明以及在项目中使用

一、window 安装 1.1、下载安装包 下载kafka 地址&#xff0c;其中官方版内置zk&#xff0c; kafka_2.12-3.4.0.tgz其中这个名称的意思是 kafka3.4.0 版本 &#xff0c;所用语言 scala 版本为 2.12 1.2、安装配置 1、解压刚刚下载的配置文件&#xff0c;解压后如下&#x…

【机器学习】处理不平衡的数据集

一、介绍 假设您在一家给定的公司工作&#xff0c;并要求您创建一个模型&#xff0c;该模型根据您可以使用的各种测量来预测产品是否有缺陷。您决定使用自己喜欢的分类器&#xff0c;根据数据对其进行训练&#xff0c;瞧&#xff1a;您将获得96.2%的准确率&#xff01; …

Integer中缓存池讲解

文章目录 一、简介二、实现原理三、修改缓存范围 一、简介 Integer缓存池是一种优化技术&#xff0c;用于提高整数对象的重用和性能。在Java中&#xff0c;对于整数值在 -128 到 127 之间的整数对象&#xff0c;会被放入缓存池中&#xff0c;以便重复使用。这是因为在这个范围…

Python绘制爱心代码(七夕限定版)

写在前面&#xff1a; 又到了一年一度的七夕节啦&#xff01;你还在发愁送女朋友什么礼物&#xff0c;不知道怎样表达你满满的爱意吗&#xff1f;别担心&#xff0c;我来帮你&#xff01;今天&#xff0c;我将教你使用Python绘制一个跳动的爱心&#xff0c;用创意和幽默为这个…

Angular安全专辑之二——‘unsafe-eval’不是以下内容安全策略中允许的脚本源

一&#xff1a;错误出现 这个错误的意思是&#xff0c;拒绝将字符串评估为 JavaScript&#xff0c;因为‘unsafe-eval’不是以下内容安全策略中允许的脚本源。 二&#xff1a;错误场景 testEval() {const data eval("var sum2 new Function(a, b, return a b); sum2(em…

JavaWeb_LeadNews_Day6-Kafka

JavaWeb_LeadNews_Day6-Kafka Kafka概述安装配置kafka入门kafka高可用方案kafka详解生产者同步异步发送消息生产者参数配置消费者同步异步提交偏移量 SpringBoot集成kafka 自媒体文章上下架实现思路具体实现 来源Gitee Kafka 概述 对比 选择 介绍 producer: 发布消息的对象称…

寻路算法小游戏

寻路算法小demo 寻路算法有两种&#xff0c;一种是dfs 深度优先算法&#xff0c;一种是 dfs 深度优先算法 深度优先搜索的步骤分为 1.递归下去 2.回溯上来。顾名思义&#xff0c;深度优先&#xff0c;则是以深度为准则&#xff0c;先一条路走到底&#xff0c;直到达到目标。这…

矩形重叠问题

矩形重叠 文章目录 题目描述解题思路方法一方法二 题目描述 矩形以列表 [x1, y1, x2, y2] 的形式表示&#xff0c;其中 (x1, y1) 为左下角的坐标&#xff0c;(x2, y2) 是右上角的坐标。矩形的上下边平行于 x 轴&#xff0c;左右边平行于 y 轴。 如果相交的面积为 正 &#xff0…

linux tomcat server.xml 项目访问路径变更不生效

如果想改成默认的127.0.0.1:8080 访问项目 先确定更改的作用文件 server.xml 的 host:appBase 标签 默认找到appBase webapps 下的war包&#xff0c;并解压&#xff0c;解压后的appname为访问路径 也就变成了 127.0.0.1:8080/appname host:Context:path 标签 appBase的 优先…

深入探索:Kali Linux 网络安全之旅

目录 前言 访问官方网站 导航到下载页面 启动后界面操作 前言 "Kali" 可能指的是 Kali Linux&#xff0c;它是一种基于 Debian 的 Linux 发行版&#xff0c;专门用于渗透测试、网络安全评估、数字取证和相关的安全任务。Kali Linux 旨在提供一系列用于测试网络和…

菜鸟Vue教程 - 实现带国际化的注册登陆页面

初接触vue的时候觉得vue好难&#xff0c;因为项目中要用到&#xff0c;就硬着头皮上&#xff0c;慢慢的发现也不难&#xff0c;无外乎画个布局&#xff0c;然后通过样式调整界面。在通过属性和方法跟js交互。js就和我们写的java代码差不多了&#xff0c;复杂一点的就是引用这种…

Python数据分析实战-多线程并发处理列表(附源码和实现效果)

实现功能 Python数据分析实战-多线程并发处理列表 实现代码 import threading有15个列表&#xff0c;尝试多进程并发处理&#xff0c;每个列表一个进程&#xff0c;进程数和 CPU 核数一致def sum_list(lst):return sum(lst)if __name__ __main__:lists [[1,2,3], [4,5,6], …

BDA初级分析——SQL清洗和整理数据

一、数据处理 数据处理之类型转换 字符格式与数值格式存储的数据&#xff0c;同样是进行大小排序&#xff0c; 会有什么区别&#xff1f; 以rev为例&#xff0c;看看字符格式与数值格式存储时&#xff0c;排序会有什么区别&#xff1f; 用cast as转换为字符后进行排序 SEL…

mysql+jdbc+servlet+java实现的学生在校疫情信息打卡系统

摘 要 I Abstract II 主 要 符 号 表 i 1 绪论 1 1.1 研究背景 1 1.2 研究目的与意义 2 1.3 国内外的研究情况 2 1.4 研究内容 2 2 系统的开发方法和关键技术 4 2.1 开发方法 4 2.1.1 结构化开发方法 4 2.1.2 面向对象方法 4 2.2 开发技术 4 2.2.1 小程序开发MINA框架 4 2.2.2 …

快速搭建图书商城小程序的简易流程与优势

很多人喜欢阅读电子书&#xff0c;又有很多人依旧喜欢实体书&#xff0c;而实体书店拥有一个图书商城小程序便成为了满足用户需求的理想选择。如果您也想进入这一充满潜力的领域&#xff0c;但担心开发难度和复杂流程&#xff0c;别担心&#xff01;您能做到快速搭建一个专业、…