微服务JWT的介绍与使用

1. 无状态登录

1.1 微服务的状态

​ 微服务集群中的每个服务,对外提供的都是Rest风格的接口,而Rest风格的一个最重要的规范就是:服务的无状态性。

​ 什么是无状态?

  1. 服务端不保存任何客户端请求者信息
  2. 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

​ 无状态,在微服务开放中,优势是?

  1. 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  2. 服务端的是否集群对客户端透明
  3. 服务端可以任意的迁移和伸缩
  4. 减小服务端存储压力

1.2 无状态登录实现原理

在这里插入图片描述

服务器端生产唯一标识(注意:最终需要进行校验)

方案1:UUID,数据单一,不能包含种类过多的信息。

方案2:JWT 生成唯一标识,数据可以自定义。【使用】

为了保证JWT生成数据安全性,采用RSA加密。

浏览器存储和自动携带数据

方案1:使用cookie,有很多局限性(大小,个数)。

方案2:请求参数,get请求URL有长度限制,每一个路径都需要处理比较麻烦。

方案3:浏览器localStorage/sessionStorage存储,通过ajax的请求头携带。【使用】

在这里插入图片描述

1.3 RSA加密

1.3.1 概述

​ RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥,“由已知加密密钥推导出解密密钥在计算上是不可逆的”密码体制。

​ 在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK [2]。

​ RSA加密:非对称加密。

​ 同时生产一对秘钥:公钥和私钥。

​ 公钥秘钥:用于加密

​ 私钥秘钥:用于解密

​ 既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;同理,既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。

使用RSA加密保证token数据在传输过程中不会被篡改。

1.3.2 工具类

package com.czxy.utils;import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/*** Created by liangtong.*/
public class RsaUtils {/*** 从文件中读取公钥** @param filename 公钥保存路径,相对于classpath* @return 公钥对象* @throws Exception*/public static PublicKey getPublicKey(String filename) throws Exception {byte[] bytes = readFile(filename);return getPublicKey(bytes);}/*** 从文件中读取密钥** @param filename 私钥保存路径,相对于classpath* @return 私钥对象* @throws Exception*/public static PrivateKey getPrivateKey(String filename) throws Exception {byte[] bytes = readFile(filename);return getPrivateKey(bytes);}/*** 获取公钥** @param bytes 公钥的字节形式* @return* @throws Exception*/public static PublicKey getPublicKey(byte[] bytes) throws Exception {X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);KeyFactory factory = KeyFactory.getInstance("RSA");return factory.generatePublic(spec);}/*** 获取密钥** @param bytes 私钥的字节形式* @return* @throws Exception*/public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);KeyFactory factory = KeyFactory.getInstance("RSA");return factory.generatePrivate(spec);}/*** 根据密文,生存rsa公钥和私钥,并写入指定文件** @param publicKeyFilename  公钥文件路径* @param privateKeyFilename 私钥文件路径* @param secret             生成密钥的密文* @throws Exception*/public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");SecureRandom secureRandom = new SecureRandom(secret.getBytes());keyPairGenerator.initialize(1024, secureRandom);KeyPair keyPair = keyPairGenerator.genKeyPair();// 获取公钥并写出byte[] publicKeyBytes = keyPair.getPublic().getEncoded();writeFile(publicKeyFilename, publicKeyBytes);// 获取私钥并写出byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();writeFile(privateKeyFilename, privateKeyBytes);}private static byte[] readFile(String fileName) throws Exception {return Files.readAllBytes(new File(fileName).toPath());}private static void writeFile(String destPath, byte[] bytes) throws IOException {File dest = new File(destPath);//创建父文件夹if(!dest.getParentFile().exists()){dest.getParentFile().mkdirs();}//创建需要的文件if (!dest.exists()) {dest.createNewFile();}Files.write(dest.toPath(), bytes);}
}

1.3.3 生产公钥和私钥

package com.czxy.utils;import org.junit.Test;import java.security.PrivateKey;
import java.security.PublicKey;/*** Created by liangtong.*/
public class TestRsa {private static final String pubKeyPath = "D:\\rsa\\rsa.pub";private static final String priKeyPath = "D:\\rsa\\rsa.pri";@Testpublic void testRas() throws Exception {//生产公钥和私钥RsaUtils.generateKey(pubKeyPath, priKeyPath, "234");}@Testpublic void testGetRas() throws Exception {//获得公钥和私钥PublicKey publicKey = RasUtils.getPublicKey(pubKeyPath);PrivateKey privateKey = RasUtils.getPrivateKey(priKeyPath);System.out.println(publicKey.toString());System.out.println(privateKey.toString());}

1.4 JWT

1.4.1 概述

​ JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

1.4.2 添加坐标

<properties><jwt.jjwt.version>0.9.0</jwt.jjwt.version><jwt.joda.version>2.9.7</jwt.joda.version><lombok.version>1.16.20</lombok.version><beanutils.version>1.9.3</beanutils.version>
</properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>${beanutils.version}</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.jjwt.version}</version></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>${jwt.joda.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope></dependency></dependencies>

1.4.3 时间处理工具:DateTime

//当前时间
DateTime.now().toDate().toLocaleString()
//当前时间加5分钟
DateTime.now().plusMinutes(5).toDate().toLocaleString()
//当前时间减5分钟
DateTime.now().minusMinutes(5).toDate().toLocaleString()

1.4.4 测试JWT

生成Token
@Test
public void testGenerateToken() throws Exception {String str = Jwts.builder().claim("test","测试数据").setExpiration(DateTime.now().plusMinutes(60).toDate()).signWith(SignatureAlgorithm.RS256,RsaUtils.getPrivateKey(priKeyPath)).compact();System.out.println(str);
}
解析Token
@Test
public void testParseToken() throws Exception {String token = "";Claims claims = Jwts.parser().setSigningKey(RsaUtils.getPublicKey(pubKeyPath)).parseClaimsJws(token).getBody();String text = claims.get("test",String.class);System.out.println(text);
}

1.4.5 工具类:JwtUtils

package com.czxy.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.beanutils.BeanUtils;
import org.joda.time.DateTime;import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.security.PrivateKey;
import java.security.PublicKey;/*** Created by liangtong.*/
public class JwtUtils {/***  私钥加密token* @param data 需要加密的数据(载荷内容)* @param expireMinutes 过期时间,单位:分钟* @param privateKey 私钥* @return*/public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {//1 获得jwt构建对象JwtBuilder jwtBuilder = Jwts.builder();//2 设置数据if( data == null ) {throw new RuntimeException("数据不能为空");}BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {// 获得属性名String name = propertyDescriptor.getName();// 获得属性值Object value = propertyDescriptor.getReadMethod().invoke(data);if(value != null) {jwtBuilder.claim(name,value);}}//3 设置过期时间jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());//4 设置加密jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);//5 构建return jwtBuilder.compact();}/*** 通过公钥解析token* @param token 需要解析的数据* @param publicKey 公钥* @param beanClass 封装的JavaBean* @return* @throws Exception*/public static <T> T  getObjectFromToken(String token, PublicKey publicKey,Class<T> beanClass) throws Exception {//1 获得解析后内容Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();//2 将内容封装到对象JavaBeanT bean = beanClass.newInstance();BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {// 获得属性名String name = propertyDescriptor.getName();// 通过属性名,获得对应解析的数据Object value = body.get(name);if(value != null) {// 将获得的数据封装到对应的JavaBean中BeanUtils.setProperty(bean,name,value);}}return bean;}
}

1.4.6 生产token和校验token

编写测试对象UserInfo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {private Long id;private String username;
}
编写测试类
package com.czxy.utils;import com.czxy.entity.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import org.junit.Test;/*** Created by liangtong.*/
public class TestJWT {private static final String pubKeyPath = "D:\\rsa\\rsa.pub";private static final String priKeyPath = "D:\\rsa\\rsa.pri";@Testpublic void testToken() throws Exception {UserInfo userInfo = new UserInfo();userInfo.setId(10L);userInfo.setUsername("用户名");String token = JwtUtils.generateToken(userInfo, 30, RsaUtils.getPrivateKey(priKeyPath));System.out.println(token);}@Testpublic void testParserToken() throws Exception {String token = "eyJhbGciOiJSUzI1NiJ9.eyJjbGFzcyI6ImNvbS5jenh5LmVudGl0eS5Vc2VySW5mbyIsImlkIjoxMCwidXNlcm5hbWUiOiLnlKjmiLflkI0iLCJleHAiOjE1NzU5MTYyMDl9.W3Q3Iz1vGq1nf7RQW3eAzkMvkME9P5_5zoDcFQXX0eke07lA2PLuZGCYcB6-DI0i7UrahFOmB0OFQodrK_3CZkrh-sI_802twkGatRaI0ifetRLV_1XHVl_Lymh6SaDdBB1OT3-EQCAppjoHFb9Tyq1EGyQZ5xoU-vLp7fzNQLQ";UserInfo userInfo = JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(pubKeyPath), UserInfo.class);System.out.println(userInfo);}
}

1.4.7 扩展:JWT token组成

JWT的token包含三部分数据:头部、载荷、签名。

名称描述组成部分
头部(Header)通常头部有两部分信息1. 声明类型,这里是JW2. 加密算法,自定义
载荷(Payload)就是有效数据1. 用户身份信息2. 注册声明
签名(Signature)整个数据的认证信息一般根据前两步的数据,再加上服务的的密钥(secret),通过加密算法生成。用于验证整个数据完整和可靠性
生成的数据格式:

在这里插入图片描述

1.5 Zuul整合JWT

1.5.1 自定义配置内容

修改application.yml 添加内容
#自定义内容
sc:jwt:secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥(自定义内容)pubKeyPath: D:/rsa/rsa.pub # 公钥地址priKeyPath: D:/rsa/rsa.pri # 私钥地址expire: 360 # 过期时间,单位分钟filter:allowPaths:- /checkusername- /checkmobile- /sms- /register- /login- /verifycode- /categorys- /news- /brands- /sku/esData- /specifications- /search- /goods- /comments- /pay/callback

1.5.2 JWT配置类

package com.czxy.changgou3.config;import com.czxy.utils.RasUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;/*** Created by liangtong.*/
@Data
@ConfigurationProperties(prefix = "sc.jwt")
@Component
public class JwtProperties {private String secret; // 密钥private String pubKeyPath;// 公钥private String priKeyPath;// 私钥private int expire;// token过期时间private PublicKey publicKey; // 公钥private PrivateKey privateKey; // 私钥private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);@PostConstructpublic void init(){try {File pubFile = new File(this.pubKeyPath);File priFile = new File(this.priKeyPath);if( !pubFile.exists() || !priFile.exists()){RasUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);}this.publicKey = RasUtils.getPublicKey( this.pubKeyPath );this.privateKey = RasUtils.getPrivateKey( this.priKeyPath );} catch (Exception e) {e.printStackTrace();}}}

1.5.3 过滤路径配置类

package com.czxy.changgou3.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;/*** Created by liangtong.*/
@Data
@ConfigurationProperties(prefix="sc.filter")
public class FilterProperties {//允许访问路径集合private List<String> allowPaths;
}

1.5.4 过滤器

package com.czxy.changgou3.filter;import com.czxy.changgou3.config.FilterProperties;
import com.czxy.changgou3.config.JwtProperties;
import com.czxy.changgou3.pojo.User;
import com.czxy.utils.JwtUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;/*** Created by liangtong.*/
@Component
//2.1 加载JWT配置类
@EnableConfigurationProperties({JwtProperties.class , FilterProperties.class} )
public class LoginFilter  extends ZuulFilter {//2.2 注入jwt配置类实例@Resourceprivate JwtProperties jwtProperties;@Resourceprivate FilterProperties filterProperties;@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return 5;}@Overridepublic boolean shouldFilter() {     //03.当前过滤器是否执行,true执行,false不执行//3.1 获得用户请求路径// 3.1.1 获得上下文RequestContext currentContext = RequestContext.getCurrentContext();// 3.1.2 获得requestHttpServletRequest request = currentContext.getRequest();// 3.1.2 获得请求路径  , /v3/cgwebservice/loginString requestURI = request.getRequestURI();//3.2 如果路径是 /auth-service/login ,当前拦截不执行for (String path  : filterProperties.getAllowPaths()) {//判断结尾if(requestURI.contains(path)){return false;}}//3.3 其他都执行 run()方法return true;}@Overridepublic Object run() throws ZuulException {//1 获得token//1.1 获得上下文RequestContext currentContext = RequestContext.getCurrentContext();//1.2 获得request对象HttpServletRequest request = currentContext.getRequest();//1.3 获得指定请求头的值String token = request.getHeader("Authorization");//2 校验token -- 使用JWT工具类进行解析// 2.3 使用工具类,通过公钥获得对应信息try {JwtUtils.getObjectFromToken(token , jwtProperties.getPublicKey() , User.class);} catch (Exception e) {// 2.4 如果有异常--没有登录(没有权限)currentContext.addOriginResponseHeader("content-type","text/html;charset=UTF-8");currentContext.addZuulResponseHeader("content-type","text/html;charset=UTF-8");currentContext.setResponseStatusCode( 403 );        //响应的状态码:403currentContext.setResponseBody("token失效或无效");currentContext.setSendZuulResponse( false );        //没有响应内容}// 2.5 如果没有异常,登录了--放行return null;}
}

1.6 下游服务获得token

在这里插入图片描述

zuul默认配置
zuul:sensitive-headers: Cookie,Set-Cookie,Authorization

在这里插入图片描述

修改zuul配置,及删除配置

在这里插入图片描述

其他服务中,可以通过request获得内容

在这里插入图片描述

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

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

相关文章

数据结构—基础知识(13):树的存储结构

数据结构—基础知识&#xff08;13&#xff09;&#xff1a;树的存储结构 双亲表示法 这种表示方法中&#xff0c;以一组连续的存储单元存储树的结点&#xff0c;每个结点除了数据域data外&#xff0c;还附设一个parent域用以指示其双亲结点的位置。 这种存储结构利用了每个结…

短信登录接口

后台 urls.py path(mobile/login/, views.MobileLoginViewSet.as_view({post: login})),serializers.py import re from django.core.cache import cache class MobileLoginSerializer(serializers.ModelSerializer):# 覆盖mobile serializers.CharField(requiredTrue, wri…

手搓反向迭代器

前言 关于反向迭代器&#xff0c;字如其名&#xff0c;就是将正向迭代器&#xff0c;从反方向再迭代一次就成了&#xff0c;所以我们如此设计反向迭代器&#xff1a; 假设我们已经拥有了一套能够使用&#xff0c;且包含模板的正向迭代器利用适配器模式&#xff0c;让反向迭代…

列举MySQL对于Where子句的优化

列举MySQL对于Where子句的优化 在MySQL8之前&#xff0c;我们自己额外注意一些查询语句的写法&#xff0c;牺牲了代码的可读性。在MySQL8中&#xff0c;会自动进行类似的优化。可以保持查询的易理解性和可维护性。 这边文章会列举这些优化点。 查询语句优化 删除不必要的括号…

软件测试生命周期

本章简要介绍了软件开发项目中常用的生命周期模型&#xff0c;并解释了测试在每个模型中扮演的角色。它讨论了各种测试级别和测试类型之间的区别&#xff0c;并解释了这些在开发过程中的应用位置和方式。 大多数软件开发项目是按照事先选择的软件开发生命周期模型来计划和执行…

Spring Boot + EasyExcel实现Excel文件导入导出

Java解析、生成Excel比较有名的框架有Apache poi、jxl等&#xff0c;使用者可自行斟酌。 一、 为什么使用 EasyExcel 1.1 内存控制 Apache poi、jxl也能解析Excel&#xff0c;但他们都存在一个问题就是非常的耗内存&#xff0c;poi有一套SAX模式的API可以一定程度的解决一些…

ZK监控方法以及核心指标

文章目录 1. 监控指标采集1.1 zk版本高于3.6.0监控指标采集1.2 zk版本低于3.6.0监控指标采集1.3 配置promethues采集和大盘 2. 核心告警指标3. 参考文章 探讨zk的监控数据采集方式以及需要关注的核心指标&#xff0c;便于日常生产进行监控和巡检。 1. 监控指标采集 3.6.0 版本…

apache 前30个开源项目

由于Apache软件基金会的开源项目 前30个具有代表性的项目 序号项目名称功能描述业务范围活跃度&#xff08;参考性描述&#xff09;1Apache HTTP Server高性能Web服务器提供HTTP服务支持非常活跃2Apache TomcatJava应用服务器部署Java Web应用程序非常活跃3Apache Hadoop分布式…

ORA-12528: TNS: 监听程序: 所有适用例程都无法建立新连

用了网上的办法&#xff1a; 1、修改listener.ora的参数,把动态的参数设置为静态的参数,红色标注部分 位置D:\oracle\product\10.2.0\db_1\NETWORK\ADMIN SID_LIST_LISTENER (SID_LIST (SID_DESC (SID_NAME PLSExtProc) (ORACLE_HOME D:\oracle\produ…

基于PHP反序列化练习

PHP创建一个以自己姓名命名的类&#xff0c;要求存在两个属性&#xff0c;name&#xff0c;age&#xff0c;进行序列化&#xff0c;输出序列化以后的数据。 <!-- PHP创建一个以自己姓名命名的类&#xff0c;要求存在两个属性&#xff0c;name&#xff0c;age --> <?…

【C/C++】C/C++编程——第一个 C++ 程序:HelloWorld

第一个 C 程序&#xff1a;HelloWorld 大家好&#xff0c;我是 shopeeai&#xff0c;也可以叫我虾皮&#xff0c;中科大菜鸟研究生。昨天我们成功搭建好了 C 的开发环境&#xff0c;今天我们来介绍一下第一个 C 程序,打印一个"hello world"。首先我们先贴一下示例代…

《WebKit技术内幕》学习之十三(2):移动WebKit

2 移动化用户界面 HTML5为移动领域做了大量的工作&#xff0c;其中“meta”标签中的众多设置值能够帮助提供非常好的移动用户体验。一个典型的例子就是上面提到的用该标签来控制网页缩放&#xff0c;如示例代码13-2使用了一些JavaScript代码来完成&#xff0c;而实际上&#x…

【FPGA Verilog开发实战指南】初识Verilog HDL-基础语法

这里写目录标题 Verilog HDL简介与VHDL比较 Verilog HDL基础语法逻辑值关键字moduleendmodule 模块名输入信号输出信号既做输入也做输出线网型变量 wire寄存器型变量 reg参数 parameter参数 localparam常量赋值方式阻塞赋值非阻塞赋值 always语句assign 语句 算数运算符归元运算…

【C语言】分支循环语句

分支 if elseif else if else if elseswitch() case default 循环 while 防止死循环for 已知所需循环次数do while 至少执行一次 break 和continue在循环中的应用 break &#xff1a;退出当前循环&#xff1b; continue&#xff1a;推出本次/轮循环 goto 太乱了&#xff0c;不…

C++ Qt day2

自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show() #include <io…

翻译: 使用 GPT-4 将您的 Streamlit 应用程序提升到一个新的水平一

帮助您更快地设计、调试和优化 Streamlit 应用的专业技巧 设计和扩展 Streamlit 应用程序可能是一项艰巨的任务&#xff01;作为开发人员&#xff0c;我们经常面临一些挑战&#xff0c;例如设计良好的 UI、快速调试我们的应用程序以及快速制作它们。 如果有一个工具可以加快速…

Tomcat运维

目录 一、Tomcat简介 二、系统环境说明 1、关闭防火墙&#xff0c;selinux 2、安装JDK 3、安装Tomcat 三、Tomcat目录介绍 1、tomcat主目录介绍 2、webapps目录介绍 3、Tomcat配置介绍&#xff08;conf&#xff09; 4、Tomcat的管理 四、Tomcat 配置管理页面(了解) …

类和对象 第三部分第三小节:const修饰成员函数

一.常函数&#xff1a; &#xff08;一&#xff09;成员函数后面加const后我们成这个函数为常函数 &#xff08;二&#xff09;常函数内不可以修改成员函数属性 额外补充&#xff1a; this指针的本质&#xff0c;是指针常量&#xff0c;指针指向的是不可以修改的 但是指针指向的…

点击查看灯塔工厂

“灯塔工厂”项目由达沃斯世界经济论坛与管理咨询公司麦肯锡合作开展遴选&#xff0c;被誉为“世界上最先进的工厂”&#xff0c;是工业4.0技术应用的最佳实践工厂&#xff0c;代表着全球智能制造的最高水平。目前全球“灯塔工厂”共有 153 座&#xff0c;包含中国 62 座。 做…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-11 HTML5 表单验证

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>HTML5 表单验证</title> </head><body> <form action"#" method"get" novalidate>请输入您的邮箱:<input type&q…