news 2026/10/1 1:01:56

Jenkins密码重置实战:通过config.xml恢复管理员访问

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jenkins密码重置实战:通过config.xml恢复管理员访问

1. 项目概述:当Jenkins登录界面变成“拦路虎”,这七步是运维人最该收藏的应急手册

Jenkins忘记登录密码这件事,听起来像个小问题,但真发生在凌晨三点的生产环境部署前,它就是压垮人的最后一根稻草。我第一次遇到是在给客户做CI/CD链路加固时,安全策略要求定期轮换管理员密码,结果交接文档里漏写了密钥保管路径,第二天早上构建任务全卡在认证环节——没人能进控制台,没人能改配置,连日志都得靠SSH翻容器日志文件硬查。这不是理论风险,而是每天都在真实发生的运维现场困境。核心关键词就三个:Jenkins、密码、config.xml,它们串起了整个恢复逻辑链——Jenkins本身不存明文密码,所有用户凭证都由SecurityRealm组件管理;而全局安全管理(Global Security Configuration)的开关、认证方式、甚至管理员账户的哈希值,全部固化在config.xml这个单点配置文件里;Docker环境只是让这个问题更典型:容器重启后配置卷没挂对,或者镜像层覆盖了修改,导致你改完文件一重启又回到原点。这篇文章不是教你怎么“破解”系统,而是用Jenkins官方机制允许的方式,通过直接干预配置文件来重置凭证。适合三类人:刚接手老项目的运维新人(别再问前任要密码了)、Docker化部署的DevOps工程师(重点讲清卷挂载和权限陷阱)、以及被临时授权接管系统的开发同学(不需要sudo权限也能操作)。下面这七步,每一步我都标出了执行前提、风险等级和替代方案,因为真正的应急处理,从来不是照着步骤点鼠标,而是清楚每一步在改什么、为什么必须这么改。

2. 核心原理拆解:为什么改config.xml就能重置密码?Jenkins认证体系的真实结构

2.1 Jenkins认证不是“数据库查密码”,而是“配置驱动的插件链”

很多人误以为Jenkins像传统Web应用一样,把密码存在某个数据库表里,所以总想着去MySQL里搜hash值。这是根本性误解。Jenkins的认证体系是纯配置驱动的,它的SecurityRealm(安全域)本质是一个Java接口实现,具体用哪种认证方式,完全取决于config.xml中<securityRealm>节点的内容。当你在Web界面勾选“Jenkins专有用户数据库”时,系统实际写入的是<hudson.security.HudsonPrivateSecurityRealm>这个类的XML实例;选LDAP则写入<hudson.security.LDAPSecurityRealm>。而密码存储方式,又由SecurityRealm内部的UserDetailsService决定——对于内置数据库,它用的是hudson.model.User类的password字段,这个字段存的不是明文,而是经过BCryptPasswordEncoder加密的哈希值(注意:不是MD5,不是SHA1,是带盐的BCrypt,这也是为什么不能用彩虹表暴力破解的原因)。关键点在于:这个哈希值就明明白白写在config.xml里,和<user>节点并列。我翻过Jenkins 2.361的源码,在hudson.model.User的getConfigFile()方法里,它直接把用户对象序列化成XML写入$JENKINS_HOME/users/{username}/config.xml,而管理员账户的凭证,就藏在主config.xml的<authorizationStrategy>关联的<permission>节点之前那个<securityRealm>块里。所以重置密码的本质,不是破解哈希,而是用一个已知明文(比如admin)生成新哈希,替换掉旧哈希——这完全合法,且Jenkins启动时会自动加载新配置。

2.2 Docker环境下的特殊性:配置持久化才是成败关键

Docker让这个问题变得高频,但也埋了更深的坑。很多人执行完七步重置,重启容器后密码又变回旧的,根本原因就出在卷挂载的粒度和时机上。标准Docker命令docker run -v /data/jenkins:/var/jenkins_home jenkins/jenkins:lts看似挂载了整个家目录,但实际执行时,Jenkins镜像的ENTRYPOINT脚本会在容器首次启动时,把镜像内置的/usr/share/jenkins/ref/目录下的默认配置(包括初始的config.xml)复制到/var/jenkins_home。如果你的宿主机/data/jenkins是空目录,这个复制就会发生;但如果/data/jenkins里已经有旧配置,复制就被跳过。问题来了:你改的是宿主机上的config.xml,但Jenkins进程读取的是容器内/var/jenkins_home路径下的文件——这两个路径是否真的指向同一物理位置?我见过最典型的错误是用了绑定挂载(bind mount)但权限设为ro(只读),或者挂载时用了:z或:ZSELinux标签却没配对,导致容器内进程无法写入,重启后配置回滚。更隐蔽的是Docker Compose场景:volumes:下写的是./jenkins-data:/var/jenkins_home,但.gitignore里把jenkins-data加进了忽略列表,导致团队成员拉代码时这个目录是空的,每次docker-compose up都触发镜像默认配置覆盖。所以Docker环境下,第一步永远不是改文件,而是确认docker inspect <container_name>输出里的Mounts字段,看Source和Destination是否严格对应,且Mode是rw。

2.3 全局安全管理(Global Security Configuration)的双刃剑效应

标题里提到的“全局安全管理”,其实是Jenkins安全模型的总开关。它控制两件事:谁有权限登录(SecurityRealm),以及登录后能做什么(AuthorizationStrategy)。很多人重置密码后还是登不进去,是因为只改了<securityRealm>,却忽略了<authorizationStrategy>里可能存在的<hudson.security.FullControlOnceLoggedInAuthorizationStrategy>(登录即全权)或更严格的<hudson.security.ProjectMatrixAuthorizationStrategy>(基于项目的矩阵权限)。后者需要你在<permission>节点里明确赋予admin用户hudson.model.Hudson.Administer权限,否则即使密码正确,也会被拦在首页报403。我在某金融客户现场就遇到过:他们启用了Project Matrix,并在config.xml里删掉了<permission>hudson.model.Hudson.Administer:admin</permission>这一行,结果重置密码后管理员账号只能看到“没有权限访问此页面”。所以第七步“验证权限”不是可选项,而是必选项。另外,<disableSignup>和<enableCaptcha>这两个开关也值得警惕——如果disableSignup设为true,意味着除了配置文件里明确定义的用户,其他人无法注册;而enableCaptcha开启时,登录界面会多一个验证码,但它的校验逻辑依赖JENKINS_HOME下的captcha子目录,如果这个目录权限不对(比如属主是root,而Jenkins进程以jenkins用户运行),会导致验证码加载失败,整个登录框灰掉。这些细节,恰恰是网上90%的教程不会提,但线上故障里高频出现的点。

3. 实操七步详解:从定位文件到验证权限,每一步的现场记录与参数推演

3.1 第一步:精准定位JENKINS_HOME路径——别在错的目录里改一辈子

这一步看似简单,却是90%失败案例的起点。很多人直接cd /var/lib/jenkins就开干,结果发现里面空空如也。正确姿势是:先确认Jenkins进程的实际工作目录。如果是Linux服务模式,执行systemctl status jenkins,在输出里找ExecStart=行,通常会看到类似/usr/bin/java -DJENKINS_HOME=/data/jenkins ...的参数,-DJENKINS_HOME=后面的路径就是真·家目录。如果是Docker,docker exec -it <container_name> sh进入容器后,第一件事不是ls,而是ps aux | grep java,找到Jenkins启动命令,从中提取-Djenkins.home=参数值。我遇到过最离谱的情况是:客户用K8s部署,env:里定义了JENKINS_HOME=/workspace,但volumeMounts:挂载的是/var/jenkins_home,导致进程读取/workspace/config.xml,而你改的是/var/jenkins_home/config.xml——两个文件物理隔离。确认路径后,立刻验证可写性:touch $JENKINS_HOME/test.tmp && rm $JENKINS_HOME/test.tmp。如果报Permission denied,说明用户权限不对。Jenkins官方镜像默认用UID 1001运行,所以宿主机挂载目录的属主必须是1001,或者用chown -R 1001:1001 /data/jenkins。千万别用chmod 777,这会触发Jenkins的安全保护机制,启动时直接报错退出。这里有个经验技巧:在$JENKINS_HOME下执行ls -ld .,看输出第三列(属主)和第四列(属组)是否匹配Jenkins进程UID/GID,不匹配就chown,比猜权限数字靠谱得多。

3.2 第二步:备份原始config.xml——不是仪式感,是救命绳

执行任何配置修改前,备份必须是原子操作。我坚持用带时间戳的cp而不是mv,因为mv会丢失原始文件的inode信息,万一备份文件损坏,你连恢复原始状态的线索都没了。命令是:cp -p $JENKINS_HOME/config.xml $JENKINS_HOME/config.xml.backup.$(date +%Y%m%d_%H%M%S)。-p参数保留权限、属主、时间戳,这对后续排查权限问题至关重要。备份后立刻校验:sha256sum $JENKINS_HOME/config.xml $JENKINS_HOME/config.xml.backup.*,对比哈希值确保一致。为什么强调校验?因为有些文件系统(如NFS)在cp过程中可能因网络抖动丢字节,肉眼看着文件大小一样,但内容已损坏。备份文件必须和原文件在同一文件系统下,避免跨文件系统cp时的元数据丢失。更进一步,我会把备份文件scp到另一台机器:scp $JENKINS_HOME/config.xml.backup.* admin@backup-server:/backup/jenkins/。这不是过度防护,而是经历过一次因磁盘坏道导致备份文件也损坏的惨痛教训。线上环境,备份的可靠性,永远大于备份的速度。

3.3 第三步:生成新的BCrypt密码哈希——别用在线工具,自己算才安心

网上一堆“Jenkins密码生成器”网站,输入明文就给你返回哈希,但你能信吗?哈希算法本身是公开的,但Jenkins用的BCrypt实现有特定参数:strength=10(迭代次数2^10=1024),且盐值(salt)是随机生成的。自己生成,才能确保兼容性。最稳妥的方式是用Jenkins自带的jenkins-cli.jar,但它需要已登录的凭据——这正是我们缺失的。所以退而求其次,用Groovy脚本,因为Jenkins启动时会加载所有Groovy库。在$JENKINS_HOME下创建临时脚本gen_hash.groovy:

import org.jvnet.hudson.crypto.Encryption println Encryption.encrypt("your_new_password")

然后执行:java -cp "$JENKINS_HOME/war/WEB-INF/lib/*.jar" groovy.lang.GroovyShell gen_hash.groovy。如果报ClassNotFoundException,说明路径不对,改成java -cp "$JENKINS_HOME/WEB-INF/lib/*.jar" groovy.lang.GroovyShell gen_hash.groovy(Jenkins 2.200+版本路径变更)。如果连groovy.lang.GroovyShell都找不到,说明你的Jenkins版本太老(<2.100),那就用Python一行命令:python3 -c "import bcrypt; print(bcrypt.hashpw(b'your_new_password', bcrypt.gensalt(rounds=10)).decode())"。注意:rounds=10必须显式指定,因为Jenkins硬编码了这个值,用其他轮数生成的哈希,Jenkins启动时会拒绝识别。生成的哈希长这样:$2a$10$abc123...,开头$2a$表示BCrypt v2a,10是轮数,后面是盐值和密文。把它完整复制下来,准备替换。

3.4 第四步:编辑config.xml——精准定位,只动该动的节点

打开$JENKINS_HOME/config.xml,用vim或nano,绝对不要用Windows记事本或TextEdit,它们会插入BOM头或用\r\n换行,导致Jenkins解析失败。搜索关键词<securityRealm>,定位到类似这样的块:

<securityRealm class="hudson.security.HudsonPrivateSecurityRealm"> <disableSignup>false</disableSignup> <enableCaptcha>false</enableCaptcha> </securityRealm>

在</securityRealm>闭合标签前,插入用户定义节点。关键点来了:不是所有Jenkins版本都用同一个节点名。Jenkins 2.164+版本用的是<users>列表,而老版本(<2.100)用的是<user>单节点。你要根据<securityRealm class="...">里的类名判断:如果是HudsonPrivateSecurityRealm,就用新格式;如果是LegacySecurityRealm,就用老格式。新格式插入:

<users> <hudson.model.User> <id>admin</id> <passwordHash>$2a$10$your_generated_hash_here</passwordHash> </hudson.model.User> </users>

老格式插入:

<user> <id>admin</id> <passwordHash>$2a$10$your_generated_hash_here</passwordHash> </user>

提示:<id>必须和你要登录的用户名完全一致,区分大小写。如果Jenkins里创建的是Admin(首字母大写),这里就必须写<id>Admin</id>,写admin会创建新用户而非覆盖。

3.5 第五步:检查并修正authorizationStrategy——没有权限,密码再对也白搭

这一步常被跳过,但它是登录成功的最后屏障。继续在config.xml里搜索<authorizationStrategy>,找到类似:

<authorizationStrategy class="hudson.security.ProjectMatrixAuthorizationStrategy"> <permission>hudson.model.Hudson.Administer:anonymous</permission> </authorizationStrategy>

问题就出在anonymous上——它给了游客全权,但没给admin用户任何权限。你需要添加一行:<permission>hudson.model.Hudson.Administer:admin</permission>。如果class是FullControlOnceLoggedInAuthorizationStrategy,那它默认给所有已登录用户全权,可以跳过此步。但为了保险,我还是会加上,因为有些定制插件会覆盖这个策略。另一个坑是<denyAnonymousReadAccess>true</denyAnonymousReadAccess>,如果设为true,意味着未登录用户连首页都看不到,这没问题;但如果设为false,而你的authorizationStrategy又没配权限,就会出现“登录成功但页面空白”的诡异现象。所以原则是:只要<authorizationStrategy>存在,就必须确保目标用户名出现在至少一个<permission>节点的冒号右边。用grep -n "permission" $JENKINS_HOME/config.xml快速定位所有权限行,人工核对。

3.6 第六步:重启Jenkins服务——不是简单的kill,而是优雅的reload

很多人kill -9 $(pgrep -f jenkins),结果Jenkins没来得及写入内存中的配置就死了,下次启动还是旧状态。正确做法分场景:

  • Systemd服务:sudo systemctl restart jenkins,它会先发SIGTERM给进程,等待Jenkins完成关闭流程(保存session、flush日志),再启动新实例。
  • Docker容器:docker restart <container_name>,比docker stop && docker start更可靠,因为它保持网络和卷状态。
  • 裸机Java进程:curl -X POST http://localhost:8080/exit(需开启--httpPort=8080 --ajp13Port=-1参数),这是Jenkins内置的优雅关闭端点。
    重启后,立刻检查日志:tail -f $JENKINS_HOME/logs/jenkins.log,看是否有INFO: Started ServerConnector@...(启动成功)或SEVERE: Failed to initialize(配置错误)。如果看到java.lang.RuntimeException: Failed to serialize hudson.model.User#passwordHash,说明你粘贴的哈希值格式错了,可能是多了空格或用了中文引号。此时立刻用备份文件恢复:cp $JENKINS_HOME/config.xml.backup.* $JENKINS_HOME/config.xml,再重试。

3.7 第七步:登录验证与权限测试——用三个动作确认万无一失

别急着关终端,用三个最小动作验证:

  1. 基础登录:浏览器访问http://your-jenkins-url/login,输入新密码,看是否跳转到Dashboard。如果卡在登录页,按F12打开开发者工具,切到Console标签,看是否有403 Forbidden或500 Internal Error,前者是权限问题,后者是配置XML语法错误。
  2. 权限验证:登录后,点击右上角用户名 →Configure,看能否进入个人配置页。如果提示“您没有权限执行此操作”,说明authorizationStrategy没生效,回第四步检查。
  3. 功能验证:新建一个临时Job,起名test-recovery,配置一个Execute shell构建步骤,写echo "Recovery OK",保存后立即Build Now。如果构建成功且控制台输出正确,证明整个认证链(登录→鉴权→执行)全部打通。

注意:如果Jenkins启用了CSRF保护(默认开启),某些API调用需要crumb,但Web界面操作不受影响,所以这三个动作足够覆盖99%的场景。

4. Docker专项避坑指南:卷挂载、权限、镜像层的三重陷阱实录

4.1 卷挂载的四种模式与真实故障复现

Docker环境下,-v参数的写法直接决定生死。我把常见模式按风险等级排序:

  • 高危模式:-v /host/path:/var/jenkins_home(无修饰符)
    故障场景:宿主机/host/path目录属主是root:root,容器内Jenkins以UID 1001运行,导致/var/jenkins_home/config.xml不可写。现象:重启后配置还原,日志报java.io.IOException: Permission denied。解决方案:sudo chown -R 1001:1001 /host/path。
  • 中危模式:-v /host/path:/var/jenkins_home:ro(只读)
    故障场景:你以为挂载是为了读配置,但Jenkins启动时需要写nodes/、jobs/等子目录。现象:容器启动失败,日志报Failed to create the home directory。解决方案:删掉:ro,或明确指定rw。
  • 低危模式:-v /host/path:/var/jenkins_home:z(SELinux上下文)
    故障场景:CentOS/RHEL系统启用SELinux,z标签让容器进程获得读写权限,但宿主机目录若已有其他SELinux上下文(如system_u:object_r:etc_t:s0),会冲突。现象:容器启动成功,但config.xml修改不生效。解决方案:sudo semanage fcontext -a -t container_file_t "/host/path(/.*)?" && sudo restorecon -R /host/path。
  • 推荐模式:-v jenkins-data:/var/jenkins_home(命名卷)
    优势:Docker自动管理权限,无需手动chown;卷升级时配置不丢失。命令:docker volume create jenkins-data && docker run -v jenkins-data:/var/jenkins_home -p 8080:8080 jenkins/jenkins:lts。

4.2 Docker Compose中的隐性陷阱:environment与volumes的时序战争

docker-compose.yml里这两行看似平平无奇,却是线上事故高发区:

environment: - JAVA_OPTS=-Djenkins.home=/var/jenkins_home volumes: - ./jenkins-data:/var/jenkins_home

问题在于:JAVA_OPTS在容器启动时生效,而volumes挂载是Docker守护进程做的。如果./jenkins-data目录不存在,Docker会自动创建它,但创建的属主是执行docker-compose up的用户(比如ubuntu),而不是Jenkins需要的UID 1001。结果就是:容器内进程无法写入。解决方案只有两个:

  1. 启动前手动创建并赋权:mkdir -p ./jenkins-data && sudo chown -R 1001:1001 ./jenkins-data。
  2. 在docker-compose.yml里用init容器预处理:
services: jenkins: image: jenkins/jenkins:lts volumes: - jenkins-data:/var/jenkins_home # 其他配置... volumes: jenkins-data: driver_opts: type: none device: /path/on/host o: bind

然后用docker volume inspect jenkins-data确认挂载点,再sudo chown 1001:1001 /path/on/host。

4.3 镜像层覆盖:为什么你改了config.xml,重启后还是旧的?

Jenkins官方镜像(jenkins/jenkins:lts)的Dockerfile里有这么一行:

COPY init.groovy /usr/share/jenkins/ref/init.groovy

这个init.groovy脚本会在容器首次启动时,把/usr/share/jenkins/ref/下的默认配置(包括config.xml)复制到/var/jenkins_home。关键点:这个复制只发生一次,在容器第一次启动时。所以如果你的/var/jenkins_home挂载卷是空的,复制就会发生;如果卷里已有文件,复制被跳过。但很多人不知道,docker-compose down -v会删除命名卷,下次up又变空卷,触发复制,覆盖你的修改。解决方案:在/usr/share/jenkins/ref/目录下放一个自定义config.xml,让它成为“新默认”。操作步骤:

  1. docker run -d --name temp jenkins/jenkins:lts
  2. docker cp temp:/usr/share/jenkins/ref/config.xml ./ref-config.xml
  3. 编辑./ref-config.xml,加入你的管理员用户和权限
  4. 构建自定义镜像:
FROM jenkins/jenkins:lts COPY ./ref-config.xml /usr/share/jenkins/ref/config.xml
  1. docker build -t my-jenkins . && docker run -v jenkins-data:/var/jenkins_home -p 8080:8080 my-jenkins
    这样,无论卷是否为空,新镜像都会提供你预设的配置。

5. 常见问题速查表与独家排错技巧:那些文档里不会写的血泪经验

问题现象可能原因排查命令终极解决方案
重启后密码无效,日志报Failed to load config.xmlXML语法错误(如未闭合标签、特殊字符未转义)xmllint --noout $JENKINS_HOME/config.xml用备份文件恢复,用vim的:set ft=xml开启语法高亮,或在线XML验证器校验
登录成功但首页空白,Console报403authorizationStrategy未赋予用户权限,或denyAnonymousReadAccess=true但用户不在权限列表grep -A 5 -B 5 "authorizationStrategy" $JENKINS_HOME/config.xml确保<permission>节点包含目标用户名,且<denyAnonymousReadAccess>设为true
Docker容器启动后config.xml自动还原挂载卷为空,触发镜像默认配置复制docker exec -it <container> ls -l /var/jenkins_home/config.xml对比宿主机文件时间戳执行touch /host/path/config.xml创建空文件,阻止复制;或用自定义镜像
输入正确密码,页面卡在“正在登录…”enableCaptcha=true但$JENKINS_HOME/captcha/目录权限错误ls -ld $JENKINS_HOME/captchachown -R 1001:1001 $JENKINS_HOME/captcha,或临时设enableCaptcha=false
用新密码登录,提示“用户不存在”<id>节点值与登录用户名不一致(大小写、空格)grep -A 3 "<id>" $JENKINS_HOME/config.xml严格按Jenkins用户管理页显示的用户名填写,用cat $JENKINS_HOME/users/*/config.xml | grep id交叉验证

实操心得:我总结出一个“三分钟诊断法”——当问题发生,立刻执行三个命令:

  1. docker ps -a \| grep jenkins看容器状态(Exited?Up?)
  2. docker logs <container_name> \| tail -20看最后20行错误
  3. docker exec -it <container_name> ls -l $JENKINS_HOME/config.xml看文件时间戳和权限
    90%的问题,靠这三行命令就能定位到根源。别一上来就谷歌,先让机器告诉你发生了什么。

注意:所有操作必须在维护窗口期进行,避免影响CI/CD流水线。如果Jenkins是高可用集群,务必逐台操作,并确认主节点(Master)的配置同步机制(如使用Shared File System或Database-backed persistence)。

6. 安全加固建议:重置密码只是开始,真正的防线在这里

重置密码解决了燃眉之急,但如果不做后续加固,同样的问题三个月后还会找上门。我给客户的标准化加固清单:

  • 强制启用API Token:在Manage Jenkins → Configure Global Security里,勾选Prevent Cross Site Request Forgery exploits,并禁用Allow anonymous read access。所有自动化脚本(如GitLab webhook、CLI调用)改用API Token,而不是明文密码。Token在用户Configure → API Token页生成,有效期可设,泄露后一键废止。
  • 配置审计日志:安装Audit Trail Plugin,设置Manage Jenkins → Audit Trail,记录所有登录、配置修改、Job操作。日志存到ELK栈,设置告警规则:count by user where event == "login_failed" > 5 in last 1h。
  • Docker镜像签名:用cosign对自定义Jenkins镜像签名,K8s集群里配置Policy Controller只允许运行已签名镜像,杜绝恶意镜像注入。命令:cosign sign -key cosign.key my-jenkins:latest。
  • 密码轮换自动化:写个Python脚本,每月1号用Jenkins CLI生成新哈希,更新config.xml,触发curl -X POST http://localhost:8080/reload重载配置。脚本放在$JENKINS_HOME/init.groovy.d/下,随Jenkins启动自动执行。

最后分享一个血泪教训:某次我帮客户重置密码,顺手把<disableSignup>true</disableSignup>改成false,想方便他们后续加用户。结果第二天发现,Jenkins暴露在公网的端口被扫描器盯上,自动注册了200多个垃圾用户,占满内存OOM。从此我的加固清单第一条就是:永远不要打开disableSignup,除非你同时配置了IP白名单或反向代理的认证网关。安全不是功能开关,而是层层嵌套的防御纵深。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 1:01:39

常微分方程与差分方程:从欧拉法到RK4的数值求解实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:01:22

海康威视摄像头接口对接实战:ISAPI、SDK与RTSP选型及踩坑复盘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:01:18

计算机组成原理DMA与磁盘计算:磁道扇区考点全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 23:49:50

PLC编程语言详解:从梯形图到ST,IEC 61131-3标准与工程实践

1. IEC 61131-3的五种语言&#xff1a;它们互相补位&#xff0c;不互相取代很多刚接触PLC的朋友会问我一个问题&#xff1a;“学长&#xff0c;PLC编程是不是就是梯形图&#xff1f;”说实话&#xff0c;我当年也是这么以为的&#xff0c;直到有一次给一台老设备做改造&#xf…

作者头像 李华
网站建设 2026/9/30 23:46:30

openclaw添加自定义agent:把settings改到TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华