MFA多因素认证与TOTP算法核心解析(含Java案例)

article/2025/7/20 0:31:17

目录

    • 一、多因素认证(MFA)概述
      • MFA基本概念
      • MFA与2FA的区别
      • MFA的重要性
    • 二、TOTP算法原理
      • TOTP基本概念
      • 时间变量T的计算
      • TOTP生成过程
      • TOTP验证过程
    • 三、TOTP在MFA中的应用
      • 绑定流程
      • 认证流程
      • TOTP的优势
    • 四、TOTP的安全考虑
      • 哈希算法选择
      • 密钥管理
      • 防暴力破解
      • 时间同步
      • 通信安全
    • 五、TOTP的实现方式
      • 虚拟MFA设备
      • 硬件MFA设备
    • 六、MFA的应用场景
      • 企业安全
      • 行业应用
      • 个人应用
    • 七、MFA的部署实施
      • 实施步骤
      • 企业级解决方案
    • 八、TOTP算法的扩展与变种
      • HOTP算法
      • 算法增强
    • 九、MFA的挑战与未来发展
      • 当前挑战
      • 未来趋势
    • 十、Java代码案例
      • MultiFactorAuthenticatorUtil.java
      • QRCodeUtil.java
      • pom.xml
    • 十一、总结

在这里插入图片描述

多因素认证(MFA)已成为现代网络安全体系的重要组成部分,它通过结合多种身份验证因素大幅提升了系统安全性。本文将全面介绍MFA的概念、原理、实现方式,并深入解析其核心算法TOTP(基于时间的一次性密码)的技术细节与实现机制。

一、多因素认证(MFA)概述

MFA基本概念

多因素认证(Multi-Factor Authentication,MFA)是一种安全验证方法,要求用户在登录过程中提供两个或多个不同类型的身份验证因素,以确认其身份真实性。根据微软的研究数据,启用MFA的用户账户被黑客入侵的可能性降低了99%。

MFA的核心思想是结合多种独立的验证因素,通常包括以下三类:

  1. 知识因素(What you know):用户记忆的信息,如密码、PIN码或安全问题答案
  2. 所有权因素(What you have):用户拥有的物理设备或令牌,如智能手机、硬件令牌或智能卡
  3. 生物因素(What you are):用户独特的生物特征,如指纹、面部识别或虹膜扫描

MFA与2FA的区别

双因素认证(2FA)是MFA的一个子集,特指使用两种不同验证因素的认证方式。而MFA可以包含两种或更多因素,提供更高的安全性。例如,同时使用密码(知识因素)、手机验证码(所有权因素)和指纹(生物因素)就构成了三因素认证。

MFA的重要性

在当今网络安全威胁日益复杂的背景下,MFA的重要性体现在多个方面:

  1. 防止密码破解:即使攻击者获取了用户密码,仍需突破其他验证因素
  2. 抵御钓鱼攻击:虚假网站难以同时获取多种验证因素
  3. 符合合规要求:许多行业法规要求对敏感系统实施MFA保护
  4. 降低数据泄露风险:多层次的防御显著减少未经授权访问的可能性

二、TOTP算法原理

TOTP基本概念

TOTP(Time-Based One-Time Password)是基于时间的一次性密码算法,是MFA领域最普遍的实现方式之一。它已被IETF接纳为RFC 6238标准,成为开放认证的基石。

TOTP实际上是HOTP(HMAC-Based One-Time Password)算法的一个特例,使用时间变量代替了HOTP中的计数器。其核心公式为:

TOTP = HOTP(K, T)

其中:

  • K:客户端与服务器预先共享的密钥
  • T:基于当前时间计算的时间变量

时间变量T的计算

时间变量T并非简单的时间戳,而是通过以下公式计算:

T = Floor((当前时间戳 - T0) / X)

其中:

  • X:时间步长(默认30秒)
  • T0:UTC起始时间戳(1970年1月1日)
  • Floor:向下取整函数

例如,当X=30秒时:

  • 时间戳59秒对应的T=1
  • 时间戳60秒对应的T=2

TOTP生成过程

TOTP的6位验证码生成过程如下:

  1. 获取共享密钥K(通常为Base32编码)
  2. 计算当前时间片段T
  3. 使用HMAC-SHA1算法计算哈希值:HMAC-SHA1(K, T)
  4. 动态截取哈希值的部分字节
  5. 将截取结果转换为整数并取模1000000,得到6位数字

TOTP验证过程

服务器端验证TOTP的流程:

  1. 接收用户提交的TOTP代码
  2. 使用相同算法和共享密钥生成当前时间片的TOTP
  3. 比较两者是否一致
  4. 可选:检查该TOTP是否已被使用过(保证一次性)

三、TOTP在MFA中的应用

绑定流程

  1. 生成密钥:后台生成随机密钥(通常16字符Base32)
  2. 展示二维码:将密钥以二维码形式展示给用户
  3. 扫码绑定:用户使用MFA应用(如Google Authenticator)扫码添加账户
  4. 验证绑定:用户输入应用生成的6位代码完成绑定

认证流程

  1. 用户输入用户名和密码(第一因素)
  2. 系统要求提供MFA验证码
  3. 用户打开MFA应用获取当前6位代码
  4. 用户输入代码提交验证
  5. 服务器验证代码有效性

TOTP的优势

  1. 离线工作:客户端和服务端无需网络通信,只需时间同步
  2. 标准化:遵循RFC标准,兼容多种应用和设备
  3. 易实施:无需专用硬件,智能手机应用即可实现
  4. 用户体验:30秒有效期的6位数字易于输入

四、TOTP的安全考虑

哈希算法选择

虽然TOTP标准推荐HMAC-SHA1,但实现上也可使用更安全的HMAC-SHA256或HMAC-SHA5123。不过HMAC-SHA1仍然是兼容性最好的选择,被Google Authenticator等主流应用采用。

密钥管理

  1. 密钥随机性:共享密钥必须足够随机,长度应与哈希算法匹配
  2. 安全存储:服务器应加密存储密钥,仅在验证时解密
  3. 最小权限:限制只有验证系统能访问密钥

防暴力破解

6位TOTP代码理论上存在暴力破解风险,工程实践中应:

  • 限制尝试次数(如5次失败后锁定)
  • 记录已使用的TOTP,防止重复使用

时间同步

  1. 时间片段选择:默认30秒在安全性和可用性间取得平衡
  2. 时钟偏移处理:允许±1个时间片段的容错窗口
  3. 校准机制:支持时钟偏差检测和调整

通信安全

TOTP代码传输应通过安全通道(如SSL/TLS)进行,防止中间人攻击。

五、TOTP的实现方式

虚拟MFA设备

通过手机应用程序模拟硬件MFA设备,常见应用包括:

  • Google Authenticator
  • Microsoft Authenticator
  • FreeOPT

虚拟MFA通过以下方式获取共享密钥:

  1. 扫描二维码
  2. 手动输入Base32密钥

硬件MFA设备

专用硬件设备生成TOTP代码,如:

  • 信用卡形状的硬件令牌(按下按钮显示6位数字)
  • USB安全令牌
  • 智能卡

硬件设备优势:

  • 不依赖智能手机
  • 更高的物理安全性

六、MFA的应用场景

企业安全

  1. 远程访问:VPN、云桌面等远程办公场景
  2. 数据中心:网络设备、服务器、数据库的账号保护
  3. 网络接入:有线/无线网络的认证加固

行业应用

  1. 金融服务:网上银行、证券交易等高安全需求场景
  2. 医疗保健:保护患者敏感医疗数据
  3. 政府机构:国防、司法等机密信息系统
  4. 云计算:云服务控制台和敏感操作保护

个人应用

  1. 电子邮件:防止邮箱被盗导致的连锁反应
  2. 社交媒体:保护个人隐私和社交账户
  3. 在线购物:支付和交易安全

七、MFA的部署实施

实施步骤

  1. 需求评估:确定需要MFA保护的系统和场景
  2. 因素选择:组合知识、所有权和生物因素
  3. 技术选型:选择虚拟MFA、硬件令牌或生物识别方案
  4. 系统集成:将MFA集成到现有身份验证流程中
  5. 用户培训:指导用户正确使用MFA

企业级解决方案

以宁盾MFA为例,企业级解决方案包含:

  1. 认证服务器:集中管理MFA策略和用户
  2. 多种令牌形式:手机APP、硬件令牌、企业微信/钉钉集成等
  3. 高级功能
    • 多账号源兼容(AD、LDAP等)
    • 令牌批量派发和管理
    • 基于角色的访问策略
    • 安全审计和告警

八、TOTP算法的扩展与变种

HOTP算法

TOTP的前身是HOTP(HMAC-Based OTP),使用计数器而非时间作为变量。其公式为:

HOTP = Truncate(HMAC-SHA1(K, C))

其中C为递增计数器3。

算法增强

  1. 哈希算法升级:可使用HMAC-SHA256或HMAC-SHA512增强安全性
  2. 代码长度:可扩展至7-8位提高安全性(但影响用户体验)
  3. 时间窗口调整:缩短时间片段(如15秒)增加安全性,但降低可用性

九、MFA的挑战与未来发展

当前挑战

  1. 用户体验:增加登录步骤和时间
  2. 设备依赖:需要智能手机或专用硬件
  3. 恢复机制:设备丢失时的账户恢复流程复杂

未来趋势

  1. 无密码认证:结合生物识别和通行密钥(Passkey)逐步取代密码
  2. 自适应MFA:基于风险评估动态调整认证要求
  3. 量子安全算法:抗量子计算的密码学算法研究
  4. 生态系统融合:跨平台、跨应用的统一MFA体验

十、Java代码案例

MultiFactorAuthenticatorUtil.java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;/*** MFA身份验证器工具类** @author qiuyu*/
@Slf4j
public final class MultiFactorAuthenticatorUtil {private static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";private static final int SECRET_SIZE = 10;private static final int WINDOW_SIZE = 1;private static final int TIME_STEP_SECONDS = 30;private static final int CODE_DIGITS = 6;// 密钥种子(这里可以采取配置的方式自行维护)private static final String SEED = "Yu$s&L4@LsRqIn7b";// 令牌签发者(这里可以采取配置的方式自行维护,一般为登录系统名称,例如:gitlab)private static final String ISSUER = "gitlab";private static final Base32 BASE_32 = new Base32();private MultiFactorAuthenticatorUtil2() {}/*** 生成令牌秘钥** @return 令牌秘钥,如果生成失败则返回null*/public static String generateSecretKey() {try {SecureRandom sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);sr.setSeed(Base64.decodeBase64(SEED));byte[] buffer = sr.generateSeed(SECRET_SIZE);return BASE_32.encodeToString(buffer);} catch (NoSuchAlgorithmException e) {log.error("Failed to generate secret key", e);return null;}}/*** 生成Base64格式的二维码图片** @param user   用户名,不能为空* @param secret 令牌秘钥,不能为空* @return Base64图片字符串* @throws IllegalArgumentException 如果参数为空*/public static String getQRBarcode(String user, String secret) {if (user == null || user.trim().isEmpty() || secret == null || secret.trim().isEmpty()) {throw new IllegalArgumentException("User and secret must not be empty");}String format = "otpauth://totp/%s?secret=%s&issuer=%s";String imageContent = String.format(format, user, secret, ISSUER);log.debug("Generating QR code for: {}", imageContent);return QRCodeUtil.getBase64QRCode(imageContent);}/*** 验证令牌码** @param secret 令牌秘钥,不能为空* @param code   令牌码* @param time   当前时间(毫秒)* @return 验证成功返回true,否则返回false* @throws IllegalArgumentException 如果secret为空* @throws RuntimeException         如果发生加密相关错误*/public static boolean checkCode(String secret, long code, long time) {if (secret == null || secret.trim().isEmpty()) {throw new IllegalArgumentException("Secret must not be empty");}byte[] decodedKey = BASE_32.decode(secret);long timeWindow = (time / 1000L) / TIME_STEP_SECONDS;// 检查窗口范围内的代码for (int i = -WINDOW_SIZE; i <= WINDOW_SIZE; ++i) {try {if (verifyCode(decodedKey, timeWindow + i) == code) {return true;}} catch (NoSuchAlgorithmException | InvalidKeyException e) {log.error("Failed to verify code", e);throw new RuntimeException("Authentication error", e);}}return false;}/*** 验证代码** @param key 解码后的密钥* @param t   时间窗口* @return 生成的验证码* @throws NoSuchAlgorithmException* @throws InvalidKeyException*/private static long verifyCode(byte[] key, long t)throws NoSuchAlgorithmException, InvalidKeyException {byte[] data = new byte[8];for (int i = 8; i-- > 0; t >>>= 8) {data[i] = (byte) t;}SecretKeySpec signKey = new SecretKeySpec(key, HMAC_SHA1_ALGORITHM);Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);mac.init(signKey);byte[] hash = mac.doFinal(data);int offset = hash[hash.length - 1] & 0xF;long truncatedHash = 0;for (int i = 0; i < 4; ++i) {truncatedHash <<= 8;truncatedHash |= (hash[offset + i] & 0xFF);}truncatedHash &= 0x7FFFFFFF;return truncatedHash % (long) Math.pow(10, CODE_DIGITS);}
}

QRCodeUtil.java

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;/*** 二维码工具类** @author qiuyu*/
@Slf4j
public final class QRCodeUtil {private static final int DEFAULT_WIDTH = 280;private static final int DEFAULT_HEIGHT = 280;private static final int DEFAULT_LOGO_WIDTH = 44;private static final int DEFAULT_LOGO_HEIGHT = 44;private static final String IMAGE_FORMAT = "png";private static final String CHARSET = "utf-8";private static final String BASE64_IMAGE_PREFIX = "data:image/png;base64,";private static final int LOGO_CORNER_RADIUS = 6;private static final float LOGO_STROKE_WIDTH = 3f;// 二维码生成参数private static final Map<EncodeHintType, Comparable<?>> DEFAULT_HINTS = createDefaultHints();private QRCodeUtil() {}private static Map<EncodeHintType, Comparable<?>> createDefaultHints() {Map<EncodeHintType, Comparable<?>> hints = new HashMap<>(3);hints.put(EncodeHintType.CHARACTER_SET, CHARSET);hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);hints.put(EncodeHintType.MARGIN, 2);return hints;}/*** 生成Base64格式的二维码图片(默认尺寸)** @param content 二维码内容,不能为空* @return Base64编码的图片字符串* @throws IllegalArgumentException 如果内容为空*/public static String getBase64QRCode(String content) {return getBase64QRCode(content, DEFAULT_WIDTH, DEFAULT_HEIGHT, null, null, null);}/*** 生成带Logo的Base64格式二维码图片(默认尺寸)** @param content 二维码内容,不能为空* @param logoUrl Logo URL地址* @return Base64编码的图片字符串* @throws IllegalArgumentException 如果内容为空*/public static String getBase64QRCode(String content, String logoUrl) {return getBase64QRCode(content, DEFAULT_WIDTH, DEFAULT_HEIGHT, logoUrl, DEFAULT_LOGO_WIDTH, DEFAULT_LOGO_HEIGHT);}/*** 生成自定义尺寸的Base64格式二维码图片** @param content    二维码内容,不能为空* @param width      二维码宽度* @param height     二维码高度* @param logoUrl    Logo URL地址* @param logoWidth  Logo宽度* @param logoHeight Logo高度* @return Base64编码的图片字符串* @throws IllegalArgumentException 如果内容为空或尺寸参数无效*/public static String getBase64QRCode(String content, Integer width, Integer height,String logoUrl, Integer logoWidth, Integer logoHeight) {validateContent(content);width = validateSize(width, DEFAULT_WIDTH);height = validateSize(height, DEFAULT_HEIGHT);logoWidth = validateSize(logoWidth, DEFAULT_LOGO_WIDTH);logoHeight = validateSize(logoHeight, DEFAULT_LOGO_HEIGHT);try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {BufferedImage image = createQRCode(content, width, height, logoUrl, logoWidth, logoHeight);if (image != null) {ImageIO.write(image, IMAGE_FORMAT, os);return BASE64_IMAGE_PREFIX + Base64.encode(os.toByteArray());}} catch (IOException e) {log.error("生成二维码失败", e);}return null;}/*** 生成二维码图片到输出流(默认尺寸)** @param content 二维码内容* @param output  输出流* @throws IOException 如果写入输出流失败* @throws IllegalArgumentException 如果内容为空*/public static void getQRCode(String content, OutputStream output) throws IOException {validateContent(content);BufferedImage image = createQRCode(content, DEFAULT_WIDTH, DEFAULT_HEIGHT, null, 0, 0);if (image != null) {ImageIO.write(image, IMAGE_FORMAT, output);}}/*** 生成带Logo的二维码图片到输出流(默认尺寸)** @param content 二维码内容* @param logoUrl Logo URL地址* @param output  输出流* @throws IOException 如果写入输出流失败* @throws IllegalArgumentException 如果内容为空*/public static void getQRCode(String content, String logoUrl, OutputStream output) throws IOException {validateContent(content);BufferedImage image = createQRCode(content, DEFAULT_WIDTH, DEFAULT_HEIGHT,logoUrl, DEFAULT_LOGO_WIDTH, DEFAULT_LOGO_HEIGHT);if (image != null) {ImageIO.write(image, IMAGE_FORMAT, output);}}/*** 创建二维码图片*/private static BufferedImage createQRCode(String content, int width, int height,String logoUrl, int logoWidth, int logoHeight) {try {QRCodeWriter writer = new QRCodeWriter();BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, DEFAULT_HINTS);BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);renderQRCode(bitMatrix, image, width, height);if (StrUtil.isNotBlank(logoUrl)) {addLogo(image, width, height, logoUrl, logoWidth, logoHeight);}return image;} catch (Exception e) {log.error("生成二维码异常", e);return null;}}/*** 渲染二维码图片*/private static void renderQRCode(BitMatrix bitMatrix, BufferedImage image, int width, int height) {for (int x = 0; x < width; x++) {for (int y = 0; y < height; y++) {image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);}}}/*** 添加Logo到二维码*/private static void addLogo(BufferedImage image, int width, int height,String logoUrl, int logoWidth, int logoHeight) throws IOException {Image logoImage = ImageIO.read(new URL(logoUrl));Graphics2D graphics = image.createGraphics();try {int x = (width - logoWidth) / 2;int y = (height - logoHeight) / 2;graphics.drawImage(logoImage, x, y, logoWidth, logoHeight, null);Shape roundRect = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight,LOGO_CORNER_RADIUS, LOGO_CORNER_RADIUS);graphics.setStroke(new BasicStroke(LOGO_STROKE_WIDTH));graphics.draw(roundRect);} finally {graphics.dispose();}}/*** 验证内容是否有效*/private static void validateContent(String content) {if (StrUtil.isBlank(content)) {throw new IllegalArgumentException("二维码内容不能为空");}}/*** 验证尺寸参数是否有效*/private static int validateSize(Integer size, int defaultValue) {return size != null && size > 0 ? size : defaultValue;}
}

pom.xml

<!-- 二维码生成  -->
<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version>
</dependency>
<dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.3</version>
</dependency>
<!-- hutool-all  -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version>
</dependency>

十一、总结

MFA通过多因素验证机制显著提升了身份认证的安全性,而TOTP作为其核心算法之一,因其标准化、易用性和离线工作能力成为广泛应用的选择。理解TOTP的原理和实现细节有助于开发更安全的认证系统,也为系统管理员提供了部署MFA的理论基础。随着技术发展,MFA将继续演化,在安全性和用户体验间寻求更优平衡,成为网络安全防御体系中不可或缺的一环。


http://www.hkcw.cn/article/KpfAphTcwP.shtml

相关文章

openssl-aes-ctr使用openmp加速

openssl-aes-ctr使用openmp加速 openssl-aes-ctropenmp omp for openssl-aes-ctr 本文采用openssl-1.1.1w进行开发验证开发&#xff1b;因为aes-ctr加解密模式中&#xff0c;不依赖与上一个模块的加/解密的内容&#xff0c;所以对于aes-ctr加解密模式是比较适合进行并行加速的…

git查看commit属于那个tag

1. 快速确认commit原始分支及合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 查看commit合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 --all 查看commit原始分支 2.查看分支与master关系 # git show --all 0.5.67_0006 --stat 以缩…

怎么在window上打开ubuntu虚拟机?

怎么在window上打开ubuntu虚拟机&#xff1f; 1.先下载ubuntu镜像包并解压&#xff08;VM-ubuntu18-202304.7z&#xff09;,下载地址在本文档中有链接&#xff0c;自行查找。&#xff08;解压路径不要有中文&#xff09; 2.打开VMware软件&#xff0c;&#xff08;软件下载地址…

中国移动咪咕助力第五届全国人工智能大赛“AI+数智创新”专项赛道开展

第五届全国人工智能大赛由鹏城实验室主办&#xff0c;新一代人工智能产业技术创新战略联盟承办&#xff0c;华为、中国移动、鹏城实验室科教基金会等单位协办&#xff0c;广东省人工智能与机器人学会支持。 大赛发布“AI图像编码”、“AI增强视频质量评价”、“AI数智创新”三大…

第十三章 MQTT消息

系列文章目录 系列文章目录 第一章 总体概述 第二章 在实体机上安装ubuntu 第三章 Windows远程连接ubuntu 第四章 使用Docker安装和运行EMQX 第五章 Docker卸载EMQX 第六章 EMQX客户端MQTTX Desktop的安装与使用 第七章 EMQX客户端MQTTX CLI的安装与使用 第八章 Wireshark工具…

国芯思辰| 16通道12位模数转换器SC1425高性价比SGM5200替代方案,专为数字电源优化

与传统的模拟电源相比&#xff0c;数字电源的主要区别是控制与通信部分。在复杂的多系统业务中&#xff0c;数字电源是通过软件编程来实现多方面的应用&#xff0c;数字电源广泛应用于在可控因素较多、实时反应速度更快、需要多个模拟系统电源管理的、复杂的高性能系统应用中。…

Java 大数据处理:使用 Hadoop 和 Spark 进行大规模数据处理

Java 大数据处理&#xff1a;使用 Hadoop 和 Spark 进行大规模数据处理 在当今数字化时代&#xff0c;数据呈现出爆炸式增长&#xff0c;如何高效地处理大规模数据成为企业面临的重要挑战。Java 作为一门广泛使用的编程语言&#xff0c;在大数据处理领域同样发挥着关键作用。本…

深度学习复习笔记

深度前馈神经网络 卷积神经网络 Advanced卷积神经网络 Lightweight CNN dwconv squeezenet 这边右侧的e3是3x3卷积吧 SENet 在通道维度压一下&#xff0c;强迫各维度混合学习&#xff0c;再还原 ShuffleNet Group Convolution在 AlexNet 中引入&#xff0c;用于将模型分布到…

Linux系统编程收尾(35)

文章目录 前言一、读写锁二、自旋锁总结 前言 大家好&#xff0c;这是我们Linux系统编程的最后一节课了&#xff01;   大家请再撑住一会儿~ 一、读写锁 提到读写锁&#xff0c;我们就不得不提到 读者写者模型 &#xff0c;跟 生产者消费者模型 不同的是&#xff0c;本模型的…

C文件操作1

一、为什么使用文件 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失 了&#xff0c;等再次运行程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进行持久化的保存&am…

基于 AUTOSAR 的域控产品软件开发:从 CP 到 AP 的跨越

基于 AUTOSAR 的域控产品软件开发&#xff1a;从 CP 到 AP 的跨越 一、AUTOSAR AP 架构解析&#xff1a;面向智能汽车的自适应框架 &#xff08;一&#xff09;引言 随着汽车智能化向 L3 演进&#xff0c;传统 AUTOSAR CP&#xff08;经典平台&#xff09;在实时性、动态性和…

解密震颤背后的神经隐情

在人体精密运行的神经世界里&#xff0c;有一种疾病悄然打破生命的节奏&#xff0c;它就是帕金森。这一病症并非突然降临&#xff0c;而是随着时间&#xff0c;如潮水般慢慢侵蚀着身体的正常机能。​ 患病后&#xff0c;最直观的变化体现在肢体运动上。双手会不受控制地颤抖&a…

数据即资产:GEO如何重塑企业的信息价值链

在数字经济时代&#xff0c;数据早已被公认为企业的核心资产。然而&#xff0c;随着生成式AI的崛起&#xff0c;数据资产的定义、价值和管理方式正在发生根本性变革。深耕数字营销二十余年&#xff0c;我们亲历了从"数据即记录"到"数据即洞察"&#xff0c;…

2025年文学与文化发展国际会议(ICLCD 2025)

2025年文学与文化发展国际会议&#xff08;ICLCD 2025&#xff09; 2025 International Conference on Literature and Cultural Development 一、大会信息 会议简称&#xff1a;ICLCD 2025 大会地点&#xff1a;中国天津 审稿通知&#xff1a;投稿后2-3日内通知 投稿邮箱&am…

SPL 轻量级多源混算实践 4 - 查询 MongoDB

除了以上常见数据源&#xff0c;还有 NoSQL、MQ 等数据源&#xff0c;其中以 MongoDB 最为常用。我们用 SPL 连接 MongoDB 做计算。 导入 MongoDB 数据。 外部库 SPL 支持的多种数据源大概分两类&#xff0c;一类是像 RDB 有 JDBC 直接使用&#xff0c;或者文件等直接读取&a…

特别篇-产品经理(三)

一、市场与竞品分析—竞品分析 1. 课后总结 案例框架&#xff1a;通过"小新吃蛋糕"案例展示行业分析方法&#xff0c;包含四个关键步骤&#xff1a; 明确目标行业调研确定竞品分析竞争策略输出结论 1&#xff09;行业背景分析方法 PEST分析法&#xff1a;从四个…

Photoshop使用钢笔绘制图形

1、绘制脸部路径 选择钢笔工具&#xff0c;再选择“路径”。 基于两个点绘制一个弯曲的曲线 使用Alt键移动单个点&#xff0c;该点决定了后续的曲线方向 继续绘制第3个点 最后一个点首尾是同一个点&#xff0c;使用钢笔保证是闭合回路。 以同样的方式绘制2个眼睛外框。 使用椭…

EMQX社区版5.8.5集群搭建踩坑记

一、首先要在三台物理机上分别搭建EMQX的实例 正常的安装是很简单的&#xff0c;并且有多种方式&#xff0c;我选取的是tar.gz安装包。 只需要把安装包解压tar -zxvf emqx-5.8.5xxx.tar.gz&#xff0c;然后进入到bin目录下&#xff0c;执行./emqx start即可。 1. 需要考虑操作…

长短期记忆(LSTM)网络模型

一、概述 长短期记忆&#xff08;Long Short-Term Memory&#xff0c;LSTM&#xff09;网络是一种特殊的循环神经网络&#xff08;RNN&#xff09;&#xff0c;专门设计用于解决传统 RNN 在处理长序列数据时面临的梯度消失 / 爆炸问题&#xff0c;能够有效捕捉长距离依赖关系。…

t014-项目申报管理系统 【springBoot 含源码】

项目演示视频 摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;项目信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行…