SSH连接管理工具开发:从原生配置到动态化、安全化实践

news2026/5/4 2:28:05
1. 项目概述一个面向开发者的SSH连接管理工具在开发运维的日常工作中SSHSecure Shell连接管理是一个高频且基础的操作。无论是登录远程服务器进行部署、调试还是管理多台云主机我们都需要与SSH打交道。然而原生的SSH客户端配置如~/.ssh/config文件在管理大量连接、复杂跳板Jump Host或需要动态凭证时往往会显得力不从心。配置文件冗长、密钥管理分散、连接信息难以复用和分享这些问题在团队协作或个人拥有多个项目环境时尤为突出。lilyjem/ssh这个项目从其命名来看很可能是一个旨在优化SSH连接体验的工具或库。它没有直接使用“ssh-manager”、“ssh-client-wrapper”这类直白的名称而是采用了“用户名/项目名”的格式这暗示它可能是一个托管在代码仓库如GitHub上的个人或组织项目。其核心目标我推测是提供一个更结构化、更便捷、或许更安全的方式来定义、管理和使用SSH连接配置。它可能是一个命令行工具、一个配置生成器或者是一个集成到现有工作流中的脚本集合。对于谁需要关注这个项目如果你是频繁使用SSH的开发者、系统管理员、DevOps工程师或SRE经常需要维护十几个甚至上百个服务器连接如果你的团队需要一套统一、安全的服务器访问配置方案或者你厌倦了手动编辑复杂的SSH config文件那么这个项目所解决的问题很可能正是你的痛点。接下来我将基于常见的工程实践深入拆解这类工具的核心设计思路、关键技术实现以及在实际操作中会遇到的各种细节。2. 核心设计思路与方案选型当我们决定要构建或使用一个SSH连接管理工具时首先需要明确我们想要解决的具体问题以及为什么原生SSH配置在某些场景下不够用。2.1 原生SSH配置的局限性分析原生的SSH客户端主要通过~/.ssh/config文件和~/.ssh/known_hosts文件进行配置和管理。它的工作模式简单直接一个文本文件按行定义Host别名以及对应的连接参数如HostName,User,Port,IdentityFile等。这种方式在连接数量少、模式简单时非常高效。但当场景复杂后其局限性便暴露无遗配置冗余与缺乏模块化如果多台服务器使用相同的用户名、密钥或代理设置你需要在每个Host块中重复写入这些配置违反了DRYDon‘t Repeat Yourself原则。没有继承或模板机制。动态信息处理困难SSH config是静态文件。如果服务器IP是动态分配的如弹性云主机或者你需要根据环境变量如DEPLOY_ENV决定连接哪个目标原生配置无法直接处理。密钥管理分散私钥文件IdentityFile通常散落在各处缺乏统一的安全管理和生命周期控制。特别是当使用密码保护的密钥时每次连接都需要交互式输入密码自动化脚本会受阻。跳板机Bastion Host/Jump Host配置繁琐通过一台跳板机连接内网服务器是一种常见模式。在SSH config中配置ProxyJump或ProxyCommand虽然可行但当跳板机本身也需要复杂认证或多层跳转时配置会变得难以阅读和维护。团队协作与配置分享不便如何安全地将连接配置分享给团队成员直接发送config文件可能包含敏感信息如内网IP。每个人都需要手动复制粘贴且后续更新无法同步。缺乏连接上下文与元数据一个连接别名如Host prod-web-01背后可能关联着项目、环境、角色等元信息。原生config文件无法附加这些信息不利于按维度筛选和管理。基于这些痛点一个现代化的SSH连接管理工具通常会围绕以下几个核心设计目标展开配置即代码Configuration as Code、中心化管理、动态能力、安全增强和良好的开发者体验DX。2.2 主流解决方案的选型逻辑在动手之前看看社区已有的方案是明智的。这能帮助我们避免重复造轮子或者借鉴优秀的设计思想。常见的SSH管理方案大致分为几类SSH Config 增强工具这类工具不替代原生SSH而是作为ssh命令的前端或config文件的生成器。例如ssh-config库Node.js或python-sshconfig库它们提供了编程式生成和修改SSH config文件的能力。lilyjem/ssh很可能属于此类或在其基础上进行了封装。其优势在于完全兼容现有生态所有SSH的高级选项如LocalForward端口转发都能得到支持。连接管理器桌面应用如SecureCRT、Termius、Tabby等。它们提供图形化界面管理连接、分组、并集成SFTP、多标签等功能。适合偏好GUI的用户但通常不易与脚本化、自动化的工作流集成。基础设施即代码IaC集成在Terraform、Ansible等工具中创建资源如云服务器的同时可以动态输出连接信息并可能自动更新本地的SSH配置。这种方式与云资源生命周期强绑定非常适合云原生环境。秘密管理服务集成将SSH私钥甚至整个连接配置存储在HashiCorp Vault、AWS Secrets Manager等秘密管理服务中通过CLI工具动态获取并建立连接。这提供了最高的安全性但架构复杂度也最高。对于lilyjem/ssh这样一个以代码仓库形式存在的项目选择第一条路——即“SSH Config增强工具”——是最可能也是最合理的。因为它技术门槛相对适中能快速解决核心痛点并且以代码库的形式易于分发、版本控制和集成到CI/CD流水线中。注意在选择或设计工具时一个重要的原则是“最小化攻击面”。即工具本身不应引入新的安全风险比如明文存储密码、不安全的临时文件处理等。所有涉及密钥和密码的操作都应遵循最小权限和加密存储的原则。3. 关键技术实现细节拆解假设lilyjem/ssh是一个基于某种脚本语言如Python、Go、Node.js的命令行工具它读取一个自定义的、结构化的配置文件比如YAML或JSON然后动态生成或更新标准的~/.ssh/config文件。我们来拆解其中的关键技术点。3.1 结构化配置设计这是工具的核心。我们需要设计一个比原生SSH config更富表现力的配置文件格式。YAML因其可读性和支持复杂数据结构而成为热门选择。一个基础的配置结构可能如下所示# config.yaml defaults: user: “deploy” identity_file: “~/.ssh/id_ed25519” port: 22 # 可以定义通用设置如超时时间、压缩等 options: ServerAliveInterval: 60 ServerAliveCountMax: 3 hosts: # 简单主机定义继承defaults web-prod-01: hostname: “192.168.1.100” tags: [“production”, “web”, “us-east”] # 覆盖默认值 db-staging: hostname: “db.staging.example.com” user: “admin” port: 3306 # 指定不同的密钥 identity_file: “~/.ssh/staging_db_key” tags: [“staging”, “database”] # 复杂场景通过跳板机连接 internal-app: hostname: “10.0.0.5” # 内网地址 # 跳板配置支持多层嵌套 proxy: - hostname: “bastion.example.com” user: “jumper” # 跳板机也可以有自己的认证方式 identity_file: “~/.ssh/bastion_key” # 理论上可以继续添加更多跳板层 tags: [“internal”, “app”] # 动态主机通过脚本或API获取真实地址 dynamic-host: # 使用命令输出来决定hostname hostname_cmd: “aws ec2 describe-instances --instance-ids i-12345678 --query ‘Reservations[0].Instances[0].PrivateIpAddress’ --output text” # 可以设置缓存时间避免每次连接都执行命令 hostname_cache_ttl: 300 # 5分钟在这个设计里我们引入了几个关键概念Defaults默认值避免重复配置。Tags标签为主机打上多维标签便于通过工具命令进行筛选和列出如ssh-tool list --tag production。Proxy代理链用清晰的结构定义多层跳转比在SSH config中写一长串ProxyJump或ProxyCommand更易维护。动态属性如hostname_cmd允许通过执行外部命令来动态决定连接目标。这是解决云环境动态IP问题的关键。3.2 配置渲染与SSH Config生成工具需要将上述结构化配置“编译”成原生SSH客户端能识别的config文件。这个过程不仅仅是简单的字符串拼接还需要处理很多边界情况。模板渲染为每个hosts条目生成一个对应的Host块。需要正确处理继承关系主机配置覆盖默认配置。动态命令执行对于hostname_cmd这类字段需要在渲染时执行命令捕获其标准输出去除首尾空白字符作为最终的HostName值。这里必须考虑命令执行失败、超时、输出格式异常等情况并给出明确的错误提示。代理链转换将proxy数组转换为SSH的ProxyJump指令SSH 7.3或更通用的ProxyCommand指令。例如上面的internal-app会被转换为Host internal-app HostName 10.0.0.5 ProxyJump jumperbastion.example.com # 或者使用ProxyCommand: ssh -W %h:%p jumperbastion.example.com需要注意的是跳板机自身的认证信息如identity_file需要被正确地应用到对应的ProxyJump或ProxyCommand中这可能需要生成中间Host块或内联在命令里。敏感信息处理绝对不能在生成的config文件中明文写入密码。密钥路径是安全的但密码必须通过SSH Agent或其他交互方式提供。工具应鼓励并使用SSH Agent来管理受密码保护的密钥。3.3 命令行接口CLI设计一个友好的CLI是提升开发者体验的关键。工具至少应提供以下命令ssh-tool generate或ssh-tool sync读取自定义配置生成或更新~/.ssh/config文件。这是核心命令。ssh-tool list [--tag TAG]列出所有已定义的主机支持按标签过滤。输出可以是简洁的列表也可以是包含更多细节的表格。ssh-tool connect host-alias这是一个便捷命令它本质上等同于ssh host-alias但可以在连接前确保配置是最新的即先隐式执行generate。这对于动态主机特别有用。ssh-tool export host-alias以标准SSH config格式输出单个主机的配置片段便于调试或分享给不使用此工具的人。ssh-tool doctor或ssh-tool check检查配置文件的语法有效性、密钥文件是否存在、网络可达性等是一个诊断命令。CLI框架的选择很多如Python的click、argparseGo的cobraNode.js的commander等。它们能帮助快速构建结构清晰、带帮助文档的命令行工具。3.4 安全考量与密钥管理安全是SSH管理的生命线。工具设计必须包含以下考量配置文件权限自定义的YAML配置文件可能包含内网IP等敏感信息应建议用户设置合适的文件权限如chmod 600 config.yaml。密钥存储鼓励将私钥存储在~/.ssh/目录下并设置400或600权限。工具可以提供一个ssh-tool add-key命令将密钥导入到SSH Agentssh-add避免重复输入密码。避免密码持久化任何情况下都不应在配置文件中存储明文密码。如果必须使用密码认证应通过交互式提示、环境变量不推荐因为可能泄漏或与系统钥匙串Keychain/Secret Service集成的方式在运行时获取。审计日志对于企业级应用可以考虑记录连接日志谁、在何时、连接了哪个主机但这通常超出了个人工具的范围可能需要与堡垒机Bastion Host方案结合。4. 完整实操流程从零构建一个简易版工具为了更透彻地理解lilyjem/ssh这类项目的内部机理我们不妨用Python快速实现一个具备核心功能的简化版本。我们将这个工具命名为mysshctl。4.1 环境准备与项目初始化首先确保你的开发环境有Python 3.7。我们使用pipenv或venv创建虚拟环境来管理依赖。# 创建项目目录 mkdir mysshctl cd mysshctl # 创建虚拟环境 python3 -m venv .venv # 激活虚拟环境 (Linux/macOS) source .venv/bin/activate # 激活虚拟环境 (Windows PowerShell) # .venv\Scripts\Activate.ps1 # 创建必要的项目文件 touch mysshctl.py config.example.yaml README.md # 初始化一个简单的setup.py或pyproject.toml用于打包此处略过我们的核心依赖主要是PyYAML用于解析YAML配置以及click用于构建CLI。创建一个requirements.txt文件PyYAML6.0 click8.0然后安装它们pip install -r requirements.txt。4.2 核心代码实现解析接下来是mysshctl.py的主要内容。我们将分步实现。第一步定义配置模型Pydantic可选为了更好的数据验证我们可以使用pydantic但为了简化这里用原生字典和简单校验。# mysshctl.py import yaml import subprocess import sys from pathlib import Path from typing import Dict, List, Optional, Any import click CONFIG_PATH Path(‘~/.mysshctl/config.yaml‘).expanduser() SSH_CONFIG_PATH Path(‘~/.ssh/config‘).expanduser() def load_config(config_path: Path CONFIG_PATH) - Dict[str, Any]: “”“加载并解析YAML配置文件。”“” if not config_path.exists(): raise FileNotFoundError(f“Config file not found: {config_path}”) with open(config_path, ‘r‘) as f: raw_config yaml.safe_load(f) or {} # 简单的默认值合并逻辑 defaults raw_config.get(‘defaults‘, {}) hosts raw_config.get(‘hosts‘, {}) return {‘defaults‘: defaults, ‘hosts‘: hosts}第二步实现动态主机名解析这是工具的一个亮点允许通过命令输出设置主机名。def resolve_dynamic_hostname(host_config: Dict) - str: “”“如果主机配置包含‘hostname_cmd’则执行命令获取真实主机名。”“” cmd host_config.get(‘hostname_cmd‘) if not cmd: return host_config.get(‘hostname‘, ‘’) try: # 执行命令超时时间设为10秒 result subprocess.run( cmd, shellTrue, capture_outputTrue, textTrue, timeout10 ) if result.returncode ! 0: raise RuntimeError(f“Command ‘{cmd}‘ failed: {result.stderr}”) # 去除首尾空白字符作为主机名 resolved_name result.stdout.strip() if not resolved_name: raise ValueError(f“Command ‘{cmd}‘ returned empty output”) return resolved_name except subprocess.TimeoutExpired: raise TimeoutError(f“Command ‘{cmd}‘ timed out after 10 seconds”) except Exception as e: raise RuntimeError(f“Failed to resolve dynamic hostname via ‘{cmd}‘: {e}”)第三步生成SSH Config内容这是核心的渲染逻辑将结构化配置转换为SSH config格式。def generate_ssh_config(config: Dict) - str: “”“根据配置字典生成SSH config文件内容。”“” lines [] defaults config.get(‘defaults‘, {}) hosts config.get(‘hosts‘, {}) for host_alias, host_info in hosts.items(): lines.append(f“Host {host_alias}”) # 合并默认值和主机特定值主机值优先 merged {**defaults, **host_info} # 解析动态主机名 final_hostname resolve_dynamic_hostname(merged) if final_hostname: lines.append(f“ HostName {final_hostname}”) # 处理其他标准SSH选项 ssh_options_map { ‘user‘: ‘User‘, ‘port‘: ‘Port‘, ‘identity_file‘: ‘IdentityFile‘, } for our_key, ssh_key in ssh_options_map.items(): value merged.get(our_key) if value: lines.append(f“ {ssh_key} {value}”) # 处理自定义的SSH选项在defaults.options或hosts.options中 custom_options merged.get(‘options‘, {}) for key, value in custom_options.items(): lines.append(f“ {key} {value}”) # 处理代理链简化版只支持一层跳板 proxy merged.get(‘proxy‘) if proxy and isinstance(proxy, list) and len(proxy) 0: jump_host proxy[0] # 取第一个跳板机 jump_user jump_host.get(‘user‘, defaults.get(‘user‘, ‘’)) jump_hostname jump_host.get(‘hostname‘) if jump_hostname: # 使用ProxyJump (SSH 7.3) lines.append(f“ ProxyJump {jump_user}{jump_hostname}” if jump_user else f“ ProxyJump {jump_hostname}”) lines.append(“”) # 空行分隔每个Host块 return “\n”.join(lines)第四步构建CLI使用click库创建命令行接口。click.group() def cli(): “”“MySSH Control Tool”“” pass cli.command() click.option(‘--config‘, ‘config_path‘, defaultCONFIG_PATH, typeclick.Path(), help‘Path to custom config YAML.‘) click.option(‘--output‘, ‘output_path‘, defaultSSH_CONFIG_PATH, typeclick.Path(), help‘Path to output SSH config.‘) click.option(‘--backup/--no-backup‘, defaultTrue, help‘Backup existing SSH config before overwriting.‘) def generate(config_path, output_path, backup): “”“Generate SSH config from YAML definition.”“” config_path, output_path Path(config_path), Path(output_path) try: config load_config(config_path) ssh_config_content generate_ssh_config(config) # 备份原文件 if backup and output_path.exists(): backup_path output_path.with_suffix(output_path.suffix ‘.backup‘) import shutil shutil.copy2(output_path, backup_path) click.echo(f“Backed up existing config to {backup_path}”) # 写入新文件 output_path.parent.mkdir(parentsTrue, exist_okTrue) output_path.write_text(ssh_config_content) click.echo(f“Successfully generated SSH config to {output_path}”) except Exception as e: click.echo(f“Error: {e}”, errTrue) sys.exit(1) cli.command() click.option(‘--config‘, ‘config_path‘, defaultCONFIG_PATH, typeclick.Path()) def list_hosts(config_path): “”“List all defined hosts.”“” config_path Path(config_path) try: config load_config(config_path) hosts config.get(‘hosts‘, {}) click.echo(“Defined hosts:“) for alias, info in hosts.items(): hostname info.get(‘hostname‘, ‘dynamic‘) tags info.get(‘tags‘, []) tags_str f“ [tags: {‘, ‘.join(tags)}]” if tags else “” click.echo(f“ {alias} - {hostname}{tags_str}”) except Exception as e: click.echo(f“Error: {e}”, errTrue) sys.exit(1) if __name__ ‘__main__‘: cli()4.3 配置与使用示例创建一个示例配置文件~/.mysshctl/config.yamldefaults: user: “ubuntu” identity_file: “~/.ssh/id_ed25519” options: ServerAliveInterval: 30 StrictHostKeyChecking: accept-new hosts: my-web-server: hostname: “147.182.xxx.xxx” tags: [“production”, “web”] staging-db: hostname: “db.staging.myapp.com” user: “postgres” port: 5432 identity_file: “~/.ssh/staging_key” dynamic-ec2: hostname_cmd: “aws ec2 describe-instances --instance-ids i-0abcdef1234567890 --query ‘Reservations[0].Instances[0].PublicIpAddress‘ --output text --region us-east-1” tags: [“aws”, “ec2”, “dynamic”]现在你可以运行工具了# 生成SSH配置 python mysshctl.py generate # 列出所有主机 python mysshctl.py list-hosts # 使用生成的配置连接服务器使用原生ssh命令 ssh my-web-server这个简易工具已经具备了核心功能结构化配置、动态主机名和基础CLI。lilyjem/ssh项目很可能会在此基础上增加更多功能如更强大的代理链、配置验证、模板继承、插件系统等。5. 进阶功能探讨与扩展方向一个成熟的SSH管理工具不会止步于基础功能。lilyjem/ssh可能还包含或计划包含以下进阶特性这些也是此类工具演化的常见方向5.1 多环境与配置继承支持类似“开发”、“测试”、“生产”等多环境配置。可以通过在YAML中定义environments并在hosts中引用environment字段来实现共享的基线配置。environments: production: user: “prod-admin” identity_file: “~/.ssh/prod_key” options: LogLevel: VERBOSE staging: user: “staging-user” identity_file: “~/.ssh/staging_key” hosts: app-server: environment: “production” # 继承production环境的配置 hostname: “app.prod.example.com” staging-app-server: environment: “staging” hostname: “app.staging.example.com”5.2 连接隧道与端口转发管理SSH的端口转发本地转发-L、远程转发-R、动态转发-D功能强大但配置繁琐。工具可以将其抽象为配置项。hosts: gateway: hostname: “bastion.example.com” tunnels: local: # 将本地8080端口通过跳板机转发到内部web服务器的80端口 - local_port: 8080 remote_host: “internal.web.server” remote_port: 80 dynamic: # 创建一个本地SOCKS5代理端口 - local_port: 1080工具可以提供ssh-tool tunnel start host-alias命令来建立并管理这些隧道进程甚至记录PID以便后续关闭。5.3 与秘密管理服务集成为了企业级安全私钥不应放在每个开发者的磁盘上。工具可以集成Vault或AWS Secrets Manager在运行时动态获取密钥。hosts: secure-host: hostname: “very.secure.server” # 指定密钥来源为Vault并给出路径 identity_file: vault: “secret/data/ssh/keys/my_key”对应的代码需要在generate或connect阶段调用Vault API获取密钥将其临时写入一个仅对当前用户可读的文件tempfile模块并在SSH命令中使用该临时文件路径连接结束后立即删除。这大大提升了密钥管理的安全性和集中度。5.4 配置文件版本控制与团队共享如何让团队共享连接配置一种方法是将定义主机的YAML文件放在项目仓库中注意不要包含真正的密钥只包含主机名、标签等元数据密钥路径使用变量。工具可以支持从多个来源本地文件、Git仓库URL、HTTP端点合并配置。或者提供一个ssh-tool import git-url命令从指定的Git仓库拉取并合并主机定义。6. 常见问题与实战排查技巧在实际使用或开发这类工具时你会遇到各种各样的问题。下面是一些典型问题及其排查思路。6.1 连接失败权限被拒绝Permission Denied这是最常见的问题。排查链如下检查生成的SSH Config首先运行ssh -v host-alias。在输出的开头SSH客户端会显示它正在读取的config文件和使用哪个Host块。确认工具生成的配置是否正确特别是HostName、User、IdentityFile这几个关键参数。验证密钥文件确认IdentityFile指向的私钥文件存在且权限正确600或400。使用ssh-keygen -y -f /path/to/private_key可以测试私钥是否有效。检查公钥是否授权确保对应的公钥已经添加到目标服务器的~/.ssh/authorized_keys文件中。可以手动尝试ssh -i /path/to/key userhostname进行验证。检查SSH Agent如果你使用密码保护的密钥并依赖SSH Agent使用ssh-add -l查看密钥是否已加载。如果没有用ssh-add /path/to/key添加。用户与端口确认用户名和端口号是否正确。特别是当覆盖了默认的22端口时。6.2 动态主机名解析失败如果配置了hostname_cmd但连接时提示“Could not resolve hostname”说明命令执行出了问题。手动执行命令在终端中运行配置文件中写的命令看是否能得到预期的IP或主机名。检查命令环境工具在执行命令时其环境变量可能与你的交互式Shell不同。特别是PATH和任何与云提供商CLI相关的环境变量如AWS_PROFILE。考虑在命令中使用绝对路径或在工具配置中指定环境。处理命令输出确保命令输出是干净的主机名或IP没有多余的空行、警告信息。工具中的resolve_dynamic_hostname函数已经做了strip()处理但如果命令输出多行可能需要取第一行或做更复杂的解析。缓存与更新如果命令执行慢如调用云API考虑实现缓存机制。我们的简单示例没有缓存每次生成配置都会执行命令。可以设计为将解析结果缓存到临时文件并设置TTL生存时间。6.3 代理跳转ProxyJump不工作多层跳转配置容易出错。检查跳板机配置确保跳板机自身的Host块无论是工具生成的还是你手动写在config里的配置正确能够独立连接成功。你可以先尝试ssh bastion-alias。理解ProxyJump语法ProxyJump userhost1,userhost2表示先跳到host1再从host1跳到host2。确保你的proxy数组顺序与此对应。回退到ProxyCommand如果目标服务器SSH版本较旧7.3不支持ProxyJump需要工具生成ProxyCommand指令。一个健壮的工具应该能检测并兼容这两种方式。调试使用ssh -v查看详细的连接过程可以看到SSH客户端是如何尝试通过跳板机连接的在哪一步失败了。6.4 工具本身的管理问题配置冲突如果你的~/.ssh/config中既有工具生成的内容也有手动添加的内容在重新生成时工具是覆盖整个文件还是只更新特定部分我们的简单实现是覆盖整个文件。更复杂的工具可能会在文件首尾添加标记只更新标记之间的内容保留用户的手写配置。配置验证在生成前对YAML配置文件进行语法和语义验证非常有用。例如检查必需的字段是否存在hostname和hostname_cmd是否互斥port是否为数字等。可以使用像Cerberus或Pydantic这样的库来定义模式Schema并进行验证。性能当主机数量非常多成千上万时每次生成都遍历所有主机并可能执行命令会变得很慢。需要考虑增量生成、并行执行命令等优化策略。开发或使用这样一个工具本质上是在构建一套适合自己或团队工作习惯的“基础设施”。它可能始于一个简单的脚本随着需求增长逐渐演化。lilyjem/ssh项目无论其具体实现如何其核心价值在于将SSH连接这一底层操作进行了抽象和标准化从而提升了运维效率和体验的一致性。理解其背后的设计逻辑和实现细节不仅能帮助你更好地使用它也能在它不符合你特定需求时知道如何改造或构建属于自己的版本。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…