SpringBoot登录功能实现思路(验证码+拦截器+jwt)

总括

用户输入用户名和密码和验证码即可进行登录

验证码

VerifyCode:生成验证码的工具类

/*** 	生成验证码的工具类*/
public class VerifyCode {private int w = 70;//设置缓冲区的宽private int h = 35;//设置缓冲区的宽private Random r = new Random();//从字体中随机选一个 {"宋体", "华文楷体", "黑体", "华文新魏", "华文隶书", "微软雅黑", "楷体_GB2312"}private String[] fontNames  = {"宋体", "华文楷体", "黑体", "华文新魏", "华文隶书", "微软雅黑", "楷体_GB2312"};//从下述字符中随机选 源private String codes  = "123456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ";// 从0~255背景颜色中随机选private Color bgColor  = new Color(255, 255, 255);// 保存随机生成的图片当中的内容。private String text ;// 随机生成颜色private Color randomColor () {int red = r.nextInt(150);int green = r.nextInt(150);int blue = r.nextInt(150);return new Color(red, green, blue);}// 随机生成字体private Font randomFont () {int index = r.nextInt(fontNames.length);String fontName = fontNames[index];//根据随机的索引,获取随机字体int style = r.nextInt(4);//0,1,2,3, 0:没有任何样式,1,加粗,2,斜体,3,加粗和斜体  PLAIN(0)、BOLD(1)、ITALIC(2) 或 BOLD+ITALIC(3)。int size = r.nextInt(5) + 24; //随机生成字号return new Font(fontName, style, size);}// 画干扰线private void drawLine (BufferedImage image) {int num  = 3;//画三条干扰线Graphics2D g2 = (Graphics2D)image.getGraphics();for(int i = 0; i < num; i++) {int x1 = r.nextInt(w);int y1 = r.nextInt(h);int x2 = r.nextInt(w);int y2 = r.nextInt(h);g2.setStroke(new BasicStroke(1.5F));g2.setColor(Color.BLUE); //给干扰线设置了颜色g2.drawLine(x1, y1, x2, y2);//划线}}//随机生成字符private char randomChar () {int index = r.nextInt(codes.length());return codes.charAt(index);}// 得到一个缓冲区private BufferedImage createImage () {// 获取一个缓冲区BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);// 得到一个画笔Graphics2D g2 = (Graphics2D)image.getGraphics();// 设置画笔的颜色 白颜色g2.setColor(this.bgColor);// 填充图片的缓冲区。g2.fillRect(0, 0, w, h);// 将缓冲区返回。return image;}// 调用该方法,可以得到验证码public BufferedImage getImage () {BufferedImage image = createImage();//创建图片的缓冲区Graphics2D g2 = (Graphics2D)image.getGraphics();//得到绘制环境(画笔)StringBuilder sb = new StringBuilder();//定义一个容器,用来装在生成的验证码//向图片当中画四个字符for(int i = 0; i < 4; i++)  {//循环四次,每次生成一个字符String s = randomChar() + "";//随机成成一个字符sb.append(s); //将生成的字符放在缓冲区float x = i * 1.0F * w / 4; //设置当前字符的x轴坐标g2.setFont(randomFont()); //设置随机生成的字体g2.setColor(randomColor()); //设置字符的随机颜色g2.drawString(s, x, h-5); //画图}this.text = sb.toString(); //随机生成的图片的内容复制给this.textdrawLine(image); //画干扰线return image;}// 获取图片当中的内容public String getText() {return text;}// 保存图片到指定的输出流public static void output (BufferedImage image, OutputStream out)throws IOException {ImageIO.write(image, "JPEG", out);}
}

再在LoginController中写一个方法来获取验证码

// 获取验证码@RequestMapping("/login/VerifyCode")public void VerifyCode(HttpServletRequest request, HttpServletResponse response) throws Exception{request.setCharacterEncoding("utf-8");response.setContentType("text/html;charset=utf-8");VerifyCode code = new VerifyCode();BufferedImage image = code.getImage();  //得到验证码的图片String text = code.getText();  //得到验证码的文本HttpSession session = request.getSession();  //将验证码的值存放到session中session.setAttribute("verify",text);VerifyCode.output(image,response.getOutputStream());  //将验证码图片输出到页面}

相应的接收生成的验证码内容的前端页面

验证码部分html

<div class="item" id="code-item"><span>验证码</span><input class="input_code" type="text" name="Imgcode" id="Imgcode" /><img alt="验证码" src="/login/VerifyCode" id="imgCode" onclick="reloadCode()"></div>

验证码部分JavaScript

//更换验证码/** 由于用户可能每次切换的速度是非常快的,服务器默认的选项是短时间内多次点击只响应一下* 我们既不能让用户连续点我们就连续响应,也不能使用服务器默认的点很多很多下只响应一次,如何达到一个平衡?*  较为经典的做法是连续点很多次的话,显示“更新太频繁”* */function reloadCode(){var time=new Date().getTime(); //时间戳,每点一次就是一个时间,用于区分用户是否点击切换了验证码//下述做法是:用户点击几次就更新几次验证码//鼠标每单击一次验证码图片,设置img标签的src属性,然后图片标签就会调用src指向的资源document.getElementById("imgCode").src="/login/VerifyCode?id="+time;}

向后台发送用户输入的数据(发送了用户名、密码、验证码

// 登录按钮点击事件处理
$(".login").on("click", function () {//获取用户输入的值var username = $(".username").val();var password = $(".password").val();var Imgcode = $("#Imgcode").val(); //验证码值$.ajax({url: "/login/managerLogin", // 修改为与后端LoginController中checkLogin方法对应的路径type: "get",data: {username: username,password: password,Imgcode:Imgcode,},success: function (value){console.log(value.msg)if(value.code === 200){//进入到页面alert(value.msg)window.location.href = "http://localhost:8080/backend";}else if(value.code === 444){alert(value.msg);}else if(value.code === 999){alert(value.msg);}},error: function () {alert("登录失败,请检查账号、密码、邮箱、用户类型或验证码是否正确");}});
});

用户点击登录后LoginController部分
注意Login实体类中多添加了一个验证码字段

public class Login {private Integer id;private String username;private String password;private Integer admin; //0:不是管理员 1:是管理员private String email;private String Imgcode; //非数据库中字段,只是用于接收前端发过来的验证码信息
}
// 登录验证及相关操作@RequestMapping(value = "/login/managerLogin", method = RequestMethod.GET)@ResponseBodypublic ResponseResult managerLogin(Login login, HttpServletRequest request, HttpServletResponse response) throws IOException {//获取上边VerifyCode()方法中的session信息HttpSession session = request.getSession();// 使用合并后的managerLogin方法查询用户信息(写sql语句,用用户名和密码在数据库中查询是否存在这个用户)List<Login> logins = loginDao.managerLogin(login);//如果能在数据库中查询到数据且数据有且仅有一个if (logins.size() == 1) {// 获取邮箱信息(Login对象有email属性),后续用于发送邮件的时候获取邮箱信息String email = logins.get(0).getEmail();// 进行验证码验证if (session.getAttribute("verify").equals(login.getImgcode())) {// 验证码正确,生成token等操作// null是因为在JwtUtil中已经设置了过期时间,所以这里不用设置了String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(logins.get(0).getId()), email, null);// 生成cookie对象,将token信息设置在cookie当中Cookie cookie = new Cookie("token", token);cookie.setPath("/");cookie.setMaxAge(36000);response.addCookie(cookie);// 登录成功return new ResponseResult(200, "登录成功", logins.get(0).getAdmin());} else {return new ResponseResult(999, "验证码错误");}} else {// 登录失败return new ResponseResult(444, "登录失败");}}

拦截器

LoginConfig:登录拦截配置类
一般不对登录、注册、静态资源信息等进行拦截

/*** 登录拦截配置类*/
@Configuration //表明为配置类
public class LoginConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;/*** @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor) //添加拦截器.addPathPatterns("/**")  //配置拦截路径 **表示拦截所有路径.excludePathPatterns("/login/**","/login_not_admin","/login_admin","/","/static/**","/templates/**,/register");//配置排除路径,排除了就不会拦截了}
}

LoginInterceptor:拦截器
继承自HandlerInterceptor,它里边有三个方法,我们这里重写了第一个

public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}

拦截其实就是拦截的从处理器适配器到相应的Mapping注解下的方法(去与回都可做拦截)
我的LoginInterceptor是在相应方法执行前进行拦截

/*** 拦截器*/
@Component //将类交给spring管理
public class LoginInterceptor implements HandlerInterceptor {/*** 除了登录请求以外的其他请求都要进行过滤* @param request* @param response* @param handler* @return* @throws Exception*/// 除了前面登录拦截配置类LoginConfig中配置过排除拦截的路径,前端的其余所有请求都要打入preHandle中,判断有无正确的登陆信息@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Cookie[] cookies = request.getCookies(); //获取前端的cookie信息if(cookies != null){for (Cookie cookie:cookies){if("token".equals(cookie.getName())){String userToken = cookie.getValue();//如果token信息为空,发送错误提示if(!StringUtils.hasText(userToken)){response.sendError(HttpServletResponse.SC_UNAUTHORIZED);}//解析token看看是否成功try {Claims claims = JwtUtil.parseJWT(userToken);claims.getSubject();}catch (Exception e){//e.printStackTrace();System.out.println("token信息出错");return false;}return true;  //有登录信息且登录信息正确→放行}}}return false;  //没有登录信息或登录信息错误→不放行,访问不到相应路径下的方法}
}

JWT

一、什么是JWT

JWT(JSON WEB TOKEN),通过数字签名的方式,以json对象为载体,在不同的服务终端之间安全的传输信息,用来解决传统session的弊端。
JWT在前后端分离系统,或跨平台系统中,通过JSON形式作为WEB应用中的令牌(token),用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中,还可以完成数据加密、签名等相关处理。
token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。

二、JWT能做什么

1.授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
2.信息交换:jwt是在各方之间安全地传输信息的好方法,因为可以对JWT进行签名(例如,使用公钥/私钥对),所以可以确保发件人是他们所说的人,此外,由于签名嘶使用标头和有效负债计算的,因此可以验证内容是否遭到篡改。
这次讲课我们主要针对的是JWT实现的授权登录

三、为什么有JWT

传统的session认证有如下的问题
1.每个用户经过我们的应用认证之后,将认证信息保存在session中,由于session服务器中对象,随着认证用户的增多,服务器内存开销会明显增大;
2.用户认证之后,服务端使用session保存认证信息,那么要取到认证信息,只能访问同一台服务器,才能拿到授权的资源。这样在分布式应用上,就需要实现session共享机制,不方便集群应用;
3.因为session是基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于JWT的认证流程

  • 前端通过web表单将自己的用户和密码发送到后端接口(一般是http-post请求,建议使用SSL加密传输(https协议),以免敏感信息被嗅探)目前只是发起普通的http请求,未进行加密
  • 后端核对用户名和密码成功后,将用户的Id等其他信息作为JWT Payload(负载),将其与头部分别进行BASE64编码拼接后签名,形成一个JWT(Token),形成的JWT就是一个字符(head.payload.singueater)
  • 后端将JWT字符串作为登录成功的结果返回给前端。前端结果保存在localStorage(本地缓存)或sessionStorage上,退出登录时前端删除保存的JWT即可。
  • 前端在每次请求时将JWT放入http header中的Authorization位(解决XSS和XSRF问题)
  • 后端检查是否存在,如存在验证JWT的有效性,例如:检查签名是否正确,检查token是否过期,检查token的接收方是否是自己(可选)。
    JWT的优势
  • 简洁,可以通过URL、POST参数或Http header发送,因为数据量小,传输速度快;
  • 自包含,负载(属于JWT的一部分)中包含了用户所需要的信息,不需要在服务器端保存会话信息,不占服务器内存,也避免了多次查询数据库,特别适用于分布式微服务;
  • 因为token是以json加密的形式保存在客户端的,所以JWT可以跨语言使用,原则上任何WEB形式都支持。
  • 不需要再服务端保存会话信息,特别适用于分布式微服务。

四、JWT的结构

使用JWTUtil工具类
JWT其实就是一段字符串,由标头(Header)、有效载荷(Payload)和签名(Signature)这三部分组成,用 . 拼接。在传输的时候,会将JWT的三部分分别进行Base64编码后用 . 进行连接形成最终传输的字符串。

1.头部(Header): JWT的头部是一个JSON对象,用于描述JWT的元数据(默认会生成,可以不设置),例如令牌的类型(typ)和签名算法(alg)。通常情况下,头部会包含以下信息:

{"alg": "HS256","typ": "JWT"
}
  • alg:指定签名算法,常见的有HMAC SHA256(HS256)和RSA SHA256(RS256)等。 默认HS256
    • typ:指定令牌的类型,一般为JWT。
      头部需要经过Base64编码后作为JWT的第一部分。

2.载荷(Payload):JWT的载荷是存储实际数据的部分(即存储用户信息的部分),也是一个JSON对象。它包含了一些声明(claims),用于描述令牌的信息。常见的声明有

{"sub": "1234567890","name": "John Doe","admin": true
}

前面两部分都使用Base64进行编码,前端可以解开知道里面的信息, Signature需要使用编码后的header和payload以及我们提供的一密钥,然后使用header中指定的签名算法进行签名,以保证JWT没有被篡改过。
3. 签名(Signature):使用Signature签名可以防止内容被篡改(签名即类似于现实生活中你的签名,比如一个合同,得有你的亲笔签名才能生效)。如果有人对头部及负载内容解码后进行修改,再进行编码,最后加上之前签名组成新的JWT。那么服务器会判断出新的头部和负载形成的签名和JWT附带的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。签名一般不允许对外泄露
需要注意的是,JWT中的信息是经过Base64编码的,虽然可以通过Base64解码获取原始内容,但是不能修改JWT中的内容,因为签名会验证JWT的完整性和真实性。
五、JWT的第一个程序
在测试类当中获取token

/*** 获取token*/
@Test
void setToken(){HashMap<String,Object> map = new HashMap<>();//Calendar java提供的日历类Calendar instance = Calendar.getInstance();instance.add(Calendar.SECOND,20);String token = JWT.create().withHeader(map)  //header---------------->这个可以去掉,不影响.withClaim("userId",21)  //payload.withClaim("username","xiaoming").withExpiresAt(instance.getTime())  //指定令牌的过期时间.sign(Algorithm.HMAC256("!@Q#WW22")); //签名:一般不对外泄露System.out.println(token);
}
在测试类当中验证token
@Test
public void getToken(){//创建验证对象JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!@Q#WW22")).build(); //签名就相当于是密钥,没有签名就打不开字符串DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3Mjc4NjE3ODYsInVzZXJJZCI6MjEsInVzZXJuYW1lIjoieGlhb21pbmcifQ.GODRy8B3ZC1SlV49w4sI0QBDlwvx_ayzezjE0OdJS6k");System.out.println(verify.getClaim("userId").asInt());System.out.println(verify.getClaim("username").asString());System.out.println("过期时间:"+verify.getExpiresAt());
}

六、JWT的工具类封装

/*** JWT工具类* @author 王*/
public class JWTUtils {//秘钥---》这个不能被外界获取private static final String SING = "@#S2$$*^^";/*** 生成 token* @param map* @return*/public static String getToken(Map<String,String> map){Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7 ); //默认7天过期//创建jwt builderJWTCreator.Builder builder = JWT.create();//payloadmap.forEach((k,v)->{builder.withClaim(k,v);});String token = builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SING));return token;}/*** 验证token的合法性* @param token*/public static void verify(String token){JWT.require(Algorithm.HMAC256(SING)).build().verify(token);}/*** 获取token信息方式* @param token* @return*/public static DecodedJWT getTokenInfo(String token){DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);return verify;}}

七、进行使用

同样是验证字符串,session与token有什么区别
1.session生成的字符串JSESSIONID存储在服务器上,用户登录后访问相应路径时会携带着JSESSIONID,这时会将服务器中存储的JSESSIONID与用户携带的进行比对,一致即放行
在这里插入图片描述
2.token生成的字符串是一个加密后的信息,我们只需要在该字符串发送过来后,使用密钥进行解密即可,如果能解密成功,那就是我们发送过去的token信息;如果不能机密成功或解密成功后发现登录信息已经过期了,那就认为当前字符串不正确。故无需存储在服务器上,减小了服务器的压力

在LoginController中,一旦用户登陆成功,就马上生成相应的token

// 登录验证及相关操作@RequestMapping(value = "/login/managerLogin", method = RequestMethod.GET)@ResponseBodypublic ResponseResult managerLogin(Login login, HttpServletRequest request, HttpServletResponse response) throws IOException {//获取上边VerifyCode()方法中的session信息HttpSession session = request.getSession();// 使用合并后的managerLogin方法查询用户信息(写sql语句,用用户名和密码在数据库中查询是否存在这个用户)List<Login> logins = loginDao.managerLogin(login);//如果能在数据库中查询到数据且数据有且仅有一个if (logins.size() == 1) {// 获取邮箱信息(Login对象有email属性),后续用于发送邮件的时候获取邮箱信息String email = logins.get(0).getEmail();// 进行验证码验证if (session.getAttribute("verify").equals(login.getImgcode())) {// 验证码正确,生成token等操作/** UUID.randomUUID():随机生成一个数* logins.get(0).getId():也可以不获取id,获取其他信息如username也行* email:后续发送邮件功能会用到,故也设置上了* null是因为在JwtUtil中已经设置了过期时间,所以这里不用设置了会使用默认的过期时间;如果设置了就用你设置的过期时间* */String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(logins.get(0).getId()), email, null);// 生成cookie对象,将token信息设置在cookie当中//注意:cookie是保存在浏览器上的Cookie cookie = new Cookie("token", token);cookie.setPath("/"); //设置浏览器访问路径cookie.setMaxAge(36000); //设置cookie的过期时间response.addCookie(cookie); //将cookie返回给浏览器// 登录成功return new ResponseResult(200, "登录成功", logins.get(0).getAdmin());} else {return new ResponseResult(999, "验证码错误");}} else {// 登录失败return new ResponseResult(444, "登录失败");}}

JwtUtil:JWT工具类

package com.qcby.springbootdemo.util;/*** JWT工具类*/import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;public class JwtUtil {//设置时间:有效期为public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 * 1000//设置秘钥明文public static final String JWT_KEY = "qcby";/*** 创建token* @param id* @param subject* @param ttlMillis* @return*/public static String a_email; //设置全局变量存储用户输入的email信息public static String createJWT(String id, String subject, String email, Long ttlMillis) {a_email = email;SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if(ttlMillis==null){ttlMillis=JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);SecretKey secretKey = generalKey();//生成token信息:一般想设置啥就设置啥,但用户id一般是要设置上的JwtBuilder builder = Jwts.builder().setId(id)              //唯一的ID(默认下是必须有用户id的,因为用户id一般是不重复的,后续添加删除之类的一般会用到用户id,所以这里必须要保存着).setSubject(subject)   // 主题  可以是JSON数据.setIssuer("wd")     // 签发者.setIssuedAt(now)      // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate)// 设置过期时间.claim("email", email); // 将用户邮箱信息添加到Claims中,这里键名为"email",可根据需求自定义return builder.compact();}/*** 生成加密后的秘钥 secretKey* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析* @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}public static void main(String[] args) {String token = JwtUtil.createJWT(UUID.randomUUID().toString(),"qd", a_email,null );System.out.println(token);}}

当用户登陆后就会在浏览器中生成包含token的cookie信息
在这里插入图片描述
这样用户在访问其他页面的时候就会在之前提到的拦截器类LoginInterceptor中被拦截,核对信息后放行

/*** 拦截器*/
@Component //将类交给spring管理
public class LoginInterceptor implements HandlerInterceptor {/*** 除了登录请求以外的其他请求都要进行过滤* @param request* @param response* @param handler* @return* @throws Exception*/// 除了前面登录拦截配置类LoginConfig中配置过排除拦截的路径,前端的其余所有请求都要打入preHandle中,判断有无正确的登陆信息@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Cookie[] cookies = request.getCookies(); //获取前端的cookie信息//先看看是否有cookie信息if(cookies != null){for (Cookie cookie:cookies){//看看cookie中是否有token信息if("token".equals(cookie.getName())){String userToken = cookie.getValue();//如果token信息为空,发送错误提示if(!StringUtils.hasText(userToken)){response.sendError(HttpServletResponse.SC_UNAUTHORIZED);}//使用JwtUtil中的parseJWT解析token,看看是否成功try {Claims claims = JwtUtil.parseJWT(userToken);claims.getSubject();}catch (Exception e){//e.printStackTrace();System.out.println("token信息出错");return false;}return true;  //有登录信息且登录信息正确→放行}}}return false;  //没有登录信息或登录信息错误→不放行,访问不到相应路径下的方法}
}

这样便实现了只有登录后才能访问到相应页面信息的功能

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

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

相关文章

小米路由器用外网域名访问管理界面

本文在Redmi AX3000 (RA81)设置&#xff0c;其他型号路由器的管理界面端口可能各不相同。 开始之前需要保证路由器SSH功能正常&#xff0c;如果没有SSH可以参考这里。 1. 给WAN口开放80端口 可以通过下载mixbox的firewall插件或者其他防火墙插件开放端口。 2. 把域名解析到路…

杰发科技AC7801——ADC定时器触发的简单使用

使用场景 在需要多次采样结果的情况下&#xff0c;比如1s需要10w次的采样结果&#xff0c;可以考虑使用定时器触发采样&#xff0c;定时器设置多少的时间就会多久采样转换一次。 再加上使用dma&#xff0c;采样的结果直接放在dma的数组里面。 实现了自动采样&#xff0c;自动…

pytest结合allure做接口自动化

这是一个采用pytest框架&#xff0c;结合allure完成接口自动化测试的项目&#xff0c;最后采用allure生成直观美观的测试报告&#xff0c;由于添加了allure的特性&#xff0c;使得测试报告覆盖的内容更全面和阅读起来更方便。 1. 使用pytest构建测试框架&#xff0c;首先配置好…

现代分布式系统新法宝:基于单元的架构

- 前言 - 数十年来&#xff0c;IT 业界一直在努力掌握分布式系统。然而&#xff0c;随着系统日益复杂&#xff0c;给开发数字产品的组织带来巨大挑战。可以说&#xff0c;分布式系统最棘手的方面之一是面对故障时的可靠性&#xff0c;特别是现代分布式系统使用大量物理与虚拟资…

C#桌面应用制作计算器

C#桌面应用制作简易计算器&#xff0c;可实现数字之间的加减乘除、AC按键清屏、Del按键清除末尾数字、/-按键取数字相反数、%按键使数字缩小100倍、按键显示运算结果等...... 页面实现效果 功能实现 布局 计算器主体使用Panel容器&#xff0c;然后将button控件排列放置Pane…

python: generator IDAL and DAL using sql server 2019

其它数据库也是一样的思维方式 create IDAL # encoding: utf-8 # 版权所有 2024 ©涂聚文有限公司 # 许可信息查看&#xff1a;言語成了邀功盡責的功臣&#xff0c;還需要行爲每日來值班嗎 # 描述&#xff1a; # Author : geovindu,Geovin Du 涂聚文. # IDE : P…

鲸鱼机器人和乐高机器人的比较

鲸鱼机器人和乐高机器人各有其独特的优势和特点&#xff0c;家长在选择时可以根据孩子的年龄、兴趣、经济能力等因素进行综合考虑&#xff0c;选择最适合孩子的教育机器人产品。 优势 鲸鱼机器人 1&#xff09;价格亲民&#xff1a;鲸鱼机器人的产品价格相对乐高更为亲民&…

探究IOC容器刷新环节初始化前的预处理

目录 一、IOC容器的刷新环节快速回顾 二、初始化前的预处理prepareRefresh源码分析 三、初始化属性源 &#xff08;一&#xff09;GenericWebApplicationContext初始化属性源 &#xff08;二&#xff09;StaticWebApplicationContext初始化属性源 四、初始化早期事件集合…

【Java SE】PreparedStatement

PreparedStatement 是一个接口&#xff0c;它继承自 Statement&#xff0c;用于预编译 SQL 语句。简单来说&#xff0c;PreparedStatement 是 JDBC 提供的一个对象&#xff0c;用于执行 SQL 语句。它的重要功能是帮助防止 SQL 注入攻击&#xff0c;并提高执行效率。 SQL 注入问…

集群聊天服务器(7)数据模块

目录 Mysql数据库代码封装头文件与源文件 Mysql数据库代码封装 业务层代码不要直接写数据库&#xff0c;因为业务层和数据层的代码逻辑也想完全区分开。万一不想存储mysql&#xff0c;想存redis的话&#xff0c;就要改动大量业务代码。解耦合就是改起来很方便。 首先需要安装m…

数造科技亮相第26届高交会并接受媒体采访,以数据智能赋能未来

11 月 14 日至 16 日&#xff0c;第二十六届中国国际高新技术成果交易会&#xff08;简称“高交会”&#xff09;在深圳成功举办。本届大会以“科技引领发展&#xff0c;产业融合聚变”为主题&#xff0c;汇聚了全球最新的科技成果&#xff0c;打造了一场科技界的盛大聚会。 在…

C#获取视频第一帧_腾讯云媒体处理获取视频第一帧

一、 使用步骤&#xff1a; 第一步、腾讯云开启万象 第二步、安装Tencent.QCloud.Cos.Sdk 包 第三步、修改 腾讯云配置 图片存储目录配置 第四步、执行获取图片并保存 二、封装代码 using System.Text; using System.Threading.Tasks;using COSXML.Model.CI; using COSXML.A…

概念解读|K8s/容器云/裸金属/云原生...这些都有什么区别?

随着容器技术的日渐成熟&#xff0c;不少企业用户都对应用系统开展了容器化改造。而在容器基础架构层面&#xff0c;很多运维人员都更熟悉虚拟化环境&#xff0c;对“容器圈”的各种概念容易混淆&#xff1a;容器就是 Kubernetes 吗&#xff1f;容器云又是什么&#xff1f;容器…

吴恩达深度学习笔记:序列模型(Sequence Models) 1.3-1.4

目录 第五门课 序列模型(Sequence Models)第一周 循环序列模型&#xff08;Recurrent Neural Networks&#xff09;1.3 循环神经网络模型&#xff08;Recurrent Neural Network Model&#xff09;1.4 通过时间的反向传播&#xff08;Backpropagation through time&#xff09; …

语义分割(semantic segmentation)

语义分割(semantic segmentation) 文章目录 语义分割(semantic segmentation)图像分割和实例分割代码实现 语义分割指将图片中的每个像素分类到对应的类别&#xff0c;语义区域的标注和预测是 像素级的&#xff0c;语义分割标注的像素级的边界框显然更加精细。应用&#xff1a…

C++ —— string类(上)

目录 string的介绍 string类功能的使用介绍 constructor —— 构造 介绍使用&#xff08;1&#xff09;&#xff08;2&#xff09;&#xff08;4&#xff09; &#xff1a;构造、拷贝构造、带参构造 介绍&#xff08;3&#xff09;&#xff1a;拷贝string类对象的一部分字符…

Python小白学习教程从入门到入坑------第三十二课 生成器(语法进阶)

目录 一、生成器 generator 1.1 生成器表达式 1.1.1 表达式一 1.1.2 表达式二 二、可迭代对象、迭代器、生成器三者之间的关系 2.1 定义与特性 2.2 关系与区别 一、生成器 generator 在Python中&#xff0c;生成器&#xff08;Generators&#xff09;是一种用于迭代对象…

商业iOS端路由架构演进

背景 目前商业SDK中的点击事件&#xff0c;会根据不同的「事件类型」「业务类型」&#xff0c;去执行不同的路由跳转逻辑&#xff0c;然而不同的跳转事件内部又有着很复杂的跳转逻辑&#xff0c; 痛点 不同的跳转逻辑之间存在耦合 例如&#xff0c;在deeplink的跳转逻辑之中…

数据结构(单向链表——c语言实现)

链式存储的优缺点&#xff1a; 优点&#xff1a; 1、动态分配内存&#xff1a; 链式存储不需要在数据插入之前分配固定大小的数组或内存块&#xff0c;因此它更适合存储动态变化的数据 2、高效的插入和删除操作&#xff1a; 在链表中插入或删除元素只需要调整相邻节点的指…

【SQL】E-R模型(实体-联系模型)

目录 一、介绍 1、实体集 定义和性质 属性 E-R图表示 2. 联系集 定义和性质 属性 E-R图表示 一、介绍 实体-联系数据模型&#xff08;E-R数据模型&#xff09;被开发来方便数据库的设计&#xff0c;它是通过允许定义代表数据库全局逻辑结构的企业模式&#xf…