SpringSecurity Oauth2 -账号密码实现多因子身份认证
1. 密码策略问题
CREATE TABLE `t_storage` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',`nameSpace` varchar(64) NOT NULL COMMENT '隔离字段',`groupId` varchar(128) NOT NULL COMMENT '分组,比如不同app',`dataId` varchar(64) NOT NULL COMMENT '数据存储id',`dataValue` mediumtext DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `nameSpace` (`nameSpace`,`groupId`,`dataId`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (1352, '_system', 'auth', 'access_token_validity_seconds', '43200');INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (1353, '_system', 'auth', 'refresh_token_validity_seconds', '86400');INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (36, '_system', 'console', 'consoleConfig', '{\r\n \"overTime\": 360,\r\n \"stageName\": \"安全大数据平台\",\r\n \"icmpEnable\": true,\r\n \"sshEnable\": false\r\n }');INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (45, '_system', 'auth', 'policy', '{\"passwordPeriod\":90,\"errorTimes\":5,\"lockTime\":5,\"minLength\":8}');
1. JsonStorage 封装处理Storage操作
@Slf4j
public class JsonStorage {private IStorage storage;@Autowiredprivate void setStorage(IStorage storage) {this.storage = storage;}private final ObjectMapper JSON_MAPPER = new ObjectMapper();/*** 通过实体对象设置Json字符串** @param nameSpace 命名空间* @param groupId 分组* @param key 键* @param entity 实体对象* @return* @throws JsonProcessingException*/public boolean setJsonDataWithEntity(String nameSpace, String groupId, String key, Object entity) {try {String jsonString = JSON_MAPPER.writeValueAsString(entity);storage.setDataValue(nameSpace, groupId, key, jsonString);return true;} catch (JsonProcessingException | StorageException e) {log.error("Storage setValue failed, error: {}", e);return false;}}/*** 获取由JsonString转化后的实体对象** @param nameSpace 命名空间* @param groupId 分组* @param key 键* @param classType 实体类型* @param <T> 泛型* @return 通过传入的类型返回对应的实体对象* @throws StorageException*/public <T> T getJsonDataAsEntity(String nameSpace, String groupId, String key, Class<T> classType) throws StorageException {try {String jsonString = storage.getDataValue(nameSpace, groupId, key);if (!StringUtils.isEmpty(jsonString)) {JSON_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);return JSON_MAPPER.readValue(jsonString, classType);} else {log.error("Storage get value failed, value is empty");throw new StorageException("storage.value.empty");}} catch (JsonProcessingException e) {log.error("Storage get value failed, jsonString format error", e);throw new StorageException("storage.getValue.format.error");}}
}
2. StorageDb 数据库数据查询和更新
@Slf4j
public class StorageDb implements IStorage {@Autowiredprivate RedisUtils redisUtils;@Autowired@Qualifier("storageJdbcTemplate")private JdbcTemplate jdbcTemplate;@Autowiredprivate StorageConfig storageConfig;@Override@CheckParampublic void setDataValue(@CheckParam(length = 48) String nameSpace,@CheckParam(length = 96) String groupId,@CheckParam(length = 96) String dataId,@CheckParam(length = 10000, isCheckLegal = false, required = false) String value) throws StorageException {// 同一nameSpace, groupId, dataId下数据不存在则新增,否则更新String updateSql = String.format("insert into %s(nameSpace,groupId,dataId,dataValue) values (?,?,?,?) "+ " on duplicate key update dataValue=values(dataValue)",StorageConstants.TABLE_NAME);int rows = jdbcTemplate.update(updateSql, nameSpace, groupId, dataId, value);if (rows > 0) {// 更新完数据将Redis缓存删除String keyName = generateKey(nameSpace, groupId, dataId);redisUtils.delete(keyName);}}@Override@CheckParampublic String getDataValue(@CheckParam(length = 48) String nameSpace,@CheckParam(length = 96) String groupId,@CheckParam(length = 96) String dataId) {// 拼接redis缓存keyString keyName = generateKey(nameSpace, groupId, dataId);// 先从redis进行获取数据String dataValue = redisUtils.getString(keyName);// 缓存不存在则查询数据库if (Objects.isNull(dataValue)) {String sql = String.format("select dataValue from %s where nameSpace=? and groupId=? and dataId=?", StorageConstants.TABLE_NAME);try {dataValue = jdbcTemplate.queryForObject(sql, String.class, nameSpace, groupId, dataId);} catch (DataAccessException e) {log.error("Failed to query storage, nameSpace = {}, groupId = {}, dataId = {}, error {}", nameSpace, groupId, dataId, e);}// 查询完数据将其写入redis,空值也缓存,防止缓存穿透redisUtils.setWithExpired(keyName, StringUtils.isEmpty(dataValue) ? "" : dataValue, storageConfig.getExpiredTime(), TimeUnit.SECONDS);}return dataValue;}/*** 生成rediskey** @param fields* @return*/private String generateKey(String... fields) {return StorageConstants.REDIS_KEY_PREFIX.concat(String.join(StorageConstants.SEPARATOR, fields));}
}
缓存查询优先:在 getDataValue
方法中,先查询 Redis 缓存,如果没有命中缓存,再查询数据库。这样有效减少了数据库查询次数,提高了性能。
缓存穿透防护:通过将空结果也缓存起来,避免恶意或频繁访问不存在的数据造成缓存穿透,进而影响数据库的负载。
缓存一致性:在 setDataValue
方法中,在更新或插入数据库后,会清除 Redis 缓存中的旧值,确保缓存与数据库的一致性。
3. 获取密码策略
1. PasswordPolicyEntity 密码策略
@ApiModel
@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class PasswordPolicyEntity {@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)@ApiModelProperty(value = "密码周期", required = true)private Integer passwordPeriod = 90;@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)@ApiModelProperty(value = "登录密码输错次数限制", required = true)private Integer errorTimes = 5;@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)@ApiModelProperty(value = "密码输入错误达到限制后锁定锁定时间", required = true)private Integer lockTime = 5;@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)@ApiModelProperty(value = "密码最小长度", required = true)private Integer minLength = 8;}
密码策略升级通过flyway初始化数据的:
INSERT INTO `t_storage`(`id`, `nameSpace`, `groupId`, `dataId`, `dataValue`) VALUES (45, '_system', 'auth', 'policy', '{\"passwordPeriod\":90,\"errorTimes\":5,\"lockTime\":5,\"minLength\":8}');
2. 获取密码策略并判断客户端IP是否被锁定
读取数据库中的数据并dataValue字段转换为PasswordPolicyEntity对象
@Slf4j
@Service("loginService")
public class LoginServiceImpl implements LoginService {@Overridepublic LoginResult checkLogin(LoginEntity loginEntity, HttpServletRequest request) {LoginResult loginResult = new LoginResult();// 获取客户端真实IPString clientIp = ClientUtils.getClientIp(request);// 读取密码策略PasswordPolicyEntity policyEntity = userService.readPasswordPolicy();// 判断客户端IP是否被锁定if (isLocked(clientIp)) {loginResult.setStatus(I18nUtils.i18n("response.login.lock.time", policyEntity.getErrorTimes(), policyEntity.getLockTime()));return loginResult;}}
}
获取密码策略:
@Slf4j
@Service("userService")
public class UserServiceImpl implements IUserService {private final ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock().readLock();@Overridepublic PasswordPolicyEntity readPasswordPolicy() {PasswordPolicyEntity passwordPolicyEntity = null;readLock.lock();try {passwordPolicyEntity = jsonStorage.getJsonDataAsEntity(SettingNameSpaceConstant.Config.SYSTEM_CONFIG, AuthLoginConstant.AUTH_DIRECTORY, AuthLoginConstant.POLICY, PasswordPolicyEntity.class);} catch (StorageException e) {log.error("Error read pwd policy", e);} finally {readLock.unlock();}return passwordPolicyEntity;}
}
判断客户端IP是否被锁定,密码输入错误次数达到5次后就会被锁定,锁定时间为5分钟:
@Override
public boolean isLocked(String ipAddress) {HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();String authIpAddressKey = RedisKeyUtil.getAuthIpAddressKey(ipAddress);String next = hashOperations.get(authIpAddressKey, Constants.NgsocAuth.NEXT);if (next != null) {Integer lockTime = userService.readPasswordPolicy().getLockTime();// 当前时间(分钟)-锁定时间<=5分钟,说明锁定了double differentTime = (LocalDateUtils.getNowMilliseconds() - Long.parseLong(next)) / (1000 * 60 * 1.0);return (int) differentTime <= lockTime;}return false;
}
判断某个 IP 地址是否被锁定。具体来说,它通过从 Redis 中获取该 IP 地址相关的锁定时间(next
),并根据当前时间与锁定时间的差值来判断是否仍处于锁定状态。如果锁定时间未超过指定的时长(lockTime
),则该 IP 地址被视为仍然锁定。
2. 多因子认证登录
1. 登录实体类 LoginEntity2. 认证方式枚举 AuthMethodEnum
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum AuthMethodEnum {/*** 密码校验*/PASSWORD("password", "密码校验"),/*** 手机短信验证码*/SMS("sms", "手机短信验证码"),;private String code;private String desc;public static AuthMethodEnum getByCode(String code) {for (AuthMethodEnum authMethodEnum : values()) {if (authMethodEnum.getCode().equals(code)) {return authMethodEnum;}}return null;}
}
3. 身份认证接口 AuthMethod
public interface AuthMethod {/*** 进行认证** @param loginEntity 参数* @return String 用户ID*/String doAuthenticate(LoginEntity loginEntity);
}
4. 密码认证方式 PasswordAuthMethod
利用责任链模式实现账号密码的多因子身份认证机制,在密码认证时利用Redis Hash结构处理登录失败的次数记录和客户端锁定逻辑。
对用户账号和密码进行校验,并在校验通过后生成并返回用户的身份标识 (userId
)。同时,它还处理了登录失败的次数记录和账户锁定逻辑。
定义了一个名为 PasswordAuthMethod
的类,该类实现了 AuthMethod
接口,用于处理用户通过密码进行身份认证的逻辑。它使用了 Redis 来管理登录失败次数和客户端 IP 的锁定机制。
密码认证逻辑 (doAuthenticate
):
- 获取用户输入的用户名、域 ID、原始密码和客户端 IP 地址。
- 调用
userService.findUserByNameAndDomainId
获取用户信息,如果用户不存在,记录登录失败次数并返回。 - 通过
BcryptUtil.bEncryptMatch
比较明文密码和数据库中加密的密码,判断密码是否正确。如果密码不正确,记录失败次数并返回。 - 如果密码正确且未超过登录失败次数限制,删除与该 IP 相关的锁定信息,允许登录。
记录登录失败次数 (recordLoginAttempts
):
- 通过 Redis 记录 IP 地址对应的登录失败次数,使用
increment
方法将登录失败次数加 1。 - 根据从
userService.readPasswordPolicy()
获取的密码策略,设置失败次数锁定的过期时间和锁定阈值。 - 当登录失败次数超过或等于设定的错误次数时,锁定客户端 IP,并记录锁定的时间。
/*** 密码登陆认证方式*/
@Slf4j
@Component("passwordAuthMethod")
public class PasswordAuthMethod implements AuthMethod {@Autowiredprivate IUserService userService;@Qualifier("stringRedisTemplate")@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 对客户端输入的账号密码校验:先校验账号是否存在,再校验密码是否正确*/@Overridepublic String doAuthenticate(LoginEntity loginEntity) {String clientIp = loginEntity.getClientIp();String username = loginEntity.getAuth().getIdentity().getPassword().getUser().getName();String domainId = loginEntity.getAuth().getIdentity().getPassword().getUser().getDomain().getId();String originalPassword = loginEntity.getAuth().getIdentity().getPassword().getUser().getOriginalPwd();// 根据账号名称查询账号信息UserEntity userInfo = userService.findUserByNameAndDomainId(username, domainId);// 账号不存在登录失败,将尝试登录次数加1,并判断是否锁定客户端IPif (Objects.isNull(userInfo)) {log.info("the domainId:{}, account:{}, not exist", domainId, username);recordLoginAttempts(clientIp);return StringUtils.EMPTY;}// 进行密码的比对,传输过来的是明文String userId = userInfo.getId();String dbEncryptPwd = userInfo.getPassword();boolean isPassed = BcryptUtil.bEncryptMatch(originalPassword, dbEncryptPwd);// 密码错误登录失败,将尝试登录次数加1,并判断是否锁定客户端IPif (!isPassed) {log.info("the user: {} login failed, account or password is wrong", userId);recordLoginAttempts(clientIp);return StringUtils.EMPTY;}// 账号存在且密码正确登陆成功,去除客户端锁定的限制String authIpAddressKey = RedisKeyUtil.getAuthIpAddressKey(clientIp);redisTemplate.delete(authIpAddressKey);// 密码校验成功后返回userIdreturn userId;}/*** 记录客户端登录失败次数*/private void recordLoginAttempts(String ip) {// 获取客户端IP的keyString authIpAddressKey = RedisKeyUtil.getAuthIpAddressKey(ip);// 客户端登录失败次数加1,并设置过期时间为5分钟HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();int attempts = hashOperations.increment(authIpAddressKey, Constants.NgsocAuth.ATTEMPTS, 1).intValue();PasswordPolicyEntity policyEntity = userService.readPasswordPolicy();redisTemplate.expire(authIpAddressKey, policyEntity.getLockTime(), TimeUnit.MINUTES);// 如果客户端登录失败次数大于等于5,则将客户端IP锁定,设置value当前时间,过期时间为5分钟if (attempts >= policyEntity.getErrorTimes()) {hashOperations.put(authIpAddressKey, Constants.NgsocAuth.NEXT, String.valueOf(LocalDateUtils.getNowMilliseconds()));redisTemplate.expire(authIpAddressKey, policyEntity.getLockTime(), TimeUnit.MINUTES);}}
}
记录登录错误次数:
- 通过
redisTemplate.opsForHash().increment
方法,将 IP 地址对应的登录错误次数递增 1,并将其存储在 Redis 的哈希结构中,键为authIpAddressKey
,字段为Constants.NgsocAuth.ATTEMPTS
。
获取密码策略:
- 使用
userService.readPasswordPolicy()
获取密码策略,包含锁定时间(lockTime
)和允许的最大错误次数(errorTimes
)。
设置 Redis 键过期时间:
- 每次记录登录失败后,都会设置或更新该 IP 地址的 Redis 键的过期时间,过期时间是根据密码策略中的
lockTime
决定的,单位为分钟。
超过错误次数,记录锁定时间:
- 如果错误次数超过策略中定义的最大次数,则在 Redis 中记录锁定时间(
NEXT
),以便在后续检查时根据该时间判断是否锁定 IP 地址。
5. 短信验证码认证方式 SmsAuthMethod
@Component("smsAuthMethod")
public class SmsAuthMethod implements AuthMethod {@Overridepublic String doAuthenticate(LoginEntity loginEntity) {return null;}
}
6. 身份认证责任链 AuthMethodChain
public class AuthMethodChain {private final List<AuthMethod> authMethodList = new ArrayList<>();public AuthMethodChain add(AuthMethod authMethod) {authMethodList.add(authMethod);return this;}/*** 获取用户ID*/public String execute(LoginEntity loginEntity) {String userId = StringUtils.EMPTY;Set<String> userIdSet = new HashSet<>();for (AuthMethod chain : authMethodList) {userId = chain.doAuthenticate(loginEntity);// 只要有一个校验不通过则返回if (StringUtils.isBlank(userId)) {return StringUtils.EMPTY;}// 校验返回的不是同一个用户则不通过userIdSet.add(userId);if (userIdSet.size() > 1) {return StringUtils.EMPTY;}}return userId;}
}
7. 进行多因子登陆认证的校验
在实现多因子登录认证时,使用责任链模式是一种优雅的解决方案。责任链模式通过将一系列的身份认证步骤(例如密码验证、短信验证码验证、二次确认等)串联起来,每个步骤负责处理一部分认证逻辑,若该步骤无法完成认证,则将任务传递给下一个步骤。
通过责任链模式,实现了灵活且可扩展的多因子身份认证机制。每个认证步骤各自负责一部分逻辑,并按照顺序串联起来,如果某一步认证失败,链条会立即中断,从而简化了认证流程的管理逻辑。
可以轻松地增加新的认证步骤(如生物识别、二维码认证等),只需实现 AuthMethod 接口并插入责任链中即可。
@Slf4j
@Service("loginService")
public class LoginServiceImpl implements LoginService {@Resource(name = "passwordAuthMethod")private AuthMethod passwordAuthMethod;@Resource(name = "smsAuthMethod")private AuthMethod smsAuthMethod;/*** 登陆校验*/@Overridepublic LoginResult checkLogin(LoginEntity loginEntity, HttpServletRequest request) {LoginResult loginResult = new LoginResult();String clientIp = ClientUtils.getClientIp(request);// 读取密码策略PasswordPolicyEntity policyEntity = userService.readPasswordPolicy();// 登录错误次数限定if (isLocked(clientIp)) {loginResult.setStatus(I18nUtils.i18n("response.login.lock.time", policyEntity.getErrorTimes(), policyEntity.getLockTime()));return loginResult;}// 进行多因子登陆认证的校验List<String> authMethodList = loginEntity.getAuth().getIdentity().getMethods();AuthMethodChain authMethodChain = new AuthMethodChain();for (String authMethod : authMethodList) {AuthMethodEnum authMethodEnum = AuthMethodEnum.getByCode(authMethod);switch (authMethodEnum) {// 密码校验case PASSWORD:authMethodChain.add(passwordAuthMethod);break;// 短信验证码校验case SMS:authMethodChain.add(smsAuthMethod);break;default:loginResult.setStatus(I18nUtils.i18n("auth.method.undefined"));return loginResult;}}// 执行认证loginEntity.setClientIp(clientIp);String userId = authMethodChain.execute(loginEntity);// userId为空,说明认证失败if (StringUtils.isBlank(userId)) {loginResult.setStatus(I18nUtils.i18n("response.account.login.failed"));return loginResult;}}
}
相关文章:
SpringSecurity Oauth2 -账号密码实现多因子身份认证
1. 密码策略问题 CREATE TABLE t_storage (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 自增主键,nameSpace varchar(64) NOT NULL COMMENT 隔离字段,groupId varchar(128) NOT NULL COMMENT 分组,比如不同app,dataId varchar(64) NOT NULL COMMENT 数据存储id…...
【CSS in Depth 2 精译_071】11.4 思考字体颜色的对比效果 + 11.5 本章小结
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 11 章 颜色与对比】 ✔️ 11.1 通过对比进行交流 11.1.1 模式的建立11.1.2 还原设计稿 11.2 颜色的定义 11.2.1 色域与色彩空间11.2.2 CSS 颜色表示法 11.2.2.1 RGB…...
Y3编辑器文档4:触发器1(对话、装备、特效、行为树、排行榜、不同步问题)
文章目录 一、触发器简介1.1 触发器界面1.2 ECA语句编辑及快捷键1.3 参数设置1.4 变量设置1.5 实体触发器1.6 函数库与触发器复用 二、触发器的多层结构2.1 子触发器(在游戏内对新的事件进行注册)2.2 触发器变量作用域2.3 复合条件2.4 循环2.5 计时器2.6…...
趣味编程:猜拳小游戏
1.简介 这个系列的第一篇以猜拳小游戏开始,这是源于我们生活的灵感,在忙碌的时代中,我们每个人都在为自己的生活各自忙碌着,奔赴着自己所走向的那条路上,即使遍体鳞伤。 但是,生活虽然很苦,也不…...
软件工程 概述
软件 不仅仅是一个程序代码。程序是一个可执行的代码,它提供了一些计算的目的。 软件被认为是集合可执行的程序代码,相关库和文档的软件。当满足一个特定的要求,就被称为软件产品。 工程 是所有有关开发的产品,使用良好定义的&…...
CountDownLatch阻塞后countDown未执行会如何?
背景 某项目封装了 Kafka 消费者 API,根据传递的消费者线程数,创建 N 个消费者线程同时消费对应 topic 的数据,并在线程启动后收集到全局列表中,方便在程序调用 stop 流程时逐个停止。 主控类在创建 Kafka 消费线程时使用了 Cou…...
k8s,operator
相对更加灵活和编程友好的管理“有状态应用”的解决方案,它就是:Operator 会议一下有状态应用: 比如数据库集群,数据挂载需要有顺序维护拓扑关系的应用 使用statefulSet这个对象来描述。 CRD又是什么? Operator的工作…...
使用 pyperclip 进行跨平台剪贴板操作
简介:pyperclip 是一个轻量级的 Python 库,支持在不同操作系统(Windows、macOS、Linux)中进行剪贴板的复制和粘贴。这个库的设计简单易用,非常适合需要频繁进行文本复制粘贴操作的场景。 历史攻略: 使用f…...
20 设计模式之职责链模式(问题处理案例)
一、什么是职责链模式 职责链模式是一种行为型设计模式,它允许将请求沿着处理者的链进行传递,直到有一个处理者能够处理它为止。换句话说,它将请求的发送者和接收者解耦,使得多个对象都有机会处理这个请求,从而避免了将…...
SpringBoot3集成MybatisPlus3和knife4j(swagger3兼容增强版)
针对Swagger2规范和OpenAPI3规范的说明: 在Spring Boot框架中,Knife4j对于服务端将Spring的开放接口解析成Swagger2或者OpenAPI3规范的框架,也是依赖的第三方框架组件。说明如下: Swagger2规范:依赖Springfox项目,该项目目前几乎处于停更状态,但很多老项目依然使用的是该…...
【MIT-OS6.S081作业1.3】Lab1-utilities primes
本文记录MIT-OS6.S081 Lab1 utilities 的primes函数的实现过程 文章目录 1. 作业要求primes (moderate)/(hard) 2. 实现过程2.1 代码实现 1. 作业要求 primes (moderate)/(hard) Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, in…...
游戏引擎学习第35天
开场介绍 今天的任务是继续改进一个虚拟的瓦片地图系统,使其适合处理更大的世界。我们希望这个系统能管理大范围的游戏世界,其中包含按需存储的小区域。昨天,我们介绍了“内存区域”的概念,用于管理持久性存储。我们计划今天继续…...
learn-(Uni-app)输入框u-search父子组件与input输入框(防抖与搜索触发)
1.父子组件u-search (1)父组件 <!-- 父组件 --> <template> <div><searchBar change"change" search"search"></searchBar> </div> </template> <script> // 子组件搜索 import…...
设置IMX6ULL开发板的网卡IP的两种方法(临时生效和永久有效两种方法)
设置开发板网卡的IP,有两种方法。 方法一:临时生效 第一种方式是临时设置,只有本次有效,重启后又要重新设,命令为: ifconfig eth0 192.168.5.9设置成功后可以使用ifconfig命令来查看已设置的 IP 地址。 …...
流量转发利器之Burpsuite概述(1)
目录 一、Burpsuite Burp Suite Spider 的主要特点: 在 Burp Suite 中使用 Spider: Spider 的用例: 限制: 声明:学习视频来自b站up主 泷羽sec,如涉及侵权马上删除文章 声明:本文主要用作技…...
Transformer入门(6)Transformer编码器的前馈网络、加法和归一化模块
文章目录 7.前馈网络8.加法和归一化组件9.组合所有编码器组件构成完整编码器 7.前馈网络 编码器块中的前馈网络子层如下图所示: 图1.32 – 编码器块 前馈网络由两个带有ReLU激活函数的全连接层组成。全连接层(Fully Connected Layer)有时也…...
element-plus中的resetFields()方法
resetFields()确实是Element Plus中的方法,该方法主要用于重置表单,将其值重置为初始值,并移除校验结果。以下是对该方法的详细解释: 一、resetFields方法的作用 在Vue3结合Element Plus开发时࿰…...
【过滤器】.NET开源 ORM 框架 SqlSugar 系列
目录 0、 过滤器介绍 1、表过滤器 (推荐) 1.1 手动添加过滤器 1.2 禁用、清空、备份和还原 1.3 联表查询设置 1.4 动态添加 2、修改和删除用过滤器 2.1 局部设置 2.2 全局设置 (5.1.4.62) 3、子查询用过滤器 4、联表过滤…...
Jmeter Address already in use: connect 解决
做压测接口时,并发一段时间后,会报java.net.BindException: Address already in use: connect 原因: windows提供给TCP/IP链接的端口为 1024-5000,并且要四分钟来循环回收它们,就导致在短时间内跑大量的请求时将端口占…...
C#常见错误—空对象错误
System.NullReferenceException:未将对象引用设置到对象的实例 在C#编程中,System.NullReferenceException是一个常见的运行时异常,其错误信息“未将对象引用设置到对象的实例”意味着代码试图访问一个未被初始化或已被设置为null的对象的成…...
Leetcode数学部分笔记
Leetcode数学部分笔记 1. 回文数2. 加一3. 阶乘后的零4. x 的平方根5. Pow(x, n) 1. 回文数 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数 是指正序(从左向右)和倒序&…...
微信小程序web-view 嵌套h5界面 实现文件预览效果
实现方法:(这里我是在小程序里面单独加了一个页面用来下载预览文件) 安装 使用方法请参考文档 npm 安装 npm install weixin-js-sdk import wx from weixin-js-sdk预览 h5界面代码 <u-button click"onclick" type"primary" :loading"…...
【汽车】-- 燃油发动机3缸和4缸
3缸和4缸燃油发动机是小轿车常见的发动机配置。以下从结构特点、性能、经济性等方面对两者进行对比,并分析优缺点及使用注意事项: 1. 结构与运行原理 3缸发动机 特点:少一个气缸,内部零部件更少,整体结构更紧凑。优点…...
轻量级的 HTML 模板引擎
Mustache 简介:Mustache 是一个非常简单的逻辑少的模板引擎,支持 HTML 文件中的占位符替换。它不会执行复杂的逻辑,只支持简单的变量替换。 安装: npm install mustache示例: const Mustache require(mustache);c…...
Mysql | 尚硅谷 | 第02章_MySQL环境搭建
Mysql笔记:第02章_MySQL环境搭建 说明:本内容整理自尚硅谷B站MySQL视频>>尚硅谷B站MySQL视频 文章目录 Mysql笔记:第02章_MySQL环境搭建第02章_MySQL环境搭建 1. MySQL的卸载步骤1:停止MySQL服务步骤2:[软件](h…...
Maven学习(传统Jar包管理、Maven依赖管理(导入坐标)、快速下载指定jar包)
目录 一、传统Jar包管理。 (1)基本介绍。 (2)传统的Jar包导入方法。 1、手动寻找Jar包。并放置到指定目录下。 2、使用IDEA的库管理功能。 3、配置环境变量。 (3)传统的Jar包管理缺点。 二、Maven。 &#…...
CTF: 在本地虚拟机内部署CTF题目docker
step 1 安装基本依赖 sudo apt-get update sudo apt-get install -y \ca-certificates \curl \gnupg \lsb-releasestep 2 安装docker sudo apt-get remove docker docker.io containerd runc sudo apt-get update sudo apt-get install \apt-transport-https \ca-certificate…...
视频推拉流EasyDSS无人机直播技术巡查焚烧、烟火情况
焚烧作为一种常见的废弃物处理方式,往往会对环境造成严重污染。因此,减少焚烧、推广绿色能源和循环经济成为重要措施。通过加强森林防灭火队伍能力建设与长效机制建立,各地努力减少因焚烧引发的森林火灾,保护生态环境。 巡察烟火…...
SpringBoot【十一】mybatis-plus实现多数据源配置,开箱即用!
一、前言🔥 环境说明:Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE 正常情况下我们在开发系统的时候都是使用一个数据源,但是由于有些项目同步数据的时候不想造成数据库io消耗压力过大,便会一个项目对应多个数据源…...
【嵌入式linux基础】关于linux文件多次的open
在 Linux 中,设备文件可以被多次打开(open()),但这取决于具体的设备类型和其驱动程序的实现。以下是关于设备文件多次打开的一些关键点: 普通字符设备: 对于大多数字符设备,如串口、TTY 设备等&…...
网站整体结构/深圳英文网站推广
AspAccess的程序在NTFS分区上常常出现这样那样的问题,这切都是安全权限惹的祸,所以要想正常调试一个网站还需要更详细的设置。1.安装IIS7右单击的桌面上的[计算机]》选择[管理]扩展[角色]展卷栏》单击[添加角色]》在[添加角色向导]对话框中选择[Web服务器…...
做网站在线视频如何添加/app推广30元一单
文章目录基于阈值的分割方法Otsu阈值分割自适应阈值分割最大熵阈值分割法迭代阈值分割基于边缘的分割方法基于区域的分割方法基于图论的分割方法基于聚类的分割方法基于能量泛函的分割方法曲线演化理论snake方法水平集(LevelSet)Active Contours Without Edges能量函数用水平集…...
asp做网站好不好/seo搜索优化公司排名
一般有时候我们需要生成唯一主键id,如果数据库是mysql我们可以使用主键自增,如果是oracle我们可以创建触发器或者序列,如果不借助数据库我们也可以在java层面自己生成唯一主键。 使用随机数: /*生成唯一主键格式:时间…...
手机网站服务器/宁波超值关键词优化
常量与常量指针 #include <iostream> using namespace std; int main() {int a 3;const int *p &a;cout << *p << endl; // *p 20; // cout << *p << endl;//变量的值是常量,不能通过修改指向的变量的值,都…...
海南高端网站建设/郑州网站关键词推广
某些情况下我们需要对小程序某些用户的行为进行数据进行统计,比如统计某个页面的UV, PV等,统计某个功能的使用情况等。好让产品对于产品的整个功能有所了解。 在网页里,我们很多人都用过谷歌统计,小程序里也有一些第三方数据统计的…...
集团网站建设的要求/seo优化排名公司
时间安排 7:00~7:10 先看题,三道计数,一道DS ,这个DS一看就很像树上莫队 7:10~7:40 T2套路地枚举中位数,然后就能dp了,有60pts,于是赶紧写 7:50~8:00 发现T3需要写平衡树,而且莫队套平衡树…...