JAVA WEB案例-登录校验-日志记录

news2025/1/25 9:06:27

一 前言

在现代社会中,随着互联网的快速发展,WEB应用的安全性问题变得越来越突出。作为一名程序员,我们不仅要注重WEB应用的功能实现,还需要重视安全性问题。在实际开发中,登录校验是非常重要的安全措施,能够有效地保护用户数据和系统信息免受攻击。本文将从薛慕昭的角度出发,针对WEB案例中的登录校验进行探讨,帮助jym更好地理解和实践这个关键安全功能。

二 登录校验

1 理论

* 我们的系统目前在不经过登录的情况下,直接输入员工页面地址就可以访问,这是非常不安全的。
* 正确的流程应该是:当访问请求到达服务器后,服务器要校验当前用户是否已经登录过
	如果登录过,就放行请求
	如果未登录过,就禁止请求访问
	
* 那如何知道用户是否已经登录过呢?这就需要在用户登录成功后,由服务器为其颁发一个token(身份标识)
	然后后面用户每次发送请求,都会携带着这个token
	而作为系统会对每次的请求进行拦截,校验token的合法性即可

image-20230225161056449.png

2 JWT

介绍

全称:JSON Web Token (https://jwt.io/),用于对应用程序上的用户进行身份标记

本质上就是一个经过加密处理与校验处理的字符串,它由三部分组成:

  • 头信息(Header):记录令牌类型和签名算法,例如:{“alg”: “HS256”,“typ”: “JWT”}
  • 有效载荷(Payload):记录一些自定义能够区分身份的非敏感信息,例如:{“id”: “1”,“username”: “tom”}
  • 签名(Signature):用于保证Token在传输过程中不被篡改,它是header、payload,加入指定算法计算得来的

image-20230225162325593.png

使用流程

image-20201216160302413.png

代码测试

① 在pom.xml中引入依赖

<!--Token生成与解析-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

② 生成并校验token

package com.itheima.test;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtTest {

    //生成token
    @Test
    public void genJwt() {
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", 1);
        claims.put("username", "Tom");

        String jwt = Jwts.builder().
                setClaims(claims) //自定义内容(载荷)
                .signWith(SignatureAlgorithm.HS256, "itheima") //签名算法和盐
                .setExpiration(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)) //有效期
                .compact();
        System.out.println(jwt);
    }

    //校验token
    @Test
    public void checkJwt() {
        Claims claims = Jwts.parser()
                .setSigningKey("itheima")//盐
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjc3MzU3MjE0LCJ1c2VybmFtZSI6IlRvbSJ9.RBtRZGHUefLElDWWIlQRoy0_Dl71sZysPP61vVa46oo")//上一步得到的值
                .getBody();
        System.out.println(claims);
    }
}

功能改进

修改目前的登录功能,当登录成功后,创建token并返回给客户端

image-20230415204710070.png

image-20230411131454729.png

3 过滤器入门

介绍

当用户访问服务器资源时,过滤器将请求拦截下来,完成一些通用的功能,比如:登录校验、统一编码处理、敏感字符处理等

image-20230225181741135.png

入门案例

实现一个服务器资源访问,然后使用过滤器拦截住请求,打印下日志。

1. 定义Filter:定义一个类实现 Filter 接口,并重写其所有方法。
2. 配置Filter:Filter类上加 @WebFilter 注解,配置拦截资源的路径。
3. 引导类上加 @ServletComponentScan 开启Servlet组件支持

① 导入提料中提供的测试项目

image-20230411132503256.png
② 创建LogFilter类

image-20230411133314659.png

③ 开启Filter支持

image-20230411133144780.png

执行流程

一个Filter的访问流程

1. 客户端向服务器发起访问资源的请求
2. Filter将请求拦截住,开始处理访问资源之前的逻辑
3. Filter决定是否要放行访问请求,如果放行,请求继续向后运行
4. 请求访问到相关资源,然后服务器给出响应
5. Filter将响应拦截住,开始处理访问资源之后的逻辑
6. 服务器将响应返回给浏览器

image-20201027004219184.png

拦截路径

Filter的拦截路径支持下面三种匹配方式

1. 精确匹配:直接匹配到某个资源上,例如 `/a`    `/a/b`

2. 路径匹配:匹配某个目录,要求以/开头,以`*`结尾,例如 `/a/*`   `/*`

3. 后缀匹配:根据后缀匹配,要求以`*.`开头,例如 `*.html   *.do`

过滤器链

程序有时需要对同一个资源进行多重过滤,这就可以配置多个过滤器,称为过滤器链。

只有过滤器链中的所有的过滤器都对请求放行,请求才能访问到目标资源。

过滤器的执行顺序是按照过滤器类名(字符串)的自然排序

image-20230415211630693.png

image-20230411154522238.png

==先进后出,有头有尾==

4 过滤器实现访问校验

下面使用过滤器是请求的访问校验,在实现之前先考虑两个问题

  1. 所有的请求,拦截到了之后,都需要校验令牌吗?
  2. 拦截到请求后,在满足什么条件下才可以放行?

思路分析

image-20230415211751646.png

image-20230226172138627.png

代码实现

① 创建Filter

创建com.itheima.filter.LoginCheckFilter编写过滤逻辑

image-20230411140028093.png

package com.itheima.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.util.JwtUtil;
import com.itheima.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
@Slf4j
public class LoginCheckFilter implements Filter {


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1. 将请求和响应强制转换为HTTP的
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //2. 获取请求url
        String uri = request.getRequestURI();
        log.info("请求路径{}", uri);

        //3. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if (uri.equals("/login")) {
            filterChain.doFilter(servletRequest, servletResponse);//放行请求
            return;//结束判断
        }

        //4. 获取请求头中的令牌(token)
        String token = request.getHeader("token");

        //5. 解析token,如果解析失败,返回错误结果(未登录)。
        try {
            JwtUtil.parseJWT(token);
        }catch (Exception e){
            log.info("token错误");
            //返回错误消息
            String json = new ObjectMapper().writeValueAsString(Result.error("NOT_LOGIN"));
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(json);
            return;//结束判断
        }

        //6.放行
        filterChain.doFilter(request, response);
    }
}

② 开启Filter支持

在启动类上添加@ServletComponentScan注解

image-20230411134730593.png

5 拦截器入门

介绍

拦截器是Spring提供的一种技术,它的功能似于过滤器,它会在进入controller之前,离开controller之后以及响应离开服务时进行拦截。

image-20230415212909964.png

入门案例

① 开发拦截器

作用:要对拦截的资源做什么

语法:实现HandlerInterceptor接口,重写3个方法

image-20230411153511445.png
② 配置拦截器

作用:确定你要拦截那些资源

语法:在配置类中添加拦截路径的配置

image-20230411153543374.png

拦截路径

拦截器的路径写法相对简单,其实只有两个:*表示一层路径 **表示多层路径

路径解释备注
/*一级路径能匹配/depts,/emps,/login,不能匹配 /depts/1
/**任意级路径能匹配/depts,/depts/1,/depts/1/2
/depts/*/depts下的一级路径能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/**/depts下的任意级路径能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1

拦截器链

多个拦截器也可以同时使用,一条拦截器链,他们的顺序是有.order()方法控制

image-20230411154414549.png

6 拦截器实现访问校验

注释掉过滤器代码

image-20230416150707680.png

<!--添加依赖-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

① 创建Interceptor

image-20230416150759508.png

创建com.itheima.interceptor.LoginCheckInterceptor编写过滤逻辑

package com.itheima.interceptor;

import com.alibaba.fastjson.JSON;
import com.itheima.util.JwtUtil;
import com.itheima.vo.Result;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 登录拦截器
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {

    // 登录拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.取出token
        String token = request.getHeader("token");
        // 2.判断token是否正确
        try {
            Claims claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            // e.printStackTrace();
            log.error("令牌失效");
            // 返回错误信息
            // String json = new ObjectMapper().writeValueAsString(Result.error("NOT_LOGIN"));
            String json = JSON.toJSONString(Result.error("NOT_LOGIN"));
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(json);
            // 拦截
            return false;
        }
        // 3.放行
        return true;
    }
}

② 配置Interceptor

image-20230411155708317.png

7 过滤器VS拦截器

过滤器和拦截器实现的功能基本相似,不同点在于:

  1. 技术范围不同:过滤器需要JavaWeb技术,而拦截器属于Spring提供的技术
  2. 拦截范围不同:过滤器会拦截所有的资源,而拦截器只会拦截Spring环境中的资源
  3. 如果项目中同时出现了过滤器和拦截器,它们的执行位置如下

image-20230226193306670.png

三 日志记录

本小节我们要实现的功能是,要记录所有到controller中方法的运行日志保存到日志表中

日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法作用、方法运行时参数、返回值、方法执行时长

1 准备工作

创建数据表

-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
	class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
	method_desc varchar(100) comment '方法用途',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
	operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

创建日志类

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //ID
    private String className; //操作类名
    private String methodName; //操作方法名
	private String methodDesc; //方法用途
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
	private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private Long costTime; //操作耗时
}

创建日志的Mapper

package com.itheima.mapper;

import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name,method_desc, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName},#{methodDesc}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

2 制作切面

添加aop的启动器

<!--aop-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

自定义注解

image-20230411165746501.png

标识切点

image-20230411163532082.png

创建切面

package com.itheima.aspect;

import com.itheima.anno.LogAnno;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Arrays;

// 日志切面类
@Aspect
@Component
public class LogAspect {

    // 设置切点表达式
    @Pointcut("@annotation(com.itheima.anno.LogAnno)")
    public void pt() {
    }

    @Autowired
    private OperateLogMapper operateLogMapper;

    // 环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
        // 开始时间
        long start = System.currentTimeMillis();

        OperateLog log = new OperateLog();
        // 记录类名
        log.setClassName(pjp.getTarget().getClass().getName());
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 记录方法名
        log.setMethodName(methodSignature.getMethod().getName());
        // 记录方法描述
        log.setMethodDesc(methodSignature.getMethod().getAnnotation(LogAnno.class).methodDesc());
        // 记录方法参数
        log.setMethodParams(Arrays.toString(pjp.getArgs()));
        // 记录调用时间
        log.setOperateTime(LocalDateTime.now());
        // 记录操作人
        log.setOperateUser(1); // 暂时写死1

        Object obj = null;
        try {
            // 执行切点原有功能
            obj = pjp.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        } finally {
            // 结束时间
            long end = System.currentTimeMillis();
            // 记录方法返回值
            log.setReturnValue(obj.toString());
            // 记录耗时
            log.setCostTime(end-start);
            // 保存到数据库
            operateLogMapper.insert(log);
        }
        return obj;
    }
}

3 用户信息共享

目前代码问题

目前的代码中是这样设置操作用户Id的operateLog.setOperateUser(1);

那么怎样才能在切面中获取当前登录的用户的信息呢?

image-20220101205349264.png

ThreadLocal

线程局部变量,该变量对其他线程而言是隔离的;在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

ThreadLocal的三个方法:

  • set(T value) :设置当前线程绑定的变量
  • get():获取当前线程绑定的变量
  • remove() :移除当前线程绑定的变量

image-20221020211141112.png

image-20230416172759898.png

代码实现

① EmpContext

image-20230416173002008.png

② 修改LoginCheckInterceptor

image-20230416173101852.png

③ 修改LogAspect代码

image-20230416173134796.png

四 总结

1. 系统登录
	设计LoginDto用于前端用户名和密码
	登录成功需要制作一个令牌
	
2. JWT三部分组成
	头部、载荷(不能敏感信息)、签名
	JwtUtil
		制作令牌(登录)
		解析令牌(过滤、拦截)
		
3. 过滤器
	JavaWeb技术
	自定义类实现Filter接口
		doFilter(){
			controller执行前
			放行
			controller执行后
		}
	实现了登录校验
	
4. 拦截器
	SpringMVC技术
	自定义类实现HandlerInterceptor接口
		preHandler(){
			controller执行前
			放行
		}
		postHandler(){
			controller执行后
		}
		afterComplation(){
			服务器返回前
		}
	实现了登录校验
	

5. 日志记录
	自定义注解+切面类
	ThreadLocal(工具类操作线程内的map集合,实现数据)

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

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

相关文章

使用mapbox navigation搭建一个安卓导航 示例

一.代码示例地址&#xff1a; https://github.com/mapbox/mapbox-navigation-android-examples/tree/main 二. 具体步骤&#xff1a; git clone gitgithub.com:mapbox/mapbox-navigation-android-examples.git Go to app/src/main/res/values Look for mapbox_access_token.…

[R] Underline your idea with ggplot2

Preview: # 介绍&#xff1a;之前的教程中&#xff0c;我们学习了如何使条形图或直方图看起来更好 比如&#xff1a; 1. How to select a graph calibrate the geom part 2. How to select variables calibrate the aes part 3. How to add a title calibrate the labs …

Golang各版本的GC详解

go v1.3的标记清除法 清除的第一步&#xff1a;stw将可达对象标记删除未被标记对象 go v1.5三色标记法 从根节点出发&#xff0c;将下一个节点遍历为灰色&#xff0c;放入灰色集合中遍历灰色节点集合&#xff0c;把灰色能到达的节点标记为灰色&#xff0c;把自身标记为黑色&a…

安全增强型 Linux

书接上篇 一查看selinux状态 SELinux的状态&#xff1a; enforcing&#xff1a;强制&#xff0c;每个受限的进程都必然受限 permissive&#xff1a;允许&#xff0c;每个受限的进程违规操作不会被禁止&#xff0c;但会被记录于审计日志 disabled&#xff1a;禁用 相关命令…

SystemVerilog Support

介绍 AMD Vivado™合成支持可以合成的SystemVerilog RTL的子集。这个以下部分介绍了这些数据类型。 针对特定文件的SystemVerilog 默认情况下&#xff0c;Vivado合成工具使用Verilog 2005语法编译*.v文件和*.sv文件使用SystemVerilog语法。要在Vivado IDE中将SystemVerilog作…

**蓝桥OJ 178全球变暖 DFS

蓝桥OJ 178全球变暖 思路: 将每一座岛屿用一个颜色scc代替, 用dx[]和dy[]判断他的上下左右是否需要标记颜色,如果已经标记过颜色或者是海洋就跳过.后面的淹没,实际上就是哪个块上下左右有陆地,那么就不会被淹没,我用一个tag标记,如果上下左右一旦有海洋,tag就变为false.如果tag…

开发者如何选择代码签名证书?

代码签名证书是一种由权威认证机构颁发的数字证书&#xff0c;它允许软件开发者对其代码进行数字签名。这种签名基于公钥基础设施&#xff08;PKI&#xff09;技术&#xff0c;使用一对密钥&#xff1a;一个私钥和一个公钥。私钥用于生成签名&#xff0c;而公钥则嵌入到代码签名…

Linux学习-二级指针的使用

目录 ###指针传参时要用二级指针 ###函数体内部想要修改函数外部指针变量值的时候需要使用二级指针(指针变量的地址) ###指针传参时要用二级指针 char *str[5]; int Fun(char **ppstr,int len); ###函数体内部想要修改函数外部指针变量值的时候需要使用二级指针(指针变量的…

GCN原理回顾

Cora_dataset description Cora数据集是一个常用的学术文献用网络数据集&#xff0c;用于研究学术文献分类和图网络分析等任务。 该数据集由机器学习领域的博士论文摘要组成&#xff0c;共计2708篇论文&#xff0c;涵盖了7个不同的学科领域。每篇论文都有一个唯一的ID&#xf…

桥接模式: 消息发送器设计

桥接模式是一种结构型设计模式&#xff0c;它将抽象部分与它的实现部分分离&#xff0c;使它们可以独立地变化。桥接模式通过将抽象和实现分离&#xff0c;可以让它们可以独立地变化&#xff0c;从而提高系统的灵活性和可扩展性。 在桥接模式中&#xff0c;有两个重要的概念&a…

JavaBoy假期如何学习项目?弯道块才是真的快!

至暗时刻 老话说的好&#xff0c;弯道快才是真的快&#xff0c;谁直线不会加油&#xff1f;每到假期都是在座的各位弯道超车的时候。转眼自己已经出来搬了快四年砖头了&#xff0c;偶尔访问下牛客发现行情真是一年不如一年。。。不由得回想起自己春招时候的经历。 回想起2020年…

数据分析-Pandas数据的直方图探查

数据分析-Pandas数据的直方图探查 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&…

【贪心算法】Leetcode 455.分发饼干 376. 摆动序列 53. 最大子数组和

【贪心算法】Leetcode 455 分发饼干 376. 摆动序列【规律很多】53. 最大子数组和 455 分发饼干局部最优推全局最优&#xff1a;尽量用大饼干去满足大胃口的小朋友 376. 摆动序列【规律很多】思想&#xff1a;注意考虑一个坡度留首尾两个点、平坡、首尾 53. 最大子数组和【好思想…

FreeRTOS学习笔记-基于stm32(1)任务基础知识

一、裸机与RTOS 我们使用的32板子是裸机&#xff0c;又称前后台系统。裸机有如下缺点&#xff1a; 1、实时性差。只能一步一步执行任务&#xff0c;比如在一个while循环中&#xff0c;要想执行上一个任务&#xff0c;就必须把下面的任务执行完&#xff0c;循环一遍后才能执行…

从0开始学习NEON(2)

1、前言 继上一个例子&#xff0c;本次继续来学习NEON&#xff0c;本次学习NEON中向量拼接的操作&#xff0c;主要应用在图像的padding中。 https://blog.csdn.net/weixin_42108183/article/details/136440707 2、案例 2.1 案例1 在某些情况下&#xff0c;需要取在每个向量…

轻松压缩照片大小:简单实用的方法

当您需要通过网络传输或共享照片时&#xff0c;较小的文件大小可以提高传输速度并减少带宽消耗。这适用于通过电子邮件、社交媒体、即时消息应用程序等发送照片的场景。为了解决这个问题&#xff0c;本文将介绍一些简单而有效的方法来压缩照片的大小&#xff0c;以便更方便地分…

python并发编程:IO模型

一 IO模型 二 network IO 再说一下IO发生时涉及的对象和步骤。对于一个network IO \(这里我们以read举例\)&#xff0c;它会涉及到两个系统对象&#xff0c;一个是调用这个IO的process \(or thread\)&#xff0c;另一个就是系统内核\(kernel\)。当一个read操作发生时&#xff…

面试经典150题——基本计算器

​A husband is a man of many miles. ——Unknown 1. 题目描述 2. 题目分析与解析 2.1 思路一——先算括号内的内容 这个题目其实就是编译原理中很小的一个模块了&#xff0c;基本思路还是通过栈来实现。题目的难点主要在&#xff1a; 其中括号优先级的处理&#xff0c;以…

Spring揭秘:ImportBeanDefinitionRegistrar应用场景及实现原理!

内容概念 ImportBeanDefinitionRegistrar接口提供了强大的动态注册Bean的能力&#xff0c;它允许开发者在Spring容器初始化时&#xff0c;灵活地根据特定条件或需求来添加或修改Bean定义&#xff0c;从而实现更为精细的控制和扩展性。这是构建可扩展框架、插件系统或处理复杂配…

请说说你对Vue模板编译的理解

Vue模板编译是Vue.js框架的核心之一&#xff0c;它负责将Vue模板转换成渲染函数&#xff0c;从而实现模板的解析和渲染。要深入了解Vue模板编译&#xff0c;我们需要从编译过程、作用、特点等方面进行详细解析。 1. Vue模板编译的作用 Vue模板编译的主要作用是将Vue模板字符串…