当前位置: 首页 > news >正文

怎么实现一个登录时需要输入验证码的功能

今天给项目换了一个登录页面,而这个登录页面设计了验证码,于是想着把这个验证码功能实现一下吧。

这篇文章就如何实现登录时的验证码的验证功能结合代码进行详细地介绍,以及介绍功能实现的思路。

目录

页面效果

实现思路

生成验证码的控制器类

前端页面代码

localStorage.js

login.html

login.js

后端登录代码

UserLoginDTO.java

UserController.java

UserServiceImpl.java

潜在问题

改进方案


页面效果

登录的时候会把用户名、密码和验证码一起传到后端,并对验证码进行验证,只有验证码正确才能登录。

实现思路

那么,具体是如何实现的呢,首先大概介绍一下我实现这个功能的思路:

  • 验证码图片的url由后端的一个Controller生成,前端请求这个接口的时候根据当前生成一个uuid,并把这个uuid在前端缓存起来,下一次还是从前端的缓存获取,在这里使用的是localStorage。
  • Controller生成验证码之后,把前端传过来的uuid通过redis缓存起来,这里分两次缓存
    • 缓存uuid
    • 以uuid为key,缓存验证码
  • 这样,当点击登录按钮将数据提交到后台登录接口时,会从redis中获取uuid,然后通过从redis中拿到的uuid去获取验证码,和前端用户输入的验证码进行比较。

由于博主也是第一次做这个功能,就随便在网上找了一个生成验证码的工具easy-captcha

<!--生成验证码工具-->
<dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version>
</dependency>

生成验证码的控制器类

CaptchaController.java

package cn.edu.sgu.www.mhxysy.controller;import cn.edu.sgu.www.mhxysy.annotation.AnonymityAccess;
import cn.edu.sgu.www.mhxysy.config.CaptchaConfig;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import cn.edu.sgu.www.mhxysy.util.UserUtils;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author heyunlin* @version 1.0*/
@Slf4j
@RestController
@Api(tags = "验证码管理")
@RequestMapping(value = "/captcha", produces = "application/json;charset=utf-8")
public class CaptchaController {private final CaptchaConfig captchaConfig;private final StringRedisTemplate stringRedisTemplate;@Autowiredpublic CaptchaController(CaptchaConfig captchaConfig, StringRedisTemplate stringRedisTemplate) {this.captchaConfig = captchaConfig;this.stringRedisTemplate = stringRedisTemplate;}/*** 生成验证码* @param type 验证码图片类型* @param uuid 前端生成的uuid*/@AnonymityAccess@ApiOperation("生成验证码")@RequestMapping(value = "/generate", method = RequestMethod.GET)public void generate(@RequestParam String type, @RequestParam String uuid) throws IOException {// 获取HttpServletResponse对象HttpServletResponse response = UserUtils.getResponse();// 设置请求头response.setContentType("image/gif");response.setDateHeader("Expires", 0);response.setHeader("Pragma", "No-cache");response.setHeader("Cache-Control", "no-cache");Captcha captcha;Integer width = captchaConfig.getWidth();Integer height = captchaConfig.getHeight();switch (type) {case "png":captcha = new SpecCaptcha(width, height);break;case "gif":captcha = new GifCaptcha(width, height);break;default:throw new GlobalException(ResponseCode.BAD_REQUEST, "不合法的验证码类型:" + type);}captcha.setLen(4);captcha.setCharType(Captcha.TYPE_DEFAULT);String code = captcha.text();log.debug("生成的验证码:{}", code);// 保存uuidstringRedisTemplate.opsForValue().set("uuid", uuid);stringRedisTemplate.opsForValue().expire("uuid", 5, TimeUnit.MINUTES);// 缓存验证码stringRedisTemplate.opsForValue().set(uuid, code);stringRedisTemplate.opsForValue().expire(uuid, 5, TimeUnit.MINUTES);// 输出图片流captcha.out(response.getOutputStream());}}

前端页面代码

localStorage.js

/*** 保存数据到localStorage* @param name 数据的名称* @param value 数据的值*/
function storage(name, value) {localStorage.setItem(name, value);
}/*** localStorage根据name获取value* @param name 数据的名称*/
function getStorage(name) {return localStorage.getItem(name);
}

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>梦幻西游手游管理登录</title><link rel="stylesheet" href="/css/login.css"><script src="/js/public/jquery.min.js"></script><script src="/js/public/util.js"></script><script src="/js/public/localStorage.js"></script><script src="/js/login.js"></script></head><body style="overflow:hidden"><div class="pagewrap"><div class="main"><div class="header"></div><div class="content"><div class="con_left"></div><div class="con_right"><div class="con_r_top"><a href="javascript:" class="left">下载游戏</a><a href="javascript:" class="right">登录管理</a></div><ul><li class="con_r_left" style="display:none;"><div class="erweima"><div class="qrcode"><div id="output"><img src="/images/login/mhxysy.png" /></div></div></div><div style="height:70px;"><p>扫码下载梦幻西游手游</p></div></li><li class="con_r_right" style="display:block;"><div><div class="user"><div><span class="user-icon"></span><input type="text" id="login_username" /></div><div><span class="mima-icon"></span><input type="password" id="login_password" /></div><div><span class="yzmz-icon"></span><input type="text" id="code" />  <img id="captcha" alt="看不清?点击更换" /></div></div><br><button id="btn_Login" type="button">登 录</button></div></li></ul></div></div></div></div></body>
</html>

login.js

/*** 禁止输入空格*/
function preventSpace() {let event = window.event;if(event.keyCode === 32) {event.returnValue = false;}
}// 登录
function login() {let username = $("#login_username").val();let password = $("#login_password").val();let code = $("#code").val();if (!username) {alert("请输入用户名!");$("#login_username").focus();} else if (!password) {alert("请输入密码!");$("#login_password").focus();} else if (!code) {alert("请输入验证码!");} else {post("/user/login", {username: username,password: password,code: code}, function() {location.href = "/index.html";}, function (res) {if (res && res.responseJSON) {let response = res.responseJSON;if (res.status && res.status === 404) {let message;if(response.path) {message = "路径" + response.path + "不存在。";} else {message = response.message;}alert(message);} else {alert(response.message);}}});}
}$(function() {$("#login_username").keydown(function() {preventSpace();}).attr("placeholder", "请输入用户名");/*** 给密码输入框绑定回车登录事件*/$("#login_password").keydown(function(event) {if(event.keyCode === 13) {login();}preventSpace();}).attr("placeholder", "请输入密码");$("#code").keydown(function() {preventSpace();}).attr("placeholder", "验证码");// 获取uuidlet uuid = getStorage("uuid");if (!uuid) {uuid = new Date().getTime();storage("uuid", uuid);}$("#captcha").attr("src", "/captcha/generate?type=png&uuid=" + uuid);// 点击登录按钮$("#btn_Login").on("click", function () {login();});$(".content .con_right .left").on("click", function () {$(this).css({"color": "#333333","border-bottom": "2px solid #2e558e"});$(".content .con_right .right").css({"color": "#999999","border-bottom": "2px solid #dedede"});$(".content .con_right ul .con_r_left").css("display", "block");$(".content .con_right ul .con_r_right").css("display", "none");});$(".content .con_right .right").on("click", function () {$(this).css({"color": "#333333","border-bottom": "2px solid #2e558e"});$(".content .con_right .left").css({"color": "#999999","border-bottom": "2px solid #dedede"});$(".content .con_right ul .con_r_right").css("display", "block");$(".content .con_right ul .con_r_left").css("display", "none");});});

后端登录代码

UserLoginDTO.java

package cn.edu.sgu.www.mhxysy.dto.system;import lombok.Data;import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;/*** @author heyunlin* @version 1.0*/
@Data
public class UserLoginDTO implements Serializable {private static final long serialVersionUID = 18L;/*** 验证码*/@NotNull(message = "验证码不允许为空")@NotEmpty(message = "验证码不允许为空")private String code;/*** 用户名*/@NotNull(message = "用户名不允许为空")@NotEmpty(message = "用户名不允许为空")private String username;/*** 密码*/@NotNull(message = "密码不允许为空")@NotEmpty(message = "密码不允许为空")private String password;
}

UserController.java

package cn.edu.sgu.www.mhxysy.controller.system;import cn.edu.sgu.www.mhxysy.annotation.AnonymityAccess;
import cn.edu.sgu.www.mhxysy.annotation.Exclusion;
import cn.edu.sgu.www.mhxysy.dto.system.UserLoginDTO;
import cn.edu.sgu.www.mhxysy.restful.JsonResult;
import cn.edu.sgu.www.mhxysy.service.system.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;/*** @author heyunlin* @version 1.0*/
@Exclusion
@RestController
@Api(tags = "用户管理")
@RequestMapping(path = "/user", produces="application/json;charset=utf-8")
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@AnonymityAccess@ApiOperation("登录认证")@RequestMapping(value = "/login", method = RequestMethod.POST)public JsonResult<Void> login(@Validated UserLoginDTO loginDTO) {userService.login(loginDTO);return JsonResult.success();}/*省略的其他代码*/}

UserServiceImpl.java

package cn.edu.sgu.www.mhxysy.service.system.impl;import cn.edu.sgu.www.mhxysy.dto.system.UserLoginDTO;
import cn.edu.sgu.www.mhxysy.entity.system.User;
import cn.edu.sgu.www.mhxysy.entity.system.UserLoginLog;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.feign.FeignService;
import cn.edu.sgu.www.mhxysy.redis.RedisRepository;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import cn.edu.sgu.www.mhxysy.service.system.UserService;
import cn.edu.sgu.www.mhxysy.util.IpUtils;
import cn.edu.sgu.www.mhxysy.util.StringUtils;
import cn.edu.sgu.www.mhxysy.util.UserUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;/*** @author heyunlin* @version 1.0*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {private final FeignService feignService;private final RedisRepository redisRepository;private final StringRedisTemplate stringRedisTemplate;@Value("${syslog.enable}")private boolean enable;@Autowiredpublic UserServiceImpl(FeignService feignService,RedisRepository redisRepository,StringRedisTemplate stringRedisTemplate) {this.feignService = feignService;this.redisRepository = redisRepository;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic void login(UserLoginDTO loginDTO) {String code = loginDTO.getCode();String uuid = stringRedisTemplate.opsForValue().get("uuid");// 得到的uuid为空,则获取验证码到登录之间的时间已经过了5分钟,uuid已经过期if (uuid == null) {throw new GlobalException(ResponseCode.BAD_REQUEST, "验证码已失效,请刷新页面重新获取~");}if (!code.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(uuid))) {throw new GlobalException(ResponseCode.BAD_REQUEST, "验证码错误~");}// 得到用户名String username = loginDTO.getUsername();log.debug("用户{}正在登录...", username);// 查询用户信息,如果用户被锁定,提前退出User user = feignService.selectByUsername(username);if (user != null) {if (user.getEnable()) {// shiro登录认证UsernamePasswordToken token = new UsernamePasswordToken(username, loginDTO.getPassword());Subject subject = UserUtils.getSubject();subject.login(token);// 设置session失效时间:永不超时subject.getSession().setTimeout(-1001);// 修改管理员上一次登录时间User usr = new User();usr.setId(user.getId());usr.setLastLoginTime(LocalDateTime.now());feignService.updateById(usr);// 如果开启了系统日志if (enable) {// 添加管理员登录历史UserLoginLog loginLog = new UserLoginLog();loginLog.setId(StringUtils.uuid());loginLog.setUserId(user.getId());loginLog.setLoginTime(LocalDateTime.now());loginLog.setLoginIp(IpUtils.getLocalHostAddress());loginLog.setLoginHostName(IpUtils.getLocalHostName());feignService.saveLoginLog(loginLog);}// 从redis中删除用户权限redisRepository.remove(username);// 查询用户的权限信息,并保存到redisredisRepository.save(username);} else {throw new GlobalException(ResponseCode.FORBIDDEN, "账号已被锁定,禁止登录!");}} else {throw new GlobalException(ResponseCode.NOT_FOUND, "用户名不存在~");}}}

潜在问题

这样的设计会有一个问题,uuid这个key有可能会被其他用户修改,但是验证码并不会被修改。

改进方案

目前正在找解决方案~

好了,文章就分享到这里了,看完要是觉得对你有所帮助,不要忘了点赞+收藏哦~

相关文章:

怎么实现一个登录时需要输入验证码的功能

今天给项目换了一个登录页面&#xff0c;而这个登录页面设计了验证码&#xff0c;于是想着把这个验证码功能实现一下吧。 这篇文章就如何实现登录时的验证码的验证功能结合代码进行详细地介绍&#xff0c;以及介绍功能实现的思路。 目录 页面效果 实现思路 生成验证码的控制…...

在android工程中新建Android模块报错

复制了复制正常的build.gradle文件&#xff0c;然后把theme里面的东西改成了下面这个样就好了 <resources xmlns:tools"http://schemas.android.com/tools"><!-- Base application theme. --><style name"Theme.JiQuan" parent"Theme…...

电脑桌面的复选框如何取消

电脑桌面图标的复选框如何取消 1. 概述2. 去掉图标的复选框方法结束语 1. 概述 当你拿到新的电脑开机后&#xff0c;发现桌面上软件应用的图标左上角有个小框&#xff0c;每次点击图标都会显示&#xff0c;并且点击图标时&#xff0c;小框还会打上√&#xff1b; 这个小框的…...

【Unity每日一记】资源加载相关和检测相关

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…...

【数据结构】长篇详解堆,堆的向上/向下调整算法,堆排序及TopK问题

文章目录 堆的概念性质图解 向上调整算法算法分析代码整体实现 向下调整算法算法分析整体代码实现 堆的接口实现初始化堆销毁堆插入元素删除元素打印元素判断是否为空取首元素实现堆 堆排序创建堆调整堆整合步骤 TopK问题 堆的概念 堆就是将一组数据所有元素按完全二叉树的顺序…...

DAQ高频量化平台:引领Ai高频量化交易模式变革

近年来&#xff0c;数字货币投资市场掀起了一股热潮&#xff0c;以&#xff08;BTC&#xff09;为代表的区块链技术带来了巨大的商业变革。数字资产的特点&#xff0c;如无国界、无阶级、无门槛、高流动性和高透明度&#xff0c;吸引了越来越多的人们的关注和认可&#xff0c;创…...

vue3 element plus获取el-cascader级联选择器选中的当前结点的label值 附vue2获取当前label

各位大佬&#xff0c;有时我们在处理级联选择组件数据时&#xff0c;不仅需要拿到id,还需要拿到label名称&#xff0c;但是通常组件直接绑定的是id,所以就需要我们用别的方法去拿到label,此处官方是有这个方法的&#xff0c;具体根据不同的element 版本进行分别处理。 VUE3 e…...

Spring Boot常见面试题

Spring Boot简介 Spring Boot 是由 Pivotal 团队提供&#xff0c;用来简化 Spring 应用创建、开发、部署的框架。它提供了丰富的Spring模块化支持&#xff0c;可以帮助开发者更轻松快捷地构建出企业级应用。Spring Boot通过自动配置功能&#xff0c;降低了复杂性&#xff0c;同…...

分块矩阵求逆

另可参考Block matrix on Wikipedia2018.4.3 补充补充两个参考文献&#xff0c;都是对工科很实用的矩阵手册&#xff1a;D. S. Bernstein, Matrix mathematics: Theory, facts, and formulas with application to linear systems theory. Princeton, NJ: Princeton University …...

Python 文件写入操作

视频版教程 Python3零基础7天入门实战视频教程 w模式是写入&#xff0c;通过write方法写入内容。 # 打开文件 模式w写入&#xff0c;文件不存在&#xff0c;则自动创建 f open("D:/测试3.txt", "w", encoding"UTF-8")# write写入操作 内容写入…...

【Spring Boot系列】- Spring Boot侦听器Listener

【Spring Boot系列】- Spring Boot侦听器Listener 文章目录 【Spring Boot系列】- Spring Boot侦听器Listener一、概述二、监听器Listener分类2.1 监听ServletContext的事件监听器2.2 监听HttpSeesion的事件监听器2.3 监听ServletRequest的事件监听器 三、SpringMVC中的监听器3…...

JavaScript速成课—事件处理

目录 一.事件类型 1.窗口事件 2.表单元素事件 3.图像事件 4.键盘事件 5.鼠标事件 二.JavaScript事件处理的基本机制 三.绑定事件的方法 1.DOM元素绑定 2.JavaScript代码绑定事件 3.监听事件函数绑定 四.JavaScript事件的event对象 1.获取event对象 2.鼠标坐标获取…...

【入门篇】ClickHouse最优秀的开源列式存储数据库

文章目录 一、什么是ClickHouse&#xff1f;OLAP场景的关键特征列式数据库更适合OLAP场景的原因输入/输出CPU 1.1 ClickHouse的定义与发展历程1.2 ClickHouse的版本介绍 二、ClickHouse的主要特性2.1 高性能的列式存储2.2 实时的分析查询2.3 高度可扩展性2.4 数据压缩2.5 SQL支…...

【C++ Exceptions】异常处理的成本

最低成本 exception是C的一部分&#xff0c;编译器必须支持。即使从未使用任何异常处理机制&#xff0c;也必须付出一些空间放置某些数据结构&#xff0c;付出一些时间随时保持那些数据结构的正确性。 第二种成本&#xff1a;来自try语句块 避免非必要的try语句块。 粗略估计&a…...

API接口:原理、实现及应用

API&#xff08;Application Programming Interface&#xff09;接口是现代软件开发中不可或缺的一部分。它们提供了一种机制&#xff0c;使得不同的应用程序和服务可以相互通信&#xff0c;共享数据和功能。在这篇文章中&#xff0c;我们将探讨API接口的原理、实现及应用&…...

SpringBoot学习笔记(项目创建,yaml,多环境开发,整合mybatis SMM)

一、SpringBoot入门 1.1 SpringBoot概述 SpringBoot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化Spring应用的初始搭建以及开发过程。 Spring程序缺点&#xff1a;配置繁琐&#xff0c;依赖设置繁琐。SpringBoot程序优点&#xff1a;自动装配&#xff0c…...

Linux内核分析:输入输出,字符与块设备 31-35

CPU 并不直接和设备打交道,它们中间有一个叫作设备控制器(Device Control Unit)的组件,例如硬盘有磁盘控制器、USB 有 USB 控制器、显示器有视频控制器等。这些控制器就像代理商一样,它们知道如何应对硬盘、鼠标、键盘、显示器的行为。 输入输出设备我们大致可以分为两类…...

Linux抓包工具tcpdump

一、介绍 tcpdump是一个抓包工具&#xff0c;用于实时捕获和分析网络流量。它通常在unix和linux操作系统上使用。tcpdump能够捕获流经网络接口的数据包&#xff0c;并显示或保存它们以供进一步分析。它提供有关每个数据包的详细信息&#xff0c;包括源IP地址、目标IP地址、使用…...

Qt消息机制和事件

事件 事件是由Qt或者系统在不同时刻发出的,当敲下鼠标,或者按下键盘,或者当窗口需要重新绘制的时候,就会发出一个相应的事件,一些操作由用户的操作发出,一些则由系统自动发出,如系统定时器事件等。 Qt 中所有事件类都继承于 QEvent。 在事件对象创建完毕后, Qt 将这个…...

LeetCode-739-每日温度-单调栈

题目描述&#xff1a;给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 题目…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

C# winform教程(二)----checkbox

一、作用 提供一个用户选择或者不选的状态&#xff0c;这是一个可以多选的控件。 二、属性 其实功能大差不差&#xff0c;除了特殊的几个外&#xff0c;与button基本相同&#xff0c;所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...

MeshGPT 笔记

[2311.15475] MeshGPT: Generating Triangle Meshes with Decoder-Only Transformers https://library.scholarcy.com/try 真正意义上的AI生成三维模型MESHGPT来袭&#xff01;_哔哩哔哩_bilibili GitHub - lucidrains/meshgpt-pytorch: Implementation of MeshGPT, SOTA Me…...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

Selenium 查找页面元素的方式

Selenium 查找页面元素的方式 Selenium 提供了多种方法来查找网页中的元素&#xff0c;以下是主要的定位方式&#xff1a; 基本定位方式 通过ID定位 driver.find_element(By.ID, "element_id")通过Name定位 driver.find_element(By.NAME, "element_name"…...